diff options
author | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
---|---|---|
committer | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
commit | 5f8de423f190bbb79a62f804151bc24824fa32d8 (patch) | |
tree | 10027f336435511475e392454359edea8e25895d /widget/gtk | |
parent | 49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff) | |
download | UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip |
Add m-esr52 at 52.6.0
Diffstat (limited to 'widget/gtk')
107 files changed, 38347 insertions, 0 deletions
diff --git a/widget/gtk/CompositorWidgetChild.cpp b/widget/gtk/CompositorWidgetChild.cpp new file mode 100644 index 000000000..066251060 --- /dev/null +++ b/widget/gtk/CompositorWidgetChild.cpp @@ -0,0 +1,45 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 "CompositorWidgetChild.h" +#include "mozilla/Unused.h" + +namespace mozilla { +namespace widget { + +CompositorWidgetChild::CompositorWidgetChild(RefPtr<CompositorVsyncDispatcher> aVsyncDispatcher, + RefPtr<CompositorWidgetVsyncObserver> aVsyncObserver) + : mVsyncDispatcher(aVsyncDispatcher) + , mVsyncObserver(aVsyncObserver) +{ + MOZ_ASSERT(XRE_IsParentProcess()); +} + +CompositorWidgetChild::~CompositorWidgetChild() +{ +} + +bool +CompositorWidgetChild::RecvObserveVsync() +{ + mVsyncDispatcher->SetCompositorVsyncObserver(mVsyncObserver); + return true; +} + +bool +CompositorWidgetChild::RecvUnobserveVsync() +{ + mVsyncDispatcher->SetCompositorVsyncObserver(nullptr); + return true; +} + +void +CompositorWidgetChild::NotifyClientSizeChanged(const LayoutDeviceIntSize& aClientSize) +{ + Unused << SendNotifyClientSizeChanged(aClientSize); +} + +} // namespace widget +} // namespace mozilla diff --git a/widget/gtk/CompositorWidgetChild.h b/widget/gtk/CompositorWidgetChild.h new file mode 100644 index 000000000..403b90506 --- /dev/null +++ b/widget/gtk/CompositorWidgetChild.h @@ -0,0 +1,38 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#ifndef widget_gtk_CompositorWidgetChild_h +#define widget_gtk_CompositorWidgetChild_h + +#include "X11CompositorWidget.h" +#include "mozilla/widget/PCompositorWidgetChild.h" +#include "mozilla/widget/CompositorWidgetVsyncObserver.h" + +namespace mozilla { +namespace widget { + +class CompositorWidgetChild final + : public PCompositorWidgetChild + , public CompositorWidgetDelegate +{ +public: + CompositorWidgetChild(RefPtr<CompositorVsyncDispatcher> aVsyncDispatcher, + RefPtr<CompositorWidgetVsyncObserver> aVsyncObserver); + ~CompositorWidgetChild() override; + + bool RecvObserveVsync() override; + bool RecvUnobserveVsync() override; + + void NotifyClientSizeChanged(const LayoutDeviceIntSize& aClientSize) override; + +private: + RefPtr<CompositorVsyncDispatcher> mVsyncDispatcher; + RefPtr<CompositorWidgetVsyncObserver> mVsyncObserver; +}; + +} // namespace widget +} // namespace mozilla + +#endif // widget_gtk_CompositorWidgetChild_h diff --git a/widget/gtk/CompositorWidgetParent.cpp b/widget/gtk/CompositorWidgetParent.cpp new file mode 100644 index 000000000..c882f4f08 --- /dev/null +++ b/widget/gtk/CompositorWidgetParent.cpp @@ -0,0 +1,48 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 "CompositorWidgetParent.h" +#include "mozilla/Unused.h" + +namespace mozilla { +namespace widget { + +CompositorWidgetParent::CompositorWidgetParent(const CompositorWidgetInitData& aInitData) + : X11CompositorWidget(aInitData) +{ + MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_GPU); +} + +CompositorWidgetParent::~CompositorWidgetParent() +{ +} + +void +CompositorWidgetParent::ObserveVsync(VsyncObserver* aObserver) +{ + if (aObserver) { + Unused << SendObserveVsync(); + } else { + Unused << SendUnobserveVsync(); + } + mVsyncObserver = aObserver; +} + +RefPtr<VsyncObserver> +CompositorWidgetParent::GetVsyncObserver() const +{ + MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_GPU); + return mVsyncObserver; +} + +bool +CompositorWidgetParent::RecvNotifyClientSizeChanged(const LayoutDeviceIntSize& aClientSize) +{ + NotifyClientSizeChanged(aClientSize); + return true; +} + +} // namespace widget +} // namespace mozilla diff --git a/widget/gtk/CompositorWidgetParent.h b/widget/gtk/CompositorWidgetParent.h new file mode 100644 index 000000000..e80c0f8b2 --- /dev/null +++ b/widget/gtk/CompositorWidgetParent.h @@ -0,0 +1,37 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#ifndef widget_gtk_CompositorWidgetParent_h +#define widget_gtk_CompositorWidgetParent_h + +#include "X11CompositorWidget.h" +#include "mozilla/widget/PCompositorWidgetParent.h" + +namespace mozilla { +namespace widget { + +class CompositorWidgetParent final + : public PCompositorWidgetParent, + public X11CompositorWidget +{ +public: + explicit CompositorWidgetParent(const CompositorWidgetInitData& aInitData); + ~CompositorWidgetParent() override; + + void ActorDestroy(ActorDestroyReason aWhy) override { } + + void ObserveVsync(VsyncObserver* aObserver) override; + RefPtr<VsyncObserver> GetVsyncObserver() const override; + + bool RecvNotifyClientSizeChanged(const LayoutDeviceIntSize& aClientSize) override; + +private: + RefPtr<VsyncObserver> mVsyncObserver; +}; + +} // namespace widget +} // namespace mozilla + +#endif // widget_gtk_CompositorWidgetParent_h diff --git a/widget/gtk/IMContextWrapper.cpp b/widget/gtk/IMContextWrapper.cpp new file mode 100644 index 000000000..58d7a3681 --- /dev/null +++ b/widget/gtk/IMContextWrapper.cpp @@ -0,0 +1,2359 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim: set ts=4 et sw=4 tw=80: */ +/* 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/Logging.h" +#include "prtime.h" + +#include "IMContextWrapper.h" +#include "nsGtkKeyUtils.h" +#include "nsWindow.h" +#include "mozilla/AutoRestore.h" +#include "mozilla/Likely.h" +#include "mozilla/MiscEvents.h" +#include "mozilla/Preferences.h" +#include "mozilla/TextEventDispatcher.h" +#include "mozilla/TextEvents.h" +#include "WritingModes.h" + +namespace mozilla { +namespace widget { + +LazyLogModule gGtkIMLog("nsGtkIMModuleWidgets"); + +static inline const char* +ToChar(bool aBool) +{ + return aBool ? "true" : "false"; +} + +static const char* +GetEnabledStateName(uint32_t aState) +{ + switch (aState) { + case IMEState::DISABLED: + return "DISABLED"; + case IMEState::ENABLED: + return "ENABLED"; + case IMEState::PASSWORD: + return "PASSWORD"; + case IMEState::PLUGIN: + return "PLUG_IN"; + default: + return "UNKNOWN ENABLED STATUS!!"; + } +} + +static const char* +GetEventType(GdkEventKey* aKeyEvent) +{ + switch (aKeyEvent->type) { + case GDK_KEY_PRESS: + return "GDK_KEY_PRESS"; + case GDK_KEY_RELEASE: + return "GDK_KEY_RELEASE"; + default: + return "Unknown"; + } +} + +class GetWritingModeName : public nsAutoCString +{ +public: + explicit GetWritingModeName(const WritingMode& aWritingMode) + { + if (!aWritingMode.IsVertical()) { + AssignLiteral("Horizontal"); + return; + } + if (aWritingMode.IsVerticalLR()) { + AssignLiteral("Vertical (LTR)"); + return; + } + AssignLiteral("Vertical (RTL)"); + } + virtual ~GetWritingModeName() {} +}; + +class GetTextRangeStyleText final : public nsAutoCString +{ +public: + explicit GetTextRangeStyleText(const TextRangeStyle& aStyle) + { + if (!aStyle.IsDefined()) { + AssignLiteral("{ IsDefined()=false }"); + return; + } + + if (aStyle.IsLineStyleDefined()) { + AppendLiteral("{ mLineStyle="); + AppendLineStyle(aStyle.mLineStyle); + if (aStyle.IsUnderlineColorDefined()) { + AppendLiteral(", mUnderlineColor="); + AppendColor(aStyle.mUnderlineColor); + } else { + AppendLiteral(", IsUnderlineColorDefined=false"); + } + } else { + AppendLiteral("{ IsLineStyleDefined()=false"); + } + + if (aStyle.IsForegroundColorDefined()) { + AppendLiteral(", mForegroundColor="); + AppendColor(aStyle.mForegroundColor); + } else { + AppendLiteral(", IsForegroundColorDefined()=false"); + } + + if (aStyle.IsBackgroundColorDefined()) { + AppendLiteral(", mBackgroundColor="); + AppendColor(aStyle.mBackgroundColor); + } else { + AppendLiteral(", IsBackgroundColorDefined()=false"); + } + + AppendLiteral(" }"); + } + void AppendLineStyle(uint8_t aLineStyle) + { + switch (aLineStyle) { + case TextRangeStyle::LINESTYLE_NONE: + AppendLiteral("LINESTYLE_NONE"); + break; + case TextRangeStyle::LINESTYLE_SOLID: + AppendLiteral("LINESTYLE_SOLID"); + break; + case TextRangeStyle::LINESTYLE_DOTTED: + AppendLiteral("LINESTYLE_DOTTED"); + break; + case TextRangeStyle::LINESTYLE_DASHED: + AppendLiteral("LINESTYLE_DASHED"); + break; + case TextRangeStyle::LINESTYLE_DOUBLE: + AppendLiteral("LINESTYLE_DOUBLE"); + break; + case TextRangeStyle::LINESTYLE_WAVY: + AppendLiteral("LINESTYLE_WAVY"); + break; + default: + AppendPrintf("Invalid(0x%02X)", aLineStyle); + break; + } + } + void AppendColor(nscolor aColor) + { + AppendPrintf("{ R=0x%02X, G=0x%02X, B=0x%02X, A=0x%02X }", + NS_GET_R(aColor), NS_GET_G(aColor), NS_GET_B(aColor), + NS_GET_A(aColor)); + } + virtual ~GetTextRangeStyleText() {}; +}; + +const static bool kUseSimpleContextDefault = MOZ_WIDGET_GTK == 2; + +/****************************************************************************** + * IMContextWrapper + ******************************************************************************/ + +IMContextWrapper* IMContextWrapper::sLastFocusedContext = nullptr; +bool IMContextWrapper::sUseSimpleContext; + +NS_IMPL_ISUPPORTS(IMContextWrapper, + TextEventDispatcherListener, + nsISupportsWeakReference) + +IMContextWrapper::IMContextWrapper(nsWindow* aOwnerWindow) + : mOwnerWindow(aOwnerWindow) + , mLastFocusedWindow(nullptr) + , mContext(nullptr) + , mSimpleContext(nullptr) + , mDummyContext(nullptr) + , mComposingContext(nullptr) + , mCompositionStart(UINT32_MAX) + , mProcessingKeyEvent(nullptr) + , mCompositionState(eCompositionState_NotComposing) + , mIsIMFocused(false) + , mIsDeletingSurrounding(false) + , mLayoutChanged(false) + , mSetCursorPositionOnKeyEvent(true) + , mPendingResettingIMContext(false) + , mRetrieveSurroundingSignalReceived(false) +{ + static bool sFirstInstance = true; + if (sFirstInstance) { + sFirstInstance = false; + sUseSimpleContext = + Preferences::GetBool( + "intl.ime.use_simple_context_on_password_field", + kUseSimpleContextDefault); + } + Init(); +} + +void +IMContextWrapper::Init() +{ + MOZ_LOG(gGtkIMLog, LogLevel::Info, + ("0x%p Init(), mOwnerWindow=0x%p", + this, mOwnerWindow)); + + MozContainer* container = mOwnerWindow->GetMozContainer(); + NS_PRECONDITION(container, "container is null"); + GdkWindow* gdkWindow = gtk_widget_get_window(GTK_WIDGET(container)); + + // NOTE: gtk_im_*_new() abort (kill) the whole process when it fails. + // So, we don't need to check the result. + + // Normal context. + mContext = gtk_im_multicontext_new(); + gtk_im_context_set_client_window(mContext, gdkWindow); + g_signal_connect(mContext, "preedit_changed", + G_CALLBACK(IMContextWrapper::OnChangeCompositionCallback), this); + g_signal_connect(mContext, "retrieve_surrounding", + G_CALLBACK(IMContextWrapper::OnRetrieveSurroundingCallback), this); + g_signal_connect(mContext, "delete_surrounding", + G_CALLBACK(IMContextWrapper::OnDeleteSurroundingCallback), this); + g_signal_connect(mContext, "commit", + G_CALLBACK(IMContextWrapper::OnCommitCompositionCallback), this); + g_signal_connect(mContext, "preedit_start", + G_CALLBACK(IMContextWrapper::OnStartCompositionCallback), this); + g_signal_connect(mContext, "preedit_end", + G_CALLBACK(IMContextWrapper::OnEndCompositionCallback), this); + + // Simple context + if (sUseSimpleContext) { + mSimpleContext = gtk_im_context_simple_new(); + gtk_im_context_set_client_window(mSimpleContext, gdkWindow); + g_signal_connect(mSimpleContext, "preedit_changed", + G_CALLBACK(&IMContextWrapper::OnChangeCompositionCallback), + this); + g_signal_connect(mSimpleContext, "retrieve_surrounding", + G_CALLBACK(&IMContextWrapper::OnRetrieveSurroundingCallback), + this); + g_signal_connect(mSimpleContext, "delete_surrounding", + G_CALLBACK(&IMContextWrapper::OnDeleteSurroundingCallback), + this); + g_signal_connect(mSimpleContext, "commit", + G_CALLBACK(&IMContextWrapper::OnCommitCompositionCallback), + this); + g_signal_connect(mSimpleContext, "preedit_start", + G_CALLBACK(IMContextWrapper::OnStartCompositionCallback), + this); + g_signal_connect(mSimpleContext, "preedit_end", + G_CALLBACK(IMContextWrapper::OnEndCompositionCallback), + this); + } + + // Dummy context + mDummyContext = gtk_im_multicontext_new(); + gtk_im_context_set_client_window(mDummyContext, gdkWindow); +} + +IMContextWrapper::~IMContextWrapper() +{ + if (this == sLastFocusedContext) { + sLastFocusedContext = nullptr; + } + MOZ_LOG(gGtkIMLog, LogLevel::Info, + ("0x%p ~IMContextWrapper()", this)); +} + +NS_IMETHODIMP +IMContextWrapper::NotifyIME(TextEventDispatcher* aTextEventDispatcher, + const IMENotification& aNotification) +{ + switch (aNotification.mMessage) { + case REQUEST_TO_COMMIT_COMPOSITION: + case REQUEST_TO_CANCEL_COMPOSITION: { + nsWindow* window = + static_cast<nsWindow*>(aTextEventDispatcher->GetWidget()); + return EndIMEComposition(window); + } + case NOTIFY_IME_OF_FOCUS: + OnFocusChangeInGecko(true); + return NS_OK; + case NOTIFY_IME_OF_BLUR: + OnFocusChangeInGecko(false); + return NS_OK; + case NOTIFY_IME_OF_POSITION_CHANGE: + OnLayoutChange(); + return NS_OK; + case NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED: + OnUpdateComposition(); + return NS_OK; + case NOTIFY_IME_OF_SELECTION_CHANGE: { + nsWindow* window = + static_cast<nsWindow*>(aTextEventDispatcher->GetWidget()); + OnSelectionChange(window, aNotification); + return NS_OK; + } + default: + return NS_ERROR_NOT_IMPLEMENTED; + } +} + +NS_IMETHODIMP_(void) +IMContextWrapper::OnRemovedFrom(TextEventDispatcher* aTextEventDispatcher) +{ + // XXX When input transaction is being stolen by add-on, what should we do? +} + +NS_IMETHODIMP_(void) +IMContextWrapper::WillDispatchKeyboardEvent( + TextEventDispatcher* aTextEventDispatcher, + WidgetKeyboardEvent& aKeyboardEvent, + uint32_t aIndexOfKeypress, + void* aData) +{ + KeymapWrapper::WillDispatchKeyboardEvent(aKeyboardEvent, + static_cast<GdkEventKey*>(aData)); +} + +TextEventDispatcher* +IMContextWrapper::GetTextEventDispatcher() +{ + if (NS_WARN_IF(!mLastFocusedWindow)) { + return nullptr; + } + TextEventDispatcher* dispatcher = + mLastFocusedWindow->GetTextEventDispatcher(); + // nsIWidget::GetTextEventDispatcher() shouldn't return nullptr. + MOZ_RELEASE_ASSERT(dispatcher); + return dispatcher; +} + +nsIMEUpdatePreference +IMContextWrapper::GetIMEUpdatePreference() const +{ + // While a plugin has focus, IMContextWrapper doesn't need any + // notifications. + if (mInputContext.mIMEState.mEnabled == IMEState::PLUGIN) { + return nsIMEUpdatePreference(); + } + + nsIMEUpdatePreference::Notifications notifications = + nsIMEUpdatePreference::NOTIFY_NOTHING; + // If it's not enabled, we don't need position change notification. + if (IsEnabled()) { + notifications |= nsIMEUpdatePreference::NOTIFY_POSITION_CHANGE; + } + nsIMEUpdatePreference updatePreference(notifications); + return updatePreference; +} + +void +IMContextWrapper::OnDestroyWindow(nsWindow* aWindow) +{ + MOZ_LOG(gGtkIMLog, LogLevel::Info, + ("0x%p OnDestroyWindow(aWindow=0x%p), mLastFocusedWindow=0x%p, " + "mOwnerWindow=0x%p, mLastFocusedModule=0x%p", + this, aWindow, mLastFocusedWindow, mOwnerWindow, sLastFocusedContext)); + + NS_PRECONDITION(aWindow, "aWindow must not be null"); + + if (mLastFocusedWindow == aWindow) { + EndIMEComposition(aWindow); + if (mIsIMFocused) { + Blur(); + } + mLastFocusedWindow = nullptr; + } + + if (mOwnerWindow != aWindow) { + return; + } + + if (sLastFocusedContext == this) { + sLastFocusedContext = nullptr; + } + + /** + * NOTE: + * The given window is the owner of this, so, we must release the + * contexts now. But that might be referred from other nsWindows + * (they are children of this. But we don't know why there are the + * cases). So, we need to clear the pointers that refers to contexts + * and this if the other referrers are still alive. See bug 349727. + */ + if (mContext) { + PrepareToDestroyContext(mContext); + gtk_im_context_set_client_window(mContext, nullptr); + g_object_unref(mContext); + mContext = nullptr; + } + + if (mSimpleContext) { + gtk_im_context_set_client_window(mSimpleContext, nullptr); + g_object_unref(mSimpleContext); + mSimpleContext = nullptr; + } + + if (mDummyContext) { + // mContext and mDummyContext have the same slaveType and signal_data + // so no need for another workaround_gtk_im_display_closed. + gtk_im_context_set_client_window(mDummyContext, nullptr); + g_object_unref(mDummyContext); + mDummyContext = nullptr; + } + + if (NS_WARN_IF(mComposingContext)) { + g_object_unref(mComposingContext); + mComposingContext = nullptr; + } + + mOwnerWindow = nullptr; + mLastFocusedWindow = nullptr; + mInputContext.mIMEState.mEnabled = IMEState::DISABLED; + + MOZ_LOG(gGtkIMLog, LogLevel::Debug, + ("0x%p OnDestroyWindow(), succeeded, Completely destroyed", + this)); +} + +// Work around gtk bug http://bugzilla.gnome.org/show_bug.cgi?id=483223: +// (and the similar issue of GTK+ IIIM) +// The GTK+ XIM and IIIM modules register handlers for the "closed" signal +// on the display, but: +// * The signal handlers are not disconnected when the module is unloaded. +// +// The GTK+ XIM module has another problem: +// * When the signal handler is run (with the module loaded) it tries +// XFree (and fails) on a pointer that did not come from Xmalloc. +// +// To prevent these modules from being unloaded, use static variables to +// hold ref of GtkIMContext class. +// For GTK+ XIM module, to prevent the signal handler from being run, +// find the signal handlers and remove them. +// +// GtkIMContextXIMs share XOpenIM connections and display closed signal +// handlers (where possible). + +void +IMContextWrapper::PrepareToDestroyContext(GtkIMContext* aContext) +{ +#if (MOZ_WIDGET_GTK == 2) + GtkIMMulticontext *multicontext = GTK_IM_MULTICONTEXT(aContext); + GtkIMContext *slave = multicontext->slave; +#else + GtkIMContext *slave = nullptr; //TODO GTK3 +#endif + if (!slave) { + return; + } + + GType slaveType = G_TYPE_FROM_INSTANCE(slave); + const gchar *im_type_name = g_type_name(slaveType); + if (strcmp(im_type_name, "GtkIMContextIIIM") == 0) { + // Add a reference to prevent the IIIM module from being unloaded + static gpointer gtk_iiim_context_class = + g_type_class_ref(slaveType); + // Mute unused variable warning: + (void)gtk_iiim_context_class; + } +} + +void +IMContextWrapper::OnFocusWindow(nsWindow* aWindow) +{ + if (MOZ_UNLIKELY(IsDestroyed())) { + return; + } + + MOZ_LOG(gGtkIMLog, LogLevel::Info, + ("0x%p OnFocusWindow(aWindow=0x%p), mLastFocusedWindow=0x%p", + this, aWindow, mLastFocusedWindow)); + mLastFocusedWindow = aWindow; + Focus(); +} + +void +IMContextWrapper::OnBlurWindow(nsWindow* aWindow) +{ + if (MOZ_UNLIKELY(IsDestroyed())) { + return; + } + + MOZ_LOG(gGtkIMLog, LogLevel::Info, + ("0x%p OnBlurWindow(aWindow=0x%p), mLastFocusedWindow=0x%p, " + "mIsIMFocused=%s", + this, aWindow, mLastFocusedWindow, ToChar(mIsIMFocused))); + + if (!mIsIMFocused || mLastFocusedWindow != aWindow) { + return; + } + + Blur(); +} + +bool +IMContextWrapper::OnKeyEvent(nsWindow* aCaller, + GdkEventKey* aEvent, + bool aKeyDownEventWasSent /* = false */) +{ + NS_PRECONDITION(aEvent, "aEvent must be non-null"); + + if (!mInputContext.mIMEState.MaybeEditable() || + MOZ_UNLIKELY(IsDestroyed())) { + return false; + } + + MOZ_LOG(gGtkIMLog, LogLevel::Info, + ("0x%p OnKeyEvent(aCaller=0x%p, aKeyDownEventWasSent=%s), " + "mCompositionState=%s, current context=0x%p, active context=0x%p, " + "aEvent(0x%p): { type=%s, keyval=%s, unicode=0x%X }", + this, aCaller, ToChar(aKeyDownEventWasSent), + GetCompositionStateName(), GetCurrentContext(), GetActiveContext(), + aEvent, GetEventType(aEvent), gdk_keyval_name(aEvent->keyval), + gdk_keyval_to_unicode(aEvent->keyval))); + + if (aCaller != mLastFocusedWindow) { + MOZ_LOG(gGtkIMLog, LogLevel::Error, + ("0x%p OnKeyEvent(), FAILED, the caller isn't focused " + "window, mLastFocusedWindow=0x%p", + this, mLastFocusedWindow)); + return false; + } + + // Even if old IM context has composition, key event should be sent to + // current context since the user expects so. + GtkIMContext* currentContext = GetCurrentContext(); + if (MOZ_UNLIKELY(!currentContext)) { + MOZ_LOG(gGtkIMLog, LogLevel::Error, + ("0x%p OnKeyEvent(), FAILED, there are no context", + this)); + return false; + } + + if (mSetCursorPositionOnKeyEvent) { + SetCursorPosition(currentContext); + mSetCursorPositionOnKeyEvent = false; + } + + mKeyDownEventWasSent = aKeyDownEventWasSent; + mFilterKeyEvent = true; + mProcessingKeyEvent = aEvent; + gboolean isFiltered = + gtk_im_context_filter_keypress(currentContext, aEvent); + mProcessingKeyEvent = nullptr; + + // We filter the key event if the event was not committed (because + // it's probably part of a composition) or if the key event was + // committed _and_ changed. This way we still let key press + // events go through as simple key press events instead of + // composed characters. + bool filterThisEvent = isFiltered && mFilterKeyEvent; + + if (IsComposingOnCurrentContext() && !isFiltered) { + if (aEvent->type == GDK_KEY_PRESS) { + if (!mDispatchedCompositionString.IsEmpty()) { + // If there is composition string, we shouldn't dispatch + // any keydown events during composition. + filterThisEvent = true; + } else { + // A Hangul input engine for SCIM doesn't emit preedit_end + // signal even when composition string becomes empty. On the + // other hand, we should allow to make composition with empty + // string for other languages because there *might* be such + // IM. For compromising this issue, we should dispatch + // compositionend event, however, we don't need to reset IM + // actually. + DispatchCompositionCommitEvent(currentContext, &EmptyString()); + filterThisEvent = false; + } + } else { + // Key release event may not be consumed by IM, however, we + // shouldn't dispatch any keyup event during composition. + filterThisEvent = true; + } + } + + MOZ_LOG(gGtkIMLog, LogLevel::Debug, + ("0x%p OnKeyEvent(), succeeded, filterThisEvent=%s " + "(isFiltered=%s, mFilterKeyEvent=%s), mCompositionState=%s", + this, ToChar(filterThisEvent), ToChar(isFiltered), + ToChar(mFilterKeyEvent), GetCompositionStateName())); + + return filterThisEvent; +} + +void +IMContextWrapper::OnFocusChangeInGecko(bool aFocus) +{ + MOZ_LOG(gGtkIMLog, LogLevel::Info, + ("0x%p OnFocusChangeInGecko(aFocus=%s), " + "mCompositionState=%s, mIsIMFocused=%s", + this, ToChar(aFocus), GetCompositionStateName(), + ToChar(mIsIMFocused))); + + // We shouldn't carry over the removed string to another editor. + mSelectedString.Truncate(); + mSelection.Clear(); +} + +void +IMContextWrapper::ResetIME() +{ + MOZ_LOG(gGtkIMLog, LogLevel::Info, + ("0x%p ResetIME(), mCompositionState=%s, mIsIMFocused=%s", + this, GetCompositionStateName(), ToChar(mIsIMFocused))); + + GtkIMContext* activeContext = GetActiveContext(); + if (MOZ_UNLIKELY(!activeContext)) { + MOZ_LOG(gGtkIMLog, LogLevel::Error, + ("0x%p ResetIME(), FAILED, there are no context", + this)); + return; + } + + RefPtr<IMContextWrapper> kungFuDeathGrip(this); + RefPtr<nsWindow> lastFocusedWindow(mLastFocusedWindow); + + mPendingResettingIMContext = false; + gtk_im_context_reset(activeContext); + + // The last focused window might have been destroyed by a DOM event handler + // which was called by us during a call of gtk_im_context_reset(). + if (!lastFocusedWindow || + NS_WARN_IF(lastFocusedWindow != mLastFocusedWindow) || + lastFocusedWindow->Destroyed()) { + return; + } + + nsAutoString compositionString; + GetCompositionString(activeContext, compositionString); + + MOZ_LOG(gGtkIMLog, LogLevel::Debug, + ("0x%p ResetIME() called gtk_im_context_reset(), " + "activeContext=0x%p, mCompositionState=%s, compositionString=%s, " + "mIsIMFocused=%s", + this, activeContext, GetCompositionStateName(), + NS_ConvertUTF16toUTF8(compositionString).get(), + ToChar(mIsIMFocused))); + + // XXX IIIMF (ATOK X3 which is one of the Language Engine of it is still + // used in Japan!) sends only "preedit_changed" signal with empty + // composition string synchronously. Therefore, if composition string + // is now empty string, we should assume that the IME won't send + // "commit" signal. + if (IsComposing() && compositionString.IsEmpty()) { + // WARNING: The widget might have been gone after this. + DispatchCompositionCommitEvent(activeContext, &EmptyString()); + } +} + +nsresult +IMContextWrapper::EndIMEComposition(nsWindow* aCaller) +{ + if (MOZ_UNLIKELY(IsDestroyed())) { + return NS_OK; + } + + MOZ_LOG(gGtkIMLog, LogLevel::Info, + ("0x%p EndIMEComposition(aCaller=0x%p), " + "mCompositionState=%s", + this, aCaller, GetCompositionStateName())); + + if (aCaller != mLastFocusedWindow) { + MOZ_LOG(gGtkIMLog, LogLevel::Error, + ("0x%p EndIMEComposition(), FAILED, the caller isn't " + "focused window, mLastFocusedWindow=0x%p", + this, mLastFocusedWindow)); + return NS_OK; + } + + if (!IsComposing()) { + return NS_OK; + } + + // Currently, GTK has API neither to commit nor to cancel composition + // forcibly. Therefore, TextComposition will recompute commit string for + // the request even if native IME will cause unexpected commit string. + // So, we don't need to emulate commit or cancel composition with + // proper composition events. + // XXX ResetIME() might not enough for finishing compositoin on some + // environments. We should emulate focus change too because some IMEs + // may commit or cancel composition at blur. + ResetIME(); + + return NS_OK; +} + +void +IMContextWrapper::OnLayoutChange() +{ + if (MOZ_UNLIKELY(IsDestroyed())) { + return; + } + + if (IsComposing()) { + SetCursorPosition(GetActiveContext()); + } else { + // If not composing, candidate window position is updated before key + // down + mSetCursorPositionOnKeyEvent = true; + } + mLayoutChanged = true; +} + +void +IMContextWrapper::OnUpdateComposition() +{ + if (MOZ_UNLIKELY(IsDestroyed())) { + return; + } + + if (!IsComposing()) { + // Composition has been committed. So we need update selection for + // caret later + mSelection.Clear(); + EnsureToCacheSelection(); + mSetCursorPositionOnKeyEvent = true; + } + + // If we've already set candidate window position, we don't need to update + // the position with update composition notification. + if (!mLayoutChanged) { + SetCursorPosition(GetActiveContext()); + } +} + +void +IMContextWrapper::SetInputContext(nsWindow* aCaller, + const InputContext* aContext, + const InputContextAction* aAction) +{ + if (MOZ_UNLIKELY(IsDestroyed())) { + return; + } + + MOZ_LOG(gGtkIMLog, LogLevel::Info, + ("0x%p SetInputContext(aCaller=0x%p, aContext={ mIMEState={ " + "mEnabled=%s }, mHTMLInputType=%s })", + this, aCaller, GetEnabledStateName(aContext->mIMEState.mEnabled), + NS_ConvertUTF16toUTF8(aContext->mHTMLInputType).get())); + + if (aCaller != mLastFocusedWindow) { + MOZ_LOG(gGtkIMLog, LogLevel::Error, + ("0x%p SetInputContext(), FAILED, " + "the caller isn't focused window, mLastFocusedWindow=0x%p", + this, mLastFocusedWindow)); + return; + } + + if (!mContext) { + MOZ_LOG(gGtkIMLog, LogLevel::Error, + ("0x%p SetInputContext(), FAILED, " + "there are no context", + this)); + return; + } + + + if (sLastFocusedContext != this) { + mInputContext = *aContext; + MOZ_LOG(gGtkIMLog, LogLevel::Debug, + ("0x%p SetInputContext(), succeeded, " + "but we're not active", + this)); + return; + } + + bool changingEnabledState = + aContext->mIMEState.mEnabled != mInputContext.mIMEState.mEnabled || + aContext->mHTMLInputType != mInputContext.mHTMLInputType; + + // Release current IME focus if IME is enabled. + if (changingEnabledState && mInputContext.mIMEState.MaybeEditable()) { + EndIMEComposition(mLastFocusedWindow); + Blur(); + } + + mInputContext = *aContext; + + if (changingEnabledState) { +#if (MOZ_WIDGET_GTK == 3) + static bool sInputPurposeSupported = !gtk_check_version(3, 6, 0); + if (sInputPurposeSupported && mInputContext.mIMEState.MaybeEditable()) { + GtkIMContext* currentContext = GetCurrentContext(); + if (currentContext) { + GtkInputPurpose purpose = GTK_INPUT_PURPOSE_FREE_FORM; + const nsString& inputType = mInputContext.mHTMLInputType; + // Password case has difficult issue. Desktop IMEs disable + // composition if input-purpose is password. + // For disabling IME on |ime-mode: disabled;|, we need to check + // mEnabled value instead of inputType value. This hack also + // enables composition on + // <input type="password" style="ime-mode: enabled;">. + // This is right behavior of ime-mode on desktop. + // + // On the other hand, IME for tablet devices may provide a + // specific software keyboard for password field. If so, + // the behavior might look strange on both: + // <input type="text" style="ime-mode: disabled;"> + // <input type="password" style="ime-mode: enabled;"> + // + // Temporarily, we should focus on desktop environment for now. + // I.e., let's ignore tablet devices for now. When somebody + // reports actual trouble on tablet devices, we should try to + // look for a way to solve actual problem. + if (mInputContext.mIMEState.mEnabled == IMEState::PASSWORD) { + purpose = GTK_INPUT_PURPOSE_PASSWORD; + } else if (inputType.EqualsLiteral("email")) { + purpose = GTK_INPUT_PURPOSE_EMAIL; + } else if (inputType.EqualsLiteral("url")) { + purpose = GTK_INPUT_PURPOSE_URL; + } else if (inputType.EqualsLiteral("tel")) { + purpose = GTK_INPUT_PURPOSE_PHONE; + } else if (inputType.EqualsLiteral("number")) { + purpose = GTK_INPUT_PURPOSE_NUMBER; + } + + g_object_set(currentContext, "input-purpose", purpose, nullptr); + } + } +#endif // #if (MOZ_WIDGET_GTK == 3) + + // Even when aState is not enabled state, we need to set IME focus. + // Because some IMs are updating the status bar of them at this time. + // Be aware, don't use aWindow here because this method shouldn't move + // focus actually. + Focus(); + + // XXX Should we call Blur() when it's not editable? E.g., it might be + // better to close VKB automatically. + } +} + +InputContext +IMContextWrapper::GetInputContext() +{ + mInputContext.mIMEState.mOpen = IMEState::OPEN_STATE_NOT_SUPPORTED; + return mInputContext; +} + +GtkIMContext* +IMContextWrapper::GetCurrentContext() const +{ + if (IsEnabled()) { + return mContext; + } + if (mInputContext.mIMEState.mEnabled == IMEState::PASSWORD) { + return mSimpleContext; + } + return mDummyContext; +} + +bool +IMContextWrapper::IsValidContext(GtkIMContext* aContext) const +{ + if (!aContext) { + return false; + } + return aContext == mContext || + aContext == mSimpleContext || + aContext == mDummyContext; +} + +bool +IMContextWrapper::IsEnabled() const +{ + return mInputContext.mIMEState.mEnabled == IMEState::ENABLED || + mInputContext.mIMEState.mEnabled == IMEState::PLUGIN || + (!sUseSimpleContext && + mInputContext.mIMEState.mEnabled == IMEState::PASSWORD); +} + +void +IMContextWrapper::Focus() +{ + MOZ_LOG(gGtkIMLog, LogLevel::Info, + ("0x%p Focus(), sLastFocusedContext=0x%p", + this, sLastFocusedContext)); + + if (mIsIMFocused) { + NS_ASSERTION(sLastFocusedContext == this, + "We're not active, but the IM was focused?"); + return; + } + + GtkIMContext* currentContext = GetCurrentContext(); + if (!currentContext) { + MOZ_LOG(gGtkIMLog, LogLevel::Error, + ("0x%p Focus(), FAILED, there are no context", + this)); + return; + } + + if (sLastFocusedContext && sLastFocusedContext != this) { + sLastFocusedContext->Blur(); + } + + sLastFocusedContext = this; + + gtk_im_context_focus_in(currentContext); + mIsIMFocused = true; + mSetCursorPositionOnKeyEvent = true; + + if (!IsEnabled()) { + // We should release IME focus for uim and scim. + // These IMs are using snooper that is released at losing focus. + Blur(); + } +} + +void +IMContextWrapper::Blur() +{ + MOZ_LOG(gGtkIMLog, LogLevel::Info, + ("0x%p Blur(), mIsIMFocused=%s", + this, ToChar(mIsIMFocused))); + + if (!mIsIMFocused) { + return; + } + + GtkIMContext* currentContext = GetCurrentContext(); + if (!currentContext) { + MOZ_LOG(gGtkIMLog, LogLevel::Error, + ("0x%p Blur(), FAILED, there are no context", + this)); + return; + } + + gtk_im_context_focus_out(currentContext); + mIsIMFocused = false; +} + +void +IMContextWrapper::OnSelectionChange(nsWindow* aCaller, + const IMENotification& aIMENotification) +{ + mSelection.Assign(aIMENotification); + bool retrievedSurroundingSignalReceived = + mRetrieveSurroundingSignalReceived; + mRetrieveSurroundingSignalReceived = false; + + if (MOZ_UNLIKELY(IsDestroyed())) { + return; + } + + const IMENotification::SelectionChangeDataBase& selectionChangeData = + aIMENotification.mSelectionChangeData; + + MOZ_LOG(gGtkIMLog, LogLevel::Info, + ("0x%p OnSelectionChange(aCaller=0x%p, aIMENotification={ " + "mSelectionChangeData={ mOffset=%u, Length()=%u, mReversed=%s, " + "mWritingMode=%s, mCausedByComposition=%s, " + "mCausedBySelectionEvent=%s, mOccurredDuringComposition=%s " + "} }), mCompositionState=%s, mIsDeletingSurrounding=%s, " + "mRetrieveSurroundingSignalReceived=%s", + this, aCaller, selectionChangeData.mOffset, + selectionChangeData.Length(), + ToChar(selectionChangeData.mReversed), + GetWritingModeName(selectionChangeData.GetWritingMode()).get(), + ToChar(selectionChangeData.mCausedByComposition), + ToChar(selectionChangeData.mCausedBySelectionEvent), + ToChar(selectionChangeData.mOccurredDuringComposition), + GetCompositionStateName(), ToChar(mIsDeletingSurrounding), + ToChar(retrievedSurroundingSignalReceived))); + + if (aCaller != mLastFocusedWindow) { + MOZ_LOG(gGtkIMLog, LogLevel::Error, + ("0x%p OnSelectionChange(), FAILED, " + "the caller isn't focused window, mLastFocusedWindow=0x%p", + this, mLastFocusedWindow)); + return; + } + + if (!IsComposing()) { + // Now we have no composition (mostly situation on calling this method) + // If we have it, it will set by + // NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED. + mSetCursorPositionOnKeyEvent = true; + } + + // The focused editor might have placeholder text with normal text node. + // In such case, the text node must be removed from a compositionstart + // event handler. So, we're dispatching eCompositionStart, + // we should ignore selection change notification. + if (mCompositionState == eCompositionState_CompositionStartDispatched) { + if (NS_WARN_IF(!mSelection.IsValid())) { + MOZ_LOG(gGtkIMLog, LogLevel::Error, + ("0x%p OnSelectionChange(), FAILED, " + "new offset is too large, cannot keep composing", + this)); + } else { + // Modify the selection start offset with new offset. + mCompositionStart = mSelection.mOffset; + // XXX We should modify mSelectedString? But how? + MOZ_LOG(gGtkIMLog, LogLevel::Debug, + ("0x%p OnSelectionChange(), ignored, mCompositionStart " + "is updated to %u, the selection change doesn't cause " + "resetting IM context", + this, mCompositionStart)); + // And don't reset the IM context. + return; + } + // Otherwise, reset the IM context due to impossible to keep composing. + } + + // If the selection change is caused by deleting surrounding text, + // we shouldn't need to notify IME of selection change. + if (mIsDeletingSurrounding) { + return; + } + + bool occurredBeforeComposition = + IsComposing() && !selectionChangeData.mOccurredDuringComposition && + !selectionChangeData.mCausedByComposition; + if (occurredBeforeComposition) { + mPendingResettingIMContext = true; + } + + // When the selection change is caused by dispatching composition event, + // selection set event and/or occurred before starting current composition, + // we shouldn't notify IME of that and commit existing composition. + if (!selectionChangeData.mCausedByComposition && + !selectionChangeData.mCausedBySelectionEvent && + !occurredBeforeComposition) { + // Hack for ibus-pinyin. ibus-pinyin will synthesize a set of + // composition which commits with empty string after calling + // gtk_im_context_reset(). Therefore, selecting text causes + // unexpectedly removing it. For preventing it but not breaking the + // other IMEs which use surrounding text, we should call it only when + // surrounding text has been retrieved after last selection range was + // set. If it's not retrieved, that means that current IME doesn't + // have any content cache, so, it must not need the notification of + // selection change. + if (IsComposing() || retrievedSurroundingSignalReceived) { + ResetIME(); + } + } +} + +/* static */ +void +IMContextWrapper::OnStartCompositionCallback(GtkIMContext* aContext, + IMContextWrapper* aModule) +{ + aModule->OnStartCompositionNative(aContext); +} + +void +IMContextWrapper::OnStartCompositionNative(GtkIMContext* aContext) +{ + MOZ_LOG(gGtkIMLog, LogLevel::Info, + ("0x%p OnStartCompositionNative(aContext=0x%p), " + "current context=0x%p", + this, aContext, GetCurrentContext())); + + // See bug 472635, we should do nothing if IM context doesn't match. + if (GetCurrentContext() != aContext) { + MOZ_LOG(gGtkIMLog, LogLevel::Error, + ("0x%p OnStartCompositionNative(), FAILED, " + "given context doesn't match", + this)); + return; + } + + mComposingContext = static_cast<GtkIMContext*>(g_object_ref(aContext)); + + if (!DispatchCompositionStart(aContext)) { + return; + } + mCompositionTargetRange.mOffset = mCompositionStart; + mCompositionTargetRange.mLength = 0; +} + +/* static */ +void +IMContextWrapper::OnEndCompositionCallback(GtkIMContext* aContext, + IMContextWrapper* aModule) +{ + aModule->OnEndCompositionNative(aContext); +} + +void +IMContextWrapper::OnEndCompositionNative(GtkIMContext* aContext) +{ + MOZ_LOG(gGtkIMLog, LogLevel::Info, + ("0x%p OnEndCompositionNative(aContext=0x%p)", + this, aContext)); + + // See bug 472635, we should do nothing if IM context doesn't match. + // Note that if this is called after focus move, the context may different + // from any our owning context. + if (!IsValidContext(aContext)) { + MOZ_LOG(gGtkIMLog, LogLevel::Error, + ("0x%p OnEndCompositionNative(), FAILED, " + "given context doesn't match with any context", + this)); + return; + } + + g_object_unref(mComposingContext); + mComposingContext = nullptr; + + // If we already handled the commit event, we should do nothing here. + if (IsComposing()) { + if (!DispatchCompositionCommitEvent(aContext)) { + // If the widget is destroyed, we should do nothing anymore. + return; + } + } + + if (mPendingResettingIMContext) { + ResetIME(); + } +} + +/* static */ +void +IMContextWrapper::OnChangeCompositionCallback(GtkIMContext* aContext, + IMContextWrapper* aModule) +{ + aModule->OnChangeCompositionNative(aContext); +} + +void +IMContextWrapper::OnChangeCompositionNative(GtkIMContext* aContext) +{ + MOZ_LOG(gGtkIMLog, LogLevel::Info, + ("0x%p OnChangeCompositionNative(aContext=0x%p)", + this, aContext)); + + // See bug 472635, we should do nothing if IM context doesn't match. + // Note that if this is called after focus move, the context may different + // from any our owning context. + if (!IsValidContext(aContext)) { + MOZ_LOG(gGtkIMLog, LogLevel::Error, + ("0x%p OnChangeCompositionNative(), FAILED, " + "given context doesn't match with any context", + this)); + return; + } + + nsAutoString compositionString; + GetCompositionString(aContext, compositionString); + if (!IsComposing() && compositionString.IsEmpty()) { + mDispatchedCompositionString.Truncate(); + return; // Don't start the composition with empty string. + } + + // Be aware, widget can be gone + DispatchCompositionChangeEvent(aContext, compositionString); +} + +/* static */ +gboolean +IMContextWrapper::OnRetrieveSurroundingCallback(GtkIMContext* aContext, + IMContextWrapper* aModule) +{ + return aModule->OnRetrieveSurroundingNative(aContext); +} + +gboolean +IMContextWrapper::OnRetrieveSurroundingNative(GtkIMContext* aContext) +{ + MOZ_LOG(gGtkIMLog, LogLevel::Info, + ("0x%p OnRetrieveSurroundingNative(aContext=0x%p), " + "current context=0x%p", + this, aContext, GetCurrentContext())); + + // See bug 472635, we should do nothing if IM context doesn't match. + if (GetCurrentContext() != aContext) { + MOZ_LOG(gGtkIMLog, LogLevel::Error, + ("0x%p OnRetrieveSurroundingNative(), FAILED, " + "given context doesn't match", + this)); + return FALSE; + } + + nsAutoString uniStr; + uint32_t cursorPos; + if (NS_FAILED(GetCurrentParagraph(uniStr, cursorPos))) { + return FALSE; + } + + NS_ConvertUTF16toUTF8 utf8Str(nsDependentSubstring(uniStr, 0, cursorPos)); + uint32_t cursorPosInUTF8 = utf8Str.Length(); + AppendUTF16toUTF8(nsDependentSubstring(uniStr, cursorPos), utf8Str); + gtk_im_context_set_surrounding(aContext, utf8Str.get(), utf8Str.Length(), + cursorPosInUTF8); + mRetrieveSurroundingSignalReceived = true; + return TRUE; +} + +/* static */ +gboolean +IMContextWrapper::OnDeleteSurroundingCallback(GtkIMContext* aContext, + gint aOffset, + gint aNChars, + IMContextWrapper* aModule) +{ + return aModule->OnDeleteSurroundingNative(aContext, aOffset, aNChars); +} + +gboolean +IMContextWrapper::OnDeleteSurroundingNative(GtkIMContext* aContext, + gint aOffset, + gint aNChars) +{ + MOZ_LOG(gGtkIMLog, LogLevel::Info, + ("0x%p OnDeleteSurroundingNative(aContext=0x%p, aOffset=%d, " + "aNChar=%d), current context=0x%p", + this, aContext, aOffset, aNChars, GetCurrentContext())); + + // See bug 472635, we should do nothing if IM context doesn't match. + if (GetCurrentContext() != aContext) { + MOZ_LOG(gGtkIMLog, LogLevel::Error, + ("0x%p OnDeleteSurroundingNative(), FAILED, " + "given context doesn't match", + this)); + return FALSE; + } + + AutoRestore<bool> saveDeletingSurrounding(mIsDeletingSurrounding); + mIsDeletingSurrounding = true; + if (NS_SUCCEEDED(DeleteText(aContext, aOffset, (uint32_t)aNChars))) { + return TRUE; + } + + // failed + MOZ_LOG(gGtkIMLog, LogLevel::Error, + ("0x%p OnDeleteSurroundingNative(), FAILED, " + "cannot delete text", + this)); + return FALSE; +} + +/* static */ +void +IMContextWrapper::OnCommitCompositionCallback(GtkIMContext* aContext, + const gchar* aString, + IMContextWrapper* aModule) +{ + aModule->OnCommitCompositionNative(aContext, aString); +} + +void +IMContextWrapper::OnCommitCompositionNative(GtkIMContext* aContext, + const gchar* aUTF8Char) +{ + const gchar emptyStr = 0; + const gchar *commitString = aUTF8Char ? aUTF8Char : &emptyStr; + + MOZ_LOG(gGtkIMLog, LogLevel::Info, + ("0x%p OnCommitCompositionNative(aContext=0x%p), " + "current context=0x%p, active context=0x%p, commitString=\"%s\", " + "mProcessingKeyEvent=0x%p, IsComposingOn(aContext)=%s", + this, aContext, GetCurrentContext(), GetActiveContext(), commitString, + mProcessingKeyEvent, ToChar(IsComposingOn(aContext)))); + + // See bug 472635, we should do nothing if IM context doesn't match. + if (!IsValidContext(aContext)) { + MOZ_LOG(gGtkIMLog, LogLevel::Error, + ("0x%p OnCommitCompositionNative(), FAILED, " + "given context doesn't match", + this)); + return; + } + + // If we are not in composition and committing with empty string, + // we need to do nothing because if we continued to handle this + // signal, we would dispatch compositionstart, text, compositionend + // events with empty string. Of course, they are unnecessary events + // for Web applications and our editor. + if (!IsComposingOn(aContext) && !commitString[0]) { + return; + } + + // If IME doesn't change their keyevent that generated this commit, + // don't send it through XIM - just send it as a normal key press + // event. + // NOTE: While a key event is being handled, this might be caused on + // current context. Otherwise, this may be caused on active context. + if (!IsComposingOn(aContext) && mProcessingKeyEvent && + aContext == GetCurrentContext()) { + char keyval_utf8[8]; /* should have at least 6 bytes of space */ + gint keyval_utf8_len; + guint32 keyval_unicode; + + keyval_unicode = gdk_keyval_to_unicode(mProcessingKeyEvent->keyval); + keyval_utf8_len = g_unichar_to_utf8(keyval_unicode, keyval_utf8); + keyval_utf8[keyval_utf8_len] = '\0'; + + if (!strcmp(commitString, keyval_utf8)) { + MOZ_LOG(gGtkIMLog, LogLevel::Info, + ("0x%p OnCommitCompositionNative(), " + "we'll send normal key event", + this)); + mFilterKeyEvent = false; + return; + } + } + + NS_ConvertUTF8toUTF16 str(commitString); + // Be aware, widget can be gone + DispatchCompositionCommitEvent(aContext, &str); +} + +void +IMContextWrapper::GetCompositionString(GtkIMContext* aContext, + nsAString& aCompositionString) +{ + gchar *preedit_string; + gint cursor_pos; + PangoAttrList *feedback_list; + gtk_im_context_get_preedit_string(aContext, &preedit_string, + &feedback_list, &cursor_pos); + if (preedit_string && *preedit_string) { + CopyUTF8toUTF16(preedit_string, aCompositionString); + } else { + aCompositionString.Truncate(); + } + + MOZ_LOG(gGtkIMLog, LogLevel::Info, + ("0x%p GetCompositionString(aContext=0x%p), " + "aCompositionString=\"%s\"", + this, aContext, preedit_string)); + + pango_attr_list_unref(feedback_list); + g_free(preedit_string); +} + +bool +IMContextWrapper::DispatchCompositionStart(GtkIMContext* aContext) +{ + MOZ_LOG(gGtkIMLog, LogLevel::Info, + ("0x%p DispatchCompositionStart(aContext=0x%p)", + this, aContext)); + + if (IsComposing()) { + MOZ_LOG(gGtkIMLog, LogLevel::Error, + ("0x%p DispatchCompositionStart(), FAILED, " + "we're already in composition", + this)); + return true; + } + + if (!mLastFocusedWindow) { + MOZ_LOG(gGtkIMLog, LogLevel::Error, + ("0x%p DispatchCompositionStart(), FAILED, " + "there are no focused window in this module", + this)); + return false; + } + + if (NS_WARN_IF(!EnsureToCacheSelection())) { + MOZ_LOG(gGtkIMLog, LogLevel::Error, + ("0x%p DispatchCompositionStart(), FAILED, " + "cannot query the selection offset", + this)); + return false; + } + + // Keep the last focused window alive + RefPtr<nsWindow> lastFocusedWindow(mLastFocusedWindow); + + // XXX The composition start point might be changed by composition events + // even though we strongly hope it doesn't happen. + // Every composition event should have the start offset for the result + // because it may high cost if we query the offset every time. + mCompositionStart = mSelection.mOffset; + mDispatchedCompositionString.Truncate(); + + if (mProcessingKeyEvent && !mKeyDownEventWasSent && + mProcessingKeyEvent->type == GDK_KEY_PRESS) { + // If this composition is started by a native keydown event, we need to + // dispatch our keydown event here (before composition start). + bool isCancelled; + mLastFocusedWindow->DispatchKeyDownEvent(mProcessingKeyEvent, + &isCancelled); + MOZ_LOG(gGtkIMLog, LogLevel::Debug, + ("0x%p DispatchCompositionStart(), FAILED, keydown event " + "is dispatched", + this)); + if (lastFocusedWindow->IsDestroyed() || + lastFocusedWindow != mLastFocusedWindow) { + MOZ_LOG(gGtkIMLog, LogLevel::Error, + ("0x%p DispatchCompositionStart(), FAILED, the focused " + "widget was destroyed/changed by keydown event", + this)); + return false; + } + } + + RefPtr<TextEventDispatcher> dispatcher = GetTextEventDispatcher(); + nsresult rv = dispatcher->BeginNativeInputTransaction(); + if (NS_WARN_IF(NS_FAILED(rv))) { + MOZ_LOG(gGtkIMLog, LogLevel::Error, + ("0x%p DispatchCompositionStart(), FAILED, " + "due to BeginNativeInputTransaction() failure", + this)); + return false; + } + + MOZ_LOG(gGtkIMLog, LogLevel::Debug, + ("0x%p DispatchCompositionStart(), dispatching " + "compositionstart... (mCompositionStart=%u)", + this, mCompositionStart)); + mCompositionState = eCompositionState_CompositionStartDispatched; + nsEventStatus status; + dispatcher->StartComposition(status); + if (lastFocusedWindow->IsDestroyed() || + lastFocusedWindow != mLastFocusedWindow) { + MOZ_LOG(gGtkIMLog, LogLevel::Error, + ("0x%p DispatchCompositionStart(), FAILED, the focused " + "widget was destroyed/changed by compositionstart event", + this)); + return false; + } + + return true; +} + +bool +IMContextWrapper::DispatchCompositionChangeEvent( + GtkIMContext* aContext, + const nsAString& aCompositionString) +{ + MOZ_LOG(gGtkIMLog, LogLevel::Info, + ("0x%p DispatchCompositionChangeEvent(aContext=0x%p)", + this, aContext)); + + if (!mLastFocusedWindow) { + MOZ_LOG(gGtkIMLog, LogLevel::Error, + ("0x%p DispatchCompositionChangeEvent(), FAILED, " + "there are no focused window in this module", + this)); + return false; + } + + if (!IsComposing()) { + MOZ_LOG(gGtkIMLog, LogLevel::Debug, + ("0x%p DispatchCompositionChangeEvent(), the composition " + "wasn't started, force starting...", + this)); + if (!DispatchCompositionStart(aContext)) { + return false; + } + } + + RefPtr<TextEventDispatcher> dispatcher = GetTextEventDispatcher(); + nsresult rv = dispatcher->BeginNativeInputTransaction(); + if (NS_WARN_IF(NS_FAILED(rv))) { + MOZ_LOG(gGtkIMLog, LogLevel::Error, + ("0x%p DispatchCompositionChangeEvent(), FAILED, " + "due to BeginNativeInputTransaction() failure", + this)); + return false; + } + + // Store the selected string which will be removed by following + // compositionchange event. + if (mCompositionState == eCompositionState_CompositionStartDispatched) { + if (NS_WARN_IF(!EnsureToCacheSelection(&mSelectedString))) { + // XXX How should we behave in this case?? + } else { + // XXX We should assume, for now, any web applications don't change + // selection at handling this compositionchange event. + mCompositionStart = mSelection.mOffset; + } + } + + RefPtr<TextRangeArray> rangeArray = + CreateTextRangeArray(aContext, aCompositionString); + + rv = dispatcher->SetPendingComposition(aCompositionString, rangeArray); + if (NS_WARN_IF(NS_FAILED(rv))) { + MOZ_LOG(gGtkIMLog, LogLevel::Error, + ("0x%p DispatchCompositionChangeEvent(), FAILED, " + "due to SetPendingComposition() failure", + this)); + return false; + } + + mCompositionState = eCompositionState_CompositionChangeEventDispatched; + + // We cannot call SetCursorPosition for e10s-aware. + // DispatchEvent is async on e10s, so composition rect isn't updated now + // on tab parent. + mDispatchedCompositionString = aCompositionString; + mLayoutChanged = false; + mCompositionTargetRange.mOffset = + mCompositionStart + rangeArray->TargetClauseOffset(); + mCompositionTargetRange.mLength = rangeArray->TargetClauseLength(); + + RefPtr<nsWindow> lastFocusedWindow(mLastFocusedWindow); + nsEventStatus status; + rv = dispatcher->FlushPendingComposition(status); + if (NS_WARN_IF(NS_FAILED(rv))) { + MOZ_LOG(gGtkIMLog, LogLevel::Error, + ("0x%p DispatchCompositionChangeEvent(), FAILED, " + "due to FlushPendingComposition() failure", + this)); + return false; + } + + if (lastFocusedWindow->IsDestroyed() || + lastFocusedWindow != mLastFocusedWindow) { + MOZ_LOG(gGtkIMLog, LogLevel::Error, + ("0x%p DispatchCompositionChangeEvent(), FAILED, the " + "focused widget was destroyed/changed by " + "compositionchange event", + this)); + return false; + } + return true; +} + +bool +IMContextWrapper::DispatchCompositionCommitEvent( + GtkIMContext* aContext, + const nsAString* aCommitString) +{ + MOZ_LOG(gGtkIMLog, LogLevel::Info, + ("0x%p DispatchCompositionCommitEvent(aContext=0x%p, " + "aCommitString=0x%p, (\"%s\"))", + this, aContext, aCommitString, + aCommitString ? NS_ConvertUTF16toUTF8(*aCommitString).get() : "")); + + if (!mLastFocusedWindow) { + MOZ_LOG(gGtkIMLog, LogLevel::Error, + ("0x%p DispatchCompositionCommitEvent(), FAILED, " + "there are no focused window in this module", + this)); + return false; + } + + if (!IsComposing()) { + if (!aCommitString || aCommitString->IsEmpty()) { + MOZ_LOG(gGtkIMLog, LogLevel::Error, + ("0x%p DispatchCompositionCommitEvent(), FAILED, " + "there is no composition and empty commit string", + this)); + return true; + } + MOZ_LOG(gGtkIMLog, LogLevel::Debug, + ("0x%p DispatchCompositionCommitEvent(), " + "the composition wasn't started, force starting...", + this)); + if (!DispatchCompositionStart(aContext)) { + return false; + } + } + + RefPtr<TextEventDispatcher> dispatcher = GetTextEventDispatcher(); + nsresult rv = dispatcher->BeginNativeInputTransaction(); + if (NS_WARN_IF(NS_FAILED(rv))) { + MOZ_LOG(gGtkIMLog, LogLevel::Error, + ("0x%p DispatchCompositionCommitEvent(), FAILED, " + "due to BeginNativeInputTransaction() failure", + this)); + return false; + } + + RefPtr<nsWindow> lastFocusedWindow(mLastFocusedWindow); + + mCompositionState = eCompositionState_NotComposing; + mCompositionStart = UINT32_MAX; + mCompositionTargetRange.Clear(); + mDispatchedCompositionString.Truncate(); + + nsEventStatus status; + rv = dispatcher->CommitComposition(status, aCommitString); + if (NS_WARN_IF(NS_FAILED(rv))) { + MOZ_LOG(gGtkIMLog, LogLevel::Error, + ("0x%p DispatchCompositionChangeEvent(), FAILED, " + "due to CommitComposition() failure", + this)); + return false; + } + + if (lastFocusedWindow->IsDestroyed() || + lastFocusedWindow != mLastFocusedWindow) { + MOZ_LOG(gGtkIMLog, LogLevel::Error, + ("0x%p DispatchCompositionCommitEvent(), FAILED, " + "the focused widget was destroyed/changed by " + "compositioncommit event", + this)); + return false; + } + + return true; +} + +already_AddRefed<TextRangeArray> +IMContextWrapper::CreateTextRangeArray(GtkIMContext* aContext, + const nsAString& aCompositionString) +{ + MOZ_LOG(gGtkIMLog, LogLevel::Info, + ("0x%p CreateTextRangeArray(aContext=0x%p, " + "aCompositionString=\"%s\" (Length()=%u))", + this, aContext, NS_ConvertUTF16toUTF8(aCompositionString).get(), + aCompositionString.Length())); + + RefPtr<TextRangeArray> textRangeArray = new TextRangeArray(); + + gchar *preedit_string; + gint cursor_pos_in_chars; + PangoAttrList *feedback_list; + gtk_im_context_get_preedit_string(aContext, &preedit_string, + &feedback_list, &cursor_pos_in_chars); + if (!preedit_string || !*preedit_string) { + if (!aCompositionString.IsEmpty()) { + MOZ_LOG(gGtkIMLog, LogLevel::Error, + ("0x%p CreateTextRangeArray(), FAILED, due to " + "preedit_string is null", + this)); + } + pango_attr_list_unref(feedback_list); + g_free(preedit_string); + return textRangeArray.forget(); + } + + // Convert caret offset from offset in characters to offset in UTF-16 + // string. If we couldn't proper offset in UTF-16 string, we should + // assume that the caret is at the end of the composition string. + uint32_t caretOffsetInUTF16 = aCompositionString.Length(); + if (NS_WARN_IF(cursor_pos_in_chars < 0)) { + // Note that this case is undocumented. We should assume that the + // caret is at the end of the composition string. + } else if (cursor_pos_in_chars == 0) { + caretOffsetInUTF16 = 0; + } else { + gchar* charAfterCaret = + g_utf8_offset_to_pointer(preedit_string, cursor_pos_in_chars); + if (NS_WARN_IF(!charAfterCaret)) { + MOZ_LOG(gGtkIMLog, LogLevel::Warning, + ("0x%p CreateTextRangeArray(), failed to get UTF-8 " + "string before the caret (cursor_pos_in_chars=%d)", + this, cursor_pos_in_chars)); + } else { + glong caretOffset = 0; + gunichar2* utf16StrBeforeCaret = + g_utf8_to_utf16(preedit_string, charAfterCaret - preedit_string, + nullptr, &caretOffset, nullptr); + if (NS_WARN_IF(!utf16StrBeforeCaret) || + NS_WARN_IF(caretOffset < 0)) { + MOZ_LOG(gGtkIMLog, LogLevel::Warning, + ("0x%p CreateTextRangeArray(), WARNING, failed to " + "convert to UTF-16 string before the caret " + "(cursor_pos_in_chars=%d, caretOffset=%d)", + this, cursor_pos_in_chars, caretOffset)); + } else { + caretOffsetInUTF16 = static_cast<uint32_t>(caretOffset); + uint32_t compositionStringLength = aCompositionString.Length(); + if (NS_WARN_IF(caretOffsetInUTF16 > compositionStringLength)) { + MOZ_LOG(gGtkIMLog, LogLevel::Warning, + ("0x%p CreateTextRangeArray(), WARNING, " + "caretOffsetInUTF16=%u is larger than " + "compositionStringLength=%u", + this, caretOffsetInUTF16, compositionStringLength)); + caretOffsetInUTF16 = compositionStringLength; + } + } + if (utf16StrBeforeCaret) { + g_free(utf16StrBeforeCaret); + } + } + } + + PangoAttrIterator* iter; + iter = pango_attr_list_get_iterator(feedback_list); + if (!iter) { + MOZ_LOG(gGtkIMLog, LogLevel::Error, + ("0x%p CreateTextRangeArray(), FAILED, iterator couldn't " + "be allocated", + this)); + pango_attr_list_unref(feedback_list); + g_free(preedit_string); + return textRangeArray.forget(); + } + + uint32_t minOffsetOfClauses = aCompositionString.Length(); + do { + TextRange range; + if (!SetTextRange(iter, preedit_string, caretOffsetInUTF16, range)) { + continue; + } + MOZ_ASSERT(range.Length()); + minOffsetOfClauses = std::min(minOffsetOfClauses, range.mStartOffset); + textRangeArray->AppendElement(range); + } while (pango_attr_iterator_next(iter)); + + // If the IME doesn't define clause from the start of the composition, + // we should insert dummy clause information since TextRangeArray assumes + // that there must be a clause whose start is 0 when there is one or + // more clauses. + if (minOffsetOfClauses) { + TextRange dummyClause; + dummyClause.mStartOffset = 0; + dummyClause.mEndOffset = minOffsetOfClauses; + dummyClause.mRangeType = TextRangeType::eRawClause; + textRangeArray->InsertElementAt(0, dummyClause); + MOZ_LOG(gGtkIMLog, LogLevel::Warning, + ("0x%p CreateTextRangeArray(), inserting a dummy clause " + "at the beginning of the composition string mStartOffset=%u, " + "mEndOffset=%u, mRangeType=%s", + this, dummyClause.mStartOffset, dummyClause.mEndOffset, + ToChar(dummyClause.mRangeType))); + } + + TextRange range; + range.mStartOffset = range.mEndOffset = caretOffsetInUTF16; + range.mRangeType = TextRangeType::eCaret; + textRangeArray->AppendElement(range); + MOZ_LOG(gGtkIMLog, LogLevel::Debug, + ("0x%p CreateTextRangeArray(), mStartOffset=%u, " + "mEndOffset=%u, mRangeType=%s", + this, range.mStartOffset, range.mEndOffset, + ToChar(range.mRangeType))); + + pango_attr_iterator_destroy(iter); + pango_attr_list_unref(feedback_list); + g_free(preedit_string); + + return textRangeArray.forget(); +} + +/* static */ +nscolor +IMContextWrapper::ToNscolor(PangoAttrColor* aPangoAttrColor) +{ + PangoColor& pangoColor = aPangoAttrColor->color; + uint8_t r = pangoColor.red / 0x100; + uint8_t g = pangoColor.green / 0x100; + uint8_t b = pangoColor.blue / 0x100; + return NS_RGB(r, g, b); +} + +bool +IMContextWrapper::SetTextRange(PangoAttrIterator* aPangoAttrIter, + const gchar* aUTF8CompositionString, + uint32_t aUTF16CaretOffset, + TextRange& aTextRange) const +{ + // Set the range offsets in UTF-16 string. + gint utf8ClauseStart, utf8ClauseEnd; + pango_attr_iterator_range(aPangoAttrIter, &utf8ClauseStart, &utf8ClauseEnd); + if (utf8ClauseStart == utf8ClauseEnd) { + MOZ_LOG(gGtkIMLog, LogLevel::Error, + ("0x%p SetTextRange(), FAILED, due to collapsed range", + this)); + return false; + } + + if (!utf8ClauseStart) { + aTextRange.mStartOffset = 0; + } else { + glong utf16PreviousClausesLength; + gunichar2* utf16PreviousClausesString = + g_utf8_to_utf16(aUTF8CompositionString, utf8ClauseStart, nullptr, + &utf16PreviousClausesLength, nullptr); + + if (NS_WARN_IF(!utf16PreviousClausesString)) { + MOZ_LOG(gGtkIMLog, LogLevel::Error, + ("0x%p SetTextRange(), FAILED, due to g_utf8_to_utf16() " + "failure (retrieving previous string of current clause)", + this)); + return false; + } + + aTextRange.mStartOffset = utf16PreviousClausesLength; + g_free(utf16PreviousClausesString); + } + + glong utf16CurrentClauseLength; + gunichar2* utf16CurrentClauseString = + g_utf8_to_utf16(aUTF8CompositionString + utf8ClauseStart, + utf8ClauseEnd - utf8ClauseStart, + nullptr, &utf16CurrentClauseLength, nullptr); + + if (NS_WARN_IF(!utf16CurrentClauseString)) { + MOZ_LOG(gGtkIMLog, LogLevel::Error, + ("0x%p SetTextRange(), FAILED, due to g_utf8_to_utf16() " + "failure (retrieving current clause)", + this)); + return false; + } + + // iBus Chewing IME tells us that there is an empty clause at the end of + // the composition string but we should ignore it since our code doesn't + // assume that there is an empty clause. + if (!utf16CurrentClauseLength) { + MOZ_LOG(gGtkIMLog, LogLevel::Warning, + ("0x%p SetTextRange(), FAILED, due to current clause length " + "is 0", + this)); + return false; + } + + aTextRange.mEndOffset = aTextRange.mStartOffset + utf16CurrentClauseLength; + g_free(utf16CurrentClauseString); + utf16CurrentClauseString = nullptr; + + // Set styles + TextRangeStyle& style = aTextRange.mRangeStyle; + + // Underline + PangoAttrInt* attrUnderline = + reinterpret_cast<PangoAttrInt*>( + pango_attr_iterator_get(aPangoAttrIter, PANGO_ATTR_UNDERLINE)); + if (attrUnderline) { + switch (attrUnderline->value) { + case PANGO_UNDERLINE_NONE: + style.mLineStyle = TextRangeStyle::LINESTYLE_NONE; + break; + case PANGO_UNDERLINE_DOUBLE: + style.mLineStyle = TextRangeStyle::LINESTYLE_DOUBLE; + break; + case PANGO_UNDERLINE_ERROR: + style.mLineStyle = TextRangeStyle::LINESTYLE_WAVY; + break; + case PANGO_UNDERLINE_SINGLE: + case PANGO_UNDERLINE_LOW: + style.mLineStyle = TextRangeStyle::LINESTYLE_SOLID; + break; + default: + MOZ_LOG(gGtkIMLog, LogLevel::Warning, + ("0x%p SetTextRange(), retrieved unknown underline " + "style: %d", + this, attrUnderline->value)); + style.mLineStyle = TextRangeStyle::LINESTYLE_SOLID; + break; + } + style.mDefinedStyles |= TextRangeStyle::DEFINED_LINESTYLE; + + // Underline color + PangoAttrColor* attrUnderlineColor = + reinterpret_cast<PangoAttrColor*>( + pango_attr_iterator_get(aPangoAttrIter, + PANGO_ATTR_UNDERLINE_COLOR)); + if (attrUnderlineColor) { + style.mUnderlineColor = ToNscolor(attrUnderlineColor); + style.mDefinedStyles |= TextRangeStyle::DEFINED_UNDERLINE_COLOR; + } + } else { + style.mLineStyle = TextRangeStyle::LINESTYLE_NONE; + style.mDefinedStyles |= TextRangeStyle::DEFINED_LINESTYLE; + } + + // Don't set colors if they are not specified. They should be computed by + // textframe if only one of the colors are specified. + + // Foreground color (text color) + PangoAttrColor* attrForeground = + reinterpret_cast<PangoAttrColor*>( + pango_attr_iterator_get(aPangoAttrIter, PANGO_ATTR_FOREGROUND)); + if (attrForeground) { + style.mForegroundColor = ToNscolor(attrForeground); + style.mDefinedStyles |= TextRangeStyle::DEFINED_FOREGROUND_COLOR; + } + + // Background color + PangoAttrColor* attrBackground = + reinterpret_cast<PangoAttrColor*>( + pango_attr_iterator_get(aPangoAttrIter, PANGO_ATTR_BACKGROUND)); + if (attrBackground) { + style.mBackgroundColor = ToNscolor(attrBackground); + style.mDefinedStyles |= TextRangeStyle::DEFINED_BACKGROUND_COLOR; + } + + /** + * We need to judge the meaning of the clause for a11y. Before we support + * IME specific composition string style, we used following rules: + * + * 1: If attrUnderline and attrForground are specified, we assumed the + * clause is TextRangeType::eSelectedClause. + * 2: If only attrUnderline is specified, we assumed the clause is + * TextRangeType::eConvertedClause. + * 3: If only attrForground is specified, we assumed the clause is + * TextRangeType::eSelectedRawClause. + * 4: If neither attrUnderline nor attrForeground is specified, we assumed + * the clause is TextRangeType::eRawClause. + * + * However, this rules are odd since there can be two or more selected + * clauses. Additionally, our old rules caused that IME developers/users + * cannot specify composition string style as they want. + * + * So, we shouldn't guess the meaning from its visual style. + */ + + if (!attrUnderline && !attrForeground && !attrBackground) { + MOZ_LOG(gGtkIMLog, LogLevel::Warning, + ("0x%p SetTextRange(), FAILED, due to no attr, " + "aTextRange= { mStartOffset=%u, mEndOffset=%u }", + this, aTextRange.mStartOffset, aTextRange.mEndOffset)); + return false; + } + + // If the range covers whole of composition string and the caret is at + // the end of the composition string, the range is probably not converted. + if (!utf8ClauseStart && + utf8ClauseEnd == static_cast<gint>(strlen(aUTF8CompositionString)) && + aTextRange.mEndOffset == aUTF16CaretOffset) { + aTextRange.mRangeType = TextRangeType::eRawClause; + } + // Typically, the caret is set at the start of the selected clause. + // So, if the caret is in the clause, we can assume that the clause is + // selected. + else if (aTextRange.mStartOffset <= aUTF16CaretOffset && + aTextRange.mEndOffset > aUTF16CaretOffset) { + aTextRange.mRangeType = TextRangeType::eSelectedClause; + } + // Otherwise, we should assume that the clause is converted but not + // selected. + else { + aTextRange.mRangeType = TextRangeType::eConvertedClause; + } + + MOZ_LOG(gGtkIMLog, LogLevel::Debug, + ("0x%p SetTextRange(), succeeded, aTextRange= { " + "mStartOffset=%u, mEndOffset=%u, mRangeType=%s, mRangeStyle=%s }", + this, aTextRange.mStartOffset, aTextRange.mEndOffset, + ToChar(aTextRange.mRangeType), + GetTextRangeStyleText(aTextRange.mRangeStyle).get())); + + return true; +} + +void +IMContextWrapper::SetCursorPosition(GtkIMContext* aContext) +{ + MOZ_LOG(gGtkIMLog, LogLevel::Info, + ("0x%p SetCursorPosition(aContext=0x%p), " + "mCompositionTargetRange={ mOffset=%u, mLength=%u }" + "mSelection={ mOffset=%u, mLength=%u, mWritingMode=%s }", + this, aContext, mCompositionTargetRange.mOffset, + mCompositionTargetRange.mLength, + mSelection.mOffset, mSelection.mLength, + GetWritingModeName(mSelection.mWritingMode).get())); + + bool useCaret = false; + if (!mCompositionTargetRange.IsValid()) { + if (!mSelection.IsValid()) { + MOZ_LOG(gGtkIMLog, LogLevel::Error, + ("0x%p SetCursorPosition(), FAILED, " + "mCompositionTargetRange and mSelection are invalid", + this)); + return; + } + useCaret = true; + } + + if (!mLastFocusedWindow) { + MOZ_LOG(gGtkIMLog, LogLevel::Error, + ("0x%p SetCursorPosition(), FAILED, due to no focused " + "window", + this)); + return; + } + + if (MOZ_UNLIKELY(!aContext)) { + MOZ_LOG(gGtkIMLog, LogLevel::Error, + ("0x%p SetCursorPosition(), FAILED, due to no context", + this)); + return; + } + + WidgetQueryContentEvent charRect(true, + useCaret ? eQueryCaretRect : + eQueryTextRect, + mLastFocusedWindow); + if (useCaret) { + charRect.InitForQueryCaretRect(mSelection.mOffset); + } else { + if (mSelection.mWritingMode.IsVertical()) { + // For preventing the candidate window to overlap the target + // clause, we should set fake (typically, very tall) caret rect. + uint32_t length = mCompositionTargetRange.mLength ? + mCompositionTargetRange.mLength : 1; + charRect.InitForQueryTextRect(mCompositionTargetRange.mOffset, + length); + } else { + charRect.InitForQueryTextRect(mCompositionTargetRange.mOffset, 1); + } + } + InitEvent(charRect); + nsEventStatus status; + mLastFocusedWindow->DispatchEvent(&charRect, status); + if (!charRect.mSucceeded) { + MOZ_LOG(gGtkIMLog, LogLevel::Error, + ("0x%p SetCursorPosition(), FAILED, %s was failed", + this, useCaret ? "eQueryCaretRect" : "eQueryTextRect")); + return; + } + + nsWindow* rootWindow = + static_cast<nsWindow*>(mLastFocusedWindow->GetTopLevelWidget()); + + // Get the position of the rootWindow in screen. + LayoutDeviceIntPoint root = rootWindow->WidgetToScreenOffset(); + + // Get the position of IM context owner window in screen. + LayoutDeviceIntPoint owner = mOwnerWindow->WidgetToScreenOffset(); + + // Compute the caret position in the IM owner window. + LayoutDeviceIntRect rect = charRect.mReply.mRect + root - owner; + rect.width = 0; + GdkRectangle area = rootWindow->DevicePixelsToGdkRectRoundOut(rect); + + gtk_im_context_set_cursor_location(aContext, &area); +} + +nsresult +IMContextWrapper::GetCurrentParagraph(nsAString& aText, + uint32_t& aCursorPos) +{ + MOZ_LOG(gGtkIMLog, LogLevel::Info, + ("0x%p GetCurrentParagraph(), mCompositionState=%s", + this, GetCompositionStateName())); + + if (!mLastFocusedWindow) { + MOZ_LOG(gGtkIMLog, LogLevel::Error, + ("0x%p GetCurrentParagraph(), FAILED, there are no " + "focused window in this module", + this)); + return NS_ERROR_NULL_POINTER; + } + + nsEventStatus status; + + uint32_t selOffset = mCompositionStart; + uint32_t selLength = mSelectedString.Length(); + + // If focused editor doesn't have composition string, we should use + // current selection. + if (!EditorHasCompositionString()) { + // Query cursor position & selection + if (NS_WARN_IF(!EnsureToCacheSelection())) { + MOZ_LOG(gGtkIMLog, LogLevel::Error, + ("0x%p GetCurrentParagraph(), FAILED, due to no " + "valid selection information", + this)); + return NS_ERROR_FAILURE; + } + + selOffset = mSelection.mOffset; + selLength = mSelection.mLength; + } + + MOZ_LOG(gGtkIMLog, LogLevel::Debug, + ("0x%p GetCurrentParagraph(), selOffset=%u, selLength=%u", + this, selOffset, selLength)); + + // XXX nsString::Find and nsString::RFind take int32_t for offset, so, + // we cannot support this request when the current offset is larger + // than INT32_MAX. + if (selOffset > INT32_MAX || selLength > INT32_MAX || + selOffset + selLength > INT32_MAX) { + MOZ_LOG(gGtkIMLog, LogLevel::Error, + ("0x%p GetCurrentParagraph(), FAILED, The selection is " + "out of range", + this)); + return NS_ERROR_FAILURE; + } + + // Get all text contents of the focused editor + WidgetQueryContentEvent queryTextContentEvent(true, eQueryTextContent, + mLastFocusedWindow); + queryTextContentEvent.InitForQueryTextContent(0, UINT32_MAX); + mLastFocusedWindow->DispatchEvent(&queryTextContentEvent, status); + NS_ENSURE_TRUE(queryTextContentEvent.mSucceeded, NS_ERROR_FAILURE); + + nsAutoString textContent(queryTextContentEvent.mReply.mString); + if (selOffset + selLength > textContent.Length()) { + MOZ_LOG(gGtkIMLog, LogLevel::Error, + ("0x%p GetCurrentParagraph(), FAILED, The selection is " + "invalid, textContent.Length()=%u", + this, textContent.Length())); + return NS_ERROR_FAILURE; + } + + // Remove composing string and restore the selected string because + // GtkEntry doesn't remove selected string until committing, however, + // our editor does it. We should emulate the behavior for IME. + if (EditorHasCompositionString() && + mDispatchedCompositionString != mSelectedString) { + textContent.Replace(mCompositionStart, + mDispatchedCompositionString.Length(), mSelectedString); + } + + // Get only the focused paragraph, by looking for newlines + int32_t parStart = (selOffset == 0) ? 0 : + textContent.RFind("\n", false, selOffset - 1, -1) + 1; + int32_t parEnd = textContent.Find("\n", false, selOffset + selLength, -1); + if (parEnd < 0) { + parEnd = textContent.Length(); + } + aText = nsDependentSubstring(textContent, parStart, parEnd - parStart); + aCursorPos = selOffset - uint32_t(parStart); + + MOZ_LOG(gGtkIMLog, LogLevel::Debug, + ("0x%p GetCurrentParagraph(), succeeded, aText=%s, " + "aText.Length()=%u, aCursorPos=%u", + this, NS_ConvertUTF16toUTF8(aText).get(), + aText.Length(), aCursorPos)); + + return NS_OK; +} + +nsresult +IMContextWrapper::DeleteText(GtkIMContext* aContext, + int32_t aOffset, + uint32_t aNChars) +{ + MOZ_LOG(gGtkIMLog, LogLevel::Info, + ("0x%p DeleteText(aContext=0x%p, aOffset=%d, aNChars=%u), " + "mCompositionState=%s", + this, aContext, aOffset, aNChars, GetCompositionStateName())); + + if (!mLastFocusedWindow) { + MOZ_LOG(gGtkIMLog, LogLevel::Error, + ("0x%p DeleteText(), FAILED, there are no focused window " + "in this module", + this)); + return NS_ERROR_NULL_POINTER; + } + + if (!aNChars) { + MOZ_LOG(gGtkIMLog, LogLevel::Error, + ("0x%p DeleteText(), FAILED, aNChars must not be zero", + this)); + return NS_ERROR_INVALID_ARG; + } + + RefPtr<nsWindow> lastFocusedWindow(mLastFocusedWindow); + nsEventStatus status; + + // First, we should cancel current composition because editor cannot + // handle changing selection and deleting text. + uint32_t selOffset; + bool wasComposing = IsComposing(); + bool editorHadCompositionString = EditorHasCompositionString(); + if (wasComposing) { + selOffset = mCompositionStart; + if (!DispatchCompositionCommitEvent(aContext, &mSelectedString)) { + MOZ_LOG(gGtkIMLog, LogLevel::Error, + ("0x%p DeleteText(), FAILED, quitting from DeletText", + this)); + return NS_ERROR_FAILURE; + } + } else { + if (NS_WARN_IF(!EnsureToCacheSelection())) { + MOZ_LOG(gGtkIMLog, LogLevel::Error, + ("0x%p DeleteText(), FAILED, due to no valid selection " + "information", + this)); + return NS_ERROR_FAILURE; + } + selOffset = mSelection.mOffset; + } + + // Get all text contents of the focused editor + WidgetQueryContentEvent queryTextContentEvent(true, eQueryTextContent, + mLastFocusedWindow); + queryTextContentEvent.InitForQueryTextContent(0, UINT32_MAX); + mLastFocusedWindow->DispatchEvent(&queryTextContentEvent, status); + NS_ENSURE_TRUE(queryTextContentEvent.mSucceeded, NS_ERROR_FAILURE); + if (queryTextContentEvent.mReply.mString.IsEmpty()) { + MOZ_LOG(gGtkIMLog, LogLevel::Error, + ("0x%p DeleteText(), FAILED, there is no contents", + this)); + return NS_ERROR_FAILURE; + } + + NS_ConvertUTF16toUTF8 utf8Str( + nsDependentSubstring(queryTextContentEvent.mReply.mString, + 0, selOffset)); + glong offsetInUTF8Characters = + g_utf8_strlen(utf8Str.get(), utf8Str.Length()) + aOffset; + if (offsetInUTF8Characters < 0) { + MOZ_LOG(gGtkIMLog, LogLevel::Error, + ("0x%p DeleteText(), FAILED, aOffset is too small for " + "current cursor pos (computed offset: %d)", + this, offsetInUTF8Characters)); + return NS_ERROR_FAILURE; + } + + AppendUTF16toUTF8( + nsDependentSubstring(queryTextContentEvent.mReply.mString, selOffset), + utf8Str); + glong countOfCharactersInUTF8 = + g_utf8_strlen(utf8Str.get(), utf8Str.Length()); + glong endInUTF8Characters = + offsetInUTF8Characters + aNChars; + if (countOfCharactersInUTF8 < endInUTF8Characters) { + MOZ_LOG(gGtkIMLog, LogLevel::Error, + ("0x%p DeleteText(), FAILED, aNChars is too large for " + "current contents (content length: %d, computed end offset: %d)", + this, countOfCharactersInUTF8, endInUTF8Characters)); + return NS_ERROR_FAILURE; + } + + gchar* charAtOffset = + g_utf8_offset_to_pointer(utf8Str.get(), offsetInUTF8Characters); + gchar* charAtEnd = + g_utf8_offset_to_pointer(utf8Str.get(), endInUTF8Characters); + + // Set selection to delete + WidgetSelectionEvent selectionEvent(true, eSetSelection, + mLastFocusedWindow); + + nsDependentCSubstring utf8StrBeforeOffset(utf8Str, 0, + charAtOffset - utf8Str.get()); + selectionEvent.mOffset = + NS_ConvertUTF8toUTF16(utf8StrBeforeOffset).Length(); + + nsDependentCSubstring utf8DeletingStr(utf8Str, + utf8StrBeforeOffset.Length(), + charAtEnd - charAtOffset); + selectionEvent.mLength = + NS_ConvertUTF8toUTF16(utf8DeletingStr).Length(); + + selectionEvent.mReversed = false; + selectionEvent.mExpandToClusterBoundary = false; + lastFocusedWindow->DispatchEvent(&selectionEvent, status); + + if (!selectionEvent.mSucceeded || + lastFocusedWindow != mLastFocusedWindow || + lastFocusedWindow->Destroyed()) { + MOZ_LOG(gGtkIMLog, LogLevel::Error, + ("0x%p DeleteText(), FAILED, setting selection caused " + "focus change or window destroyed", + this)); + return NS_ERROR_FAILURE; + } + + // Delete the selection + WidgetContentCommandEvent contentCommandEvent(true, eContentCommandDelete, + mLastFocusedWindow); + mLastFocusedWindow->DispatchEvent(&contentCommandEvent, status); + + if (!contentCommandEvent.mSucceeded || + lastFocusedWindow != mLastFocusedWindow || + lastFocusedWindow->Destroyed()) { + MOZ_LOG(gGtkIMLog, LogLevel::Error, + ("0x%p DeleteText(), FAILED, deleting the selection caused " + "focus change or window destroyed", + this)); + return NS_ERROR_FAILURE; + } + + if (!wasComposing) { + return NS_OK; + } + + // Restore the composition at new caret position. + if (!DispatchCompositionStart(aContext)) { + MOZ_LOG(gGtkIMLog, LogLevel::Error, + ("0x%p DeleteText(), FAILED, resterting composition start", + this)); + return NS_ERROR_FAILURE; + } + + if (!editorHadCompositionString) { + return NS_OK; + } + + nsAutoString compositionString; + GetCompositionString(aContext, compositionString); + if (!DispatchCompositionChangeEvent(aContext, compositionString)) { + MOZ_LOG(gGtkIMLog, LogLevel::Error, + ("0x%p DeleteText(), FAILED, restoring composition string", + this)); + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +void +IMContextWrapper::InitEvent(WidgetGUIEvent& aEvent) +{ + aEvent.mTime = PR_Now() / 1000; +} + +bool +IMContextWrapper::EnsureToCacheSelection(nsAString* aSelectedString) +{ + if (aSelectedString) { + aSelectedString->Truncate(); + } + + if (mSelection.IsValid() && + (!mSelection.Collapsed() || !aSelectedString)) { + return true; + } + + if (NS_WARN_IF(!mLastFocusedWindow)) { + MOZ_LOG(gGtkIMLog, LogLevel::Error, + ("0x%p EnsureToCacheSelection(), FAILED, due to " + "no focused window", + this)); + return false; + } + + nsEventStatus status; + WidgetQueryContentEvent selection(true, eQuerySelectedText, + mLastFocusedWindow); + InitEvent(selection); + mLastFocusedWindow->DispatchEvent(&selection, status); + if (NS_WARN_IF(!selection.mSucceeded)) { + MOZ_LOG(gGtkIMLog, LogLevel::Error, + ("0x%p EnsureToCacheSelection(), FAILED, due to " + "failure of query selection event", + this)); + return false; + } + + mSelection.Assign(selection); + if (!mSelection.IsValid()) { + MOZ_LOG(gGtkIMLog, LogLevel::Error, + ("0x%p EnsureToCacheSelection(), FAILED, due to " + "failure of query selection event (invalid result)", + this)); + return false; + } + + if (!mSelection.Collapsed() && aSelectedString) { + aSelectedString->Assign(selection.mReply.mString); + } + + MOZ_LOG(gGtkIMLog, LogLevel::Debug, + ("0x%p EnsureToCacheSelection(), Succeeded, mSelection=" + "{ mOffset=%u, mLength=%u, mWritingMode=%s }", + this, mSelection.mOffset, mSelection.mLength, + GetWritingModeName(mSelection.mWritingMode).get())); + return true; +} + +/****************************************************************************** + * IMContextWrapper::Selection + ******************************************************************************/ + +void +IMContextWrapper::Selection::Assign(const IMENotification& aIMENotification) +{ + MOZ_ASSERT(aIMENotification.mMessage == NOTIFY_IME_OF_SELECTION_CHANGE); + mOffset = aIMENotification.mSelectionChangeData.mOffset; + mLength = aIMENotification.mSelectionChangeData.Length(); + mWritingMode = aIMENotification.mSelectionChangeData.GetWritingMode(); +} + +void +IMContextWrapper::Selection::Assign(const WidgetQueryContentEvent& aEvent) +{ + MOZ_ASSERT(aEvent.mMessage == eQuerySelectedText); + MOZ_ASSERT(aEvent.mSucceeded); + mOffset = aEvent.mReply.mOffset; + mLength = aEvent.mReply.mString.Length(); + mWritingMode = aEvent.GetWritingMode(); +} + +} // namespace widget +} // namespace mozilla diff --git a/widget/gtk/IMContextWrapper.h b/widget/gtk/IMContextWrapper.h new file mode 100644 index 000000000..e869c160a --- /dev/null +++ b/widget/gtk/IMContextWrapper.h @@ -0,0 +1,481 @@ +/* -*- 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/. */ + +#ifndef IMContextWrapper_h_ +#define IMContextWrapper_h_ + +#include <gdk/gdk.h> +#include <gtk/gtk.h> + +#include "nsString.h" +#include "nsCOMPtr.h" +#include "nsTArray.h" +#include "nsIWidget.h" +#include "mozilla/CheckedInt.h" +#include "mozilla/EventForwards.h" +#include "mozilla/TextEventDispatcherListener.h" +#include "WritingModes.h" + +class nsWindow; + +namespace mozilla { +namespace widget { + +class IMContextWrapper final : public TextEventDispatcherListener +{ +public: + // TextEventDispatcherListener implementation + NS_DECL_ISUPPORTS + + NS_IMETHOD NotifyIME(TextEventDispatcher* aTextEventDispatcher, + const IMENotification& aNotification) override; + NS_IMETHOD_(void) OnRemovedFrom( + TextEventDispatcher* aTextEventDispatcher) override; + NS_IMETHOD_(void) WillDispatchKeyboardEvent( + TextEventDispatcher* aTextEventDispatcher, + WidgetKeyboardEvent& aKeyboardEvent, + uint32_t aIndexOfKeypress, + void* aData) override; + +public: + // aOwnerWindow is a pointer of the owner window. When aOwnerWindow is + // destroyed, the related IME contexts are released (i.e., IME cannot be + // used with the instance after that). + explicit IMContextWrapper(nsWindow* aOwnerWindow); + + // "Enabled" means the users can use all IMEs. + // I.e., the focus is in the normal editors. + bool IsEnabled() const; + + nsIMEUpdatePreference GetIMEUpdatePreference() const; + + // OnFocusWindow is a notification that aWindow is going to be focused. + void OnFocusWindow(nsWindow* aWindow); + // OnBlurWindow is a notification that aWindow is going to be unfocused. + void OnBlurWindow(nsWindow* aWindow); + // OnDestroyWindow is a notification that aWindow is going to be destroyed. + void OnDestroyWindow(nsWindow* aWindow); + // OnFocusChangeInGecko is a notification that an editor gets focus. + void OnFocusChangeInGecko(bool aFocus); + // OnSelectionChange is a notification that selection (caret) is changed + // in the focused editor. + void OnSelectionChange(nsWindow* aCaller, + const IMENotification& aIMENotification); + + // OnKeyEvent is called when aWindow gets a native key press event or a + // native key release event. If this returns TRUE, the key event was + // filtered by IME. Otherwise, this returns FALSE. + // NOTE: When the keypress event starts composition, this returns TRUE but + // this dispatches keydown event before compositionstart event. + bool OnKeyEvent(nsWindow* aWindow, GdkEventKey* aEvent, + bool aKeyDownEventWasSent = false); + + // IME related nsIWidget methods. + nsresult EndIMEComposition(nsWindow* aCaller); + void SetInputContext(nsWindow* aCaller, + const InputContext* aContext, + const InputContextAction* aAction); + InputContext GetInputContext(); + void OnUpdateComposition(); + void OnLayoutChange(); + + TextEventDispatcher* GetTextEventDispatcher(); + +protected: + ~IMContextWrapper(); + + // Owner of an instance of this class. This should be top level window. + // The owner window must release the contexts when it's destroyed because + // the IME contexts need the native window. If OnDestroyWindow() is called + // with the owner window, it'll release IME contexts. Otherwise, it'll + // just clean up any existing composition if it's related to the destroying + // child window. + nsWindow* mOwnerWindow; + + // A last focused window in this class's context. + nsWindow* mLastFocusedWindow; + + // Actual context. This is used for handling the user's input. + GtkIMContext* mContext; + + // mSimpleContext is used for the password field and + // the |ime-mode: disabled;| editors if sUseSimpleContext is true. + // These editors disable IME. But dead keys should work. Fortunately, + // the simple IM context of GTK2 support only them. + GtkIMContext* mSimpleContext; + + // mDummyContext is a dummy context and will be used in Focus() + // when the state of mEnabled means disabled. This context's IME state is + // always "closed", so it closes IME forcedly. + GtkIMContext* mDummyContext; + + // mComposingContext is not nullptr while one of mContext, mSimpleContext + // and mDummyContext has composition. + // XXX: We don't assume that two or more context have composition same time. + GtkIMContext* mComposingContext; + + // IME enabled state and other things defined in InputContext. + // Use following helper methods if you don't need the detail of the status. + InputContext mInputContext; + + // mCompositionStart is the start offset of the composition string in the + // current content. When <textarea> or <input> have focus, it means offset + // from the first character of them. When a HTML editor has focus, it + // means offset from the first character of the root element of the editor. + uint32_t mCompositionStart; + + // mDispatchedCompositionString is the latest composition string which + // was dispatched by compositionupdate event. + nsString mDispatchedCompositionString; + + // mSelectedString is the selected string which was removed by first + // compositionchange event. + nsString mSelectedString; + + // OnKeyEvent() temporarily sets mProcessingKeyEvent to the given native + // event. + GdkEventKey* mProcessingKeyEvent; + + struct Range + { + uint32_t mOffset; + uint32_t mLength; + + Range() + : mOffset(UINT32_MAX) + , mLength(UINT32_MAX) + { + } + + bool IsValid() const { return mOffset != UINT32_MAX; } + void Clear() + { + mOffset = UINT32_MAX; + mLength = UINT32_MAX; + } + }; + + // current target offset and length of IME composition + Range mCompositionTargetRange; + + // mCompositionState indicates current status of composition. + enum eCompositionState { + eCompositionState_NotComposing, + eCompositionState_CompositionStartDispatched, + eCompositionState_CompositionChangeEventDispatched + }; + eCompositionState mCompositionState; + + bool IsComposing() const + { + return (mCompositionState != eCompositionState_NotComposing); + } + + bool IsComposingOn(GtkIMContext* aContext) const + { + return IsComposing() && mComposingContext == aContext; + } + + bool IsComposingOnCurrentContext() const + { + return IsComposingOn(GetCurrentContext()); + } + + bool EditorHasCompositionString() + { + return (mCompositionState == + eCompositionState_CompositionChangeEventDispatched); + } + + /** + * Checks if aContext is valid context for handling composition. + * + * @param aContext An IM context which is specified by native + * composition events. + * @return true if the context is valid context for + * handling composition. Otherwise, false. + */ + bool IsValidContext(GtkIMContext* aContext) const; + + const char* GetCompositionStateName() + { + switch (mCompositionState) { + case eCompositionState_NotComposing: + return "NotComposing"; + case eCompositionState_CompositionStartDispatched: + return "CompositionStartDispatched"; + case eCompositionState_CompositionChangeEventDispatched: + return "CompositionChangeEventDispatched"; + default: + return "InvaildState"; + } + } + + struct Selection final + { + uint32_t mOffset; + uint32_t mLength; + WritingMode mWritingMode; + + Selection() + : mOffset(UINT32_MAX) + , mLength(UINT32_MAX) + { + } + + void Clear() + { + mOffset = UINT32_MAX; + mLength = UINT32_MAX; + mWritingMode = WritingMode(); + } + + void Assign(const IMENotification& aIMENotification); + void Assign(const WidgetQueryContentEvent& aSelectedTextEvent); + + bool IsValid() const { return mOffset != UINT32_MAX; } + bool Collapsed() const { return !mLength; } + uint32_t EndOffset() const + { + if (NS_WARN_IF(!IsValid())) { + return UINT32_MAX; + } + CheckedInt<uint32_t> endOffset = + CheckedInt<uint32_t>(mOffset) + mLength; + if (NS_WARN_IF(!endOffset.isValid())) { + return UINT32_MAX; + } + return endOffset.value(); + } + } mSelection; + bool EnsureToCacheSelection(nsAString* aSelectedString = nullptr); + + // mIsIMFocused is set to TRUE when we call gtk_im_context_focus_in(). And + // it's set to FALSE when we call gtk_im_context_focus_out(). + bool mIsIMFocused; + // mFilterKeyEvent is used by OnKeyEvent(). If the commit event should + // be processed as simple key event, this is set to TRUE by the commit + // handler. + bool mFilterKeyEvent; + // mKeyDownEventWasSent is used by OnKeyEvent() and + // DispatchCompositionStart(). DispatchCompositionStart() dispatches + // a keydown event if the composition start is caused by a native + // keypress event. If this is true, the keydown event has been dispatched. + // Then, DispatchCompositionStart() doesn't dispatch keydown event. + bool mKeyDownEventWasSent; + // mIsDeletingSurrounding is true while OnDeleteSurroundingNative() is + // trying to delete the surrounding text. + bool mIsDeletingSurrounding; + // mLayoutChanged is true after OnLayoutChange() is called. This is reset + // when eCompositionChange is being dispatched. + bool mLayoutChanged; + // mSetCursorPositionOnKeyEvent true when caret rect or position is updated + // with no composition. If true, we update candidate window position + // before key down + bool mSetCursorPositionOnKeyEvent; + // mPendingResettingIMContext becomes true if selection change notification + // is received during composition but the selection change occurred before + // starting the composition. In such case, we cannot notify IME of + // selection change during composition because we don't want to commit + // the composition in such case. However, we should notify IME of the + // selection change after the composition is committed. + bool mPendingResettingIMContext; + // mRetrieveSurroundingSignalReceived is true after "retrieve_surrounding" + // signal is received until selection is changed in Gecko. + bool mRetrieveSurroundingSignalReceived; + + // sLastFocusedContext is a pointer to the last focused instance of this + // class. When a instance is destroyed and sLastFocusedContext refers it, + // this is cleared. So, this refers valid pointer always. + static IMContextWrapper* sLastFocusedContext; + + // sUseSimpleContext indeicates if password editors and editors with + // |ime-mode: disabled;| should use GtkIMContextSimple. + // If true, they use GtkIMContextSimple. Otherwise, not. + static bool sUseSimpleContext; + + // Callback methods for native IME events. These methods should call + // the related instance methods simply. + static gboolean OnRetrieveSurroundingCallback(GtkIMContext* aContext, + IMContextWrapper* aModule); + static gboolean OnDeleteSurroundingCallback(GtkIMContext* aContext, + gint aOffset, + gint aNChars, + IMContextWrapper* aModule); + static void OnCommitCompositionCallback(GtkIMContext* aContext, + const gchar* aString, + IMContextWrapper* aModule); + static void OnChangeCompositionCallback(GtkIMContext* aContext, + IMContextWrapper* aModule); + static void OnStartCompositionCallback(GtkIMContext* aContext, + IMContextWrapper* aModule); + static void OnEndCompositionCallback(GtkIMContext* aContext, + IMContextWrapper* aModule); + + // The instance methods for the native IME events. + gboolean OnRetrieveSurroundingNative(GtkIMContext* aContext); + gboolean OnDeleteSurroundingNative(GtkIMContext* aContext, + gint aOffset, + gint aNChars); + void OnCommitCompositionNative(GtkIMContext* aContext, + const gchar* aString); + void OnChangeCompositionNative(GtkIMContext* aContext); + void OnStartCompositionNative(GtkIMContext* aContext); + void OnEndCompositionNative(GtkIMContext* aContext); + + /** + * GetCurrentContext() returns current IM context which is chosen with the + * enabled state. + * WARNING: + * When this class receives some signals for a composition after focus + * is moved in Gecko, the result of this may be different from given + * context by the signals. + */ + GtkIMContext* GetCurrentContext() const; + + /** + * GetActiveContext() returns a composing context or current context. + */ + GtkIMContext* GetActiveContext() const + { + return mComposingContext ? mComposingContext : GetCurrentContext(); + } + + // If the owner window and IM context have been destroyed, returns TRUE. + bool IsDestroyed() { return !mOwnerWindow; } + + // Sets focus to the instance of this class. + void Focus(); + + // Steals focus from the instance of this class. + void Blur(); + + // Initializes the instance. + void Init(); + + // Reset the current composition of IME. All native composition events + // during this processing are ignored. + void ResetIME(); + + // Gets the current composition string by the native APIs. + void GetCompositionString(GtkIMContext* aContext, + nsAString& aCompositionString); + + /** + * Generates our text range array from current composition string. + * + * @param aContext A GtkIMContext which is being handled. + * @param aCompositionString The data to be dispatched with + * compositionchange event. + */ + already_AddRefed<TextRangeArray> + CreateTextRangeArray(GtkIMContext* aContext, + const nsAString& aCompositionString); + + /** + * SetTextRange() initializes aTextRange with aPangoAttrIter. + * + * @param aPangoAttrIter An iter which represents a clause of the + * composition string. + * @param aUTF8CompositionString The whole composition string (UTF-8). + * @param aUTF16CaretOffset The caret offset in the composition + * string encoded as UTF-16. + * @param aTextRange The result. + * @return true if this initializes aTextRange. + * Otherwise, false. + */ + bool SetTextRange(PangoAttrIterator* aPangoAttrIter, + const gchar* aUTF8CompositionString, + uint32_t aUTF16CaretOffset, + TextRange& aTextRange) const; + + /** + * ToNscolor() converts the PangoColor in aPangoAttrColor to nscolor. + */ + static nscolor ToNscolor(PangoAttrColor* aPangoAttrColor); + + /** + * Move the candidate window with "fake" cursor position. + * + * @param aContext A GtkIMContext which is being handled. + */ + void SetCursorPosition(GtkIMContext* aContext); + + // Queries the current selection offset of the window. + uint32_t GetSelectionOffset(nsWindow* aWindow); + + // Get current paragraph text content and cursor position + nsresult GetCurrentParagraph(nsAString& aText, uint32_t& aCursorPos); + + /** + * Delete text portion + * + * @param aContext A GtkIMContext which is being handled. + * @param aOffset Start offset of the range to delete. + * @param aNChars Count of characters to delete. It depends + * on |g_utf8_strlen()| what is one character. + */ + nsresult DeleteText(GtkIMContext* aContext, + int32_t aOffset, + uint32_t aNChars); + + // Initializes the GUI event. + void InitEvent(WidgetGUIEvent& aEvent); + + // Called before destroying the context to work around some platform bugs. + void PrepareToDestroyContext(GtkIMContext* aContext); + + /** + * WARNING: + * Following methods dispatch gecko events. Then, the focused widget + * can be destroyed, and also it can be stolen focus. If they returns + * FALSE, callers cannot continue the composition. + * - DispatchCompositionStart + * - DispatchCompositionChangeEvent + * - DispatchCompositionCommitEvent + */ + + /** + * Dispatches a composition start event. + * + * @param aContext A GtkIMContext which is being handled. + * @return true if the focused widget is neither + * destroyed nor changed. Otherwise, false. + */ + bool DispatchCompositionStart(GtkIMContext* aContext); + + /** + * Dispatches a compositionchange event. + * + * @param aContext A GtkIMContext which is being handled. + * @param aCompositionString New composition string. + * @return true if the focused widget is neither + * destroyed nor changed. Otherwise, false. + */ + bool DispatchCompositionChangeEvent(GtkIMContext* aContext, + const nsAString& aCompositionString); + + /** + * Dispatches a compositioncommit event or compositioncommitasis event. + * + * @param aContext A GtkIMContext which is being handled. + * @param aCommitString If this is nullptr, the composition will + * be committed with last dispatched data. + * Otherwise, the composition will be + * committed with this value. + * @return true if the focused widget is neither + * destroyed nor changed. Otherwise, false. + */ + bool DispatchCompositionCommitEvent( + GtkIMContext* aContext, + const nsAString* aCommitString = nullptr); +}; + +} // namespace widget +} // namespace mozilla + +#endif // #ifndef IMContextWrapper_h_ diff --git a/widget/gtk/InProcessX11CompositorWidget.cpp b/widget/gtk/InProcessX11CompositorWidget.cpp new file mode 100644 index 000000000..9580b3150 --- /dev/null +++ b/widget/gtk/InProcessX11CompositorWidget.cpp @@ -0,0 +1,34 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 "InProcessX11CompositorWidget.h" + +#include "nsWindow.h" + +namespace mozilla { +namespace widget { + +/* static */ RefPtr<CompositorWidget> +CompositorWidget::CreateLocal(const CompositorWidgetInitData& aInitData, nsIWidget* aWidget) +{ + return new InProcessX11CompositorWidget(aInitData, static_cast<nsWindow*>(aWidget)); +} + +InProcessX11CompositorWidget::InProcessX11CompositorWidget(const CompositorWidgetInitData& aInitData, + nsWindow* aWindow) + : X11CompositorWidget(aInitData, aWindow) +{ +} + +void +InProcessX11CompositorWidget::ObserveVsync(VsyncObserver* aObserver) +{ + if (RefPtr<CompositorVsyncDispatcher> cvd = mWidget->GetCompositorVsyncDispatcher()) { + cvd->SetCompositorVsyncObserver(aObserver); + } +} + +} // namespace widget +} // namespace mozilla diff --git a/widget/gtk/InProcessX11CompositorWidget.h b/widget/gtk/InProcessX11CompositorWidget.h new file mode 100644 index 000000000..7f4077451 --- /dev/null +++ b/widget/gtk/InProcessX11CompositorWidget.h @@ -0,0 +1,30 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#ifndef widget_gtk_InProcessX11CompositorWidgetParent_h +#define widget_gtk_InProcessX11CompositorWidgetParent_h + +#include "X11CompositorWidget.h" + +class nsWindow; + +namespace mozilla { +namespace widget { + +class InProcessX11CompositorWidget final : public X11CompositorWidget +{ +public: + InProcessX11CompositorWidget(const CompositorWidgetInitData& aInitData, + nsWindow* aWindow); + + // CompositorWidgetDelegate + + void ObserveVsync(VsyncObserver* aObserver) override; +}; + +} // namespace widget +} // namespace mozilla + +#endif // widget_gtk_InProcessX11CompositorWidgetParent_h diff --git a/widget/gtk/NativeKeyBindings.cpp b/widget/gtk/NativeKeyBindings.cpp new file mode 100644 index 000000000..55a3508e2 --- /dev/null +++ b/widget/gtk/NativeKeyBindings.cpp @@ -0,0 +1,374 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/ArrayUtils.h" +#include "mozilla/MathAlgorithms.h" +#include "mozilla/TextEvents.h" + +#include "NativeKeyBindings.h" +#include "nsString.h" +#include "nsMemory.h" +#include "nsGtkKeyUtils.h" + +#include <gtk/gtk.h> +#include <gdk/gdkkeysyms.h> +#include <gdk/gdk.h> + +namespace mozilla { +namespace widget { + +static nsIWidget::DoCommandCallback gCurrentCallback; +static void *gCurrentCallbackData; +static bool gHandled; + +// Common GtkEntry and GtkTextView signals +static void +copy_clipboard_cb(GtkWidget *w, gpointer user_data) +{ + gCurrentCallback(CommandCopy, gCurrentCallbackData); + g_signal_stop_emission_by_name(w, "copy_clipboard"); + gHandled = true; +} + +static void +cut_clipboard_cb(GtkWidget *w, gpointer user_data) +{ + gCurrentCallback(CommandCut, gCurrentCallbackData); + g_signal_stop_emission_by_name(w, "cut_clipboard"); + gHandled = true; +} + +// GTK distinguishes between display lines (wrapped, as they appear on the +// screen) and paragraphs, which are runs of text terminated by a newline. +// We don't have this distinction, so we always use editor's notion of +// lines, which are newline-terminated. + +static const Command sDeleteCommands[][2] = { + // backward, forward + { CommandDeleteCharBackward, CommandDeleteCharForward }, // CHARS + { CommandDeleteWordBackward, CommandDeleteWordForward }, // WORD_ENDS + { CommandDeleteWordBackward, CommandDeleteWordForward }, // WORDS + { CommandDeleteToBeginningOfLine, CommandDeleteToEndOfLine }, // LINES + { CommandDeleteToBeginningOfLine, CommandDeleteToEndOfLine }, // LINE_ENDS + { CommandDeleteToBeginningOfLine, CommandDeleteToEndOfLine }, // PARAGRAPH_ENDS + { CommandDeleteToBeginningOfLine, CommandDeleteToEndOfLine }, // PARAGRAPHS + // This deletes from the end of the previous word to the beginning of the + // next word, but only if the caret is not in a word. + // XXX need to implement in editor + { CommandDoNothing, CommandDoNothing } // WHITESPACE +}; + +static void +delete_from_cursor_cb(GtkWidget *w, GtkDeleteType del_type, + gint count, gpointer user_data) +{ + g_signal_stop_emission_by_name(w, "delete_from_cursor"); + bool forward = count > 0; + +#if (MOZ_WIDGET_GTK == 3) + // Ignore GTK's Ctrl-K keybinding introduced in GTK 3.14 and removed in + // 3.18 if the user has custom bindings set. See bug 1176929. + if (del_type == GTK_DELETE_PARAGRAPH_ENDS && forward && GTK_IS_ENTRY(w) && + !gtk_check_version(3, 14, 1) && gtk_check_version(3, 17, 9)) { + GtkStyleContext* context = gtk_widget_get_style_context(w); + GtkStateFlags flags = gtk_widget_get_state_flags(w); + + GPtrArray* array; + gtk_style_context_get(context, flags, "gtk-key-bindings", &array, nullptr); + if (!array) + return; + g_ptr_array_unref(array); + } +#endif + + gHandled = true; + if (uint32_t(del_type) >= ArrayLength(sDeleteCommands)) { + // unsupported deletion type + return; + } + + if (del_type == GTK_DELETE_WORDS) { + // This works like word_ends, except we first move the caret to the + // beginning/end of the current word. + if (forward) { + gCurrentCallback(CommandWordNext, gCurrentCallbackData); + gCurrentCallback(CommandWordPrevious, gCurrentCallbackData); + } else { + gCurrentCallback(CommandWordPrevious, gCurrentCallbackData); + gCurrentCallback(CommandWordNext, gCurrentCallbackData); + } + } else if (del_type == GTK_DELETE_DISPLAY_LINES || + del_type == GTK_DELETE_PARAGRAPHS) { + + // This works like display_line_ends, except we first move the caret to the + // beginning/end of the current line. + if (forward) { + gCurrentCallback(CommandBeginLine, gCurrentCallbackData); + } else { + gCurrentCallback(CommandEndLine, gCurrentCallbackData); + } + } + + Command command = sDeleteCommands[del_type][forward]; + if (!command) { + return; // unsupported command + } + + unsigned int absCount = Abs(count); + for (unsigned int i = 0; i < absCount; ++i) { + gCurrentCallback(command, gCurrentCallbackData); + } +} + +static const Command sMoveCommands[][2][2] = { + // non-extend { backward, forward }, extend { backward, forward } + // GTK differentiates between logical position, which is prev/next, + // and visual position, which is always left/right. + // We should fix this to work the same way for RTL text input. + { // LOGICAL_POSITIONS + { CommandCharPrevious, CommandCharNext }, + { CommandSelectCharPrevious, CommandSelectCharNext } + }, + { // VISUAL_POSITIONS + { CommandCharPrevious, CommandCharNext }, + { CommandSelectCharPrevious, CommandSelectCharNext } + }, + { // WORDS + { CommandWordPrevious, CommandWordNext }, + { CommandSelectWordPrevious, CommandSelectWordNext } + }, + { // DISPLAY_LINES + { CommandLinePrevious, CommandLineNext }, + { CommandSelectLinePrevious, CommandSelectLineNext } + }, + { // DISPLAY_LINE_ENDS + { CommandBeginLine, CommandEndLine }, + { CommandSelectBeginLine, CommandSelectEndLine } + }, + { // PARAGRAPHS + { CommandLinePrevious, CommandLineNext }, + { CommandSelectLinePrevious, CommandSelectLineNext } + }, + { // PARAGRAPH_ENDS + { CommandBeginLine, CommandEndLine }, + { CommandSelectBeginLine, CommandSelectEndLine } + }, + { // PAGES + { CommandMovePageUp, CommandMovePageDown }, + { CommandSelectPageUp, CommandSelectPageDown } + }, + { // BUFFER_ENDS + { CommandMoveTop, CommandMoveBottom }, + { CommandSelectTop, CommandSelectBottom } + }, + { // HORIZONTAL_PAGES (unsupported) + { CommandDoNothing, CommandDoNothing }, + { CommandDoNothing, CommandDoNothing } + } +}; + +static void +move_cursor_cb(GtkWidget *w, GtkMovementStep step, gint count, + gboolean extend_selection, gpointer user_data) +{ + g_signal_stop_emission_by_name(w, "move_cursor"); + gHandled = true; + bool forward = count > 0; + if (uint32_t(step) >= ArrayLength(sMoveCommands)) { + // unsupported movement type + return; + } + + Command command = sMoveCommands[step][extend_selection][forward]; + if (!command) { + return; // unsupported command + } + + unsigned int absCount = Abs(count); + for (unsigned int i = 0; i < absCount; ++i) { + gCurrentCallback(command, gCurrentCallbackData); + } +} + +static void +paste_clipboard_cb(GtkWidget *w, gpointer user_data) +{ + gCurrentCallback(CommandPaste, gCurrentCallbackData); + g_signal_stop_emission_by_name(w, "paste_clipboard"); + gHandled = true; +} + +// GtkTextView-only signals +static void +select_all_cb(GtkWidget *w, gboolean select, gpointer user_data) +{ + gCurrentCallback(CommandSelectAll, gCurrentCallbackData); + g_signal_stop_emission_by_name(w, "select_all"); + gHandled = true; +} + +NativeKeyBindings* NativeKeyBindings::sInstanceForSingleLineEditor = nullptr; +NativeKeyBindings* NativeKeyBindings::sInstanceForMultiLineEditor = nullptr; + +// static +NativeKeyBindings* +NativeKeyBindings::GetInstance(NativeKeyBindingsType aType) +{ + switch (aType) { + case nsIWidget::NativeKeyBindingsForSingleLineEditor: + if (!sInstanceForSingleLineEditor) { + sInstanceForSingleLineEditor = new NativeKeyBindings(); + sInstanceForSingleLineEditor->Init(aType); + } + return sInstanceForSingleLineEditor; + + default: + // fallback to multiline editor case in release build + MOZ_FALLTHROUGH_ASSERT("aType is invalid or not yet implemented"); + case nsIWidget::NativeKeyBindingsForMultiLineEditor: + case nsIWidget::NativeKeyBindingsForRichTextEditor: + if (!sInstanceForMultiLineEditor) { + sInstanceForMultiLineEditor = new NativeKeyBindings(); + sInstanceForMultiLineEditor->Init(aType); + } + return sInstanceForMultiLineEditor; + } +} + +// static +void +NativeKeyBindings::Shutdown() +{ + delete sInstanceForSingleLineEditor; + sInstanceForSingleLineEditor = nullptr; + delete sInstanceForMultiLineEditor; + sInstanceForMultiLineEditor = nullptr; +} + +void +NativeKeyBindings::Init(NativeKeyBindingsType aType) +{ + switch (aType) { + case nsIWidget::NativeKeyBindingsForSingleLineEditor: + mNativeTarget = gtk_entry_new(); + break; + default: + mNativeTarget = gtk_text_view_new(); + if (gtk_major_version > 2 || + (gtk_major_version == 2 && (gtk_minor_version > 2 || + (gtk_minor_version == 2 && + gtk_micro_version >= 2)))) { + // select_all only exists in gtk >= 2.2.2. Prior to that, + // ctrl+a is bound to (move to beginning, select to end). + g_signal_connect(mNativeTarget, "select_all", + G_CALLBACK(select_all_cb), this); + } + break; + } + + g_object_ref_sink(mNativeTarget); + + g_signal_connect(mNativeTarget, "copy_clipboard", + G_CALLBACK(copy_clipboard_cb), this); + g_signal_connect(mNativeTarget, "cut_clipboard", + G_CALLBACK(cut_clipboard_cb), this); + g_signal_connect(mNativeTarget, "delete_from_cursor", + G_CALLBACK(delete_from_cursor_cb), this); + g_signal_connect(mNativeTarget, "move_cursor", + G_CALLBACK(move_cursor_cb), this); + g_signal_connect(mNativeTarget, "paste_clipboard", + G_CALLBACK(paste_clipboard_cb), this); +} + +NativeKeyBindings::~NativeKeyBindings() +{ + gtk_widget_destroy(mNativeTarget); + g_object_unref(mNativeTarget); +} + +bool +NativeKeyBindings::Execute(const WidgetKeyboardEvent& aEvent, + DoCommandCallback aCallback, + void* aCallbackData) +{ + // If the native key event is set, it must be synthesized for tests. + // We just ignore such events because this behavior depends on system + // settings. + if (!aEvent.mNativeKeyEvent) { + // It must be synthesized event or dispatched DOM event from chrome. + return false; + } + + guint keyval; + + if (aEvent.mCharCode) { + keyval = gdk_unicode_to_keyval(aEvent.mCharCode); + } else { + keyval = + static_cast<GdkEventKey*>(aEvent.mNativeKeyEvent)->keyval; + } + + if (ExecuteInternal(aEvent, aCallback, aCallbackData, keyval)) { + return true; + } + + for (uint32_t i = 0; i < aEvent.mAlternativeCharCodes.Length(); ++i) { + uint32_t ch = aEvent.IsShift() ? + aEvent.mAlternativeCharCodes[i].mShiftedCharCode : + aEvent.mAlternativeCharCodes[i].mUnshiftedCharCode; + if (ch && ch != aEvent.mCharCode) { + keyval = gdk_unicode_to_keyval(ch); + if (ExecuteInternal(aEvent, aCallback, aCallbackData, keyval)) { + return true; + } + } + } + +/* +gtk_bindings_activate_event is preferable, but it has unresolved bug: +http://bugzilla.gnome.org/show_bug.cgi?id=162726 +The bug was already marked as FIXED. However, somebody reports that the +bug still exists. +Also gtk_bindings_activate may work with some non-shortcuts operations +(todo: check it). See bug 411005 and bug 406407. + +Code, which should be used after fixing GNOME bug 162726: + + gtk_bindings_activate_event(GTK_OBJECT(mNativeTarget), + static_cast<GdkEventKey*>(aEvent.mNativeKeyEvent)); +*/ + + return false; +} + +bool +NativeKeyBindings::ExecuteInternal(const WidgetKeyboardEvent& aEvent, + DoCommandCallback aCallback, + void* aCallbackData, + guint aKeyval) +{ + guint modifiers = + static_cast<GdkEventKey*>(aEvent.mNativeKeyEvent)->state; + + gCurrentCallback = aCallback; + gCurrentCallbackData = aCallbackData; + + gHandled = false; +#if (MOZ_WIDGET_GTK == 2) + gtk_bindings_activate(GTK_OBJECT(mNativeTarget), + aKeyval, GdkModifierType(modifiers)); +#else + gtk_bindings_activate(G_OBJECT(mNativeTarget), + aKeyval, GdkModifierType(modifiers)); +#endif + + gCurrentCallback = nullptr; + gCurrentCallbackData = nullptr; + + return gHandled; +} + +} // namespace widget +} // namespace mozilla diff --git a/widget/gtk/NativeKeyBindings.h b/widget/gtk/NativeKeyBindings.h new file mode 100644 index 000000000..f1632f9c5 --- /dev/null +++ b/widget/gtk/NativeKeyBindings.h @@ -0,0 +1,49 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#ifndef mozilla_widget_NativeKeyBindings_h_ +#define mozilla_widget_NativeKeyBindings_h_ + +#include <gtk/gtk.h> +#include "mozilla/Attributes.h" +#include "mozilla/EventForwards.h" +#include "nsIWidget.h" + +namespace mozilla { +namespace widget { + +class NativeKeyBindings final +{ + typedef nsIWidget::NativeKeyBindingsType NativeKeyBindingsType; + typedef nsIWidget::DoCommandCallback DoCommandCallback; + +public: + static NativeKeyBindings* GetInstance(NativeKeyBindingsType aType); + static void Shutdown(); + + void Init(NativeKeyBindingsType aType); + + bool Execute(const WidgetKeyboardEvent& aEvent, + DoCommandCallback aCallback, + void* aCallbackData); + +private: + ~NativeKeyBindings(); + + bool ExecuteInternal(const WidgetKeyboardEvent& aEvent, + DoCommandCallback aCallback, + void* aCallbackData, + guint aKeyval); + + GtkWidget* mNativeTarget; + + static NativeKeyBindings* sInstanceForSingleLineEditor; + static NativeKeyBindings* sInstanceForMultiLineEditor; +}; + +} // namespace widget +} // namespace mozilla + +#endif // mozilla_widget_NativeKeyBindings_h_ diff --git a/widget/gtk/PCompositorWidget.ipdl b/widget/gtk/PCompositorWidget.ipdl new file mode 100644 index 000000000..178fe78e4 --- /dev/null +++ b/widget/gtk/PCompositorWidget.ipdl @@ -0,0 +1,30 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=99: */ +/* 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 protocol PCompositorBridge; + +using mozilla::LayoutDeviceIntSize from "Units.h"; + +namespace mozilla { +namespace widget { + +sync protocol PCompositorWidget +{ + manager PCompositorBridge; + +parent: + async __delete__(); + + async NotifyClientSizeChanged(LayoutDeviceIntSize aClientSize); + +child: + + async ObserveVsync(); + async UnobserveVsync(); +}; + +} // namespace widget +} // namespace mozilla diff --git a/widget/gtk/PlatformWidgetTypes.ipdlh b/widget/gtk/PlatformWidgetTypes.ipdlh new file mode 100644 index 000000000..145d39546 --- /dev/null +++ b/widget/gtk/PlatformWidgetTypes.ipdlh @@ -0,0 +1,24 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=99: */ +/* 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/. */ + +// This file is a stub, for platforms that do not yet support out-of-process +// compositing or do not need specialized types to do so. + +using mozilla::LayoutDeviceIntSize from "Units.h"; + +namespace mozilla { +namespace widget { + +struct CompositorWidgetInitData +{ + uintptr_t XWindow; + nsCString XDisplayString; + + LayoutDeviceIntSize InitialClientSize; +}; + +} // namespace widget +} // namespace mozilla diff --git a/widget/gtk/WakeLockListener.cpp b/widget/gtk/WakeLockListener.cpp new file mode 100644 index 000000000..54bad17ff --- /dev/null +++ b/widget/gtk/WakeLockListener.cpp @@ -0,0 +1,365 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:expandtab:shiftwidth=2:tabstop=2: + */ +/* 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/. */ + +#ifdef MOZ_ENABLE_DBUS + +#include "WakeLockListener.h" + +#include <dbus/dbus.h> +#include <dbus/dbus-glib-lowlevel.h> + +#include "mozilla/ipc/DBusMessageRefPtr.h" +#include "mozilla/ipc/DBusPendingCallRefPtr.h" + +#define FREEDESKTOP_SCREENSAVER_TARGET "org.freedesktop.ScreenSaver" +#define FREEDESKTOP_SCREENSAVER_OBJECT "/ScreenSaver" +#define FREEDESKTOP_SCREENSAVER_INTERFACE "org.freedesktop.ScreenSaver" + +#define SESSION_MANAGER_TARGET "org.gnome.SessionManager" +#define SESSION_MANAGER_OBJECT "/org/gnome/SessionManager" +#define SESSION_MANAGER_INTERFACE "org.gnome.SessionManager" + +#define DBUS_TIMEOUT (-1) + +using namespace mozilla; + +NS_IMPL_ISUPPORTS(WakeLockListener, nsIDOMMozWakeLockListener) + +WakeLockListener* WakeLockListener::sSingleton = nullptr; + + +enum DesktopEnvironment { + FreeDesktop, + GNOME, + Unsupported, +}; + +class WakeLockTopic +{ +public: + WakeLockTopic(const nsAString& aTopic, DBusConnection* aConnection) + : mTopic(NS_ConvertUTF16toUTF8(aTopic)) + , mConnection(aConnection) + , mDesktopEnvironment(FreeDesktop) + , mInhibitRequest(0) + , mShouldInhibit(false) + , mWaitingForReply(false) + { + } + + nsresult InhibitScreensaver(void); + nsresult UninhibitScreensaver(void); + +private: + bool SendInhibit(); + bool SendUninhibit(); + + bool SendFreeDesktopInhibitMessage(); + bool SendGNOMEInhibitMessage(); + bool SendMessage(DBusMessage* aMessage); + + static void ReceiveInhibitReply(DBusPendingCall* aPending, void* aUserData); + void InhibitFailed(); + void InhibitSucceeded(uint32_t aInhibitRequest); + + nsCString mTopic; + RefPtr<DBusConnection> mConnection; + + DesktopEnvironment mDesktopEnvironment; + + uint32_t mInhibitRequest; + + bool mShouldInhibit; + bool mWaitingForReply; +}; + + +bool +WakeLockTopic::SendMessage(DBusMessage* aMessage) +{ + // send message and get a handle for a reply + RefPtr<DBusPendingCall> reply; + dbus_connection_send_with_reply(mConnection, aMessage, + reply.StartAssignment(), + DBUS_TIMEOUT); + if (!reply) { + return false; + } + + dbus_pending_call_set_notify(reply, &ReceiveInhibitReply, this, NULL); + + return true; +} + +bool +WakeLockTopic::SendFreeDesktopInhibitMessage() +{ + RefPtr<DBusMessage> message = already_AddRefed<DBusMessage>( + dbus_message_new_method_call(FREEDESKTOP_SCREENSAVER_TARGET, + FREEDESKTOP_SCREENSAVER_OBJECT, + FREEDESKTOP_SCREENSAVER_INTERFACE, + "Inhibit")); + + if (!message) { + return false; + } + + const char* app = g_get_prgname(); + const char* topic = mTopic.get(); + dbus_message_append_args(message, + DBUS_TYPE_STRING, &app, + DBUS_TYPE_STRING, &topic, + DBUS_TYPE_INVALID); + + return SendMessage(message); +} + +bool +WakeLockTopic::SendGNOMEInhibitMessage() +{ + RefPtr<DBusMessage> message = already_AddRefed<DBusMessage>( + dbus_message_new_method_call(SESSION_MANAGER_TARGET, + SESSION_MANAGER_OBJECT, + SESSION_MANAGER_INTERFACE, + "Inhibit")); + + if (!message) { + return false; + } + + static const uint32_t xid = 0; + static const uint32_t flags = (1 << 3); // Inhibit idle + const char* app = g_get_prgname(); + const char* topic = mTopic.get(); + dbus_message_append_args(message, + DBUS_TYPE_STRING, &app, + DBUS_TYPE_UINT32, &xid, + DBUS_TYPE_STRING, &topic, + DBUS_TYPE_UINT32, &flags, + DBUS_TYPE_INVALID); + + return SendMessage(message); +} + + +bool +WakeLockTopic::SendInhibit() +{ + bool sendOk = false; + + switch (mDesktopEnvironment) + { + case FreeDesktop: + sendOk = SendFreeDesktopInhibitMessage(); + break; + case GNOME: + sendOk = SendGNOMEInhibitMessage(); + break; + case Unsupported: + return false; + } + + if (sendOk) { + mWaitingForReply = true; + } + + return sendOk; +} + +bool +WakeLockTopic::SendUninhibit() +{ + RefPtr<DBusMessage> message; + + if (mDesktopEnvironment == FreeDesktop) { + message = already_AddRefed<DBusMessage>( + dbus_message_new_method_call(FREEDESKTOP_SCREENSAVER_TARGET, + FREEDESKTOP_SCREENSAVER_OBJECT, + FREEDESKTOP_SCREENSAVER_INTERFACE, + "UnInhibit")); + } else if (mDesktopEnvironment == GNOME) { + message = already_AddRefed<DBusMessage>( + dbus_message_new_method_call(SESSION_MANAGER_TARGET, + SESSION_MANAGER_OBJECT, + SESSION_MANAGER_INTERFACE, + "Uninhibit")); + } + + if (!message) { + return false; + } + + dbus_message_append_args(message, + DBUS_TYPE_UINT32, &mInhibitRequest, + DBUS_TYPE_INVALID); + + dbus_connection_send(mConnection, message, nullptr); + dbus_connection_flush(mConnection); + + mInhibitRequest = 0; + + return true; +} + +nsresult +WakeLockTopic::InhibitScreensaver() +{ + if (mShouldInhibit) { + // Screensaver is inhibited. Nothing to do here. + return NS_OK; + } + + mShouldInhibit = true; + + if (mWaitingForReply) { + // We already have a screensaver inhibit request pending. This can happen + // if InhibitScreensaver is called, then UninhibitScreensaver, then + // InhibitScreensaver again quickly. + return NS_OK; + } + + return SendInhibit() ? NS_OK : NS_ERROR_FAILURE; +} + +nsresult +WakeLockTopic::UninhibitScreensaver() +{ + if (!mShouldInhibit) { + // Screensaver isn't inhibited. Nothing to do here. + return NS_OK; + } + + mShouldInhibit = false; + + if (mWaitingForReply) { + // If we're still waiting for a response to our inhibit request, we can't + // do anything until we get a dbus message back. The callbacks below will + // check |mShouldInhibit| and act accordingly. + return NS_OK; + } + + return SendUninhibit() ? NS_OK : NS_ERROR_FAILURE; +} + +void +WakeLockTopic::InhibitFailed() +{ + mWaitingForReply = false; + + if (mDesktopEnvironment == FreeDesktop) { + mDesktopEnvironment = GNOME; + } else { + NS_ASSERTION(mDesktopEnvironment == GNOME, "Unknown desktop environment"); + mDesktopEnvironment = Unsupported; + mShouldInhibit = false; + } + + if (!mShouldInhibit) { + // We were interrupted by UninhibitScreensaver() before we could find the + // correct desktop environment. + return; + } + + SendInhibit(); +} + +void +WakeLockTopic::InhibitSucceeded(uint32_t aInhibitRequest) +{ + mWaitingForReply = false; + mInhibitRequest = aInhibitRequest; + + if (!mShouldInhibit) { + // We successfully inhibited the screensaver, but UninhibitScreensaver() + // was called while we were waiting for a reply. + SendUninhibit(); + } +} + +/* static */ void +WakeLockTopic::ReceiveInhibitReply(DBusPendingCall* pending, void* user_data) +{ + if (!WakeLockListener::GetSingleton(false)) { + // The WakeLockListener (and therefore our topic) was deleted while we were + // waiting for a reply. + return; + } + + WakeLockTopic* self = static_cast<WakeLockTopic*>(user_data); + + RefPtr<DBusMessage> msg = already_AddRefed<DBusMessage>( + dbus_pending_call_steal_reply(pending)); + if (!msg) { + return; + } + + if (dbus_message_get_type(msg) == DBUS_MESSAGE_TYPE_METHOD_RETURN) { + uint32_t inhibitRequest; + + if (dbus_message_get_args(msg, nullptr, DBUS_TYPE_UINT32, + &inhibitRequest, DBUS_TYPE_INVALID)) { + self->InhibitSucceeded(inhibitRequest); + } + } else { + self->InhibitFailed(); + } +} + + +WakeLockListener::WakeLockListener() + : mConnection(already_AddRefed<DBusConnection>( + dbus_bus_get(DBUS_BUS_SESSION, nullptr))) +{ + if (mConnection) { + dbus_connection_set_exit_on_disconnect(mConnection, false); + dbus_connection_setup_with_g_main(mConnection, nullptr); + } +} + +/* static */ WakeLockListener* +WakeLockListener::GetSingleton(bool aCreate) +{ + if (!sSingleton && aCreate) { + sSingleton = new WakeLockListener(); + sSingleton->AddRef(); + } + + return sSingleton; +} + +/* static */ void +WakeLockListener::Shutdown() +{ + sSingleton->Release(); + sSingleton = nullptr; +} + +nsresult +WakeLockListener::Callback(const nsAString& topic, const nsAString& state) +{ + if (!mConnection) { + return NS_ERROR_FAILURE; + } + + if(!topic.Equals(NS_LITERAL_STRING("screen"))) + return NS_OK; + + WakeLockTopic* topicLock = mTopics.Get(topic); + if (!topicLock) { + topicLock = new WakeLockTopic(topic, mConnection); + mTopics.Put(topic, topicLock); + } + + // Treat "locked-background" the same as "unlocked" on desktop linux. + bool shouldLock = state.EqualsLiteral("locked-foreground"); + + return shouldLock ? + topicLock->InhibitScreensaver() : + topicLock->UninhibitScreensaver(); +} + +#endif diff --git a/widget/gtk/WakeLockListener.h b/widget/gtk/WakeLockListener.h new file mode 100644 index 000000000..fc7281822 --- /dev/null +++ b/widget/gtk/WakeLockListener.h @@ -0,0 +1,53 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:expandtab:shiftwidth=2:tabstop=2: + */ +/* 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 <unistd.h> + +#ifndef __WakeLockListener_h__ +#define __WakeLockListener_h__ + +#include "nsHashKeys.h" +#include "nsClassHashtable.h" + +#include "nsIDOMWakeLockListener.h" + +#ifdef MOZ_ENABLE_DBUS +#include "mozilla/ipc/DBusConnectionRefPtr.h" +#endif + +class WakeLockTopic; + +/** + * Receives WakeLock events and simply passes it on to the right WakeLockTopic + * to inhibit the screensaver. + */ +class WakeLockListener final : public nsIDOMMozWakeLockListener +{ +public: + NS_DECL_ISUPPORTS; + + static WakeLockListener* GetSingleton(bool aCreate = true); + static void Shutdown(); + + virtual nsresult Callback(const nsAString& topic, + const nsAString& state) override; + +private: + WakeLockListener(); + ~WakeLockListener() = default; + + static WakeLockListener* sSingleton; + +#ifdef MOZ_ENABLE_DBUS + RefPtr<DBusConnection> mConnection; +#endif + // Map of topic names to |WakeLockTopic|s. + // We assume a small, finite-sized set of topics. + nsClassHashtable<nsStringHashKey, WakeLockTopic> mTopics; +}; + +#endif // __WakeLockListener_h__ diff --git a/widget/gtk/WidgetStyleCache.cpp b/widget/gtk/WidgetStyleCache.cpp new file mode 100644 index 000000000..fd099681f --- /dev/null +++ b/widget/gtk/WidgetStyleCache.cpp @@ -0,0 +1,1162 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 <dlfcn.h> +#include <gtk/gtk.h> +#include "WidgetStyleCache.h" +#include "gtkdrawing.h" + +#define STATE_FLAG_DIR_LTR (1U << 7) +#define STATE_FLAG_DIR_RTL (1U << 8) +#if GTK_CHECK_VERSION(3,8,0) +static_assert(GTK_STATE_FLAG_DIR_LTR == STATE_FLAG_DIR_LTR && + GTK_STATE_FLAG_DIR_RTL == STATE_FLAG_DIR_RTL, + "incorrect direction state flags"); +#endif + +static GtkWidget* sWidgetStorage[MOZ_GTK_WIDGET_NODE_COUNT]; +static GtkStyleContext* sStyleStorage[MOZ_GTK_WIDGET_NODE_COUNT]; + +static bool sStyleContextNeedsRestore; +#ifdef DEBUG +static GtkStyleContext* sCurrentStyleContext; +#endif +static GtkStyleContext* +GetWidgetRootStyle(WidgetNodeType aNodeType); +static GtkStyleContext* +GetCssNodeStyleInternal(WidgetNodeType aNodeType); + +static GtkWidget* +CreateWindowWidget() +{ + GtkWidget *widget = gtk_window_new(GTK_WINDOW_POPUP); + gtk_widget_set_name(widget, "MozillaGtkWidget"); + return widget; +} + +static GtkWidget* +CreateWindowContainerWidget() +{ + GtkWidget *widget = gtk_fixed_new(); + gtk_container_add(GTK_CONTAINER(GetWidget(MOZ_GTK_WINDOW)), widget); + return widget; +} + +static void +AddToWindowContainer(GtkWidget* widget) +{ + gtk_container_add(GTK_CONTAINER(GetWidget(MOZ_GTK_WINDOW_CONTAINER)), widget); +} + +static GtkWidget* +CreateScrollbarWidget(WidgetNodeType aWidgetType, GtkOrientation aOrientation) +{ + GtkWidget* widget = gtk_scrollbar_new(aOrientation, nullptr); + AddToWindowContainer(widget); + return widget; +} + +static GtkWidget* +CreateCheckboxWidget() +{ + GtkWidget* widget = gtk_check_button_new_with_label("M"); + AddToWindowContainer(widget); + return widget; +} + +static GtkWidget* +CreateRadiobuttonWidget() +{ + GtkWidget* widget = gtk_radio_button_new_with_label(nullptr, "M"); + AddToWindowContainer(widget); + return widget; +} + +static GtkWidget* +CreateMenuBarWidget() +{ + GtkWidget* widget = gtk_menu_bar_new(); + AddToWindowContainer(widget); + return widget; +} + +static GtkWidget* +CreateMenuPopupWidget() +{ + GtkWidget* widget = gtk_menu_new(); + gtk_menu_attach_to_widget(GTK_MENU(widget), GetWidget(MOZ_GTK_WINDOW), + nullptr); + return widget; +} + +static GtkWidget* +CreateProgressWidget() +{ + GtkWidget* widget = gtk_progress_bar_new(); + AddToWindowContainer(widget); + return widget; +} + +static GtkWidget* +CreateTooltipWidget() +{ + MOZ_ASSERT(gtk_check_version(3, 20, 0) != nullptr, + "CreateTooltipWidget should be used for Gtk < 3.20 only."); + GtkWidget* widget = CreateWindowWidget(); + GtkStyleContext* style = gtk_widget_get_style_context(widget); + gtk_style_context_add_class(style, GTK_STYLE_CLASS_TOOLTIP); + return widget; +} + +static GtkWidget* +CreateExpanderWidget() +{ + GtkWidget* widget = gtk_expander_new("M"); + AddToWindowContainer(widget); + return widget; +} + +static GtkWidget* +CreateFrameWidget() +{ + GtkWidget* widget = gtk_frame_new(nullptr); + AddToWindowContainer(widget); + return widget; +} + +static GtkWidget* +CreateGripperWidget() +{ + GtkWidget* widget = gtk_handle_box_new(); + AddToWindowContainer(widget); + return widget; +} + +static GtkWidget* +CreateToolbarWidget() +{ + GtkWidget* widget = gtk_toolbar_new(); + gtk_container_add(GTK_CONTAINER(GetWidget(MOZ_GTK_GRIPPER)), widget); + return widget; +} + +static GtkWidget* +CreateToolbarSeparatorWidget() +{ + GtkWidget* widget = GTK_WIDGET(gtk_separator_tool_item_new()); + AddToWindowContainer(widget); + return widget; +} + +static GtkWidget* +CreateInfoBarWidget() +{ + GtkWidget* widget = gtk_info_bar_new(); + AddToWindowContainer(widget); + return widget; +} + +static GtkWidget* +CreateButtonWidget() +{ + GtkWidget* widget = gtk_button_new_with_label("M"); + AddToWindowContainer(widget); + return widget; +} + +static GtkWidget* +CreateToggleButtonWidget() +{ + GtkWidget* widget = gtk_toggle_button_new(); + AddToWindowContainer(widget); + return widget; +} + +static GtkWidget* +CreateButtonArrowWidget() +{ + GtkWidget* widget = gtk_arrow_new(GTK_ARROW_DOWN, GTK_SHADOW_OUT); + gtk_container_add(GTK_CONTAINER(GetWidget(MOZ_GTK_TOGGLE_BUTTON)), widget); + gtk_widget_show(widget); + return widget; +} + +static GtkWidget* +CreateSpinWidget() +{ + GtkWidget* widget = gtk_spin_button_new(nullptr, 1, 0); + AddToWindowContainer(widget); + return widget; +} + +static GtkWidget* +CreateEntryWidget() +{ + GtkWidget* widget = gtk_entry_new(); + AddToWindowContainer(widget); + return widget; +} + +static GtkWidget* +CreateComboBoxWidget() +{ + GtkWidget* widget = gtk_combo_box_new(); + AddToWindowContainer(widget); + return widget; +} + +typedef struct +{ + GType type; + GtkWidget** widget; +} GtkInnerWidgetInfo; + +static void +GetInnerWidget(GtkWidget* widget, gpointer client_data) +{ + auto info = static_cast<GtkInnerWidgetInfo*>(client_data); + + if (G_TYPE_CHECK_INSTANCE_TYPE(widget, info->type)) { + *info->widget = widget; + } +} + +static GtkWidget* +CreateComboBoxButtonWidget() +{ + GtkWidget* comboBox = GetWidget(MOZ_GTK_COMBOBOX); + GtkWidget* comboBoxButton = nullptr; + + /* Get its inner Button */ + GtkInnerWidgetInfo info = { GTK_TYPE_TOGGLE_BUTTON, + &comboBoxButton }; + gtk_container_forall(GTK_CONTAINER(comboBox), + GetInnerWidget, &info); + + if (!comboBoxButton) { + /* Shouldn't be reached with current internal gtk implementation; we + * use a generic toggle button as last resort fallback to avoid + * crashing. */ + comboBoxButton = GetWidget(MOZ_GTK_TOGGLE_BUTTON); + } else { + /* We need to have pointers to the inner widgets (button, separator, arrow) + * of the ComboBox to get the correct rendering from theme engines which + * special cases their look. Since the inner layout can change, we ask GTK + * to NULL our pointers when they are about to become invalid because the + * corresponding widgets don't exist anymore. It's the role of + * g_object_add_weak_pointer(). + * Note that if we don't find the inner widgets (which shouldn't happen), we + * fallback to use generic "non-inner" widgets, and they don't need that kind + * of weak pointer since they are explicit children of gProtoLayout and as + * such GTK holds a strong reference to them. */ + g_object_add_weak_pointer(G_OBJECT(comboBoxButton), + reinterpret_cast<gpointer *>(sWidgetStorage) + + MOZ_GTK_COMBOBOX_BUTTON); + } + + return comboBoxButton; +} + +static GtkWidget* +CreateComboBoxArrowWidget() +{ + GtkWidget* comboBoxButton = GetWidget(MOZ_GTK_COMBOBOX_BUTTON); + GtkWidget* comboBoxArrow = nullptr; + + /* Get the widgets inside the Button */ + GtkWidget* buttonChild = gtk_bin_get_child(GTK_BIN(comboBoxButton)); + if (GTK_IS_BOX(buttonChild)) { + /* appears-as-list = FALSE, cell-view = TRUE; the button + * contains an hbox. This hbox is there because the ComboBox + * needs to place a cell renderer, a separator, and an arrow in + * the button when appears-as-list is FALSE. */ + GtkInnerWidgetInfo info = { GTK_TYPE_ARROW, + &comboBoxArrow }; + gtk_container_forall(GTK_CONTAINER(buttonChild), + GetInnerWidget, &info); + } else if (GTK_IS_ARROW(buttonChild)) { + /* appears-as-list = TRUE, or cell-view = FALSE; + * the button only contains an arrow */ + comboBoxArrow = buttonChild; + } + + if (!comboBoxArrow) { + /* Shouldn't be reached with current internal gtk implementation; + * we gButtonArrowWidget as last resort fallback to avoid + * crashing. */ + comboBoxArrow = GetWidget(MOZ_GTK_BUTTON_ARROW); + } else { + g_object_add_weak_pointer(G_OBJECT(comboBoxArrow), + reinterpret_cast<gpointer *>(sWidgetStorage) + + MOZ_GTK_COMBOBOX_ARROW); + } + + return comboBoxArrow; +} + +static GtkWidget* +CreateComboBoxSeparatorWidget() +{ + // Ensure to search for separator only once as it can fail + // TODO - it won't initialize after ResetWidgetCache() call + static bool isMissingSeparator = false; + if (isMissingSeparator) + return nullptr; + + /* Get the widgets inside the Button */ + GtkWidget* comboBoxSeparator = nullptr; + GtkWidget* buttonChild = + gtk_bin_get_child(GTK_BIN(GetWidget(MOZ_GTK_COMBOBOX_BUTTON))); + if (GTK_IS_BOX(buttonChild)) { + /* appears-as-list = FALSE, cell-view = TRUE; the button + * contains an hbox. This hbox is there because the ComboBox + * needs to place a cell renderer, a separator, and an arrow in + * the button when appears-as-list is FALSE. */ + GtkInnerWidgetInfo info = { GTK_TYPE_SEPARATOR, + &comboBoxSeparator }; + gtk_container_forall(GTK_CONTAINER(buttonChild), + GetInnerWidget, &info); + } + + if (comboBoxSeparator) { + g_object_add_weak_pointer(G_OBJECT(comboBoxSeparator), + reinterpret_cast<gpointer *>(sWidgetStorage) + + MOZ_GTK_COMBOBOX_SEPARATOR); + } else { + /* comboBoxSeparator may be NULL + * when "appears-as-list" = TRUE or "cell-view" = FALSE; + * if there is no separator, then we just won't paint it. */ + isMissingSeparator = true; + } + + return comboBoxSeparator; +} + +static GtkWidget* +CreateComboBoxEntryWidget() +{ + GtkWidget* widget = gtk_combo_box_new_with_entry(); + AddToWindowContainer(widget); + return widget; +} + +static GtkWidget* +CreateComboBoxEntryTextareaWidget() +{ + GtkWidget* comboBoxTextarea = nullptr; + + /* Get its inner Entry and Button */ + GtkInnerWidgetInfo info = { GTK_TYPE_ENTRY, + &comboBoxTextarea }; + gtk_container_forall(GTK_CONTAINER(GetWidget(MOZ_GTK_COMBOBOX_ENTRY)), + GetInnerWidget, &info); + + if (!comboBoxTextarea) { + comboBoxTextarea = GetWidget(MOZ_GTK_ENTRY); + } else { + g_object_add_weak_pointer(G_OBJECT(comboBoxTextarea), + reinterpret_cast<gpointer *>(sWidgetStorage) + + MOZ_GTK_COMBOBOX_ENTRY); + } + + return comboBoxTextarea; +} + +static GtkWidget* +CreateComboBoxEntryButtonWidget() +{ + GtkWidget* comboBoxButton = nullptr; + + /* Get its inner Entry and Button */ + GtkInnerWidgetInfo info = { GTK_TYPE_TOGGLE_BUTTON, + &comboBoxButton }; + gtk_container_forall(GTK_CONTAINER(GetWidget(MOZ_GTK_COMBOBOX_ENTRY)), + GetInnerWidget, &info); + + if (!comboBoxButton) { + comboBoxButton = GetWidget(MOZ_GTK_TOGGLE_BUTTON); + } else { + g_object_add_weak_pointer(G_OBJECT(comboBoxButton), + reinterpret_cast<gpointer *>(sWidgetStorage) + + MOZ_GTK_COMBOBOX_ENTRY_BUTTON); + } + + return comboBoxButton; +} + +static GtkWidget* +CreateComboBoxEntryArrowWidget() +{ + GtkWidget* comboBoxArrow = nullptr; + + /* Get the Arrow inside the Button */ + GtkWidget* buttonChild = + gtk_bin_get_child(GTK_BIN(GetWidget(MOZ_GTK_COMBOBOX_ENTRY_BUTTON))); + + if (GTK_IS_BOX(buttonChild)) { + /* appears-as-list = FALSE, cell-view = TRUE; the button + * contains an hbox. This hbox is there because the ComboBox + * needs to place a cell renderer, a separator, and an arrow in + * the button when appears-as-list is FALSE. */ + GtkInnerWidgetInfo info = { GTK_TYPE_ARROW, + &comboBoxArrow }; + gtk_container_forall(GTK_CONTAINER(buttonChild), + GetInnerWidget, &info); + } else if (GTK_IS_ARROW(buttonChild)) { + /* appears-as-list = TRUE, or cell-view = FALSE; + * the button only contains an arrow */ + comboBoxArrow = buttonChild; + } + + if (!comboBoxArrow) { + /* Shouldn't be reached with current internal gtk implementation; + * we gButtonArrowWidget as last resort fallback to avoid + * crashing. */ + comboBoxArrow = GetWidget(MOZ_GTK_BUTTON_ARROW); + } else { + g_object_add_weak_pointer(G_OBJECT(comboBoxArrow), + reinterpret_cast<gpointer *>(sWidgetStorage) + + MOZ_GTK_COMBOBOX_ENTRY_ARROW); + } + + return comboBoxArrow; +} + +static GtkWidget* +CreateScrolledWindowWidget() +{ + GtkWidget* widget = gtk_scrolled_window_new(nullptr, nullptr); + AddToWindowContainer(widget); + return widget; +} + +static GtkWidget* +CreateTextViewWidget() +{ + GtkWidget* widget = gtk_text_view_new(); + gtk_container_add(GTK_CONTAINER(GetWidget(MOZ_GTK_SCROLLED_WINDOW)), + widget); + return widget; +} + +static GtkWidget* +CreateMenuSeparatorWidget() +{ + GtkWidget* widget = gtk_separator_menu_item_new(); + gtk_menu_shell_append(GTK_MENU_SHELL(GetWidget(MOZ_GTK_MENUPOPUP)), + widget); + return widget; +} + +static GtkWidget* +CreateTreeViewWidget() +{ + GtkWidget* widget = gtk_tree_view_new(); + AddToWindowContainer(widget); + return widget; +} + +static GtkWidget* +CreateTreeHeaderCellWidget() +{ + /* + * Some GTK engines paint the first and last cell + * of a TreeView header with a highlight. + * Since we do not know where our widget will be relative + * to the other buttons in the TreeView header, we must + * paint it as a button that is between two others, + * thus ensuring it is neither the first or last button + * in the header. + * GTK doesn't give us a way to do this explicitly, + * so we must paint with a button that is between two + * others. + */ + GtkTreeViewColumn* firstTreeViewColumn; + GtkTreeViewColumn* middleTreeViewColumn; + GtkTreeViewColumn* lastTreeViewColumn; + + GtkWidget *treeView = GetWidget(MOZ_GTK_TREEVIEW); + + /* Create and append our three columns */ + firstTreeViewColumn = gtk_tree_view_column_new(); + gtk_tree_view_column_set_title(firstTreeViewColumn, "M"); + gtk_tree_view_append_column(GTK_TREE_VIEW(treeView), + firstTreeViewColumn); + + middleTreeViewColumn = gtk_tree_view_column_new(); + gtk_tree_view_column_set_title(middleTreeViewColumn, "M"); + gtk_tree_view_append_column(GTK_TREE_VIEW(treeView), + middleTreeViewColumn); + + lastTreeViewColumn = gtk_tree_view_column_new(); + gtk_tree_view_column_set_title(lastTreeViewColumn, "M"); + gtk_tree_view_append_column(GTK_TREE_VIEW(treeView), + lastTreeViewColumn); + + /* Use the middle column's header for our button */ + return gtk_tree_view_column_get_button(middleTreeViewColumn); +} + +static GtkWidget* +CreateTreeHeaderSortArrowWidget() +{ + /* TODO, but it can't be NULL */ + GtkWidget* widget = gtk_button_new(); + AddToWindowContainer(widget); + return widget; +} + +static GtkWidget* +CreateHPanedWidget() +{ + GtkWidget* widget = gtk_paned_new(GTK_ORIENTATION_HORIZONTAL); + AddToWindowContainer(widget); + return widget; +} + +static GtkWidget* +CreateVPanedWidget() +{ + GtkWidget* widget = gtk_paned_new(GTK_ORIENTATION_VERTICAL); + AddToWindowContainer(widget); + return widget; +} + +static GtkWidget* +CreateScaleWidget(GtkOrientation aOrientation) +{ + GtkWidget* widget = gtk_scale_new(aOrientation, nullptr); + AddToWindowContainer(widget); + return widget; +} + +static GtkWidget* +CreateNotebookWidget() +{ + GtkWidget* widget = gtk_notebook_new(); + AddToWindowContainer(widget); + return widget; +} + +static GtkWidget* +CreateWidget(WidgetNodeType aWidgetType) +{ + switch (aWidgetType) { + case MOZ_GTK_WINDOW: + return CreateWindowWidget(); + case MOZ_GTK_WINDOW_CONTAINER: + return CreateWindowContainerWidget(); + case MOZ_GTK_CHECKBUTTON_CONTAINER: + return CreateCheckboxWidget(); + case MOZ_GTK_PROGRESSBAR: + return CreateProgressWidget(); + case MOZ_GTK_RADIOBUTTON_CONTAINER: + return CreateRadiobuttonWidget(); + case MOZ_GTK_SCROLLBAR_HORIZONTAL: + return CreateScrollbarWidget(aWidgetType, + GTK_ORIENTATION_HORIZONTAL); + case MOZ_GTK_SCROLLBAR_VERTICAL: + return CreateScrollbarWidget(aWidgetType, + GTK_ORIENTATION_VERTICAL); + case MOZ_GTK_MENUBAR: + return CreateMenuBarWidget(); + case MOZ_GTK_MENUPOPUP: + return CreateMenuPopupWidget(); + case MOZ_GTK_MENUSEPARATOR: + return CreateMenuSeparatorWidget(); + case MOZ_GTK_EXPANDER: + return CreateExpanderWidget(); + case MOZ_GTK_FRAME: + return CreateFrameWidget(); + case MOZ_GTK_GRIPPER: + return CreateGripperWidget(); + case MOZ_GTK_TOOLBAR: + return CreateToolbarWidget(); + case MOZ_GTK_TOOLBAR_SEPARATOR: + return CreateToolbarSeparatorWidget(); + case MOZ_GTK_INFO_BAR: + return CreateInfoBarWidget(); + case MOZ_GTK_SPINBUTTON: + return CreateSpinWidget(); + case MOZ_GTK_BUTTON: + return CreateButtonWidget(); + case MOZ_GTK_TOGGLE_BUTTON: + return CreateToggleButtonWidget(); + case MOZ_GTK_BUTTON_ARROW: + return CreateButtonArrowWidget(); + case MOZ_GTK_ENTRY: + return CreateEntryWidget(); + case MOZ_GTK_SCROLLED_WINDOW: + return CreateScrolledWindowWidget(); + case MOZ_GTK_TEXT_VIEW: + return CreateTextViewWidget(); + case MOZ_GTK_TREEVIEW: + return CreateTreeViewWidget(); + case MOZ_GTK_TREE_HEADER_CELL: + return CreateTreeHeaderCellWidget(); + case MOZ_GTK_TREE_HEADER_SORTARROW: + return CreateTreeHeaderSortArrowWidget(); + case MOZ_GTK_SPLITTER_HORIZONTAL: + return CreateHPanedWidget(); + case MOZ_GTK_SPLITTER_VERTICAL: + return CreateVPanedWidget(); + case MOZ_GTK_SCALE_HORIZONTAL: + return CreateScaleWidget(GTK_ORIENTATION_HORIZONTAL); + case MOZ_GTK_SCALE_VERTICAL: + return CreateScaleWidget(GTK_ORIENTATION_VERTICAL); + case MOZ_GTK_NOTEBOOK: + return CreateNotebookWidget(); + case MOZ_GTK_COMBOBOX: + return CreateComboBoxWidget(); + case MOZ_GTK_COMBOBOX_BUTTON: + return CreateComboBoxButtonWidget(); + case MOZ_GTK_COMBOBOX_ARROW: + return CreateComboBoxArrowWidget(); + case MOZ_GTK_COMBOBOX_SEPARATOR: + return CreateComboBoxSeparatorWidget(); + case MOZ_GTK_COMBOBOX_ENTRY: + return CreateComboBoxEntryWidget(); + case MOZ_GTK_COMBOBOX_ENTRY_TEXTAREA: + return CreateComboBoxEntryTextareaWidget(); + case MOZ_GTK_COMBOBOX_ENTRY_BUTTON: + return CreateComboBoxEntryButtonWidget(); + case MOZ_GTK_COMBOBOX_ENTRY_ARROW: + return CreateComboBoxEntryArrowWidget(); + default: + /* Not implemented */ + return nullptr; + } +} + +GtkWidget* +GetWidget(WidgetNodeType aWidgetType) +{ + GtkWidget* widget = sWidgetStorage[aWidgetType]; + if (!widget) { + widget = CreateWidget(aWidgetType); + sWidgetStorage[aWidgetType] = widget; + } + return widget; +} + +GtkStyleContext* +CreateStyleForWidget(GtkWidget* aWidget, GtkStyleContext* aParentStyle) +{ + static auto sGtkWidgetClassGetCSSName = + reinterpret_cast<const char* (*)(GtkWidgetClass*)> + (dlsym(RTLD_DEFAULT, "gtk_widget_class_get_css_name")); + + GtkWidgetClass *widgetClass = GTK_WIDGET_GET_CLASS(aWidget); + const gchar* name = sGtkWidgetClassGetCSSName ? + sGtkWidgetClassGetCSSName(widgetClass) : nullptr; + + GtkStyleContext *context = + CreateCSSNode(name, aParentStyle, G_TYPE_FROM_CLASS(widgetClass)); + + // Classes are stored on the style context instead of the path so that any + // future gtk_style_context_save() will inherit classes on the head CSS + // node, in the same way as happens when called on a style context owned by + // a widget. + // + // Classes can be stored on a GtkCssNodeDeclaration and/or the path. + // gtk_style_context_save() reuses the GtkCssNodeDeclaration, and appends a + // new object to the path, without copying the classes from the old path + // head. The new head picks up classes from the GtkCssNodeDeclaration, but + // not the path. GtkWidgets store their classes on the + // GtkCssNodeDeclaration, so make sure to add classes there. + // + // Picking up classes from the style context also means that + // https://bugzilla.gnome.org/show_bug.cgi?id=767312, which can stop + // gtk_widget_path_append_for_widget() from finding classes in GTK 3.20, + // is not a problem. + GtkStyleContext* widgetStyle = gtk_widget_get_style_context(aWidget); + GList* classes = gtk_style_context_list_classes(widgetStyle); + for (GList* link = classes; link; link = link->next) { + gtk_style_context_add_class(context, static_cast<gchar*>(link->data)); + } + g_list_free(classes); + + // Release any floating reference on aWidget. + g_object_ref_sink(aWidget); + g_object_unref(aWidget); + + return context; +} + +static GtkStyleContext* +CreateStyleForWidget(GtkWidget* aWidget, WidgetNodeType aParentType) +{ + return CreateStyleForWidget(aWidget, GetWidgetRootStyle(aParentType)); +} + +GtkStyleContext* +CreateCSSNode(const char* aName, GtkStyleContext* aParentStyle, GType aType) +{ + static auto sGtkWidgetPathIterSetObjectName = + reinterpret_cast<void (*)(GtkWidgetPath *, gint, const char *)> + (dlsym(RTLD_DEFAULT, "gtk_widget_path_iter_set_object_name")); + + GtkWidgetPath* path; + if (aParentStyle) { + path = gtk_widget_path_copy(gtk_style_context_get_path(aParentStyle)); + // Copy classes from the parent style context to its corresponding node in + // the path, because GTK will only match against ancestor classes if they + // are on the path. + GList* classes = gtk_style_context_list_classes(aParentStyle); + for (GList* link = classes; link; link = link->next) { + gtk_widget_path_iter_add_class(path, -1, static_cast<gchar*>(link->data)); + } + g_list_free(classes); + } else { + path = gtk_widget_path_new(); + } + + gtk_widget_path_append_type(path, aType); + + if (sGtkWidgetPathIterSetObjectName) { + (*sGtkWidgetPathIterSetObjectName)(path, -1, aName); + } + + GtkStyleContext *context = gtk_style_context_new(); + gtk_style_context_set_path(context, path); + gtk_style_context_set_parent(context, aParentStyle); + gtk_widget_path_unref(path); + + return context; +} + +// Return a style context matching that of the root CSS node of a widget. +// This is used by all GTK versions. +static GtkStyleContext* +GetWidgetRootStyle(WidgetNodeType aNodeType) +{ + GtkStyleContext* style = sStyleStorage[aNodeType]; + if (style) + return style; + + switch (aNodeType) { + case MOZ_GTK_MENUBARITEM: + style = CreateStyleForWidget(gtk_menu_item_new(), MOZ_GTK_MENUBAR); + break; + case MOZ_GTK_MENUITEM: + style = CreateStyleForWidget(gtk_menu_item_new(), MOZ_GTK_MENUPOPUP); + break; + case MOZ_GTK_IMAGEMENUITEM: + style = CreateStyleForWidget(gtk_image_menu_item_new(), MOZ_GTK_MENUPOPUP); + break; + case MOZ_GTK_CHECKMENUITEM_CONTAINER: + style = CreateStyleForWidget(gtk_check_menu_item_new(), MOZ_GTK_MENUPOPUP); + break; + case MOZ_GTK_RADIOMENUITEM_CONTAINER: + style = CreateStyleForWidget(gtk_radio_menu_item_new(nullptr), + MOZ_GTK_MENUPOPUP); + break; + default: + GtkWidget* widget = GetWidget(aNodeType); + MOZ_ASSERT(widget); + return gtk_widget_get_style_context(widget); + } + + MOZ_ASSERT(style); + sStyleStorage[aNodeType] = style; + return style; +} + +static GtkStyleContext* +CreateChildCSSNode(const char* aName, WidgetNodeType aParentNodeType) +{ + return CreateCSSNode(aName, GetCssNodeStyleInternal(aParentNodeType)); +} + +static GtkStyleContext* +GetWidgetStyleWithClass(WidgetNodeType aWidgetType, const gchar* aStyleClass) +{ + GtkStyleContext* style = GetWidgetRootStyle(aWidgetType); + gtk_style_context_save(style); + MOZ_ASSERT(!sStyleContextNeedsRestore); + sStyleContextNeedsRestore = true; + gtk_style_context_add_class(style, aStyleClass); + return style; +} + +/* GetCssNodeStyleInternal is used by Gtk >= 3.20 */ +static GtkStyleContext* +GetCssNodeStyleInternal(WidgetNodeType aNodeType) +{ + GtkStyleContext* style = sStyleStorage[aNodeType]; + if (style) + return style; + + switch (aNodeType) { + case MOZ_GTK_SCROLLBAR_CONTENTS_HORIZONTAL: + style = CreateChildCSSNode("contents", + MOZ_GTK_SCROLLBAR_HORIZONTAL); + break; + case MOZ_GTK_SCROLLBAR_TROUGH_HORIZONTAL: + style = CreateChildCSSNode(GTK_STYLE_CLASS_TROUGH, + MOZ_GTK_SCROLLBAR_CONTENTS_HORIZONTAL); + break; + case MOZ_GTK_SCROLLBAR_THUMB_HORIZONTAL: + style = CreateChildCSSNode(GTK_STYLE_CLASS_SLIDER, + MOZ_GTK_SCROLLBAR_TROUGH_HORIZONTAL); + break; + case MOZ_GTK_SCROLLBAR_CONTENTS_VERTICAL: + style = CreateChildCSSNode("contents", + MOZ_GTK_SCROLLBAR_VERTICAL); + break; + case MOZ_GTK_SCROLLBAR_TROUGH_VERTICAL: + style = CreateChildCSSNode(GTK_STYLE_CLASS_TROUGH, + MOZ_GTK_SCROLLBAR_CONTENTS_VERTICAL); + break; + case MOZ_GTK_SCROLLBAR_THUMB_VERTICAL: + style = CreateChildCSSNode(GTK_STYLE_CLASS_SLIDER, + MOZ_GTK_SCROLLBAR_TROUGH_VERTICAL); + break; + case MOZ_GTK_SCROLLBAR_BUTTON: + style = CreateChildCSSNode(GTK_STYLE_CLASS_BUTTON, + MOZ_GTK_SCROLLBAR_CONTENTS_VERTICAL); + break; + case MOZ_GTK_RADIOBUTTON: + style = CreateChildCSSNode(GTK_STYLE_CLASS_RADIO, + MOZ_GTK_RADIOBUTTON_CONTAINER); + break; + case MOZ_GTK_CHECKBUTTON: + style = CreateChildCSSNode(GTK_STYLE_CLASS_CHECK, + MOZ_GTK_CHECKBUTTON_CONTAINER); + break; + case MOZ_GTK_RADIOMENUITEM: + style = CreateChildCSSNode(GTK_STYLE_CLASS_RADIO, + MOZ_GTK_RADIOMENUITEM_CONTAINER); + break; + case MOZ_GTK_CHECKMENUITEM: + style = CreateChildCSSNode(GTK_STYLE_CLASS_CHECK, + MOZ_GTK_CHECKMENUITEM_CONTAINER); + break; + case MOZ_GTK_PROGRESS_TROUGH: + /* Progress bar background (trough) */ + style = CreateChildCSSNode(GTK_STYLE_CLASS_TROUGH, + MOZ_GTK_PROGRESSBAR); + break; + case MOZ_GTK_PROGRESS_CHUNK: + style = CreateChildCSSNode("progress", + MOZ_GTK_PROGRESS_TROUGH); + break; + case MOZ_GTK_TOOLTIP: + // We create this from the path because GtkTooltipWindow is not public. + style = CreateCSSNode("tooltip", nullptr, GTK_TYPE_TOOLTIP); + gtk_style_context_add_class(style, GTK_STYLE_CLASS_BACKGROUND); + break; + case MOZ_GTK_GRIPPER: + // TODO - create from CSS node + return GetWidgetStyleWithClass(MOZ_GTK_GRIPPER, + GTK_STYLE_CLASS_GRIP); + case MOZ_GTK_INFO_BAR: + // TODO - create from CSS node + return GetWidgetStyleWithClass(MOZ_GTK_INFO_BAR, + GTK_STYLE_CLASS_INFO); + case MOZ_GTK_SPINBUTTON_ENTRY: + // TODO - create from CSS node + return GetWidgetStyleWithClass(MOZ_GTK_SPINBUTTON, + GTK_STYLE_CLASS_ENTRY); + case MOZ_GTK_SCROLLED_WINDOW: + // TODO - create from CSS node + return GetWidgetStyleWithClass(MOZ_GTK_SCROLLED_WINDOW, + GTK_STYLE_CLASS_FRAME); + case MOZ_GTK_TEXT_VIEW: + // TODO - create from CSS node + return GetWidgetStyleWithClass(MOZ_GTK_TEXT_VIEW, + GTK_STYLE_CLASS_VIEW); + case MOZ_GTK_FRAME_BORDER: + style = CreateChildCSSNode("border", MOZ_GTK_FRAME); + break; + case MOZ_GTK_TREEVIEW_VIEW: + // TODO - create from CSS node + return GetWidgetStyleWithClass(MOZ_GTK_TREEVIEW, + GTK_STYLE_CLASS_VIEW); + case MOZ_GTK_TREEVIEW_EXPANDER: + // TODO - create from CSS node + return GetWidgetStyleWithClass(MOZ_GTK_TREEVIEW, + GTK_STYLE_CLASS_EXPANDER); + case MOZ_GTK_SPLITTER_SEPARATOR_HORIZONTAL: + style = CreateChildCSSNode("separator", + MOZ_GTK_SPLITTER_HORIZONTAL); + break; + case MOZ_GTK_SPLITTER_SEPARATOR_VERTICAL: + style = CreateChildCSSNode("separator", + MOZ_GTK_SPLITTER_VERTICAL); + break; + case MOZ_GTK_SCALE_CONTENTS_HORIZONTAL: + style = CreateChildCSSNode("contents", + MOZ_GTK_SCALE_HORIZONTAL); + break; + case MOZ_GTK_SCALE_CONTENTS_VERTICAL: + style = CreateChildCSSNode("contents", + MOZ_GTK_SCALE_VERTICAL); + break; + case MOZ_GTK_SCALE_TROUGH_HORIZONTAL: + style = CreateChildCSSNode(GTK_STYLE_CLASS_TROUGH, + MOZ_GTK_SCALE_CONTENTS_HORIZONTAL); + break; + case MOZ_GTK_SCALE_TROUGH_VERTICAL: + style = CreateChildCSSNode(GTK_STYLE_CLASS_TROUGH, + MOZ_GTK_SCALE_CONTENTS_VERTICAL); + break; + case MOZ_GTK_SCALE_THUMB_HORIZONTAL: + style = CreateChildCSSNode(GTK_STYLE_CLASS_SLIDER, + MOZ_GTK_SCALE_TROUGH_HORIZONTAL); + break; + case MOZ_GTK_SCALE_THUMB_VERTICAL: + style = CreateChildCSSNode(GTK_STYLE_CLASS_SLIDER, + MOZ_GTK_SCALE_TROUGH_VERTICAL); + break; + case MOZ_GTK_TAB_TOP: + { + // TODO - create from CSS node + style = GetWidgetStyleWithClass(MOZ_GTK_NOTEBOOK, + GTK_STYLE_CLASS_TOP); + gtk_style_context_add_region(style, GTK_STYLE_REGION_TAB, + static_cast<GtkRegionFlags>(0)); + return style; + } + case MOZ_GTK_TAB_BOTTOM: + { + // TODO - create from CSS node + style = GetWidgetStyleWithClass(MOZ_GTK_NOTEBOOK, + GTK_STYLE_CLASS_BOTTOM); + gtk_style_context_add_region(style, GTK_STYLE_REGION_TAB, + static_cast<GtkRegionFlags>(0)); + return style; + } + case MOZ_GTK_NOTEBOOK: + case MOZ_GTK_NOTEBOOK_HEADER: + case MOZ_GTK_TABPANELS: + case MOZ_GTK_TAB_SCROLLARROW: + { + // TODO - create from CSS node + GtkWidget* widget = GetWidget(MOZ_GTK_NOTEBOOK); + return gtk_widget_get_style_context(widget); + } + default: + return GetWidgetRootStyle(aNodeType); + } + + MOZ_ASSERT(style, "missing style context for node type"); + sStyleStorage[aNodeType] = style; + return style; +} + +/* GetWidgetStyleInternal is used by Gtk < 3.20 */ +static GtkStyleContext* +GetWidgetStyleInternal(WidgetNodeType aNodeType) +{ + switch (aNodeType) { + case MOZ_GTK_SCROLLBAR_TROUGH_HORIZONTAL: + return GetWidgetStyleWithClass(MOZ_GTK_SCROLLBAR_HORIZONTAL, + GTK_STYLE_CLASS_TROUGH); + case MOZ_GTK_SCROLLBAR_THUMB_HORIZONTAL: + return GetWidgetStyleWithClass(MOZ_GTK_SCROLLBAR_HORIZONTAL, + GTK_STYLE_CLASS_SLIDER); + case MOZ_GTK_SCROLLBAR_TROUGH_VERTICAL: + return GetWidgetStyleWithClass(MOZ_GTK_SCROLLBAR_VERTICAL, + GTK_STYLE_CLASS_TROUGH); + case MOZ_GTK_SCROLLBAR_THUMB_VERTICAL: + return GetWidgetStyleWithClass(MOZ_GTK_SCROLLBAR_VERTICAL, + GTK_STYLE_CLASS_SLIDER); + case MOZ_GTK_RADIOBUTTON: + return GetWidgetStyleWithClass(MOZ_GTK_RADIOBUTTON_CONTAINER, + GTK_STYLE_CLASS_RADIO); + case MOZ_GTK_CHECKBUTTON: + return GetWidgetStyleWithClass(MOZ_GTK_CHECKBUTTON_CONTAINER, + GTK_STYLE_CLASS_CHECK); + case MOZ_GTK_RADIOMENUITEM: + return GetWidgetStyleWithClass(MOZ_GTK_RADIOMENUITEM_CONTAINER, + GTK_STYLE_CLASS_RADIO); + case MOZ_GTK_CHECKMENUITEM: + return GetWidgetStyleWithClass(MOZ_GTK_CHECKMENUITEM_CONTAINER, + GTK_STYLE_CLASS_CHECK); + case MOZ_GTK_PROGRESS_TROUGH: + return GetWidgetStyleWithClass(MOZ_GTK_PROGRESSBAR, + GTK_STYLE_CLASS_TROUGH); + case MOZ_GTK_TOOLTIP: { + GtkStyleContext* style = sStyleStorage[aNodeType]; + if (style) + return style; + + // The tooltip style class is added first in CreateTooltipWidget() so + // that gtk_widget_path_append_for_widget() in CreateStyleForWidget() + // will find it. + GtkWidget* tooltipWindow = CreateTooltipWidget(); + style = CreateStyleForWidget(tooltipWindow, nullptr); + gtk_widget_destroy(tooltipWindow); // Release GtkWindow self-reference. + sStyleStorage[aNodeType] = style; + return style; + } + case MOZ_GTK_GRIPPER: + return GetWidgetStyleWithClass(MOZ_GTK_GRIPPER, + GTK_STYLE_CLASS_GRIP); + case MOZ_GTK_INFO_BAR: + return GetWidgetStyleWithClass(MOZ_GTK_INFO_BAR, + GTK_STYLE_CLASS_INFO); + case MOZ_GTK_SPINBUTTON_ENTRY: + return GetWidgetStyleWithClass(MOZ_GTK_SPINBUTTON, + GTK_STYLE_CLASS_ENTRY); + case MOZ_GTK_SCROLLED_WINDOW: + return GetWidgetStyleWithClass(MOZ_GTK_SCROLLED_WINDOW, + GTK_STYLE_CLASS_FRAME); + case MOZ_GTK_TEXT_VIEW: + return GetWidgetStyleWithClass(MOZ_GTK_TEXT_VIEW, + GTK_STYLE_CLASS_VIEW); + case MOZ_GTK_FRAME_BORDER: + return GetWidgetRootStyle(MOZ_GTK_FRAME); + case MOZ_GTK_TREEVIEW_VIEW: + return GetWidgetStyleWithClass(MOZ_GTK_TREEVIEW, + GTK_STYLE_CLASS_VIEW); + case MOZ_GTK_TREEVIEW_EXPANDER: + return GetWidgetStyleWithClass(MOZ_GTK_TREEVIEW, + GTK_STYLE_CLASS_EXPANDER); + case MOZ_GTK_SPLITTER_SEPARATOR_HORIZONTAL: + return GetWidgetStyleWithClass(MOZ_GTK_SPLITTER_HORIZONTAL, + GTK_STYLE_CLASS_PANE_SEPARATOR); + case MOZ_GTK_SPLITTER_SEPARATOR_VERTICAL: + return GetWidgetStyleWithClass(MOZ_GTK_SPLITTER_VERTICAL, + GTK_STYLE_CLASS_PANE_SEPARATOR); + case MOZ_GTK_SCALE_TROUGH_HORIZONTAL: + return GetWidgetStyleWithClass(MOZ_GTK_SCALE_HORIZONTAL, + GTK_STYLE_CLASS_TROUGH); + case MOZ_GTK_SCALE_TROUGH_VERTICAL: + return GetWidgetStyleWithClass(MOZ_GTK_SCALE_VERTICAL, + GTK_STYLE_CLASS_TROUGH); + case MOZ_GTK_SCALE_THUMB_HORIZONTAL: + return GetWidgetStyleWithClass(MOZ_GTK_SCALE_HORIZONTAL, + GTK_STYLE_CLASS_SLIDER); + case MOZ_GTK_SCALE_THUMB_VERTICAL: + return GetWidgetStyleWithClass(MOZ_GTK_SCALE_VERTICAL, + GTK_STYLE_CLASS_SLIDER); + case MOZ_GTK_TAB_TOP: + { + GtkStyleContext* style = GetWidgetStyleWithClass(MOZ_GTK_NOTEBOOK, + GTK_STYLE_CLASS_TOP); + gtk_style_context_add_region(style, GTK_STYLE_REGION_TAB, + static_cast<GtkRegionFlags>(0)); + return style; + } + case MOZ_GTK_TAB_BOTTOM: + { + GtkStyleContext* style = GetWidgetStyleWithClass(MOZ_GTK_NOTEBOOK, + GTK_STYLE_CLASS_BOTTOM); + gtk_style_context_add_region(style, GTK_STYLE_REGION_TAB, + static_cast<GtkRegionFlags>(0)); + return style; + } + case MOZ_GTK_NOTEBOOK: + case MOZ_GTK_NOTEBOOK_HEADER: + case MOZ_GTK_TABPANELS: + case MOZ_GTK_TAB_SCROLLARROW: + { + GtkWidget* widget = GetWidget(MOZ_GTK_NOTEBOOK); + return gtk_widget_get_style_context(widget); + } + default: + return GetWidgetRootStyle(aNodeType); + } +} + +void +ResetWidgetCache(void) +{ + MOZ_ASSERT(!sStyleContextNeedsRestore); +#ifdef DEBUG + MOZ_ASSERT(!sCurrentStyleContext); +#endif + + for (int i = 0; i < MOZ_GTK_WIDGET_NODE_COUNT; i++) { + if (sStyleStorage[i]) + g_object_unref(sStyleStorage[i]); + } + mozilla::PodArrayZero(sStyleStorage); + + /* This will destroy all of our widgets */ + if (sWidgetStorage[MOZ_GTK_WINDOW]) + gtk_widget_destroy(sWidgetStorage[MOZ_GTK_WINDOW]); + + /* Clear already freed arrays */ + mozilla::PodArrayZero(sWidgetStorage); +} + +GtkStyleContext* +ClaimStyleContext(WidgetNodeType aNodeType, GtkTextDirection aDirection, + GtkStateFlags aStateFlags, StyleFlags aFlags) +{ + MOZ_ASSERT(!sStyleContextNeedsRestore); + GtkStyleContext* style; + if (gtk_check_version(3, 20, 0) != nullptr) { + style = GetWidgetStyleInternal(aNodeType); + } else { + style = GetCssNodeStyleInternal(aNodeType); + } +#ifdef DEBUG + MOZ_ASSERT(!sCurrentStyleContext); + sCurrentStyleContext = style; +#endif + bool stateChanged = false; + bool stateHasDirection = gtk_get_minor_version() >= 8; + GtkStateFlags oldState = gtk_style_context_get_state(style); + MOZ_ASSERT(!(aStateFlags & (STATE_FLAG_DIR_LTR|STATE_FLAG_DIR_RTL))); + unsigned newState = aStateFlags; + if (stateHasDirection) { + switch (aDirection) { + case GTK_TEXT_DIR_LTR: + newState |= STATE_FLAG_DIR_LTR; + break; + case GTK_TEXT_DIR_RTL: + newState |= STATE_FLAG_DIR_RTL; + break; + default: + MOZ_FALLTHROUGH_ASSERT("Bad GtkTextDirection"); + case GTK_TEXT_DIR_NONE: + // GtkWidget uses a default direction if neither is explicitly + // specified, but here DIR_NONE is interpreted as meaning the + // direction is not important, so don't change the direction + // unnecessarily. + newState |= oldState & (STATE_FLAG_DIR_LTR|STATE_FLAG_DIR_RTL); + } + } else if (aDirection != GTK_TEXT_DIR_NONE) { + GtkTextDirection oldDirection = gtk_style_context_get_direction(style); + if (aDirection != oldDirection) { + gtk_style_context_set_direction(style, aDirection); + stateChanged = true; + } + } + if (oldState != newState) { + gtk_style_context_set_state(style, static_cast<GtkStateFlags>(newState)); + stateChanged = true; + } + // This invalidate is necessary for unsaved style contexts from GtkWidgets + // in pre-3.18 GTK, because automatic invalidation of such contexts + // was delayed until a resize event runs. + // + // https://bugzilla.mozilla.org/show_bug.cgi?id=1272194#c7 + // + // Avoid calling invalidate on saved contexts to avoid performing + // build_properties() (in 3.16 stylecontext.c) unnecessarily early. + if (stateChanged && !sStyleContextNeedsRestore) { + gtk_style_context_invalidate(style); + } + return style; +} + +void +ReleaseStyleContext(GtkStyleContext* aStyleContext) +{ + if (sStyleContextNeedsRestore) { + gtk_style_context_restore(aStyleContext); + } + sStyleContextNeedsRestore = false; +#ifdef DEBUG + MOZ_ASSERT(sCurrentStyleContext == aStyleContext); + sCurrentStyleContext = nullptr; +#endif +} diff --git a/widget/gtk/WidgetStyleCache.h b/widget/gtk/WidgetStyleCache.h new file mode 100644 index 000000000..2cbb5f960 --- /dev/null +++ b/widget/gtk/WidgetStyleCache.h @@ -0,0 +1,48 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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/. */ + + +#ifndef WidgetStyleCache_h +#define WidgetStyleCache_h + +#include <gtk/gtk.h> +#include "gtkdrawing.h" + + +typedef unsigned StyleFlags; +enum : StyleFlags { + NO_STYLE_FLAGS, + WHATEVER_MIGHT_BE_NEEDED = 1U << 0, +}; + +GtkWidget* +GetWidget(WidgetNodeType aNodeType); + +/* + * Return a new style context based on aWidget, as a child of aParentStyle. + * If aWidget still has a floating reference, then it is sunk and released. + */ +GtkStyleContext* +CreateStyleForWidget(GtkWidget* aWidget, GtkStyleContext* aParentStyle); + +GtkStyleContext* +CreateCSSNode(const char* aName, + GtkStyleContext* aParentStyle, + GType aType = G_TYPE_NONE); + +// Callers must call ReleaseStyleContext() on the returned context. +GtkStyleContext* +ClaimStyleContext(WidgetNodeType aNodeType, + GtkTextDirection aDirection = GTK_TEXT_DIR_NONE, + GtkStateFlags aStateFlags = GTK_STATE_FLAG_NORMAL, + StyleFlags aFlags = NO_STYLE_FLAGS); +void +ReleaseStyleContext(GtkStyleContext* style); + +void +ResetWidgetCache(void); + +#endif // WidgetStyleCache_h diff --git a/widget/gtk/WidgetTraceEvent.cpp b/widget/gtk/WidgetTraceEvent.cpp new file mode 100644 index 000000000..c09944ac0 --- /dev/null +++ b/widget/gtk/WidgetTraceEvent.cpp @@ -0,0 +1,78 @@ +/* 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/WidgetTraceEvent.h" + +#include <glib.h> +#include <mozilla/CondVar.h> +#include <mozilla/Mutex.h> +#include <stdio.h> + +using mozilla::CondVar; +using mozilla::Mutex; +using mozilla::MutexAutoLock; + +namespace { + +Mutex* sMutex = nullptr; +CondVar* sCondVar = nullptr; +bool sTracerProcessed = false; + +// This function is called from the main (UI) thread. +gboolean TracerCallback(gpointer data) +{ + mozilla::SignalTracerThread(); + return FALSE; +} + +} // namespace + +namespace mozilla { + +bool InitWidgetTracing() +{ + sMutex = new Mutex("Event tracer thread mutex"); + sCondVar = new CondVar(*sMutex, "Event tracer thread condvar"); + return true; +} + +void CleanUpWidgetTracing() +{ + delete sMutex; + delete sCondVar; + sMutex = nullptr; + sCondVar = nullptr; +} + +// This function is called from the background tracer thread. +bool FireAndWaitForTracerEvent() +{ + MOZ_ASSERT(sMutex && sCondVar, "Tracing not initialized!"); + + // Send a default-priority idle event through the + // event loop, and wait for it to finish. + MutexAutoLock lock(*sMutex); + MOZ_ASSERT(!sTracerProcessed, "Tracer synchronization state is wrong"); + g_idle_add_full(G_PRIORITY_DEFAULT, + TracerCallback, + nullptr, + nullptr); + while (!sTracerProcessed) + sCondVar->Wait(); + sTracerProcessed = false; + return true; +} + +void SignalTracerThread() +{ + if (!sMutex || !sCondVar) + return; + MutexAutoLock lock(*sMutex); + if (!sTracerProcessed) { + sTracerProcessed = true; + sCondVar->Notify(); + } +} + +} // namespace mozilla diff --git a/widget/gtk/WidgetUtilsGtk.cpp b/widget/gtk/WidgetUtilsGtk.cpp new file mode 100644 index 000000000..393f66908 --- /dev/null +++ b/widget/gtk/WidgetUtilsGtk.cpp @@ -0,0 +1,51 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 "WidgetUtilsGtk.h" + +namespace mozilla { + +namespace widget { + +int32_t WidgetUtilsGTK::IsTouchDeviceSupportPresent() +{ +#if GTK_CHECK_VERSION(3,4,0) + int32_t result = 0; + GdkDisplay* display = gdk_display_get_default(); + if (!display) { + return 0; + } + + GdkDeviceManager* manager = gdk_display_get_device_manager(display); + if (!manager) { + return 0; + } + + GList* devices = + gdk_device_manager_list_devices(manager, GDK_DEVICE_TYPE_SLAVE); + GList* list = devices; + + while (devices) { + GdkDevice* device = static_cast<GdkDevice*>(devices->data); + if (gdk_device_get_source(device) == GDK_SOURCE_TOUCHSCREEN) { + result = 1; + break; + } + devices = devices->next; + } + + if (list) { + g_list_free(list); + } + + return result; +#else + return 0; +#endif +} + +} // namespace widget + +} // namespace mozilla diff --git a/widget/gtk/WidgetUtilsGtk.h b/widget/gtk/WidgetUtilsGtk.h new file mode 100644 index 000000000..8c132c11b --- /dev/null +++ b/widget/gtk/WidgetUtilsGtk.h @@ -0,0 +1,25 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#ifndef WidgetUtilsGtk_h__ +#define WidgetUtilsGtk_h__ + +#include <stdint.h> + +namespace mozilla { +namespace widget { + +class WidgetUtilsGTK +{ +public: + /* See WidgetUtils::IsTouchDeviceSupportPresent(). */ + static int32_t IsTouchDeviceSupportPresent(); +}; + +} // namespace widget + +} // namespace mozilla + +#endif // WidgetUtilsGtk_h__ diff --git a/widget/gtk/WindowSurfaceProvider.cpp b/widget/gtk/WindowSurfaceProvider.cpp new file mode 100644 index 000000000..526fe6a25 --- /dev/null +++ b/widget/gtk/WindowSurfaceProvider.cpp @@ -0,0 +1,114 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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 "WindowSurfaceProvider.h" + +#include "gfxPlatformGtk.h" +#include "mozilla/layers/LayersTypes.h" +#include "WindowSurfaceX11Image.h" +#include "WindowSurfaceX11SHM.h" +#include "WindowSurfaceXRender.h" + +namespace mozilla { +namespace widget { + +using namespace mozilla::gfx; +using namespace mozilla::layers; + +WindowSurfaceProvider::WindowSurfaceProvider() + : mXDisplay(nullptr) + , mXWindow(0) + , mXVisual(nullptr) + , mXDepth(0) + , mWindowSurface(nullptr) +{ +} + +void WindowSurfaceProvider::Initialize( + Display* aDisplay, + Window aWindow, + Visual* aVisual, + int aDepth) +{ + // We should not be initialized + MOZ_ASSERT(!mXDisplay); + + // This should also be a valid initialization + MOZ_ASSERT(aDisplay && aWindow != X11None && aVisual); + + mXDisplay = aDisplay; + mXWindow = aWindow; + mXVisual = aVisual; + mXDepth = aDepth; +} +void WindowSurfaceProvider::CleanupResources() +{ + mWindowSurface = nullptr; +} + +UniquePtr<WindowSurface> +WindowSurfaceProvider::CreateWindowSurface() +{ + // We should be initialized + MOZ_ASSERT(mXDisplay); + + // Blit to the window with the following priority: + // 1. XRender (iff XRender is enabled && we are in-process) + // 2. MIT-SHM + // 3. XPutImage + +#ifdef MOZ_WIDGET_GTK + if (gfxVars::UseXRender()) { + LOGDRAW(("Drawing to nsWindow %p using XRender\n", (void*)this)); + return MakeUnique<WindowSurfaceXRender>(mXDisplay, mXWindow, mXVisual, mXDepth); + } +#endif // MOZ_WIDGET_GTK + +#ifdef MOZ_HAVE_SHMIMAGE + if (nsShmImage::UseShm()) { + LOGDRAW(("Drawing to nsWindow %p using MIT-SHM\n", (void*)this)); + return MakeUnique<WindowSurfaceX11SHM>(mXDisplay, mXWindow, mXVisual, mXDepth); + } +#endif // MOZ_HAVE_SHMIMAGE + + LOGDRAW(("Drawing to nsWindow %p using XPutImage\n", (void*)this)); + return MakeUnique<WindowSurfaceX11Image>(mXDisplay, mXWindow, mXVisual, mXDepth); +} + +already_AddRefed<gfx::DrawTarget> +WindowSurfaceProvider::StartRemoteDrawingInRegion(LayoutDeviceIntRegion& aInvalidRegion, + layers::BufferMode* aBufferMode) +{ + if (aInvalidRegion.IsEmpty()) + return nullptr; + + if (!mWindowSurface) { + mWindowSurface = CreateWindowSurface(); + if (!mWindowSurface) + return nullptr; + } + + *aBufferMode = BufferMode::BUFFER_NONE; + RefPtr<DrawTarget> dt = nullptr; + if (!(dt = mWindowSurface->Lock(aInvalidRegion)) && + !mWindowSurface->IsFallback()) { + gfxWarningOnce() << "Failed to lock WindowSurface, falling back to XPutImage backend."; + mWindowSurface = MakeUnique<WindowSurfaceX11Image>(mXDisplay, mXWindow, mXVisual, mXDepth); + dt = mWindowSurface->Lock(aInvalidRegion); + } + return dt.forget(); +} + +void +WindowSurfaceProvider::EndRemoteDrawingInRegion(gfx::DrawTarget* aDrawTarget, + LayoutDeviceIntRegion& aInvalidRegion) +{ + if (mWindowSurface) + mWindowSurface->Commit(aInvalidRegion); +} + +} // namespace mozilla +} // namespace widget diff --git a/widget/gtk/WindowSurfaceProvider.h b/widget/gtk/WindowSurfaceProvider.h new file mode 100644 index 000000000..73b23031e --- /dev/null +++ b/widget/gtk/WindowSurfaceProvider.h @@ -0,0 +1,69 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#ifndef _MOZILLA_WIDGET_GTK_WINDOW_SURFACE_PROVIDER_H +#define _MOZILLA_WIDGET_GTK_WINDOW_SURFACE_PROVIDER_H + +#include "mozilla/widget/WindowSurface.h" +#include "mozilla/gfx/Types.h" +#include "mozilla/gfx/2D.h" +#include "Units.h" + +#include <X11/Xlib.h> // for Window, Display, Visual, etc. + +namespace mozilla { +namespace widget { + +/* + * Holds the logic for creating WindowSurface's for a GTK nsWindow. + * The main purpose of this class is to allow sharing of logic between + * nsWindow and X11CompositorWidget, for when OMTC is enabled or disabled. + */ +class WindowSurfaceProvider final +{ +public: + WindowSurfaceProvider(); + + /** + * Initializes the WindowSurfaceProvider by giving it the window + * handle and display to attach to. WindowSurfaceProvider doesn't + * own the Display, Window, etc, and they must continue to exist + * while WindowSurfaceProvider is used. + */ + void Initialize( + Display* aDisplay, + Window aWindow, + Visual* aVisual, + int aDepth); + + /** + * Releases any surfaces created by this provider. + * This is used by X11CompositorWidget to get rid + * of resources before we close the display connection. + */ + void CleanupResources(); + + already_AddRefed<gfx::DrawTarget> + StartRemoteDrawingInRegion(LayoutDeviceIntRegion& aInvalidRegion, + layers::BufferMode* aBufferMode); + void EndRemoteDrawingInRegion(gfx::DrawTarget* aDrawTarget, + LayoutDeviceIntRegion& aInvalidRegion); + +private: + UniquePtr<WindowSurface> CreateWindowSurface(); + + Display* mXDisplay; + Window mXWindow; + Visual* mXVisual; + int mXDepth; + + UniquePtr<WindowSurface> mWindowSurface; +}; + +} // namespace widget +} // namespace mozilla + +#endif // _MOZILLA_WIDGET_GTK_WINDOW_SURFACE_PROVIDER_H diff --git a/widget/gtk/WindowSurfaceX11.cpp b/widget/gtk/WindowSurfaceX11.cpp new file mode 100644 index 000000000..32e3c43ef --- /dev/null +++ b/widget/gtk/WindowSurfaceX11.cpp @@ -0,0 +1,64 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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 "WindowSurfaceX11.h" +#include "gfxPlatform.h" +#include "X11UndefineNone.h" + +namespace mozilla { +namespace widget { + +WindowSurfaceX11::WindowSurfaceX11(Display* aDisplay, + Window aWindow, + Visual* aVisual, + unsigned int aDepth) + : mDisplay(aDisplay) + , mWindow(aWindow) + , mVisual(aVisual) + , mDepth(aDepth) + , mFormat(GetVisualFormat(aVisual, aDepth)) +{ +} + +/* static */ +gfx::SurfaceFormat +WindowSurfaceX11::GetVisualFormat(const Visual* aVisual, unsigned int aDepth) +{ + switch (aDepth) { + case 32: + if (aVisual->red_mask == 0xff0000 && + aVisual->green_mask == 0xff00 && + aVisual->blue_mask == 0xff) { + return gfx::SurfaceFormat::B8G8R8A8; + } + break; + case 24: + // Only support the BGRX layout, and report it as BGRA to the compositor. + // The alpha channel will be discarded when we put the image. + // Cairo/pixman lacks some fast paths for compositing BGRX onto BGRA, so + // just report it as BGRX directly in that case. + if (aVisual->red_mask == 0xff0000 && + aVisual->green_mask == 0xff00 && + aVisual->blue_mask == 0xff) { + gfx::BackendType backend = gfxPlatform::GetPlatform()->GetDefaultContentBackend(); + return backend == gfx::BackendType::CAIRO ? gfx::SurfaceFormat::B8G8R8X8 + : gfx::SurfaceFormat::B8G8R8A8; + } + break; + case 16: + if (aVisual->red_mask == 0xf800 && + aVisual->green_mask == 0x07e0 && + aVisual->blue_mask == 0x1f) { + return gfx::SurfaceFormat::R5G6B5_UINT16; + } + break; + } + + return gfx::SurfaceFormat::UNKNOWN; +} + +} // namespace widget +} // namespace mozilla diff --git a/widget/gtk/WindowSurfaceX11.h b/widget/gtk/WindowSurfaceX11.h new file mode 100644 index 000000000..46bc10d10 --- /dev/null +++ b/widget/gtk/WindowSurfaceX11.h @@ -0,0 +1,39 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#ifndef _MOZILLA_WIDGET_GTK_WINDOW_SURFACE_X11_H +#define _MOZILLA_WIDGET_GTK_WINDOW_SURFACE_X11_H + +#ifdef MOZ_X11 + +#include "mozilla/widget/WindowSurface.h" +#include "mozilla/gfx/Types.h" + +#include <X11/Xlib.h> + +namespace mozilla { +namespace widget { + +class WindowSurfaceX11 : public WindowSurface { +public: + WindowSurfaceX11(Display* aDisplay, Window aWindow, Visual* aVisual, + unsigned int aDepth); + +protected: + static gfx::SurfaceFormat GetVisualFormat(const Visual* aVisual, unsigned int aDepth); + + Display* const mDisplay; + const Window mWindow; + Visual* const mVisual; + const unsigned int mDepth; + const gfx::SurfaceFormat mFormat; +}; + +} // namespace widget +} // namespace mozilla + +#endif // MOZ_X11 +#endif // _MOZILLA_WIDGET_GTK_WINDOW_SURFACE_X11_H diff --git a/widget/gtk/WindowSurfaceX11Image.cpp b/widget/gtk/WindowSurfaceX11Image.cpp new file mode 100644 index 000000000..5de028804 --- /dev/null +++ b/widget/gtk/WindowSurfaceX11Image.cpp @@ -0,0 +1,119 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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 "WindowSurfaceX11Image.h" + +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/Tools.h" +#include "mozilla/gfx/gfxVars.h" +#include "gfxPlatform.h" +#include "gfx2DGlue.h" + +namespace mozilla { +namespace widget { + +WindowSurfaceX11Image::WindowSurfaceX11Image(Display* aDisplay, + Window aWindow, + Visual* aVisual, + unsigned int aDepth) + : WindowSurfaceX11(aDisplay, aWindow, aVisual, aDepth) +{ +} + +WindowSurfaceX11Image::~WindowSurfaceX11Image() +{ +} + +already_AddRefed<gfx::DrawTarget> +WindowSurfaceX11Image::Lock(const LayoutDeviceIntRegion& aRegion) +{ + gfx::IntRect bounds = aRegion.GetBounds().ToUnknownRect(); + gfx::IntSize size(bounds.XMost(), bounds.YMost()); + + if (!mWindowSurface || mWindowSurface->CairoStatus() || + !(size <= mWindowSurface->GetSize())) { + mWindowSurface = new gfxXlibSurface(mDisplay, mWindow, mVisual, size); + } + if (mWindowSurface->CairoStatus()) { + return nullptr; + } + + if (!mImageSurface || mImageSurface->CairoStatus() || + !(size <= mImageSurface->GetSize())) { + gfxImageFormat format = SurfaceFormatToImageFormat(mFormat); + if (format == gfx::SurfaceFormat::UNKNOWN) { + format = mDepth == 32 ? + gfx::SurfaceFormat::A8R8G8B8_UINT32 : + gfx::SurfaceFormat::X8R8G8B8_UINT32; + } + + mImageSurface = new gfxImageSurface(size, format); + if (mImageSurface->CairoStatus()) { + return nullptr; + } + } + + gfxImageFormat format = mImageSurface->Format(); + // Cairo prefers compositing to BGRX instead of BGRA where possible. + if (format == gfx::SurfaceFormat::X8R8G8B8_UINT32) { + gfx::BackendType backend = gfxVars::ContentBackend(); + if (!gfx::Factory::DoesBackendSupportDataDrawtarget(backend)) { +#ifdef USE_SKIA + backend = gfx::BackendType::SKIA; +#else + backend = gfx::BackendType::CAIRO; +#endif + } + if (backend != gfx::BackendType::CAIRO) { + format = gfx::SurfaceFormat::A8R8G8B8_UINT32; + } + } + + return gfxPlatform::CreateDrawTargetForData(mImageSurface->Data(), + mImageSurface->GetSize(), + mImageSurface->Stride(), + ImageFormatToSurfaceFormat(format)); +} + +void +WindowSurfaceX11Image::Commit(const LayoutDeviceIntRegion& aInvalidRegion) +{ + RefPtr<gfx::DrawTarget> dt = + gfx::Factory::CreateDrawTargetForCairoSurface(mWindowSurface->CairoSurface(), + mWindowSurface->GetSize()); + RefPtr<gfx::SourceSurface> surf = + gfx::Factory::CreateSourceSurfaceForCairoSurface(mImageSurface->CairoSurface(), + mImageSurface->GetSize(), + mImageSurface->Format()); + if (!dt || !surf) { + return; + } + + gfx::IntRect bounds = aInvalidRegion.GetBounds().ToUnknownRect(); + gfx::Rect rect(0, 0, bounds.XMost(), bounds.YMost()); + if (rect.IsEmpty()) { + return; + } + + uint32_t numRects = aInvalidRegion.GetNumRects(); + if (numRects != 1) { + AutoTArray<IntRect, 32> rects; + rects.SetCapacity(numRects); + for (auto iter = aInvalidRegion.RectIter(); !iter.Done(); iter.Next()) { + rects.AppendElement(iter.Get().ToUnknownRect()); + } + dt->PushDeviceSpaceClipRects(rects.Elements(), rects.Length()); + } + + dt->DrawSurface(surf, rect, rect); + + if (numRects != 1) { + dt->PopClip(); + } +} + +} // namespace widget +} // namespace mozilla diff --git a/widget/gtk/WindowSurfaceX11Image.h b/widget/gtk/WindowSurfaceX11Image.h new file mode 100644 index 000000000..236e275d6 --- /dev/null +++ b/widget/gtk/WindowSurfaceX11Image.h @@ -0,0 +1,38 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#ifndef _MOZILLA_WIDGET_GTK_WINDOW_SURFACE_X11_IMAGE_H +#define _MOZILLA_WIDGET_GTK_WINDOW_SURFACE_X11_IMAGE_H + +#ifdef MOZ_X11 + +#include "WindowSurfaceX11.h" +#include "gfxXlibSurface.h" +#include "gfxImageSurface.h" + +namespace mozilla { +namespace widget { + +class WindowSurfaceX11Image : public WindowSurfaceX11 { +public: + WindowSurfaceX11Image(Display* aDisplay, Window aWindow, Visual* aVisual, + unsigned int aDepth); + ~WindowSurfaceX11Image(); + + already_AddRefed<gfx::DrawTarget> Lock(const LayoutDeviceIntRegion& aRegion) override; + void Commit(const LayoutDeviceIntRegion& aInvalidRegion) override; + bool IsFallback() const override { return true; } + +private: + RefPtr<gfxXlibSurface> mWindowSurface; + RefPtr<gfxImageSurface> mImageSurface; +}; + +} // namespace widget +} // namespace mozilla + +#endif // MOZ_X11 +#endif // _MOZILLA_WIDGET_GTK_WINDOW_SURFACE_X11_IMAGE_H diff --git a/widget/gtk/WindowSurfaceXRender.cpp b/widget/gtk/WindowSurfaceXRender.cpp new file mode 100644 index 000000000..015d623b3 --- /dev/null +++ b/widget/gtk/WindowSurfaceXRender.cpp @@ -0,0 +1,83 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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 "WindowSurfaceXRender.h" + +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/Types.h" +#include "gfxPlatform.h" + +namespace mozilla { +namespace widget { + +WindowSurfaceXRender::WindowSurfaceXRender(Display* aDisplay, + Window aWindow, + Visual* aVisual, + unsigned int aDepth) + : WindowSurfaceX11(aDisplay, aWindow, aVisual, aDepth) + , mXlibSurface(nullptr) + , mGC(X11None) +{ +} + +WindowSurfaceXRender::~WindowSurfaceXRender() +{ + if (mGC != X11None) { + XFreeGC(mDisplay, mGC); + } +} + +already_AddRefed<gfx::DrawTarget> +WindowSurfaceXRender::Lock(const LayoutDeviceIntRegion& aRegion) +{ + gfx::IntRect bounds = aRegion.GetBounds().ToUnknownRect(); + gfx::IntSize size(bounds.XMost(), bounds.YMost()); + if (!mXlibSurface || mXlibSurface->CairoStatus() || + !(size <= mXlibSurface->GetSize())) { + mXlibSurface = gfxXlibSurface::Create(DefaultScreenOfDisplay(mDisplay), + mVisual, + size, + mWindow); + } + if (!mXlibSurface || mXlibSurface->CairoStatus()) { + return nullptr; + } + + return gfxPlatform::GetPlatform()->CreateDrawTargetForSurface(mXlibSurface, size); +} + +void +WindowSurfaceXRender::Commit(const LayoutDeviceIntRegion& aInvalidRegion) +{ + AutoTArray<XRectangle, 32> xrects; + xrects.SetCapacity(aInvalidRegion.GetNumRects()); + + for (auto iter = aInvalidRegion.RectIter(); !iter.Done(); iter.Next()) { + const LayoutDeviceIntRect &r = iter.Get(); + XRectangle xrect = { (short)r.x, (short)r.y, (unsigned short)r.width, (unsigned short)r.height }; + xrects.AppendElement(xrect); + } + + if (!mGC) { + mGC = XCreateGC(mDisplay, mWindow, 0, nullptr); + if (!mGC) { + NS_WARNING("Couldn't create X11 graphics context for window!"); + return; + } + } + + XSetClipRectangles(mDisplay, mGC, 0, 0, xrects.Elements(), xrects.Length(), YXBanded); + + MOZ_ASSERT(mXlibSurface && mXlibSurface->CairoStatus() == 0, + "Attempted to commit invalid surface!"); + gfx::IntRect bounds = aInvalidRegion.GetBounds().ToUnknownRect(); + gfx::IntSize size(bounds.XMost(), bounds.YMost()); + XCopyArea(mDisplay, mXlibSurface->XDrawable(), mWindow, mGC, bounds.x, bounds.y, + size.width, size.height, bounds.x, bounds.y); +} + +} // namespace widget +} // namespace mozilla diff --git a/widget/gtk/WindowSurfaceXRender.h b/widget/gtk/WindowSurfaceXRender.h new file mode 100644 index 000000000..044fd3412 --- /dev/null +++ b/widget/gtk/WindowSurfaceXRender.h @@ -0,0 +1,36 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#ifndef _MOZILLA_WIDGET_GTK_WINDOW_SURFACE_XRENDER_H +#define _MOZILLA_WIDGET_GTK_WINDOW_SURFACE_XRENDER_H + +#ifdef MOZ_X11 + +#include "WindowSurfaceX11.h" +#include "gfxXlibSurface.h" + +namespace mozilla { +namespace widget { + +class WindowSurfaceXRender : public WindowSurfaceX11 { +public: + WindowSurfaceXRender(Display* aDisplay, Window aWindow, Visual* aVisual, + unsigned int aDepth); + ~WindowSurfaceXRender(); + + already_AddRefed<gfx::DrawTarget> Lock(const LayoutDeviceIntRegion& aRegion) override; + void Commit(const LayoutDeviceIntRegion& aInvalidRegion) override; + +private: + RefPtr<gfxXlibSurface> mXlibSurface; + GC mGC; +}; + +} // namespace widget +} // namespace mozilla + +#endif // MOZ_X11 +#endif // _MOZILLA_WIDGET_GTK_WINDOW_SURFACE_XRENDER_H diff --git a/widget/gtk/X11CompositorWidget.cpp b/widget/gtk/X11CompositorWidget.cpp new file mode 100644 index 000000000..05113a0c3 --- /dev/null +++ b/widget/gtk/X11CompositorWidget.cpp @@ -0,0 +1,108 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 "X11CompositorWidget.h" + +#include "gfxPlatformGtk.h" +#include "mozilla/layers/CompositorThread.h" +#include "mozilla/widget/InProcessCompositorWidget.h" +#include "mozilla/widget/PlatformWidgetTypes.h" +#include "nsWindow.h" + +namespace mozilla { +namespace widget { + +X11CompositorWidget::X11CompositorWidget(const CompositorWidgetInitData& aInitData, + nsWindow* aWindow) + : mWidget(aWindow) +{ + // If we have a nsWindow, then grab the already existing display connection + // If we don't, then use the init data to connect to the display + if (aWindow) { + mXDisplay = aWindow->XDisplay(); + } else { + mXDisplay = XOpenDisplay(aInitData.XDisplayString().get()); + } + mXWindow = (Window)aInitData.XWindow(); + + // Grab the window's visual and depth + XWindowAttributes windowAttrs; + XGetWindowAttributes(mXDisplay, mXWindow, &windowAttrs); + + Visual* visual = windowAttrs.visual; + int depth = windowAttrs.depth; + + // Initialize the window surface provider + mProvider.Initialize( + mXDisplay, + mXWindow, + visual, + depth + ); + + mClientSize = aInitData.InitialClientSize(); +} + +X11CompositorWidget::~X11CompositorWidget() +{ + mProvider.CleanupResources(); + + // If we created our own display connection, we need to destroy it + if (!mWidget && mXDisplay) { + XCloseDisplay(mXDisplay); + mXDisplay = nullptr; + } +} + +already_AddRefed<gfx::DrawTarget> +X11CompositorWidget::StartRemoteDrawing() +{ + return nullptr; +} +void +X11CompositorWidget::EndRemoteDrawing() +{ +} + +already_AddRefed<gfx::DrawTarget> +X11CompositorWidget::StartRemoteDrawingInRegion(LayoutDeviceIntRegion& aInvalidRegion, + layers::BufferMode* aBufferMode) +{ + return mProvider.StartRemoteDrawingInRegion(aInvalidRegion, + aBufferMode); +} + +void X11CompositorWidget::EndRemoteDrawingInRegion(gfx::DrawTarget* aDrawTarget, + LayoutDeviceIntRegion& aInvalidRegion) +{ + mProvider.EndRemoteDrawingInRegion(aDrawTarget, + aInvalidRegion); +} + +nsIWidget* X11CompositorWidget::RealWidget() +{ + return mWidget; +} + +void +X11CompositorWidget::NotifyClientSizeChanged(const LayoutDeviceIntSize& aClientSize) +{ + mClientSize = aClientSize; +} + +LayoutDeviceIntSize +X11CompositorWidget::GetClientSize() +{ + return mClientSize; +} + +uintptr_t +X11CompositorWidget::GetWidgetKey() +{ + return reinterpret_cast<uintptr_t>(mWidget); +} + +} // namespace widget +} // namespace mozilla diff --git a/widget/gtk/X11CompositorWidget.h b/widget/gtk/X11CompositorWidget.h new file mode 100644 index 000000000..c0e0edeb3 --- /dev/null +++ b/widget/gtk/X11CompositorWidget.h @@ -0,0 +1,69 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#ifndef widget_gtk_X11CompositorWidget_h +#define widget_gtk_X11CompositorWidget_h + +#include "mozilla/widget/CompositorWidget.h" +#include "WindowSurfaceProvider.h" + +class nsIWidget; +class nsWindow; + +namespace mozilla { +namespace widget { + +class CompositorWidgetDelegate +{ +public: + virtual void NotifyClientSizeChanged(const LayoutDeviceIntSize& aClientSize) = 0; +}; + +class X11CompositorWidget + : public CompositorWidget + , public CompositorWidgetDelegate +{ +public: + X11CompositorWidget(const CompositorWidgetInitData& aInitData, + nsWindow* aWindow = nullptr); + ~X11CompositorWidget(); + + // CompositorWidget Overrides + + already_AddRefed<gfx::DrawTarget> StartRemoteDrawing() override; + void EndRemoteDrawing() override; + + already_AddRefed<gfx::DrawTarget> + StartRemoteDrawingInRegion(LayoutDeviceIntRegion& aInvalidRegion, + layers::BufferMode* aBufferMode) override; + void EndRemoteDrawingInRegion(gfx::DrawTarget* aDrawTarget, + LayoutDeviceIntRegion& aInvalidRegion) override; + uintptr_t GetWidgetKey() override; + + void NotifyClientSizeChanged(const LayoutDeviceIntSize& aClientSize) override; + LayoutDeviceIntSize GetClientSize() override; + + nsIWidget* RealWidget() override; + X11CompositorWidget* AsX11() override { return this; } + CompositorWidgetDelegate* AsDelegate() override { return this; } + + Display* XDisplay() const { return mXDisplay; } + Window XWindow() const { return mXWindow; } + +protected: + nsWindow* mWidget; + +private: + LayoutDeviceIntSize mClientSize; + + Display* mXDisplay; + Window mXWindow; + WindowSurfaceProvider mProvider; +}; + +} // namespace widget +} // namespace mozilla + +#endif // widget_gtk_X11CompositorWidget_h diff --git a/widget/gtk/compat-gtk3/gdk/gdkversionmacros.h b/widget/gtk/compat-gtk3/gdk/gdkversionmacros.h new file mode 100644 index 000000000..068c54b54 --- /dev/null +++ b/widget/gtk/compat-gtk3/gdk/gdkversionmacros.h @@ -0,0 +1,31 @@ +#ifndef GDKVERSIONMACROS_WRAPPER_H +#define GDKVERSIONMACROS_WRAPPER_H + +/** + * Suppress all GTK3 deprecated warnings as deprecated functions are often + * used for GTK2 compatibility. + * + * GDK_VERSION_MIN_REQUIRED cannot be used to suppress warnings for functions + * deprecated in 3.0, but still needs to be set because gdkversionmacros.h + * asserts that GDK_VERSION_MAX_ALLOWED >= GDK_VERSION_MIN_REQUIRED and + * GDK_VERSION_MIN_REQUIRED >= GDK_VERSION_3_0. + * + * Setting GDK_DISABLE_DEPRECATION_WARNINGS would also disable + * GDK_UNAVAILABLE() warnings, which are useful. + */ + +#define GDK_VERSION_MIN_REQUIRED GDK_VERSION_3_0 + +#include_next <gdk/gdkversionmacros.h> + +/* GDK_AVAILABLE_IN_ALL was introduced in 3.10 */ +#ifndef GDK_AVAILABLE_IN_ALL +#define GDK_AVAILABLE_IN_ALL +#endif + +#undef GDK_DEPRECATED +#define GDK_DEPRECATED GDK_AVAILABLE_IN_ALL +#undef GDK_DEPRECATED_FOR +#define GDK_DEPRECATED_FOR(f) GDK_AVAILABLE_IN_ALL + +#endif /* GDKVERSIONMACROS_WRAPPER_H */ diff --git a/widget/gtk/compat-gtk3/gtk/gtkenums.h b/widget/gtk/compat-gtk3/gtk/gtkenums.h new file mode 100644 index 000000000..68e4d736d --- /dev/null +++ b/widget/gtk/compat-gtk3/gtk/gtkenums.h @@ -0,0 +1,28 @@ +/* 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/. */ + +#ifndef GTKENUMS_WRAPPER_H +#define GTKENUMS_WRAPPER_H + +#include_next <gtk/gtkenums.h> + +#include <gtk/gtkversion.h> + +#if !GTK_CHECK_VERSION(3, 6, 0) +enum GtkInputPurpose +{ + GTK_INPUT_PURPOSE_FREE_FORM, + GTK_INPUT_PURPOSE_ALPHA, + GTK_INPUT_PURPOSE_DIGITS, + GTK_INPUT_PURPOSE_NUMBER, + GTK_INPUT_PURPOSE_PHONE, + GTK_INPUT_PURPOSE_URL, + GTK_INPUT_PURPOSE_EMAIL, + GTK_INPUT_PURPOSE_NAME, + GTK_INPUT_PURPOSE_PASSWORD, + GTK_INPUT_PURPOSE_PIN +}; +#endif // 3.6.0 + +#endif /* GTKENUMS_WRAPPER_H */ diff --git a/widget/gtk/compat/gdk/gdkdnd.h b/widget/gtk/compat/gdk/gdkdnd.h new file mode 100644 index 000000000..e20cdea84 --- /dev/null +++ b/widget/gtk/compat/gdk/gdkdnd.h @@ -0,0 +1,33 @@ +/* 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/. */ + +#ifndef GDKDND_WRAPPER_H +#define GDKDND_WRAPPER_H + +#define gdk_drag_context_get_actions gdk_drag_context_get_actions_ +#define gdk_drag_context_list_targets gdk_drag_context_list_targets_ +#define gdk_drag_context_get_dest_window gdk_drag_context_get_dest_window_ +#include_next <gdk/gdkdnd.h> +#undef gdk_drag_context_get_actions +#undef gdk_drag_context_list_targets +#undef gdk_drag_context_get_dest_window + +static inline GdkDragAction +gdk_drag_context_get_actions(GdkDragContext *context) +{ + return context->actions; +} + +static inline GList * +gdk_drag_context_list_targets(GdkDragContext *context) +{ + return context->targets; +} + +static inline GdkWindow * +gdk_drag_context_get_dest_window(GdkDragContext *context) +{ + return context->dest_window; +} +#endif /* GDKDND_WRAPPER_H */ diff --git a/widget/gtk/compat/gdk/gdkkeysyms.h b/widget/gtk/compat/gdk/gdkkeysyms.h new file mode 100644 index 000000000..d310ae616 --- /dev/null +++ b/widget/gtk/compat/gdk/gdkkeysyms.h @@ -0,0 +1,266 @@ +/* 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/. */ + +#ifndef GDKKEYSYMS_WRAPPER_H +#define GDKKEYSYMS_WRAPPER_H + +#include_next <gdk/gdkkeysyms.h> + +#ifndef GDK_ISO_Level5_Shift +#define GDK_ISO_Level5_Shift 0xFE11 +#endif + +#ifndef GDK_ISO_Level5_Latch +#define GDK_ISO_Level5_Latch 0xFE12 +#endif + +#ifndef GDK_ISO_Level5_Lock +#define GDK_ISO_Level5_Lock 0xFE13 +#endif + +#ifndef GDK_dead_greek +#define GDK_dead_greek 0xFE8C +#endif + +#ifndef GDK_ch +#define GDK_ch 0xFEA0 +#endif + +#ifndef GDK_Ch +#define GDK_Ch 0xFEA1 +#endif + +#ifndef GDK_CH +#define GDK_CH 0xFEA2 +#endif + +#ifndef GDK_c_h +#define GDK_c_h 0xFEA3 +#endif + +#ifndef GDK_C_h +#define GDK_C_h 0xFEA4 +#endif + +#ifndef GDK_C_H +#define GDK_C_H 0xFEA5 +#endif + +#ifndef GDK_MonBrightnessUp +#define GDK_MonBrightnessUp 0x1008FF02 +#endif + +#ifndef GDK_MonBrightnessDown +#define GDK_MonBrightnessDown 0x1008FF03 +#endif + +#ifndef GDK_AudioLowerVolume +#define GDK_AudioLowerVolume 0x1008FF11 +#endif + +#ifndef GDK_AudioMute +#define GDK_AudioMute 0x1008FF12 +#endif + +#ifndef GDK_AudioRaiseVolume +#define GDK_AudioRaiseVolume 0x1008FF13 +#endif + +#ifndef GDK_AudioPlay +#define GDK_AudioPlay 0x1008FF14 +#endif + +#ifndef GDK_AudioStop +#define GDK_AudioStop 0x1008FF15 +#endif + +#ifndef GDK_AudioPrev +#define GDK_AudioPrev 0x1008FF16 +#endif + +#ifndef GDK_AudioNext +#define GDK_AudioNext 0x1008FF17 +#endif + +#ifndef GDK_HomePage +#define GDK_HomePage 0x1008FF18 +#endif + +#ifndef GDK_Mail +#define GDK_Mail 0x1008FF19 +#endif + +#ifndef GDK_Search +#define GDK_Search 0x1008FF1B +#endif + +#ifndef GDK_AudioRecord +#define GDK_AudioRecord 0x1008FF1C +#endif + +#ifndef GDK_Back +#define GDK_Back 0x1008FF26 +#endif + +#ifndef GDK_Forward +#define GDK_Forward 0x1008FF27 +#endif + +#ifndef GDK_Stop +#define GDK_Stop 0x1008FF28 +#endif + +#ifndef GDK_Refresh +#define GDK_Refresh 0x1008FF29 +#endif + +#ifndef GDK_PowerOff +#define GDK_PowerOff 0x1008FF2A +#endif + +#ifndef GDK_Eject +#define GDK_Eject 0x1008FF2C +#endif + +#ifndef GDK_AudioPause +#define GDK_AudioPause 0x1008FF31 +#endif + +#ifndef GDK_BrightnessAdjust +#define GDK_BrightnessAdjust 0x1008FF3B +#endif + +#ifndef GDK_AudioRewind +#define GDK_AudioRewind 0x1008FF3E +#endif + +#ifndef GDK_Launch0 +#define GDK_Launch0 0x1008FF40 +#endif + +#ifndef GDK_Launch1 +#define GDK_Launch1 0x1008FF41 +#endif + +#ifndef GDK_Launch2 +#define GDK_Launch2 0x1008FF42 +#endif + +#ifndef GDK_Launch3 +#define GDK_Launch3 0x1008FF43 +#endif + +#ifndef GDK_Launch4 +#define GDK_Launch4 0x1008FF44 +#endif + +#ifndef GDK_Launch5 +#define GDK_Launch5 0x1008FF45 +#endif + +#ifndef GDK_Launch6 +#define GDK_Launch6 0x1008FF46 +#endif + +#ifndef GDK_Launch7 +#define GDK_Launch7 0x1008FF47 +#endif + +#ifndef GDK_Launch8 +#define GDK_Launch8 0x1008FF48 +#endif + +#ifndef GDK_Launch9 +#define GDK_Launch9 0x1008FF49 +#endif + +#ifndef GDK_LaunchA +#define GDK_LaunchA 0x1008FF4A +#endif + +#ifndef GDK_LaunchB +#define GDK_LaunchB 0x1008FF4B +#endif + +#ifndef GDK_LaunchC +#define GDK_LaunchC 0x1008FF4C +#endif + +#ifndef GDK_LaunchD +#define GDK_LaunchD 0x1008FF4D +#endif + +#ifndef GDK_LaunchE +#define GDK_LaunchE 0x1008FF4E +#endif + +#ifndef GDK_LaunchF +#define GDK_LaunchF 0x1008FF4F +#endif + +#ifndef GDK_Copy +#define GDK_Copy 0x1008FF57 +#endif + +#ifndef GDK_Cut +#define GDK_Cut 0x1008FF58 +#endif + +#ifndef GDK_Paste +#define GDK_Paste 0x1008FF6D +#endif + +#ifndef GDK_Reload +#define GDK_Reload 0x1008FF73 +#endif + +#ifndef GDK_AudioRandomPlay +#define GDK_AudioRandomPlay 0x1008FF99 +#endif + +#ifndef GDK_Subtitle +#define GDK_Subtitle 0x1008FF9A +#endif + +#ifndef GDK_Red +#define GDK_Red 0x1008FFA3 +#endif + +#ifndef GDK_Green +#define GDK_Green 0x1008FFA4 +#endif + +#ifndef GDK_Yellow +#define GDK_Yellow 0x1008FFA5 +#endif + +#ifndef GDK_Blue +#define GDK_Blue 0x1008FFA6 +#endif + +#ifndef GDK_TouchpadToggle +#define GDK_TouchpadToggle 0x1008FFA9 +#endif + +#ifndef GDK_TouchpadOn +#define GDK_TouchpadOn 0x1008FFB0 +#endif + +#ifndef GDK_TouchpadOff +#define GDK_TouchpadOff 0x1008ffb1 +#endif + +#ifndef GDK_LogWindowTree +#define GDK_LogWindowTree 0x1008FE24 +#endif + +#ifndef GDK_LogGrabInfo +#define GDK_LogGrabInfo 0x1008FE25 +#endif + +#ifndef GDK_Sleep +#define GDK_Sleep 0x1008FF2F +#endif + +#endif /* GDKKEYSYMS_WRAPPER_H */ diff --git a/widget/gtk/compat/gdk/gdkvisual.h b/widget/gtk/compat/gdk/gdkvisual.h new file mode 100644 index 000000000..6476b4494 --- /dev/null +++ b/widget/gtk/compat/gdk/gdkvisual.h @@ -0,0 +1,17 @@ +/* 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/. */ + +#ifndef GDKVISUAL_WRAPPER_H +#define GDKVISUAL_WRAPPER_H + +#define gdk_visual_get_depth gdk_visual_get_depth_ +#include_next <gdk/gdkvisual.h> +#undef gdk_visual_get_depth + +static inline gint +gdk_visual_get_depth(GdkVisual *visual) +{ + return visual->depth; +} +#endif /* GDKVISUAL_WRAPPER_H */ diff --git a/widget/gtk/compat/gdk/gdkwindow.h b/widget/gtk/compat/gdk/gdkwindow.h new file mode 100644 index 000000000..40efbb07d --- /dev/null +++ b/widget/gtk/compat/gdk/gdkwindow.h @@ -0,0 +1,33 @@ +/* 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/. */ + +#ifndef GDKWINDOW_WRAPPER_H +#define GDKWINDOW_WRAPPER_H + +#define gdk_window_get_display gdk_window_get_display_ +#define gdk_window_get_screen gdk_window_get_screen_ +#include_next <gdk/gdkwindow.h> +#undef gdk_window_get_display +#undef gdk_window_get_screen + +static inline GdkDisplay * +gdk_window_get_display(GdkWindow *window) +{ + return gdk_drawable_get_display(GDK_DRAWABLE(window)); +} + +static inline GdkScreen* +gdk_window_get_screen(GdkWindow *window) +{ + return gdk_drawable_get_screen (window); +} + +#if GDK_PIXBUF_MAJOR == 2 && GDK_PIXBUF_MINOR < 18 +static inline gboolean +gdk_window_is_destroyed(GdkWindow *window) +{ + return GDK_WINDOW_OBJECT(window)->destroyed; +} +#endif +#endif /* GDKWINDOW_WRAPPER_H */ diff --git a/widget/gtk/compat/gdk/gdkx.h b/widget/gtk/compat/gdk/gdkx.h new file mode 100644 index 000000000..240c12e30 --- /dev/null +++ b/widget/gtk/compat/gdk/gdkx.h @@ -0,0 +1,51 @@ +/* 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/. */ + +#ifndef GDKX_WRAPPER_H +#define GDKX_WRAPPER_H + +#include <gtk/gtkversion.h> + +#define gdk_x11_window_foreign_new_for_display gdk_x11_window_foreign_new_for_display_ +#define gdk_x11_window_lookup_for_display gdk_x11_window_lookup_for_display_ +#define gdk_x11_window_get_xid gdk_x11_window_get_xid_ +#if !GTK_CHECK_VERSION(2,24,0) +#define gdk_x11_set_sm_client_id gdk_x11_set_sm_client_id_ +#endif +#include_next <gdk/gdkx.h> +#undef gdk_x11_window_foreign_new_for_display +#undef gdk_x11_window_lookup_for_display +#undef gdk_x11_window_get_xid + +static inline GdkWindow * +gdk_x11_window_foreign_new_for_display(GdkDisplay *display, Window window) +{ + return gdk_window_foreign_new_for_display(display, window); +} + +static inline GdkWindow * +gdk_x11_window_lookup_for_display(GdkDisplay *display, Window window) +{ + return gdk_window_lookup_for_display(display, window); +} + +static inline Window +gdk_x11_window_get_xid(GdkWindow *window) +{ + return(GDK_WINDOW_XWINDOW(window)); +} + +#ifndef GDK_IS_X11_DISPLAY +#define GDK_IS_X11_DISPLAY(a) (true) +#endif + +#if !GTK_CHECK_VERSION(2,24,0) +#undef gdk_x11_set_sm_client_id +static inline void +gdk_x11_set_sm_client_id (const gchar *sm_client_id) +{ + gdk_set_sm_client_id(sm_client_id); +} +#endif +#endif /* GDKX_WRAPPER_H */ diff --git a/widget/gtk/compat/glib/gmem.h b/widget/gtk/compat/glib/gmem.h new file mode 100644 index 000000000..1e728197e --- /dev/null +++ b/widget/gtk/compat/glib/gmem.h @@ -0,0 +1,56 @@ +/* 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/. */ + +#ifndef GMEM_WRAPPER_H +#define GMEM_WRAPPER_H + +#define g_malloc_n g_malloc_n_ +#define g_malloc0_n g_malloc0_n_ +#define g_realloc_n g_realloc_n_ +#include_next <glib/gmem.h> +#undef g_malloc_n +#undef g_malloc0_n +#undef g_realloc_n + +#include <glib/gmessages.h> + +#undef g_new +#define g_new(type, num) \ + ((type *) g_malloc_n((num), sizeof(type))) + +#undef g_new0 +#define g_new0(type, num) \ + ((type *) g_malloc0_n((num), sizeof(type))) + +#undef g_renew +#define g_renew(type, ptr, num) \ + ((type *) g_realloc_n(ptr, (num), sizeof(type))) + +#define _CHECK_OVERFLOW(num, type_size) \ + if (G_UNLIKELY(type_size > 0 && num > G_MAXSIZE / type_size)) { \ + g_error("%s: overflow allocating %" G_GSIZE_FORMAT "*%" G_GSIZE_FORMAT " bytes", \ + G_STRLOC, num, type_size); \ + } + +static inline gpointer +g_malloc_n(gsize num, gsize type_size) +{ + _CHECK_OVERFLOW(num, type_size) + return g_malloc(num * type_size); +} + +static inline gpointer +g_malloc0_n(gsize num, gsize type_size) +{ + _CHECK_OVERFLOW(num, type_size) + return g_malloc0(num * type_size); +} + +static inline gpointer +g_realloc_n(gpointer ptr, gsize num, gsize type_size) +{ + _CHECK_OVERFLOW(num, type_size) + return g_realloc(ptr, num * type_size); +} +#endif /* GMEM_WRAPPER_H */ diff --git a/widget/gtk/compat/gtk/gtkwidget.h b/widget/gtk/compat/gtk/gtkwidget.h new file mode 100644 index 000000000..b1d4d1405 --- /dev/null +++ b/widget/gtk/compat/gtk/gtkwidget.h @@ -0,0 +1,50 @@ +/* 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/. */ + +#ifndef GTKWIDGET_WRAPPER_H +#define GTKWIDGET_WRAPPER_H + +#define gtk_widget_set_mapped gtk_widget_set_mapped_ +#define gtk_widget_get_mapped gtk_widget_get_mapped_ +#define gtk_widget_set_realized gtk_widget_set_realized_ +#define gtk_widget_get_realized gtk_widget_get_realized_ +#include_next <gtk/gtkwidget.h> +#undef gtk_widget_set_mapped +#undef gtk_widget_get_mapped +#undef gtk_widget_set_realized +#undef gtk_widget_get_realized + +#include <gtk/gtkversion.h> + +static inline void +gtk_widget_set_mapped(GtkWidget *widget, gboolean mapped) +{ + if (mapped) + GTK_WIDGET_SET_FLAGS (widget, GTK_MAPPED); + else + GTK_WIDGET_UNSET_FLAGS (widget, GTK_MAPPED); +} + +static inline gboolean +gtk_widget_get_mapped(GtkWidget *widget) +{ + return GTK_WIDGET_MAPPED (widget); +} + +static inline void +gtk_widget_set_realized(GtkWidget *widget, gboolean realized) +{ + if (realized) + GTK_WIDGET_SET_FLAGS(widget, GTK_REALIZED); + else + GTK_WIDGET_UNSET_FLAGS(widget, GTK_REALIZED); +} + +static inline gboolean +gtk_widget_get_realized(GtkWidget *widget) +{ + return GTK_WIDGET_REALIZED (widget); +} + +#endif /* GTKWIDGET_WRAPPER_H */ diff --git a/widget/gtk/compat/gtk/gtkwindow.h b/widget/gtk/compat/gtk/gtkwindow.h new file mode 100644 index 000000000..488061d40 --- /dev/null +++ b/widget/gtk/compat/gtk/gtkwindow.h @@ -0,0 +1,30 @@ +/* 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/. */ + +#ifndef GTKWINDOW_WRAPPER_H +#define GTKWINDOW_WRAPPER_H + +#define gtk_window_group_get_current_grab gtk_window_group_get_current_grab_ +#define gtk_window_get_window_type gtk_window_get_window_type_ +#include_next <gtk/gtkwindow.h> +#undef gtk_window_group_get_current_grab +#undef gtk_window_get_window_type + +static inline GtkWidget * +gtk_window_group_get_current_grab(GtkWindowGroup *window_group) +{ + if (!window_group->grabs) + return NULL; + + return GTK_WIDGET(window_group->grabs->data); +} + +static inline GtkWindowType +gtk_window_get_window_type(GtkWindow *window) +{ + gint type; + g_object_get(window, "type", &type, (void*)NULL); + return (GtkWindowType)type; +} +#endif /* GTKWINDOW_WRAPPER_H */ diff --git a/widget/gtk/crashtests/540078-1.xhtml b/widget/gtk/crashtests/540078-1.xhtml new file mode 100644 index 000000000..9effb4ed6 --- /dev/null +++ b/widget/gtk/crashtests/540078-1.xhtml @@ -0,0 +1,2 @@ +<html xmlns="http://www.w3.org/1999/xhtml"><hbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"><scrollcorner class="zebra"/></hbox><style style="display: none;">.zebra { -moz-appearance: checkbox; }</style></html> + diff --git a/widget/gtk/crashtests/673390-1.html b/widget/gtk/crashtests/673390-1.html new file mode 100644 index 000000000..8463f67f0 --- /dev/null +++ b/widget/gtk/crashtests/673390-1.html @@ -0,0 +1 @@ +<div style="-moz-appearance: progresschunk; position: fixed"></div> diff --git a/widget/gtk/crashtests/crashtests.list b/widget/gtk/crashtests/crashtests.list new file mode 100644 index 000000000..9ae47c223 --- /dev/null +++ b/widget/gtk/crashtests/crashtests.list @@ -0,0 +1,2 @@ +load 540078-1.xhtml +load 673390-1.html diff --git a/widget/gtk/gtk2drawing.c b/widget/gtk/gtk2drawing.c new file mode 100644 index 000000000..7fcac4de6 --- /dev/null +++ b/widget/gtk/gtk2drawing.c @@ -0,0 +1,3502 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +/* + * This file contains painting functions for each of the gtk2 widgets. + * Adapted from the gtkdrawing.c, and gtk+2.0 source. + */ + +#include <gtk/gtk.h> +#include <gdk/gdkprivate.h> +#include <string.h> +#include "gtkdrawing.h" +#include "mozilla/Assertions.h" +#include "prinrval.h" + +#include <math.h> +#include <stdbool.h> // for MOZ_ASSERT_UNREACHABLE + +#define XTHICKNESS(style) (style->xthickness) +#define YTHICKNESS(style) (style->ythickness) +#define WINDOW_IS_MAPPED(window) ((window) && GDK_IS_WINDOW(window) && gdk_window_is_visible(window)) + +static GtkWidget* gProtoWindow; +static GtkWidget* gProtoLayout; +static GtkWidget* gButtonWidget; +static GtkWidget* gToggleButtonWidget; +static GtkWidget* gButtonArrowWidget; +static GtkWidget* gCheckboxWidget; +static GtkWidget* gRadiobuttonWidget; +static GtkWidget* gHorizScrollbarWidget; +static GtkWidget* gVertScrollbarWidget; +static GtkWidget* gSpinWidget; +static GtkWidget* gHScaleWidget; +static GtkWidget* gVScaleWidget; +static GtkWidget* gEntryWidget; +static GtkWidget* gComboBoxWidget; +static GtkWidget* gComboBoxButtonWidget; +static GtkWidget* gComboBoxArrowWidget; +static GtkWidget* gComboBoxSeparatorWidget; +static GtkWidget* gComboBoxEntryWidget; +static GtkWidget* gComboBoxEntryTextareaWidget; +static GtkWidget* gComboBoxEntryButtonWidget; +static GtkWidget* gComboBoxEntryArrowWidget; +static GtkWidget* gHandleBoxWidget; +static GtkWidget* gToolbarWidget; +static GtkWidget* gFrameWidget; +static GtkWidget* gStatusbarWidget; +static GtkWidget* gProgressWidget; +static GtkWidget* gTabWidget; +static GtkWidget* gTooltipWidget; +static GtkWidget* gMenuBarWidget; +static GtkWidget* gMenuBarItemWidget; +static GtkWidget* gMenuPopupWidget; +static GtkWidget* gMenuItemWidget; +static GtkWidget* gImageMenuItemWidget; +static GtkWidget* gCheckMenuItemWidget; +static GtkWidget* gTreeViewWidget; +static GtkTreeViewColumn* gMiddleTreeViewColumn; +static GtkWidget* gTreeHeaderCellWidget; +static GtkWidget* gTreeHeaderSortArrowWidget; +static GtkWidget* gExpanderWidget; +static GtkWidget* gToolbarSeparatorWidget; +static GtkWidget* gMenuSeparatorWidget; +static GtkWidget* gHPanedWidget; +static GtkWidget* gVPanedWidget; +static GtkWidget* gScrolledWindowWidget; + +static style_prop_t style_prop_func; +static gboolean have_arrow_scaling; +static gboolean is_initialized; + +/* Because we have such an unconventional way of drawing widgets, signal to the GTK theme engine + that they are drawing for Mozilla instead of a conventional GTK app so they can do any specific + things they may want to do. */ +static void +moz_gtk_set_widget_name(GtkWidget* widget) +{ + gtk_widget_set_name(widget, "MozillaGtkWidget"); +} + +gint +moz_gtk_enable_style_props(style_prop_t styleGetProp) +{ + style_prop_func = styleGetProp; + return MOZ_GTK_SUCCESS; +} + +static gint +ensure_window_widget() +{ + if (!gProtoWindow) { + gProtoWindow = gtk_window_new(GTK_WINDOW_POPUP); + gtk_widget_realize(gProtoWindow); + moz_gtk_set_widget_name(gProtoWindow); + } + return MOZ_GTK_SUCCESS; +} + +static gint +setup_widget_prototype(GtkWidget* widget) +{ + ensure_window_widget(); + if (!gProtoLayout) { + gProtoLayout = gtk_fixed_new(); + gtk_container_add(GTK_CONTAINER(gProtoWindow), gProtoLayout); + } + + gtk_container_add(GTK_CONTAINER(gProtoLayout), widget); + gtk_widget_realize(widget); + g_object_set_data(G_OBJECT(widget), "transparent-bg-hint", GINT_TO_POINTER(TRUE)); + return MOZ_GTK_SUCCESS; +} + +static gint +ensure_button_widget() +{ + if (!gButtonWidget) { + gButtonWidget = gtk_button_new_with_label("M"); + setup_widget_prototype(gButtonWidget); + } + return MOZ_GTK_SUCCESS; +} + +static gint +ensure_hpaned_widget() +{ + if (!gHPanedWidget) { + gHPanedWidget = gtk_hpaned_new(); + setup_widget_prototype(gHPanedWidget); + } + return MOZ_GTK_SUCCESS; +} + +static gint +ensure_vpaned_widget() +{ + if (!gVPanedWidget) { + gVPanedWidget = gtk_vpaned_new(); + setup_widget_prototype(gVPanedWidget); + } + return MOZ_GTK_SUCCESS; +} + +static gint +ensure_toggle_button_widget() +{ + if (!gToggleButtonWidget) { + gToggleButtonWidget = gtk_toggle_button_new(); + setup_widget_prototype(gToggleButtonWidget); + /* toggle button must be set active to get the right style on hover. */ + GTK_TOGGLE_BUTTON(gToggleButtonWidget)->active = TRUE; + } + return MOZ_GTK_SUCCESS; +} + +static gint +ensure_button_arrow_widget() +{ + if (!gButtonArrowWidget) { + ensure_toggle_button_widget(); + + gButtonArrowWidget = gtk_arrow_new(GTK_ARROW_DOWN, GTK_SHADOW_OUT); + gtk_container_add(GTK_CONTAINER(gToggleButtonWidget), gButtonArrowWidget); + gtk_widget_realize(gButtonArrowWidget); + } + return MOZ_GTK_SUCCESS; +} + +static gint +ensure_checkbox_widget() +{ + if (!gCheckboxWidget) { + gCheckboxWidget = gtk_check_button_new_with_label("M"); + setup_widget_prototype(gCheckboxWidget); + } + return MOZ_GTK_SUCCESS; +} + +static gint +ensure_radiobutton_widget() +{ + if (!gRadiobuttonWidget) { + gRadiobuttonWidget = gtk_radio_button_new_with_label(NULL, "M"); + setup_widget_prototype(gRadiobuttonWidget); + } + return MOZ_GTK_SUCCESS; +} + +static gint +ensure_scrollbar_widget() +{ + if (!gVertScrollbarWidget) { + gVertScrollbarWidget = gtk_vscrollbar_new(NULL); + setup_widget_prototype(gVertScrollbarWidget); + } + if (!gHorizScrollbarWidget) { + gHorizScrollbarWidget = gtk_hscrollbar_new(NULL); + setup_widget_prototype(gHorizScrollbarWidget); + } + return MOZ_GTK_SUCCESS; +} + +static gint +ensure_spin_widget() +{ + if (!gSpinWidget) { + gSpinWidget = gtk_spin_button_new(NULL, 1, 0); + setup_widget_prototype(gSpinWidget); + } + return MOZ_GTK_SUCCESS; +} + +static gint +ensure_scale_widget() +{ + if (!gHScaleWidget) { + gHScaleWidget = gtk_hscale_new(NULL); + setup_widget_prototype(gHScaleWidget); + } + if (!gVScaleWidget) { + gVScaleWidget = gtk_vscale_new(NULL); + setup_widget_prototype(gVScaleWidget); + } + return MOZ_GTK_SUCCESS; +} + +static gint +ensure_entry_widget() +{ + if (!gEntryWidget) { + gEntryWidget = gtk_entry_new(); + setup_widget_prototype(gEntryWidget); + } + return MOZ_GTK_SUCCESS; +} + +/* We need to have pointers to the inner widgets (button, separator, arrow) + * of the ComboBox to get the correct rendering from theme engines which + * special cases their look. Since the inner layout can change, we ask GTK + * to NULL our pointers when they are about to become invalid because the + * corresponding widgets don't exist anymore. It's the role of + * g_object_add_weak_pointer(). + * Note that if we don't find the inner widgets (which shouldn't happen), we + * fallback to use generic "non-inner" widgets, and they don't need that kind + * of weak pointer since they are explicit children of gProtoWindow and as + * such GTK holds a strong reference to them. */ +static void +moz_gtk_get_combo_box_inner_button(GtkWidget *widget, gpointer client_data) +{ + if (GTK_IS_TOGGLE_BUTTON(widget)) { + gComboBoxButtonWidget = widget; + g_object_add_weak_pointer(G_OBJECT(widget), + (gpointer) &gComboBoxButtonWidget); + gtk_widget_realize(widget); + g_object_set_data(G_OBJECT(widget), "transparent-bg-hint", GINT_TO_POINTER(TRUE)); + } +} + +static void +moz_gtk_get_combo_box_button_inner_widgets(GtkWidget *widget, + gpointer client_data) +{ + if (GTK_IS_SEPARATOR(widget)) { + gComboBoxSeparatorWidget = widget; + g_object_add_weak_pointer(G_OBJECT(widget), + (gpointer) &gComboBoxSeparatorWidget); + } else if (GTK_IS_ARROW(widget)) { + gComboBoxArrowWidget = widget; + g_object_add_weak_pointer(G_OBJECT(widget), + (gpointer) &gComboBoxArrowWidget); + } else + return; + gtk_widget_realize(widget); + g_object_set_data(G_OBJECT(widget), "transparent-bg-hint", GINT_TO_POINTER(TRUE)); +} + +static gint +ensure_combo_box_widgets() +{ + GtkWidget* buttonChild; + + if (gComboBoxButtonWidget && gComboBoxArrowWidget) + return MOZ_GTK_SUCCESS; + + /* Create a ComboBox if needed */ + if (!gComboBoxWidget) { + gComboBoxWidget = gtk_combo_box_new(); + setup_widget_prototype(gComboBoxWidget); + } + + /* Get its inner Button */ + gtk_container_forall(GTK_CONTAINER(gComboBoxWidget), + moz_gtk_get_combo_box_inner_button, + NULL); + + if (gComboBoxButtonWidget) { + /* Get the widgets inside the Button */ + buttonChild = GTK_BIN(gComboBoxButtonWidget)->child; + if (GTK_IS_HBOX(buttonChild)) { + /* appears-as-list = FALSE, cell-view = TRUE; the button + * contains an hbox. This hbox is there because the ComboBox + * needs to place a cell renderer, a separator, and an arrow in + * the button when appears-as-list is FALSE. */ + gtk_container_forall(GTK_CONTAINER(buttonChild), + moz_gtk_get_combo_box_button_inner_widgets, + NULL); + } else if(GTK_IS_ARROW(buttonChild)) { + /* appears-as-list = TRUE, or cell-view = FALSE; + * the button only contains an arrow */ + gComboBoxArrowWidget = buttonChild; + g_object_add_weak_pointer(G_OBJECT(buttonChild), (gpointer) + &gComboBoxArrowWidget); + gtk_widget_realize(gComboBoxArrowWidget); + g_object_set_data(G_OBJECT(gComboBoxArrowWidget), + "transparent-bg-hint", GINT_TO_POINTER(TRUE)); + } + } else { + /* Shouldn't be reached with current internal gtk implementation; we + * use a generic toggle button as last resort fallback to avoid + * crashing. */ + ensure_toggle_button_widget(); + gComboBoxButtonWidget = gToggleButtonWidget; + } + + if (!gComboBoxArrowWidget) { + /* Shouldn't be reached with current internal gtk implementation; + * we gButtonArrowWidget as last resort fallback to avoid + * crashing. */ + ensure_button_arrow_widget(); + gComboBoxArrowWidget = gButtonArrowWidget; + } + + /* We don't test the validity of gComboBoxSeparatorWidget since there + * is none when "appears-as-list" = TRUE or "cell-view" = FALSE; if it + * is invalid we just won't paint it. */ + + return MOZ_GTK_SUCCESS; +} + +/* We need to have pointers to the inner widgets (entry, button, arrow) of + * the ComboBoxEntry to get the correct rendering from theme engines which + * special cases their look. Since the inner layout can change, we ask GTK + * to NULL our pointers when they are about to become invalid because the + * corresponding widgets don't exist anymore. It's the role of + * g_object_add_weak_pointer(). + * Note that if we don't find the inner widgets (which shouldn't happen), we + * fallback to use generic "non-inner" widgets, and they don't need that kind + * of weak pointer since they are explicit children of gProtoWindow and as + * such GTK holds a strong reference to them. */ +static void +moz_gtk_get_combo_box_entry_inner_widgets(GtkWidget *widget, + gpointer client_data) +{ + if (GTK_IS_TOGGLE_BUTTON(widget)) { + gComboBoxEntryButtonWidget = widget; + g_object_add_weak_pointer(G_OBJECT(widget), + (gpointer) &gComboBoxEntryButtonWidget); + } else if (GTK_IS_ENTRY(widget)) { + gComboBoxEntryTextareaWidget = widget; + g_object_add_weak_pointer(G_OBJECT(widget), + (gpointer) &gComboBoxEntryTextareaWidget); + } else + return; + gtk_widget_realize(widget); + g_object_set_data(G_OBJECT(widget), "transparent-bg-hint", GINT_TO_POINTER(TRUE)); +} + +static void +moz_gtk_get_combo_box_entry_arrow(GtkWidget *widget, gpointer client_data) +{ + if (GTK_IS_ARROW(widget)) { + gComboBoxEntryArrowWidget = widget; + g_object_add_weak_pointer(G_OBJECT(widget), + (gpointer) &gComboBoxEntryArrowWidget); + gtk_widget_realize(widget); + g_object_set_data(G_OBJECT(widget), "transparent-bg-hint", GINT_TO_POINTER(TRUE)); + } +} + +static gint +ensure_combo_box_entry_widgets() +{ + GtkWidget* buttonChild; + + if (gComboBoxEntryTextareaWidget && + gComboBoxEntryButtonWidget && + gComboBoxEntryArrowWidget) + return MOZ_GTK_SUCCESS; + + /* Create a ComboBoxEntry if needed */ + if (!gComboBoxEntryWidget) { + gComboBoxEntryWidget = gtk_combo_box_entry_new(); + setup_widget_prototype(gComboBoxEntryWidget); + } + + /* Get its inner Entry and Button */ + gtk_container_forall(GTK_CONTAINER(gComboBoxEntryWidget), + moz_gtk_get_combo_box_entry_inner_widgets, + NULL); + + if (!gComboBoxEntryTextareaWidget) { + ensure_entry_widget(); + gComboBoxEntryTextareaWidget = gEntryWidget; + } + + if (gComboBoxEntryButtonWidget) { + /* Get the Arrow inside the Button */ + buttonChild = GTK_BIN(gComboBoxEntryButtonWidget)->child; + if (GTK_IS_HBOX(buttonChild)) { + /* appears-as-list = FALSE, cell-view = TRUE; the button + * contains an hbox. This hbox is there because ComboBoxEntry + * inherits from ComboBox which needs to place a cell renderer, + * a separator, and an arrow in the button when appears-as-list + * is FALSE. Here the hbox should only contain an arrow, since + * a ComboBoxEntry doesn't need all those widgets in the + * button. */ + gtk_container_forall(GTK_CONTAINER(buttonChild), + moz_gtk_get_combo_box_entry_arrow, + NULL); + } else if(GTK_IS_ARROW(buttonChild)) { + /* appears-as-list = TRUE, or cell-view = FALSE; + * the button only contains an arrow */ + gComboBoxEntryArrowWidget = buttonChild; + g_object_add_weak_pointer(G_OBJECT(buttonChild), (gpointer) + &gComboBoxEntryArrowWidget); + gtk_widget_realize(gComboBoxEntryArrowWidget); + g_object_set_data(G_OBJECT(gComboBoxEntryArrowWidget), + "transparent-bg-hint", GINT_TO_POINTER(TRUE)); + } + } else { + /* Shouldn't be reached with current internal gtk implementation; + * we use a generic toggle button as last resort fallback to avoid + * crashing. */ + ensure_toggle_button_widget(); + gComboBoxEntryButtonWidget = gToggleButtonWidget; + } + + if (!gComboBoxEntryArrowWidget) { + /* Shouldn't be reached with current internal gtk implementation; + * we gButtonArrowWidget as last resort fallback to avoid + * crashing. */ + ensure_button_arrow_widget(); + gComboBoxEntryArrowWidget = gButtonArrowWidget; + } + + return MOZ_GTK_SUCCESS; +} + + +static gint +ensure_handlebox_widget() +{ + if (!gHandleBoxWidget) { + gHandleBoxWidget = gtk_handle_box_new(); + setup_widget_prototype(gHandleBoxWidget); + } + return MOZ_GTK_SUCCESS; +} + +static gint +ensure_toolbar_widget() +{ + if (!gToolbarWidget) { + ensure_handlebox_widget(); + gToolbarWidget = gtk_toolbar_new(); + gtk_container_add(GTK_CONTAINER(gHandleBoxWidget), gToolbarWidget); + gtk_widget_realize(gToolbarWidget); + g_object_set_data(G_OBJECT(gToolbarWidget), "transparent-bg-hint", GINT_TO_POINTER(TRUE)); + } + return MOZ_GTK_SUCCESS; +} + +static gint +ensure_toolbar_separator_widget() +{ + if (!gToolbarSeparatorWidget) { + ensure_toolbar_widget(); + gToolbarSeparatorWidget = GTK_WIDGET(gtk_separator_tool_item_new()); + setup_widget_prototype(gToolbarSeparatorWidget); + } + return MOZ_GTK_SUCCESS; +} + +static gint +ensure_tooltip_widget() +{ + if (!gTooltipWidget) { + gTooltipWidget = gtk_window_new(GTK_WINDOW_POPUP); + gtk_widget_realize(gTooltipWidget); + moz_gtk_set_widget_name(gTooltipWidget); + } + return MOZ_GTK_SUCCESS; +} + +static gint +ensure_tab_widget() +{ + if (!gTabWidget) { + gTabWidget = gtk_notebook_new(); + setup_widget_prototype(gTabWidget); + } + return MOZ_GTK_SUCCESS; +} + +static gint +ensure_progress_widget() +{ + if (!gProgressWidget) { + gProgressWidget = gtk_progress_bar_new(); + setup_widget_prototype(gProgressWidget); + } + return MOZ_GTK_SUCCESS; +} + +static gint +ensure_statusbar_widget() +{ + if (!gStatusbarWidget) { + gStatusbarWidget = gtk_statusbar_new(); + setup_widget_prototype(gStatusbarWidget); + } + return MOZ_GTK_SUCCESS; +} + +static gint +ensure_frame_widget() +{ + if (!gFrameWidget) { + ensure_statusbar_widget(); + gFrameWidget = gtk_frame_new(NULL); + gtk_container_add(GTK_CONTAINER(gStatusbarWidget), gFrameWidget); + gtk_widget_realize(gFrameWidget); + } + return MOZ_GTK_SUCCESS; +} + +static gint +ensure_menu_bar_widget() +{ + if (!gMenuBarWidget) { + gMenuBarWidget = gtk_menu_bar_new(); + setup_widget_prototype(gMenuBarWidget); + } + return MOZ_GTK_SUCCESS; +} + +static gint +ensure_menu_bar_item_widget() +{ + if (!gMenuBarItemWidget) { + ensure_menu_bar_widget(); + gMenuBarItemWidget = gtk_menu_item_new(); + gtk_menu_shell_append(GTK_MENU_SHELL(gMenuBarWidget), + gMenuBarItemWidget); + gtk_widget_realize(gMenuBarItemWidget); + g_object_set_data(G_OBJECT(gMenuBarItemWidget), + "transparent-bg-hint", GINT_TO_POINTER(TRUE)); + } + return MOZ_GTK_SUCCESS; +} + +static gint +ensure_menu_popup_widget() +{ + if (!gMenuPopupWidget) { + ensure_menu_bar_item_widget(); + gMenuPopupWidget = gtk_menu_new(); + gtk_menu_item_set_submenu(GTK_MENU_ITEM(gMenuBarItemWidget), + gMenuPopupWidget); + gtk_widget_realize(gMenuPopupWidget); + g_object_set_data(G_OBJECT(gMenuPopupWidget), + "transparent-bg-hint", GINT_TO_POINTER(TRUE)); + } + return MOZ_GTK_SUCCESS; +} + +static gint +ensure_menu_item_widget() +{ + if (!gMenuItemWidget) { + ensure_menu_popup_widget(); + gMenuItemWidget = gtk_menu_item_new_with_label("M"); + gtk_menu_shell_append(GTK_MENU_SHELL(gMenuPopupWidget), + gMenuItemWidget); + gtk_widget_realize(gMenuItemWidget); + g_object_set_data(G_OBJECT(gMenuItemWidget), + "transparent-bg-hint", GINT_TO_POINTER(TRUE)); + } + return MOZ_GTK_SUCCESS; +} + +static gint +ensure_image_menu_item_widget() +{ + if (!gImageMenuItemWidget) { + ensure_menu_popup_widget(); + gImageMenuItemWidget = gtk_image_menu_item_new(); + gtk_menu_shell_append(GTK_MENU_SHELL(gMenuPopupWidget), + gImageMenuItemWidget); + gtk_widget_realize(gImageMenuItemWidget); + g_object_set_data(G_OBJECT(gImageMenuItemWidget), + "transparent-bg-hint", GINT_TO_POINTER(TRUE)); + } + return MOZ_GTK_SUCCESS; +} + +static gint +ensure_menu_separator_widget() +{ + if (!gMenuSeparatorWidget) { + ensure_menu_popup_widget(); + gMenuSeparatorWidget = gtk_separator_menu_item_new(); + gtk_menu_shell_append(GTK_MENU_SHELL(gMenuPopupWidget), + gMenuSeparatorWidget); + gtk_widget_realize(gMenuSeparatorWidget); + g_object_set_data(G_OBJECT(gMenuSeparatorWidget), + "transparent-bg-hint", GINT_TO_POINTER(TRUE)); + } + return MOZ_GTK_SUCCESS; +} + +static gint +ensure_check_menu_item_widget() +{ + if (!gCheckMenuItemWidget) { + ensure_menu_popup_widget(); + gCheckMenuItemWidget = gtk_check_menu_item_new_with_label("M"); + gtk_menu_shell_append(GTK_MENU_SHELL(gMenuPopupWidget), + gCheckMenuItemWidget); + gtk_widget_realize(gCheckMenuItemWidget); + g_object_set_data(G_OBJECT(gCheckMenuItemWidget), + "transparent-bg-hint", GINT_TO_POINTER(TRUE)); + } + return MOZ_GTK_SUCCESS; +} + +static gint +ensure_tree_view_widget() +{ + if (!gTreeViewWidget) { + gTreeViewWidget = gtk_tree_view_new(); + setup_widget_prototype(gTreeViewWidget); + } + return MOZ_GTK_SUCCESS; +} + +static gint +ensure_tree_header_cell_widget() +{ + if(!gTreeHeaderCellWidget) { + /* + * Some GTK engines paint the first and last cell + * of a TreeView header with a highlight. + * Since we do not know where our widget will be relative + * to the other buttons in the TreeView header, we must + * paint it as a button that is between two others, + * thus ensuring it is neither the first or last button + * in the header. + * GTK doesn't give us a way to do this explicitly, + * so we must paint with a button that is between two + * others. + */ + + GtkTreeViewColumn* firstTreeViewColumn; + GtkTreeViewColumn* lastTreeViewColumn; + + ensure_tree_view_widget(); + + /* Create and append our three columns */ + firstTreeViewColumn = gtk_tree_view_column_new(); + gtk_tree_view_column_set_title(firstTreeViewColumn, "M"); + gtk_tree_view_append_column(GTK_TREE_VIEW(gTreeViewWidget), firstTreeViewColumn); + + gMiddleTreeViewColumn = gtk_tree_view_column_new(); + gtk_tree_view_column_set_title(gMiddleTreeViewColumn, "M"); + gtk_tree_view_append_column(GTK_TREE_VIEW(gTreeViewWidget), + gMiddleTreeViewColumn); + + lastTreeViewColumn = gtk_tree_view_column_new(); + gtk_tree_view_column_set_title(lastTreeViewColumn, "M"); + gtk_tree_view_append_column(GTK_TREE_VIEW(gTreeViewWidget), lastTreeViewColumn); + + /* Use the middle column's header for our button */ + gTreeHeaderCellWidget = gMiddleTreeViewColumn->button; + gTreeHeaderSortArrowWidget = gMiddleTreeViewColumn->arrow; + g_object_set_data(G_OBJECT(gTreeHeaderCellWidget), + "transparent-bg-hint", GINT_TO_POINTER(TRUE)); + g_object_set_data(G_OBJECT(gTreeHeaderSortArrowWidget), + "transparent-bg-hint", GINT_TO_POINTER(TRUE)); + } + return MOZ_GTK_SUCCESS; +} + +static gint +ensure_expander_widget() +{ + if (!gExpanderWidget) { + gExpanderWidget = gtk_expander_new("M"); + setup_widget_prototype(gExpanderWidget); + } + return MOZ_GTK_SUCCESS; +} + +static gint +ensure_scrolled_window_widget() +{ + if (!gScrolledWindowWidget) { + gScrolledWindowWidget = gtk_scrolled_window_new(NULL, NULL); + setup_widget_prototype(gScrolledWindowWidget); + } + return MOZ_GTK_SUCCESS; +} + +static GtkStateType +ConvertGtkState(GtkWidgetState* state) +{ + if (state->disabled) + return GTK_STATE_INSENSITIVE; + else if (state->depressed) + return (state->inHover ? GTK_STATE_PRELIGHT : GTK_STATE_ACTIVE); + else if (state->inHover) + return (state->active ? GTK_STATE_ACTIVE : GTK_STATE_PRELIGHT); + else + return GTK_STATE_NORMAL; +} + +static gint +TSOffsetStyleGCArray(GdkGC** gcs, gint xorigin, gint yorigin) +{ + int i; + /* there are 5 gc's in each array, for each of the widget states */ + for (i = 0; i < 5; ++i) + gdk_gc_set_ts_origin(gcs[i], xorigin, yorigin); + return MOZ_GTK_SUCCESS; +} + +static gint +TSOffsetStyleGCs(GtkStyle* style, gint xorigin, gint yorigin) +{ + TSOffsetStyleGCArray(style->fg_gc, xorigin, yorigin); + TSOffsetStyleGCArray(style->bg_gc, xorigin, yorigin); + TSOffsetStyleGCArray(style->light_gc, xorigin, yorigin); + TSOffsetStyleGCArray(style->dark_gc, xorigin, yorigin); + TSOffsetStyleGCArray(style->mid_gc, xorigin, yorigin); + TSOffsetStyleGCArray(style->text_gc, xorigin, yorigin); + TSOffsetStyleGCArray(style->base_gc, xorigin, yorigin); + gdk_gc_set_ts_origin(style->black_gc, xorigin, yorigin); + gdk_gc_set_ts_origin(style->white_gc, xorigin, yorigin); + return MOZ_GTK_SUCCESS; +} + +gint +moz_gtk_init() +{ + GtkWidgetClass *entry_class; + + if (is_initialized) + return MOZ_GTK_SUCCESS; + + is_initialized = TRUE; + have_arrow_scaling = (gtk_major_version > 2 || + (gtk_major_version == 2 && gtk_minor_version >= 12)); + + /* Add style property to GtkEntry. + * Adding the style property to the normal GtkEntry class means that it + * will work without issues inside GtkComboBox and for Spinbuttons. */ + entry_class = g_type_class_ref(GTK_TYPE_ENTRY); + gtk_widget_class_install_style_property(entry_class, + g_param_spec_boolean("honors-transparent-bg-hint", + "Transparent BG enabling flag", + "If TRUE, the theme is able to draw the GtkEntry on non-prefilled background.", + FALSE, + G_PARAM_READWRITE)); + + return MOZ_GTK_SUCCESS; +} + +GdkColormap* +moz_gtk_widget_get_colormap() +{ + /* Child widgets inherit the colormap from the GtkWindow. */ + ensure_window_widget(); + return gtk_widget_get_colormap(gProtoWindow); +} + +gint +moz_gtk_checkbox_get_metrics(gint* indicator_size, gint* indicator_spacing) +{ + ensure_checkbox_widget(); + + gtk_widget_style_get (gCheckboxWidget, + "indicator_size", indicator_size, + "indicator_spacing", indicator_spacing, + NULL); + + return MOZ_GTK_SUCCESS; +} + +gint +moz_gtk_radio_get_metrics(gint* indicator_size, gint* indicator_spacing) +{ + ensure_radiobutton_widget(); + + gtk_widget_style_get (gRadiobuttonWidget, + "indicator_size", indicator_size, + "indicator_spacing", indicator_spacing, + NULL); + + return MOZ_GTK_SUCCESS; +} + +gint +moz_gtk_get_focus_outline_size(gint* focus_h_width, gint* focus_v_width) +{ + gboolean interior_focus; + gint focus_width = 0; + + ensure_entry_widget(); + gtk_widget_style_get(gEntryWidget, + "interior-focus", &interior_focus, + "focus-line-width", &focus_width, + NULL); + if (interior_focus) { + *focus_h_width = XTHICKNESS(gEntryWidget->style) + focus_width; + *focus_v_width = YTHICKNESS(gEntryWidget->style) + focus_width; + } else { + *focus_h_width = focus_width; + *focus_v_width = focus_width; + } + return MOZ_GTK_SUCCESS; +} + +static gint +moz_gtk_widget_get_focus(GtkWidget* widget, gboolean* interior_focus, + gint* focus_width, gint* focus_pad) +{ + gtk_widget_style_get (widget, + "interior-focus", interior_focus, + "focus-line-width", focus_width, + "focus-padding", focus_pad, + NULL); + + return MOZ_GTK_SUCCESS; +} + +gint +moz_gtk_menuitem_get_horizontal_padding(gint* horizontal_padding) +{ + ensure_menu_item_widget(); + + gtk_widget_style_get (gMenuItemWidget, + "horizontal-padding", horizontal_padding, + NULL); + + return MOZ_GTK_SUCCESS; +} + +gint +moz_gtk_checkmenuitem_get_horizontal_padding(gint* horizontal_padding) +{ + ensure_check_menu_item_widget(); + + gtk_widget_style_get (gCheckMenuItemWidget, + "horizontal-padding", horizontal_padding, + NULL); + + return MOZ_GTK_SUCCESS; +} + +gint +moz_gtk_button_get_default_overflow(gint* border_top, gint* border_left, + gint* border_bottom, gint* border_right) +{ + GtkBorder* default_outside_border; + + ensure_button_widget(); + gtk_widget_style_get(gButtonWidget, + "default-outside-border", &default_outside_border, + NULL); + + if (default_outside_border) { + *border_top = default_outside_border->top; + *border_left = default_outside_border->left; + *border_bottom = default_outside_border->bottom; + *border_right = default_outside_border->right; + gtk_border_free(default_outside_border); + } else { + *border_top = *border_left = *border_bottom = *border_right = 0; + } + return MOZ_GTK_SUCCESS; +} + +static gint +moz_gtk_button_get_default_border(gint* border_top, gint* border_left, + gint* border_bottom, gint* border_right) +{ + GtkBorder* default_border; + + ensure_button_widget(); + gtk_widget_style_get(gButtonWidget, + "default-border", &default_border, + NULL); + + if (default_border) { + *border_top = default_border->top; + *border_left = default_border->left; + *border_bottom = default_border->bottom; + *border_right = default_border->right; + gtk_border_free(default_border); + } else { + /* see gtkbutton.c */ + *border_top = *border_left = *border_bottom = *border_right = 1; + } + return MOZ_GTK_SUCCESS; +} + +gint +moz_gtk_splitter_get_metrics(gint orientation, gint* size) +{ + if (orientation == GTK_ORIENTATION_HORIZONTAL) { + ensure_hpaned_widget(); + gtk_widget_style_get(gHPanedWidget, "handle_size", size, NULL); + } else { + ensure_vpaned_widget(); + gtk_widget_style_get(gVPanedWidget, "handle_size", size, NULL); + } + return MOZ_GTK_SUCCESS; +} + +static gint +moz_gtk_button_get_inner_border(GtkWidget* widget, GtkBorder* inner_border) +{ + static const GtkBorder default_inner_border = { 1, 1, 1, 1 }; + GtkBorder *tmp_border; + + gtk_widget_style_get (widget, "inner-border", &tmp_border, NULL); + + if (tmp_border) { + *inner_border = *tmp_border; + gtk_border_free(tmp_border); + } + else + *inner_border = default_inner_border; + + return MOZ_GTK_SUCCESS; +} + +static gint +moz_gtk_button_paint(GdkDrawable* drawable, GdkRectangle* rect, + GdkRectangle* cliprect, GtkWidgetState* state, + GtkReliefStyle relief, GtkWidget* widget, + GtkTextDirection direction) +{ + GtkShadowType shadow_type; + GtkStyle* style = widget->style; + GtkStateType button_state = ConvertGtkState(state); + gint x = rect->x, y=rect->y, width=rect->width, height=rect->height; + + gboolean interior_focus; + gint focus_width, focus_pad; + + moz_gtk_widget_get_focus(widget, &interior_focus, &focus_width, &focus_pad); + + if (WINDOW_IS_MAPPED(drawable)) { + gdk_window_set_back_pixmap(drawable, NULL, TRUE); + gdk_window_clear_area(drawable, cliprect->x, cliprect->y, + cliprect->width, cliprect->height); + } + + gtk_widget_set_state(widget, button_state); + gtk_widget_set_direction(widget, direction); + + if (state->isDefault) + GTK_WIDGET_SET_FLAGS(widget, GTK_HAS_DEFAULT); + + GTK_BUTTON(widget)->relief = relief; + + /* Some theme engines love to cause us pain in that gtk_paint_focus is a + no-op on buttons and button-like widgets. They only listen to this flag. */ + if (state->focused && !state->disabled) + GTK_WIDGET_SET_FLAGS(widget, GTK_HAS_FOCUS); + + if (!interior_focus && state->focused) { + x += focus_width + focus_pad; + y += focus_width + focus_pad; + width -= 2 * (focus_width + focus_pad); + height -= 2 * (focus_width + focus_pad); + } + + shadow_type = button_state == GTK_STATE_ACTIVE || + state->depressed ? GTK_SHADOW_IN : GTK_SHADOW_OUT; + + if (state->isDefault && relief == GTK_RELIEF_NORMAL) { + /* handle default borders both outside and inside the button */ + gint default_top, default_left, default_bottom, default_right; + moz_gtk_button_get_default_overflow(&default_top, &default_left, + &default_bottom, &default_right); + x -= default_left; + y -= default_top; + width += default_left + default_right; + height += default_top + default_bottom; + gtk_paint_box(style, drawable, GTK_STATE_NORMAL, GTK_SHADOW_IN, cliprect, + widget, "buttondefault", x, y, width, height); + + moz_gtk_button_get_default_border(&default_top, &default_left, + &default_bottom, &default_right); + x += default_left; + y += default_top; + width -= (default_left + default_right); + height -= (default_top + default_bottom); + } + + if (relief != GTK_RELIEF_NONE || state->depressed || + (button_state != GTK_STATE_NORMAL && + button_state != GTK_STATE_INSENSITIVE)) { + TSOffsetStyleGCs(style, x, y); + /* the following line can trigger an assertion (Crux theme) + file ../../gdk/gdkwindow.c: line 1846 (gdk_window_clear_area): + assertion `GDK_IS_WINDOW (window)' failed */ + gtk_paint_box(style, drawable, button_state, shadow_type, cliprect, + widget, "button", x, y, width, height); + } + + if (state->focused) { + if (interior_focus) { + x += widget->style->xthickness + focus_pad; + y += widget->style->ythickness + focus_pad; + width -= 2 * (widget->style->xthickness + focus_pad); + height -= 2 * (widget->style->ythickness + focus_pad); + } else { + x -= focus_width + focus_pad; + y -= focus_width + focus_pad; + width += 2 * (focus_width + focus_pad); + height += 2 * (focus_width + focus_pad); + } + + TSOffsetStyleGCs(style, x, y); + gtk_paint_focus(style, drawable, button_state, cliprect, + widget, "button", x, y, width, height); + } + + GTK_WIDGET_UNSET_FLAGS(widget, GTK_HAS_DEFAULT); + GTK_WIDGET_UNSET_FLAGS(widget, GTK_HAS_FOCUS); + return MOZ_GTK_SUCCESS; +} + +static gint +moz_gtk_toggle_paint(GdkDrawable* drawable, GdkRectangle* rect, + GdkRectangle* cliprect, GtkWidgetState* state, + gboolean selected, gboolean inconsistent, + gboolean isradio, GtkTextDirection direction) +{ + GtkStateType state_type = ConvertGtkState(state); + GtkShadowType shadow_type = (selected)?GTK_SHADOW_IN:GTK_SHADOW_OUT; + gint indicator_size, indicator_spacing; + gint x, y, width, height; + gint focus_x, focus_y, focus_width, focus_height; + GtkWidget *w; + GtkStyle *style; + + if (isradio) { + moz_gtk_radio_get_metrics(&indicator_size, &indicator_spacing); + w = gRadiobuttonWidget; + } else { + moz_gtk_checkbox_get_metrics(&indicator_size, &indicator_spacing); + w = gCheckboxWidget; + } + + // XXX we should assert rect->height >= indicator_size too + // after bug 369581 is fixed. + MOZ_ASSERT(rect->width >= indicator_size, + "GetMinimumWidgetSize was ignored"); + + // Paint it center aligned in the rect. + x = rect->x + (rect->width - indicator_size) / 2; + y = rect->y + (rect->height - indicator_size) / 2; + width = indicator_size; + height = indicator_size; + + focus_x = x - indicator_spacing; + focus_y = y - indicator_spacing; + focus_width = width + 2 * indicator_spacing; + focus_height = height + 2 * indicator_spacing; + + style = w->style; + TSOffsetStyleGCs(style, x, y); + + gtk_widget_set_sensitive(w, !state->disabled); + gtk_widget_set_direction(w, direction); + GTK_TOGGLE_BUTTON(w)->active = selected; + + if (isradio) { + gtk_paint_option(style, drawable, state_type, shadow_type, cliprect, + gRadiobuttonWidget, "radiobutton", x, y, + width, height); + if (state->focused) { + gtk_paint_focus(style, drawable, GTK_STATE_ACTIVE, cliprect, + gRadiobuttonWidget, "radiobutton", focus_x, focus_y, + focus_width, focus_height); + } + } + else { + /* + * 'indeterminate' type on checkboxes. In GTK, the shadow type + * must also be changed for the state to be drawn. + */ + if (inconsistent) { + gtk_toggle_button_set_inconsistent(GTK_TOGGLE_BUTTON(gCheckboxWidget), TRUE); + shadow_type = GTK_SHADOW_ETCHED_IN; + } else { + gtk_toggle_button_set_inconsistent(GTK_TOGGLE_BUTTON(gCheckboxWidget), FALSE); + } + + gtk_paint_check(style, drawable, state_type, shadow_type, cliprect, + gCheckboxWidget, "checkbutton", x, y, width, height); + if (state->focused) { + gtk_paint_focus(style, drawable, GTK_STATE_ACTIVE, cliprect, + gCheckboxWidget, "checkbutton", focus_x, focus_y, + focus_width, focus_height); + } + } + + return MOZ_GTK_SUCCESS; +} + +static gint +calculate_button_inner_rect(GtkWidget* button, GdkRectangle* rect, + GdkRectangle* inner_rect, + GtkTextDirection direction, + gboolean ignore_focus) +{ + GtkBorder inner_border; + gboolean interior_focus; + gint focus_width, focus_pad; + GtkStyle* style; + + style = button->style; + + /* This mirrors gtkbutton's child positioning */ + moz_gtk_button_get_inner_border(button, &inner_border); + moz_gtk_widget_get_focus(button, &interior_focus, + &focus_width, &focus_pad); + + if (ignore_focus) + focus_width = focus_pad = 0; + + inner_rect->x = rect->x + XTHICKNESS(style) + focus_width + focus_pad; + inner_rect->x += direction == GTK_TEXT_DIR_LTR ? + inner_border.left : inner_border.right; + inner_rect->y = rect->y + inner_border.top + YTHICKNESS(style) + + focus_width + focus_pad; + inner_rect->width = MAX(1, rect->width - inner_border.left - + inner_border.right - (XTHICKNESS(style) + focus_pad + focus_width) * 2); + inner_rect->height = MAX(1, rect->height - inner_border.top - + inner_border.bottom - (YTHICKNESS(style) + focus_pad + focus_width) * 2); + + return MOZ_GTK_SUCCESS; +} + +static gint +calculate_arrow_rect(GtkWidget* arrow, GdkRectangle* rect, + GdkRectangle* arrow_rect, GtkTextDirection direction) +{ + /* defined in gtkarrow.c */ + gfloat arrow_scaling = 0.7; + gfloat xalign, xpad; + gint extent; + GtkMisc* misc = GTK_MISC(arrow); + + if (have_arrow_scaling) + gtk_widget_style_get(arrow, "arrow_scaling", &arrow_scaling, NULL); + + extent = MIN((rect->width - misc->xpad * 2), + (rect->height - misc->ypad * 2)) * arrow_scaling; + + xalign = direction == GTK_TEXT_DIR_LTR ? misc->xalign : 1.0 - misc->xalign; + xpad = misc->xpad + (rect->width - extent) * xalign; + + arrow_rect->x = direction == GTK_TEXT_DIR_LTR ? + floor(rect->x + xpad) : ceil(rect->x + xpad); + arrow_rect->y = floor(rect->y + misc->ypad + + ((rect->height - extent) * misc->yalign)); + + arrow_rect->width = arrow_rect->height = extent; + + return MOZ_GTK_SUCCESS; +} + +static gint +moz_gtk_scrollbar_button_paint(GdkDrawable* drawable, GdkRectangle* rect, + GdkRectangle* cliprect, GtkWidgetState* state, + GtkScrollbarButtonFlags flags, + GtkTextDirection direction) +{ + GtkStateType state_type = ConvertGtkState(state); + GtkShadowType shadow_type = (state->active) ? + GTK_SHADOW_IN : GTK_SHADOW_OUT; + GdkRectangle arrow_rect; + GtkStyle* style; + GtkWidget *scrollbar; + GtkArrowType arrow_type; + gint arrow_displacement_x, arrow_displacement_y; + const char* detail = (flags & MOZ_GTK_STEPPER_VERTICAL) ? + "vscrollbar" : "hscrollbar"; + + ensure_scrollbar_widget(); + + if (flags & MOZ_GTK_STEPPER_VERTICAL) + scrollbar = gVertScrollbarWidget; + else + scrollbar = gHorizScrollbarWidget; + + gtk_widget_set_direction(scrollbar, direction); + + /* Some theme engines (i.e., ClearLooks) check the scrollbar's allocation + to determine where it should paint rounded corners on the buttons. + We need to trick them into drawing the buttons the way we want them. */ + + scrollbar->allocation.x = rect->x; + scrollbar->allocation.y = rect->y; + scrollbar->allocation.width = rect->width; + scrollbar->allocation.height = rect->height; + + if (flags & MOZ_GTK_STEPPER_VERTICAL) { + scrollbar->allocation.height *= 5; + if (flags & MOZ_GTK_STEPPER_DOWN) { + arrow_type = GTK_ARROW_DOWN; + if (flags & MOZ_GTK_STEPPER_BOTTOM) + scrollbar->allocation.y -= 4 * rect->height; + else + scrollbar->allocation.y -= rect->height; + + } else { + arrow_type = GTK_ARROW_UP; + if (flags & MOZ_GTK_STEPPER_BOTTOM) + scrollbar->allocation.y -= 3 * rect->height; + } + } else { + scrollbar->allocation.width *= 5; + if (flags & MOZ_GTK_STEPPER_DOWN) { + arrow_type = GTK_ARROW_RIGHT; + if (flags & MOZ_GTK_STEPPER_BOTTOM) + scrollbar->allocation.x -= 4 * rect->width; + else + scrollbar->allocation.x -= rect->width; + } else { + arrow_type = GTK_ARROW_LEFT; + if (flags & MOZ_GTK_STEPPER_BOTTOM) + scrollbar->allocation.x -= 3 * rect->width; + } + } + + style = scrollbar->style; + + TSOffsetStyleGCs(style, rect->x, rect->y); + + gtk_paint_box(style, drawable, state_type, shadow_type, cliprect, + scrollbar, detail, rect->x, rect->y, + rect->width, rect->height); + + arrow_rect.width = rect->width / 2; + arrow_rect.height = rect->height / 2; + arrow_rect.x = rect->x + (rect->width - arrow_rect.width) / 2; + arrow_rect.y = rect->y + (rect->height - arrow_rect.height) / 2; + + if (state_type == GTK_STATE_ACTIVE) { + gtk_widget_style_get(scrollbar, + "arrow-displacement-x", &arrow_displacement_x, + "arrow-displacement-y", &arrow_displacement_y, + NULL); + + arrow_rect.x += arrow_displacement_x; + arrow_rect.y += arrow_displacement_y; + } + + gtk_paint_arrow(style, drawable, state_type, shadow_type, cliprect, + scrollbar, detail, arrow_type, TRUE, arrow_rect.x, + arrow_rect.y, arrow_rect.width, arrow_rect.height); + + return MOZ_GTK_SUCCESS; +} + +static gint +moz_gtk_scrollbar_trough_paint(WidgetNodeType widget, + GdkDrawable* drawable, GdkRectangle* rect, + GdkRectangle* cliprect, GtkWidgetState* state, + GtkTextDirection direction) +{ + GtkStyle* style; + GtkScrollbar *scrollbar; + + ensure_scrollbar_widget(); + + if (widget == MOZ_GTK_SCROLLBAR_HORIZONTAL) + scrollbar = GTK_SCROLLBAR(gHorizScrollbarWidget); + else + scrollbar = GTK_SCROLLBAR(gVertScrollbarWidget); + + gtk_widget_set_direction(GTK_WIDGET(scrollbar), direction); + + style = GTK_WIDGET(scrollbar)->style; + + TSOffsetStyleGCs(style, rect->x, rect->y); + gtk_style_apply_default_background(style, drawable, TRUE, GTK_STATE_ACTIVE, + cliprect, rect->x, rect->y, + rect->width, rect->height); + + gtk_paint_box(style, drawable, GTK_STATE_ACTIVE, GTK_SHADOW_IN, cliprect, + GTK_WIDGET(scrollbar), "trough", rect->x, rect->y, + rect->width, rect->height); + + if (state->focused) { + gtk_paint_focus(style, drawable, GTK_STATE_ACTIVE, cliprect, + GTK_WIDGET(scrollbar), "trough", + rect->x, rect->y, rect->width, rect->height); + } + + return MOZ_GTK_SUCCESS; +} + +static gint +moz_gtk_scrollbar_thumb_paint(WidgetNodeType widget, + GdkDrawable* drawable, GdkRectangle* rect, + GdkRectangle* cliprect, GtkWidgetState* state, + GtkTextDirection direction) +{ + GtkStateType state_type = (state->inHover || state->active) ? + GTK_STATE_PRELIGHT : GTK_STATE_NORMAL; + GtkShadowType shadow_type = GTK_SHADOW_OUT; + GtkStyle* style; + GtkScrollbar *scrollbar; + GtkAdjustment *adj; + gboolean activate_slider; + + ensure_scrollbar_widget(); + + if (widget == MOZ_GTK_SCROLLBAR_THUMB_HORIZONTAL) + scrollbar = GTK_SCROLLBAR(gHorizScrollbarWidget); + else + scrollbar = GTK_SCROLLBAR(gVertScrollbarWidget); + + gtk_widget_set_direction(GTK_WIDGET(scrollbar), direction); + + /* Make sure to set the scrollbar range before painting so that + everything is drawn properly. At least the bluecurve (and + maybe other) themes don't draw the top or bottom black line + surrounding the scrollbar if the theme thinks that it's butted + up against the scrollbar arrows. Note the increases of the + clip rect below. */ + adj = gtk_range_get_adjustment(GTK_RANGE(scrollbar)); + + if (widget == MOZ_GTK_SCROLLBAR_THUMB_HORIZONTAL) { + adj->page_size = rect->width; + } + else { + adj->page_size = rect->height; + } + + adj->lower = 0; + adj->value = state->curpos; + adj->upper = state->maxpos; + gtk_adjustment_changed(adj); + + style = GTK_WIDGET(scrollbar)->style; + + gtk_widget_style_get(GTK_WIDGET(scrollbar), "activate-slider", + &activate_slider, NULL); + + if (activate_slider && state->active) { + shadow_type = GTK_SHADOW_IN; + state_type = GTK_STATE_ACTIVE; + } + + TSOffsetStyleGCs(style, rect->x, rect->y); + + gtk_paint_slider(style, drawable, state_type, shadow_type, cliprect, + GTK_WIDGET(scrollbar), "slider", rect->x, rect->y, + rect->width, rect->height, + (widget == MOZ_GTK_SCROLLBAR_THUMB_HORIZONTAL) ? + GTK_ORIENTATION_HORIZONTAL : GTK_ORIENTATION_VERTICAL); + + return MOZ_GTK_SUCCESS; +} + +static gint +moz_gtk_spin_paint(GdkDrawable* drawable, GdkRectangle* rect, + GtkTextDirection direction) +{ + GtkStyle* style; + + ensure_spin_widget(); + gtk_widget_set_direction(gSpinWidget, direction); + style = gSpinWidget->style; + + TSOffsetStyleGCs(style, rect->x, rect->y); + gtk_paint_box(style, drawable, GTK_STATE_NORMAL, GTK_SHADOW_IN, NULL, + gSpinWidget, "spinbutton", + rect->x, rect->y, rect->width, rect->height); + return MOZ_GTK_SUCCESS; +} + +static gint +moz_gtk_spin_updown_paint(GdkDrawable* drawable, GdkRectangle* rect, + gboolean isDown, GtkWidgetState* state, + GtkTextDirection direction) +{ + GdkRectangle arrow_rect; + GtkStateType state_type = ConvertGtkState(state); + GtkShadowType shadow_type = state_type == GTK_STATE_ACTIVE ? + GTK_SHADOW_IN : GTK_SHADOW_OUT; + GtkStyle* style; + + ensure_spin_widget(); + style = gSpinWidget->style; + gtk_widget_set_direction(gSpinWidget, direction); + + TSOffsetStyleGCs(style, rect->x, rect->y); + gtk_paint_box(style, drawable, state_type, shadow_type, NULL, gSpinWidget, + isDown ? "spinbutton_down" : "spinbutton_up", + rect->x, rect->y, rect->width, rect->height); + + /* hard code these values */ + arrow_rect.width = 6; + arrow_rect.height = 6; + arrow_rect.x = rect->x + (rect->width - arrow_rect.width) / 2; + arrow_rect.y = rect->y + (rect->height - arrow_rect.height) / 2; + arrow_rect.y += isDown ? -1 : 1; + + gtk_paint_arrow(style, drawable, state_type, shadow_type, NULL, + gSpinWidget, "spinbutton", + isDown ? GTK_ARROW_DOWN : GTK_ARROW_UP, TRUE, + arrow_rect.x, arrow_rect.y, + arrow_rect.width, arrow_rect.height); + + return MOZ_GTK_SUCCESS; +} + +static gint +moz_gtk_scale_paint(GdkDrawable* drawable, GdkRectangle* rect, + GdkRectangle* cliprect, GtkWidgetState* state, + GtkOrientation flags, GtkTextDirection direction) +{ + gint x = 0, y = 0; + GtkStateType state_type = ConvertGtkState(state); + GtkStyle* style; + GtkWidget* widget; + + ensure_scale_widget(); + widget = ((flags == GTK_ORIENTATION_HORIZONTAL) ? gHScaleWidget : gVScaleWidget); + gtk_widget_set_direction(widget, direction); + + style = widget->style; + + if (flags == GTK_ORIENTATION_HORIZONTAL) { + x = XTHICKNESS(style); + y++; + } + else { + x++; + y = YTHICKNESS(style); + } + + TSOffsetStyleGCs(style, rect->x, rect->y); + + gtk_paint_box(style, drawable, GTK_STATE_ACTIVE, GTK_SHADOW_IN, cliprect, + widget, "trough", rect->x + x, rect->y + y, + rect->width - 2*x, rect->height - 2*y); + + if (state->focused) + gtk_paint_focus(style, drawable, state_type, cliprect, widget, "trough", + rect->x, rect->y, rect->width, rect->height); + + return MOZ_GTK_SUCCESS; +} + +static gint +moz_gtk_scale_thumb_paint(GdkDrawable* drawable, GdkRectangle* rect, + GdkRectangle* cliprect, GtkWidgetState* state, + GtkOrientation flags, GtkTextDirection direction) +{ + GtkStateType state_type = ConvertGtkState(state); + GtkStyle* style; + GtkWidget* widget; + gint thumb_width, thumb_height, x, y; + + ensure_scale_widget(); + widget = ((flags == GTK_ORIENTATION_HORIZONTAL) ? gHScaleWidget : gVScaleWidget); + gtk_widget_set_direction(widget, direction); + + style = widget->style; + + /* determine the thumb size, and position the thumb in the center in the opposite axis */ + if (flags == GTK_ORIENTATION_HORIZONTAL) { + moz_gtk_get_scalethumb_metrics(GTK_ORIENTATION_HORIZONTAL, &thumb_width, &thumb_height); + x = rect->x; + y = rect->y + (rect->height - thumb_height) / 2; + } + else { + moz_gtk_get_scalethumb_metrics(GTK_ORIENTATION_VERTICAL, &thumb_height, &thumb_width); + x = rect->x + (rect->width - thumb_width) / 2; + y = rect->y; + } + + TSOffsetStyleGCs(style, rect->x, rect->y); + gtk_paint_slider(style, drawable, state_type, GTK_SHADOW_OUT, cliprect, + widget, (flags == GTK_ORIENTATION_HORIZONTAL) ? "hscale" : "vscale", + x, y, thumb_width, thumb_height, flags); + + return MOZ_GTK_SUCCESS; +} + +static gint +moz_gtk_gripper_paint(GdkDrawable* drawable, GdkRectangle* rect, + GdkRectangle* cliprect, GtkWidgetState* state, + GtkTextDirection direction) +{ + GtkStateType state_type = ConvertGtkState(state); + GtkShadowType shadow_type; + GtkStyle* style; + + ensure_handlebox_widget(); + gtk_widget_set_direction(gHandleBoxWidget, direction); + + style = gHandleBoxWidget->style; + shadow_type = GTK_HANDLE_BOX(gHandleBoxWidget)->shadow_type; + + TSOffsetStyleGCs(style, rect->x, rect->y); + gtk_paint_box(style, drawable, state_type, shadow_type, cliprect, + gHandleBoxWidget, "handlebox_bin", rect->x, rect->y, + rect->width, rect->height); + + return MOZ_GTK_SUCCESS; +} + +static gint +moz_gtk_hpaned_paint(GdkDrawable* drawable, GdkRectangle* rect, + GdkRectangle* cliprect, GtkWidgetState* state) +{ + GtkStateType hpaned_state = ConvertGtkState(state); + + ensure_hpaned_widget(); + gtk_paint_handle(gHPanedWidget->style, drawable, hpaned_state, + GTK_SHADOW_NONE, cliprect, gHPanedWidget, "paned", + rect->x, rect->y, rect->width, rect->height, + GTK_ORIENTATION_VERTICAL); + + return MOZ_GTK_SUCCESS; +} + +static gint +moz_gtk_vpaned_paint(GdkDrawable* drawable, GdkRectangle* rect, + GdkRectangle* cliprect, GtkWidgetState* state) +{ + GtkStateType vpaned_state = ConvertGtkState(state); + + ensure_vpaned_widget(); + gtk_paint_handle(gVPanedWidget->style, drawable, vpaned_state, + GTK_SHADOW_NONE, cliprect, gVPanedWidget, "paned", + rect->x, rect->y, rect->width, rect->height, + GTK_ORIENTATION_HORIZONTAL); + + return MOZ_GTK_SUCCESS; +} + +static gint +moz_gtk_entry_paint(GdkDrawable* drawable, GdkRectangle* rect, + GdkRectangle* cliprect, GtkWidgetState* state, + GtkWidget* widget, GtkTextDirection direction) +{ + GtkStateType bg_state = state->disabled ? + GTK_STATE_INSENSITIVE : GTK_STATE_NORMAL; + gint x, y, width = rect->width, height = rect->height; + GtkStyle* style; + gboolean interior_focus; + gboolean theme_honors_transparency = FALSE; + gint focus_width; + int draw_focus_outline_only = state->depressed; // NS_THEME_FOCUS_OUTLINE + + gtk_widget_set_direction(widget, direction); + + style = widget->style; + + gtk_widget_style_get(widget, + "interior-focus", &interior_focus, + "focus-line-width", &focus_width, + "honors-transparent-bg-hint", &theme_honors_transparency, + NULL); + + if (draw_focus_outline_only) { + // Inflate the given 'rect' with the focus outline size. + gint h, v; + moz_gtk_get_focus_outline_size(&h, &v); + rect->x -= h; + rect->width += 2 * h; + rect->y -= v; + rect->height += 2 * v; + width = rect->width; + height = rect->height; + } + + /* gtkentry.c uses two windows, one for the entire widget and one for the + * text area inside it. The background of both windows is set to the "base" + * color of the new state in gtk_entry_state_changed, but only the inner + * textarea window uses gtk_paint_flat_box when exposed */ + + TSOffsetStyleGCs(style, rect->x, rect->y); + + /* This gets us a lovely greyish disabledish look */ + gtk_widget_set_sensitive(widget, !state->disabled); + + /* GTK fills the outer widget window with the base color before drawing the widget. + * Some older themes rely on this behavior, but many themes nowadays use rounded + * corners on their widgets. While most GTK apps are blissfully unaware of this + * problem due to their use of the default window background, we render widgets on + * many kinds of backgrounds on the web. + * If the theme is able to cope with transparency, then we can skip pre-filling + * and notify the theme it will paint directly on the canvas. */ + if (theme_honors_transparency) { + g_object_set_data(G_OBJECT(widget), "transparent-bg-hint", GINT_TO_POINTER(TRUE)); + } else { + GdkRectangle clipped_rect; + gdk_rectangle_intersect(rect, cliprect, &clipped_rect); + if (clipped_rect.width != 0) { + gdk_draw_rectangle(drawable, style->base_gc[bg_state], TRUE, + clipped_rect.x, clipped_rect.y, + clipped_rect.width, clipped_rect.height); + } + g_object_set_data(G_OBJECT(widget), "transparent-bg-hint", GINT_TO_POINTER(FALSE)); + } + + if (!draw_focus_outline_only) { + /* Get the position of the inner window, see _gtk_entry_get_borders */ + x = XTHICKNESS(style); + y = YTHICKNESS(style); + + if (!interior_focus) { + x += focus_width; + y += focus_width; + } + + /* Simulate an expose of the inner window */ + gtk_paint_flat_box(style, drawable, bg_state, GTK_SHADOW_NONE, + cliprect, widget, "entry_bg", rect->x + x, + rect->y + y, rect->width - 2*x, rect->height - 2*y); + } + + /* Now paint the shadow and focus border. + * We do like in gtk_entry_draw_frame, we first draw the shadow, a tad + * smaller when focused if the focus is not interior, then the focus. */ + x = rect->x; + y = rect->y; + + if (state->focused && !state->disabled) { + /* This will get us the lit borders that focused textboxes enjoy on + * some themes. */ + GTK_WIDGET_SET_FLAGS(widget, GTK_HAS_FOCUS); + + if (!interior_focus) { + /* Indent the border a little bit if we have exterior focus + (this is what GTK does to draw native entries) */ + x += focus_width; + y += focus_width; + width -= 2 * focus_width; + height -= 2 * focus_width; + } + } + + if (!draw_focus_outline_only || interior_focus) { + gtk_paint_shadow(style, drawable, GTK_STATE_NORMAL, GTK_SHADOW_IN, + cliprect, widget, "entry", x, y, width, height); + } + + if (state->focused && !state->disabled) { + if (!interior_focus) { + gtk_paint_focus(style, drawable, GTK_STATE_NORMAL, cliprect, + widget, "entry", + rect->x, rect->y, rect->width, rect->height); + } + + /* Now unset the focus flag. We don't want other entries to look + * like they're focused too! */ + GTK_WIDGET_UNSET_FLAGS(widget, GTK_HAS_FOCUS); + } + + return MOZ_GTK_SUCCESS; +} + +static gint +moz_gtk_treeview_paint(GdkDrawable* drawable, GdkRectangle* rect, + GdkRectangle* cliprect, GtkWidgetState* state, + GtkTextDirection direction) +{ + gint xthickness, ythickness; + + GtkStyle *style; + GtkStateType state_type; + + ensure_tree_view_widget(); + ensure_scrolled_window_widget(); + + gtk_widget_set_direction(gTreeViewWidget, direction); + gtk_widget_set_direction(gScrolledWindowWidget, direction); + + /* only handle disabled and normal states, otherwise the whole background + * area will be painted differently with other states */ + state_type = state->disabled ? GTK_STATE_INSENSITIVE : GTK_STATE_NORMAL; + + /* In GTK the treeview sets the background of the window + * which contains the cells to the treeview base color. + * If we don't set it here the background color will not be correct.*/ + gtk_widget_modify_bg(gTreeViewWidget, state_type, + &gTreeViewWidget->style->base[state_type]); + + style = gScrolledWindowWidget->style; + xthickness = XTHICKNESS(style); + ythickness = YTHICKNESS(style); + + TSOffsetStyleGCs(gTreeViewWidget->style, rect->x, rect->y); + TSOffsetStyleGCs(style, rect->x, rect->y); + + gtk_paint_flat_box(gTreeViewWidget->style, drawable, state_type, + GTK_SHADOW_NONE, cliprect, gTreeViewWidget, "treeview", + rect->x + xthickness, rect->y + ythickness, + rect->width - 2 * xthickness, + rect->height - 2 * ythickness); + + gtk_paint_shadow(style, drawable, GTK_STATE_NORMAL, GTK_SHADOW_IN, + cliprect, gScrolledWindowWidget, "scrolled_window", + rect->x, rect->y, rect->width, rect->height); + + return MOZ_GTK_SUCCESS; +} + +static gint +moz_gtk_tree_header_cell_paint(GdkDrawable* drawable, GdkRectangle* rect, + GdkRectangle* cliprect, GtkWidgetState* state, + gboolean isSorted, GtkTextDirection direction) +{ + gtk_tree_view_column_set_sort_indicator(gMiddleTreeViewColumn, + isSorted); + + moz_gtk_button_paint(drawable, rect, cliprect, state, GTK_RELIEF_NORMAL, + gTreeHeaderCellWidget, direction); + return MOZ_GTK_SUCCESS; +} + +static gint +moz_gtk_tree_header_sort_arrow_paint(GdkDrawable* drawable, GdkRectangle* rect, + GdkRectangle* cliprect, + GtkWidgetState* state, GtkArrowType flags, + GtkTextDirection direction) +{ + GdkRectangle arrow_rect; + GtkStateType state_type = ConvertGtkState(state); + GtkShadowType shadow_type = GTK_SHADOW_IN; + GtkArrowType arrow_type = flags; + GtkStyle* style; + + ensure_tree_header_cell_widget(); + gtk_widget_set_direction(gTreeHeaderSortArrowWidget, direction); + + /* hard code these values */ + arrow_rect.width = 11; + arrow_rect.height = 11; + arrow_rect.x = rect->x + (rect->width - arrow_rect.width) / 2; + arrow_rect.y = rect->y + (rect->height - arrow_rect.height) / 2; + + style = gTreeHeaderSortArrowWidget->style; + TSOffsetStyleGCs(style, arrow_rect.x, arrow_rect.y); + + gtk_paint_arrow(style, drawable, state_type, shadow_type, cliprect, + gTreeHeaderSortArrowWidget, "arrow", arrow_type, TRUE, + arrow_rect.x, arrow_rect.y, + arrow_rect.width, arrow_rect.height); + + return MOZ_GTK_SUCCESS; +} + +static gint +moz_gtk_treeview_expander_paint(GdkDrawable* drawable, GdkRectangle* rect, + GdkRectangle* cliprect, GtkWidgetState* state, + GtkExpanderStyle expander_state, + GtkTextDirection direction) +{ + GtkStyle *style; + GtkStateType state_type; + + ensure_tree_view_widget(); + gtk_widget_set_direction(gTreeViewWidget, direction); + + style = gTreeViewWidget->style; + + /* Because the frame we get is of the entire treeview, we can't get the precise + * event state of one expander, thus rendering hover and active feedback useless. */ + state_type = state->disabled ? GTK_STATE_INSENSITIVE : GTK_STATE_NORMAL; + + TSOffsetStyleGCs(style, rect->x, rect->y); + gtk_paint_expander(style, drawable, state_type, cliprect, gTreeViewWidget, "treeview", + rect->x + rect->width / 2, rect->y + rect->height / 2, expander_state); + + return MOZ_GTK_SUCCESS; +} + +static gint +moz_gtk_combo_box_paint(GdkDrawable* drawable, GdkRectangle* rect, + GdkRectangle* cliprect, GtkWidgetState* state, + gboolean ishtml, GtkTextDirection direction) +{ + GdkRectangle arrow_rect, real_arrow_rect; + gint arrow_size, separator_width; + gboolean wide_separators; + GtkStateType state_type = ConvertGtkState(state); + GtkShadowType shadow_type = state->active ? GTK_SHADOW_IN : GTK_SHADOW_OUT; + GtkStyle* style; + GtkRequisition arrow_req; + + ensure_combo_box_widgets(); + + /* Also sets the direction on gComboBoxButtonWidget, which is then + * inherited by the separator and arrow */ + moz_gtk_button_paint(drawable, rect, cliprect, state, GTK_RELIEF_NORMAL, + gComboBoxButtonWidget, direction); + + calculate_button_inner_rect(gComboBoxButtonWidget, + rect, &arrow_rect, direction, ishtml); + /* Now arrow_rect contains the inner rect ; we want to correct the width + * to what the arrow needs (see gtk_combo_box_size_allocate) */ + gtk_widget_size_request(gComboBoxArrowWidget, &arrow_req); + if (direction == GTK_TEXT_DIR_LTR) + arrow_rect.x += arrow_rect.width - arrow_req.width; + arrow_rect.width = arrow_req.width; + + calculate_arrow_rect(gComboBoxArrowWidget, + &arrow_rect, &real_arrow_rect, direction); + + style = gComboBoxArrowWidget->style; + TSOffsetStyleGCs(style, rect->x, rect->y); + + gtk_widget_size_allocate(gComboBoxWidget, rect); + + gtk_paint_arrow(style, drawable, state_type, shadow_type, cliprect, + gComboBoxArrowWidget, "arrow", GTK_ARROW_DOWN, TRUE, + real_arrow_rect.x, real_arrow_rect.y, + real_arrow_rect.width, real_arrow_rect.height); + + + /* If there is no separator in the theme, there's nothing left to do. */ + if (!gComboBoxSeparatorWidget) + return MOZ_GTK_SUCCESS; + + style = gComboBoxSeparatorWidget->style; + TSOffsetStyleGCs(style, rect->x, rect->y); + + gtk_widget_style_get(gComboBoxSeparatorWidget, + "wide-separators", &wide_separators, + "separator-width", &separator_width, + NULL); + + if (wide_separators) { + if (direction == GTK_TEXT_DIR_LTR) + arrow_rect.x -= separator_width; + else + arrow_rect.x += arrow_rect.width; + + gtk_paint_box(style, drawable, + GTK_STATE_NORMAL, GTK_SHADOW_ETCHED_OUT, + cliprect, gComboBoxSeparatorWidget, "vseparator", + arrow_rect.x, arrow_rect.y, + separator_width, arrow_rect.height); + } else { + if (direction == GTK_TEXT_DIR_LTR) + arrow_rect.x -= XTHICKNESS(style); + else + arrow_rect.x += arrow_rect.width; + + gtk_paint_vline(style, drawable, GTK_STATE_NORMAL, cliprect, + gComboBoxSeparatorWidget, "vseparator", + arrow_rect.y, arrow_rect.y + arrow_rect.height, + arrow_rect.x); + } + + return MOZ_GTK_SUCCESS; +} + +static gint +moz_gtk_arrow_paint(GdkDrawable* drawable, GdkRectangle* rect, + GdkRectangle* cliprect, GtkWidgetState* state, + GtkArrowType arrow_type, GtkTextDirection direction) +{ + GtkStyle* style; + GtkStateType state_type = ConvertGtkState(state); + GtkShadowType shadow_type = state->active ? GTK_SHADOW_IN : GTK_SHADOW_OUT; + GdkRectangle arrow_rect; + + ensure_button_arrow_widget(); + style = gButtonArrowWidget->style; + gtk_widget_set_direction(gButtonArrowWidget, direction); + + calculate_arrow_rect(gButtonArrowWidget, rect, &arrow_rect, + direction); + + if (direction == GTK_TEXT_DIR_RTL) { + if (arrow_type == GTK_ARROW_LEFT) + arrow_type = GTK_ARROW_RIGHT; + else if (arrow_type == GTK_ARROW_RIGHT) + arrow_type = GTK_ARROW_LEFT; + } + + TSOffsetStyleGCs(style, arrow_rect.x, arrow_rect.y); + gtk_paint_arrow(style, drawable, state_type, shadow_type, cliprect, + gButtonArrowWidget, "arrow", arrow_type, TRUE, + arrow_rect.x, arrow_rect.y, arrow_rect.width, arrow_rect.height); + + return MOZ_GTK_SUCCESS; +} + +static gint +moz_gtk_combo_box_entry_button_paint(GdkDrawable* drawable, GdkRectangle* rect, + GdkRectangle* cliprect, + GtkWidgetState* state, + gboolean input_focus, + GtkTextDirection direction) +{ + gint x_displacement, y_displacement; + GdkRectangle arrow_rect, real_arrow_rect; + GtkStateType state_type = ConvertGtkState(state); + GtkShadowType shadow_type = state->active ? GTK_SHADOW_IN : GTK_SHADOW_OUT; + GtkStyle* style; + + ensure_combo_box_entry_widgets(); + + if (input_focus) { + /* Some themes draw a complementary focus ring for the dropdown button + * when the dropdown entry has focus */ + GTK_WIDGET_SET_FLAGS(gComboBoxEntryTextareaWidget, GTK_HAS_FOCUS); + } + + moz_gtk_button_paint(drawable, rect, cliprect, state, GTK_RELIEF_NORMAL, + gComboBoxEntryButtonWidget, direction); + + if (input_focus) + GTK_WIDGET_UNSET_FLAGS(gComboBoxEntryTextareaWidget, GTK_HAS_FOCUS); + + calculate_button_inner_rect(gComboBoxEntryButtonWidget, + rect, &arrow_rect, direction, FALSE); + if (state_type == GTK_STATE_ACTIVE) { + gtk_widget_style_get(gComboBoxEntryButtonWidget, + "child-displacement-x", &x_displacement, + "child-displacement-y", &y_displacement, + NULL); + arrow_rect.x += x_displacement; + arrow_rect.y += y_displacement; + } + + calculate_arrow_rect(gComboBoxEntryArrowWidget, + &arrow_rect, &real_arrow_rect, direction); + + style = gComboBoxEntryArrowWidget->style; + TSOffsetStyleGCs(style, real_arrow_rect.x, real_arrow_rect.y); + + gtk_paint_arrow(style, drawable, state_type, shadow_type, cliprect, + gComboBoxEntryArrowWidget, "arrow", GTK_ARROW_DOWN, TRUE, + real_arrow_rect.x, real_arrow_rect.y, + real_arrow_rect.width, real_arrow_rect.height); + + return MOZ_GTK_SUCCESS; +} + +static gint +moz_gtk_container_paint(GdkDrawable* drawable, GdkRectangle* rect, + GdkRectangle* cliprect, GtkWidgetState* state, + gboolean isradio, GtkTextDirection direction) +{ + GtkStateType state_type = ConvertGtkState(state); + GtkStyle* style; + GtkWidget *widget; + gboolean interior_focus; + gint focus_width, focus_pad; + + if (isradio) { + ensure_radiobutton_widget(); + widget = gRadiobuttonWidget; + } else { + ensure_checkbox_widget(); + widget = gCheckboxWidget; + } + gtk_widget_set_direction(widget, direction); + + style = widget->style; + moz_gtk_widget_get_focus(widget, &interior_focus, &focus_width, + &focus_pad); + + TSOffsetStyleGCs(style, rect->x, rect->y); + + /* The detail argument for the gtk_paint_* calls below are "checkbutton" + even for radio buttons, to match what gtk does. */ + + /* this is for drawing a prelight box */ + if (state_type == GTK_STATE_PRELIGHT || state_type == GTK_STATE_ACTIVE) { + gtk_paint_flat_box(style, drawable, GTK_STATE_PRELIGHT, + GTK_SHADOW_ETCHED_OUT, cliprect, widget, + "checkbutton", + rect->x, rect->y, rect->width, rect->height); + } + + if (state_type != GTK_STATE_NORMAL && state_type != GTK_STATE_PRELIGHT) + state_type = GTK_STATE_NORMAL; + + if (state->focused && !interior_focus) { + gtk_paint_focus(style, drawable, state_type, cliprect, widget, + "checkbutton", + rect->x, rect->y, rect->width, rect->height); + } + + return MOZ_GTK_SUCCESS; +} + +static gint +moz_gtk_toggle_label_paint(GdkDrawable* drawable, GdkRectangle* rect, + GdkRectangle* cliprect, GtkWidgetState* state, + gboolean isradio, GtkTextDirection direction) +{ + GtkStateType state_type; + GtkStyle *style; + GtkWidget *widget; + gboolean interior_focus; + + if (!state->focused) + return MOZ_GTK_SUCCESS; + + if (isradio) { + ensure_radiobutton_widget(); + widget = gRadiobuttonWidget; + } else { + ensure_checkbox_widget(); + widget = gCheckboxWidget; + } + gtk_widget_set_direction(widget, direction); + + gtk_widget_style_get(widget, "interior-focus", &interior_focus, NULL); + if (!interior_focus) + return MOZ_GTK_SUCCESS; + + state_type = ConvertGtkState(state); + + style = widget->style; + TSOffsetStyleGCs(style, rect->x, rect->y); + + /* Always "checkbutton" to match gtkcheckbutton.c */ + gtk_paint_focus(style, drawable, state_type, cliprect, widget, + "checkbutton", + rect->x, rect->y, rect->width, rect->height); + + return MOZ_GTK_SUCCESS; +} + +static gint +moz_gtk_toolbar_paint(GdkDrawable* drawable, GdkRectangle* rect, + GdkRectangle* cliprect, GtkTextDirection direction) +{ + GtkStyle* style; + GtkShadowType shadow_type; + + ensure_toolbar_widget(); + gtk_widget_set_direction(gToolbarWidget, direction); + + style = gToolbarWidget->style; + + TSOffsetStyleGCs(style, rect->x, rect->y); + + gtk_style_apply_default_background(style, drawable, TRUE, + GTK_STATE_NORMAL, + cliprect, rect->x, rect->y, + rect->width, rect->height); + + gtk_widget_style_get(gToolbarWidget, "shadow-type", &shadow_type, NULL); + + gtk_paint_box (style, drawable, GTK_STATE_NORMAL, shadow_type, + cliprect, gToolbarWidget, "toolbar", + rect->x, rect->y, rect->width, rect->height); + + return MOZ_GTK_SUCCESS; +} + +static gint +moz_gtk_toolbar_separator_paint(GdkDrawable* drawable, GdkRectangle* rect, + GdkRectangle* cliprect, + GtkTextDirection direction) +{ + GtkStyle* style; + gint separator_width; + gint paint_width; + gboolean wide_separators; + + /* Defined as constants in GTK+ 2.10.14 */ + const double start_fraction = 0.2; + const double end_fraction = 0.8; + + ensure_toolbar_separator_widget(); + gtk_widget_set_direction(gToolbarSeparatorWidget, direction); + + style = gToolbarSeparatorWidget->style; + + gtk_widget_style_get(gToolbarWidget, + "wide-separators", &wide_separators, + "separator-width", &separator_width, + NULL); + + TSOffsetStyleGCs(style, rect->x, rect->y); + + if (wide_separators) { + if (separator_width > rect->width) + separator_width = rect->width; + + gtk_paint_box(style, drawable, + GTK_STATE_NORMAL, GTK_SHADOW_ETCHED_OUT, + cliprect, gToolbarWidget, "vseparator", + rect->x + (rect->width - separator_width) / 2, + rect->y + rect->height * start_fraction, + separator_width, + rect->height * (end_fraction - start_fraction)); + + } else { + paint_width = style->xthickness; + + if (paint_width > rect->width) + paint_width = rect->width; + + gtk_paint_vline(style, drawable, + GTK_STATE_NORMAL, cliprect, gToolbarSeparatorWidget, + "toolbar", + rect->y + rect->height * start_fraction, + rect->y + rect->height * end_fraction, + rect->x + (rect->width - paint_width) / 2); + } + + return MOZ_GTK_SUCCESS; +} + +static gint +moz_gtk_tooltip_paint(GdkDrawable* drawable, GdkRectangle* rect, + GdkRectangle* cliprect, GtkTextDirection direction) +{ + GtkStyle* style; + + ensure_tooltip_widget(); + gtk_widget_set_direction(gTooltipWidget, direction); + + style = gtk_rc_get_style_by_paths(gtk_settings_get_default(), + "gtk-tooltips", "GtkWindow", + GTK_TYPE_WINDOW); + + style = gtk_style_attach(style, gTooltipWidget->window); + TSOffsetStyleGCs(style, rect->x, rect->y); + gtk_paint_flat_box(style, drawable, GTK_STATE_NORMAL, GTK_SHADOW_OUT, + cliprect, gTooltipWidget, "tooltip", + rect->x, rect->y, rect->width, rect->height); + + return MOZ_GTK_SUCCESS; +} + +static gint +moz_gtk_resizer_paint(GdkDrawable* drawable, GdkRectangle* rect, + GdkRectangle* cliprect, GtkWidgetState* state, + GtkTextDirection direction) +{ + GtkStyle* style; + GtkStateType state_type = ConvertGtkState(state); + + ensure_frame_widget(); + gtk_widget_set_direction(gStatusbarWidget, direction); + + style = gStatusbarWidget->style; + + TSOffsetStyleGCs(style, rect->x, rect->y); + + gtk_paint_resize_grip(style, drawable, state_type, cliprect, gStatusbarWidget, + "statusbar", (direction == GTK_TEXT_DIR_LTR) ? + GDK_WINDOW_EDGE_SOUTH_EAST : + GDK_WINDOW_EDGE_SOUTH_WEST, + rect->x, rect->y, rect->width, rect->height); + return MOZ_GTK_SUCCESS; +} + +static gint +moz_gtk_frame_paint(GdkDrawable* drawable, GdkRectangle* rect, + GdkRectangle* cliprect, GtkTextDirection direction) +{ + GtkStyle* style; + GtkShadowType shadow_type; + + ensure_frame_widget(); + gtk_widget_set_direction(gFrameWidget, direction); + + style = gFrameWidget->style; + + gtk_widget_style_get(gStatusbarWidget, "shadow-type", &shadow_type, NULL); + + TSOffsetStyleGCs(style, rect->x, rect->y); + gtk_paint_shadow(style, drawable, GTK_STATE_NORMAL, shadow_type, + cliprect, gFrameWidget, "frame", rect->x, rect->y, + rect->width, rect->height); + + return MOZ_GTK_SUCCESS; +} + +static gint +moz_gtk_progressbar_paint(GdkDrawable* drawable, GdkRectangle* rect, + GdkRectangle* cliprect, GtkTextDirection direction) +{ + GtkStyle* style; + + ensure_progress_widget(); + gtk_widget_set_direction(gProgressWidget, direction); + + style = gProgressWidget->style; + + TSOffsetStyleGCs(style, rect->x, rect->y); + gtk_paint_box(style, drawable, GTK_STATE_NORMAL, GTK_SHADOW_IN, + cliprect, gProgressWidget, "trough", rect->x, rect->y, + rect->width, rect->height); + + return MOZ_GTK_SUCCESS; +} + +static gint +moz_gtk_progress_chunk_paint(GdkDrawable* drawable, GdkRectangle* rect, + GdkRectangle* cliprect, GtkTextDirection direction, + WidgetNodeType widget) +{ + GtkStyle* style; + + ensure_progress_widget(); + gtk_widget_set_direction(gProgressWidget, direction); + + style = gProgressWidget->style; + + TSOffsetStyleGCs(style, rect->x, rect->y); + + if (widget == MOZ_GTK_PROGRESS_CHUNK_INDETERMINATE || + widget == MOZ_GTK_PROGRESS_CHUNK_VERTICAL_INDETERMINATE) { + /** + * The bar's size and the bar speed are set depending of the progress' + * size. These could also be constant for all progress bars easily. + */ + gboolean vertical = (widget == MOZ_GTK_PROGRESS_CHUNK_VERTICAL_INDETERMINATE); + + /* The size of the dimension we are going to use for the animation. */ + const gint progressSize = vertical ? rect->height : rect->width; + + /* The bar is using a fifth of the element size, based on GtkProgressBar + * activity-blocks property. */ + const gint barSize = MAX(1, progressSize / 5); + + /* Represents the travel that has to be done for a complete cycle. */ + const gint travel = 2 * (progressSize - barSize); + + /* period equals to travel / pixelsPerMillisecond + * where pixelsPerMillisecond equals progressSize / 1000.0. + * This is equivalent to 1600. */ + static const guint period = 1600; + const gint t = PR_IntervalToMilliseconds(PR_IntervalNow()) % period; + const gint dx = travel * t / period; + + if (vertical) { + rect->y += (dx < travel / 2) ? dx : travel - dx; + rect->height = barSize; + } else { + rect->x += (dx < travel / 2) ? dx : travel - dx; + rect->width = barSize; + } + } + + gtk_paint_box(style, drawable, GTK_STATE_PRELIGHT, GTK_SHADOW_OUT, + cliprect, gProgressWidget, "bar", rect->x, rect->y, + rect->width, rect->height); + + return MOZ_GTK_SUCCESS; +} + +gint +moz_gtk_get_tab_thickness(WidgetNodeType aNodeType) +{ + ensure_tab_widget(); + if (YTHICKNESS(gTabWidget->style) < 2) + return 2; /* some themes don't set ythickness correctly */ + + return YTHICKNESS(gTabWidget->style); +} + +static gint +moz_gtk_tab_paint(GdkDrawable* drawable, GdkRectangle* rect, + GdkRectangle* cliprect, GtkWidgetState* state, + GtkTabFlags flags, GtkTextDirection direction, + WidgetNodeType widget) +{ + /* When the tab isn't selected, we just draw a notebook extension. + * When it is selected, we overwrite the adjacent border of the tabpanel + * touching the tab with a pierced border (called "the gap") to make the + * tab appear physically attached to the tabpanel; see details below. */ + + GtkStyle* style; + GdkRectangle focusRect; + gboolean isBottomTab = (widget == MOZ_GTK_TAB_BOTTOM); + + ensure_tab_widget(); + gtk_widget_set_direction(gTabWidget, direction); + + style = gTabWidget->style; + focusRect = *rect; + TSOffsetStyleGCs(style, rect->x, rect->y); + + if ((flags & MOZ_GTK_TAB_SELECTED) == 0) { + /* Only draw the tab */ + gtk_paint_extension(style, drawable, GTK_STATE_ACTIVE, GTK_SHADOW_OUT, + cliprect, gTabWidget, "tab", + rect->x, rect->y, rect->width, rect->height, + isBottomTab ? GTK_POS_TOP : GTK_POS_BOTTOM ); + } else { + /* Draw the tab and the gap + * We want the gap to be positioned exactly on the tabpanel top + * border; since tabbox.css may set a negative margin so that the tab + * frame rect already overlaps the tabpanel frame rect, we need to take + * that into account when drawing. To that effect, nsNativeThemeGTK + * passes us this negative margin (bmargin in the graphic below) in the + * lowest bits of |flags|. We use it to set gap_voffset, the distance + * between the top of the gap and the bottom of the tab (resp. the + * bottom of the gap and the top of the tab when we draw a bottom tab), + * while ensuring that the gap always touches the border of the tab, + * i.e. 0 <= gap_voffset <= gap_height, to avoid surprinsing results + * with big negative or positive margins. + * Here is a graphical explanation in the case of top tabs: + * ___________________________ + * / \ + * | T A B | + * ----------|. . . . . . . . . . . . . . .|----- top of tabpanel + * : ^ bmargin : ^ + * : | (-negative margin, : | + * bottom : v passed in flags) : | gap_height + * of -> :.............................: | (the size of the + * the tab . part of the gap . | tabpanel top border) + * . outside of the tab . v + * ---------------------------------------------- + * + * To draw the gap, we use gtk_paint_box_gap(), see comment in + * moz_gtk_tabpanels_paint(). This box_gap is made 3 * gap_height tall, + * which should suffice to ensure that the only visible border is the + * pierced one. If the tab is in the middle, we make the box_gap begin + * a bit to the left of the tab and end a bit to the right, adjusting + * the gap position so it still is under the tab, because we want the + * rendering of a gap in the middle of a tabpanel. This is the role of + * the gints gap_{l,r}_offset. On the contrary, if the tab is the + * first, we align the start border of the box_gap with the start + * border of the tab (left if LTR, right if RTL), by setting the + * appropriate offset to 0.*/ + gint gap_loffset, gap_roffset, gap_voffset, gap_height; + + /* Get height needed by the gap */ + gap_height = moz_gtk_get_tab_thickness(widget); + + /* Extract gap_voffset from the first bits of flags */ + gap_voffset = flags & MOZ_GTK_TAB_MARGIN_MASK; + if (gap_voffset > gap_height) + gap_voffset = gap_height; + + /* Set gap_{l,r}_offset to appropriate values */ + gap_loffset = gap_roffset = 20; /* should be enough */ + if (flags & MOZ_GTK_TAB_FIRST) { + if (direction == GTK_TEXT_DIR_RTL) + gap_roffset = 0; + else + gap_loffset = 0; + } + + if (isBottomTab) { + /* Draw the tab */ + focusRect.y += gap_voffset; + focusRect.height -= gap_voffset; + gtk_paint_extension(style, drawable, GTK_STATE_NORMAL, + GTK_SHADOW_OUT, cliprect, gTabWidget, "tab", + rect->x, rect->y + gap_voffset, rect->width, + rect->height - gap_voffset, GTK_POS_TOP); + + /* Draw the gap; erase with background color before painting in + * case theme does not */ + gtk_style_apply_default_background(style, drawable, TRUE, + GTK_STATE_NORMAL, cliprect, + rect->x, + rect->y + gap_voffset + - gap_height, + rect->width, gap_height); + gtk_paint_box_gap(style, drawable, GTK_STATE_NORMAL, GTK_SHADOW_OUT, + cliprect, gTabWidget, "notebook", + rect->x - gap_loffset, + rect->y + gap_voffset - 3 * gap_height, + rect->width + gap_loffset + gap_roffset, + 3 * gap_height, GTK_POS_BOTTOM, + gap_loffset, rect->width); + } else { + /* Draw the tab */ + focusRect.height -= gap_voffset; + gtk_paint_extension(style, drawable, GTK_STATE_NORMAL, + GTK_SHADOW_OUT, cliprect, gTabWidget, "tab", + rect->x, rect->y, rect->width, + rect->height - gap_voffset, GTK_POS_BOTTOM); + + /* Draw the gap; erase with background color before painting in + * case theme does not */ + gtk_style_apply_default_background(style, drawable, TRUE, + GTK_STATE_NORMAL, cliprect, + rect->x, + rect->y + rect->height + - gap_voffset, + rect->width, gap_height); + gtk_paint_box_gap(style, drawable, GTK_STATE_NORMAL, GTK_SHADOW_OUT, + cliprect, gTabWidget, "notebook", + rect->x - gap_loffset, + rect->y + rect->height - gap_voffset, + rect->width + gap_loffset + gap_roffset, + 3 * gap_height, GTK_POS_TOP, + gap_loffset, rect->width); + } + + } + + if (state->focused) { + /* Paint the focus ring */ + focusRect.x += XTHICKNESS(style); + focusRect.width -= XTHICKNESS(style) * 2; + focusRect.y += YTHICKNESS(style); + focusRect.height -= YTHICKNESS(style) * 2; + + gtk_paint_focus(style, drawable, + /* Believe it or not, NORMAL means a selected tab and + ACTIVE means an unselected tab. */ + (flags & MOZ_GTK_TAB_SELECTED) ? GTK_STATE_NORMAL + : GTK_STATE_ACTIVE, + cliprect, gTabWidget, "tab", + focusRect.x, focusRect.y, focusRect.width, focusRect.height); + } + + return MOZ_GTK_SUCCESS; +} + +static gint +moz_gtk_tabpanels_paint(GdkDrawable* drawable, GdkRectangle* rect, + GdkRectangle* cliprect, GtkTextDirection direction) +{ + /* We have three problems here: + * - Most engines draw gtk_paint_box differently to gtk_paint_box_gap, the + * former implies there are no tabs, eg. Clearlooks. + * - Wanting a gap of width 0 doesn't actually guarantee a zero-width gap, eg. + * Clearlooks. + * - Our old approach of a negative X position could cause rendering errors + * on the box's corner, eg. themes using the Pixbuf engine. + */ + GtkStyle* style; + GdkRectangle halfClipRect; + + ensure_tab_widget(); + gtk_widget_set_direction(gTabWidget, direction); + + style = gTabWidget->style; + TSOffsetStyleGCs(style, rect->x, rect->y); + + /* Our approach is as follows: + * - Draw the box in two passes. Pass in a clip rect to draw the left half of the + * box, with the gap specified to the right outside the clip rect so that it is + * not drawn. + * - The right half is drawn with the gap to the left outside the modified clip rect. + */ + if (!gdk_rectangle_intersect(rect, cliprect, &halfClipRect)) + return MOZ_GTK_SUCCESS; + + halfClipRect.width = (halfClipRect.width / 2) + 1; + gtk_paint_box_gap(style, drawable, GTK_STATE_NORMAL, GTK_SHADOW_OUT, + &halfClipRect, gTabWidget, "notebook", rect->x, rect->y, + rect->width, rect->height, + GTK_POS_TOP, halfClipRect.width + 1, 0); + + halfClipRect.x += halfClipRect.width; + gtk_paint_box_gap(style, drawable, GTK_STATE_NORMAL, GTK_SHADOW_OUT, + &halfClipRect, gTabWidget, "notebook", rect->x, rect->y, + rect->width, rect->height, + GTK_POS_TOP, -10, 0); + + return MOZ_GTK_SUCCESS; +} + +static gint +moz_gtk_tab_scroll_arrow_paint(GdkDrawable* drawable, GdkRectangle* rect, + GdkRectangle* cliprect, GtkWidgetState* state, + GtkArrowType arrow_type, + GtkTextDirection direction) +{ + GtkStateType state_type = ConvertGtkState(state); + GtkShadowType shadow_type = state->active ? GTK_SHADOW_IN : GTK_SHADOW_OUT; + GtkStyle* style; + gint arrow_size = MIN(rect->width, rect->height); + gint x = rect->x + (rect->width - arrow_size) / 2; + gint y = rect->y + (rect->height - arrow_size) / 2; + + ensure_tab_widget(); + + style = gTabWidget->style; + TSOffsetStyleGCs(style, rect->x, rect->y); + + if (direction == GTK_TEXT_DIR_RTL) { + arrow_type = (arrow_type == GTK_ARROW_LEFT) ? + GTK_ARROW_RIGHT : GTK_ARROW_LEFT; + } + + gtk_paint_arrow(style, drawable, state_type, shadow_type, NULL, + gTabWidget, "notebook", arrow_type, TRUE, + x, y, arrow_size, arrow_size); + + return MOZ_GTK_SUCCESS; +} + +static gint +moz_gtk_menu_bar_paint(GdkDrawable* drawable, GdkRectangle* rect, + GdkRectangle* cliprect, GtkTextDirection direction) +{ + GtkStyle* style; + GtkShadowType shadow_type; + ensure_menu_bar_widget(); + gtk_widget_set_direction(gMenuBarWidget, direction); + + gtk_widget_style_get(gMenuBarWidget, "shadow-type", &shadow_type, NULL); + + style = gMenuBarWidget->style; + + TSOffsetStyleGCs(style, rect->x, rect->y); + gtk_style_apply_default_background(style, drawable, TRUE, GTK_STATE_NORMAL, + cliprect, rect->x, rect->y, + rect->width, rect->height); + + gtk_paint_box(style, drawable, GTK_STATE_NORMAL, shadow_type, + cliprect, gMenuBarWidget, "menubar", rect->x, rect->y, + rect->width, rect->height); + return MOZ_GTK_SUCCESS; +} + +static gint +moz_gtk_menu_popup_paint(GdkDrawable* drawable, GdkRectangle* rect, + GdkRectangle* cliprect, GtkTextDirection direction) +{ + GtkStyle* style; + ensure_menu_popup_widget(); + gtk_widget_set_direction(gMenuPopupWidget, direction); + + style = gMenuPopupWidget->style; + + TSOffsetStyleGCs(style, rect->x, rect->y); + gtk_style_apply_default_background(style, drawable, TRUE, GTK_STATE_NORMAL, + cliprect, rect->x, rect->y, + rect->width, rect->height); + gtk_paint_box(style, drawable, GTK_STATE_NORMAL, GTK_SHADOW_OUT, + cliprect, gMenuPopupWidget, "menu", + rect->x, rect->y, rect->width, rect->height); + + return MOZ_GTK_SUCCESS; +} + +static gint +moz_gtk_menu_separator_paint(GdkDrawable* drawable, GdkRectangle* rect, + GdkRectangle* cliprect, GtkTextDirection direction) +{ + GtkStyle* style; + gboolean wide_separators; + gint separator_height; + guint horizontal_padding; + gint paint_height; + + ensure_menu_separator_widget(); + gtk_widget_set_direction(gMenuSeparatorWidget, direction); + + style = gMenuSeparatorWidget->style; + + gtk_widget_style_get(gMenuSeparatorWidget, + "wide-separators", &wide_separators, + "separator-height", &separator_height, + "horizontal-padding", &horizontal_padding, + NULL); + + TSOffsetStyleGCs(style, rect->x, rect->y); + + if (wide_separators) { + if (separator_height > rect->height) + separator_height = rect->height; + + gtk_paint_box(style, drawable, + GTK_STATE_NORMAL, GTK_SHADOW_ETCHED_OUT, + cliprect, gMenuSeparatorWidget, "hseparator", + rect->x + horizontal_padding + style->xthickness, + rect->y + (rect->height - separator_height - style->ythickness) / 2, + rect->width - 2 * (horizontal_padding + style->xthickness), + separator_height); + } else { + paint_height = style->ythickness; + if (paint_height > rect->height) + paint_height = rect->height; + + gtk_paint_hline(style, drawable, + GTK_STATE_NORMAL, cliprect, gMenuSeparatorWidget, + "menuitem", + rect->x + horizontal_padding + style->xthickness, + rect->x + rect->width - horizontal_padding - style->xthickness - 1, + rect->y + (rect->height - style->ythickness) / 2); + } + + return MOZ_GTK_SUCCESS; +} + +static gint +moz_gtk_menu_item_paint(WidgetNodeType widget, GdkDrawable* drawable, + GdkRectangle* rect, GdkRectangle* cliprect, + GtkWidgetState* state, GtkTextDirection direction) +{ + GtkStyle* style; + GtkShadowType shadow_type; + GtkWidget* item_widget; + + if (state->inHover && !state->disabled) { + if (widget == MOZ_GTK_MENUBARITEM) { + ensure_menu_bar_item_widget(); + item_widget = gMenuBarItemWidget; + } else { + ensure_menu_item_widget(); + item_widget = gMenuItemWidget; + } + gtk_widget_set_direction(item_widget, direction); + + style = item_widget->style; + TSOffsetStyleGCs(style, rect->x, rect->y); + + gtk_widget_style_get(item_widget, "selected-shadow-type", + &shadow_type, NULL); + + gtk_paint_box(style, drawable, GTK_STATE_PRELIGHT, shadow_type, + cliprect, item_widget, "menuitem", rect->x, rect->y, + rect->width, rect->height); + } + + return MOZ_GTK_SUCCESS; +} + +static gint +moz_gtk_menu_arrow_paint(GdkDrawable* drawable, GdkRectangle* rect, + GdkRectangle* cliprect, GtkWidgetState* state, + GtkTextDirection direction) +{ + GtkStyle* style; + GtkStateType state_type = ConvertGtkState(state); + + ensure_menu_item_widget(); + gtk_widget_set_direction(gMenuItemWidget, direction); + + style = gMenuItemWidget->style; + + TSOffsetStyleGCs(style, rect->x, rect->y); + gtk_paint_arrow(style, drawable, state_type, + (state_type == GTK_STATE_PRELIGHT) ? GTK_SHADOW_IN : GTK_SHADOW_OUT, + cliprect, gMenuItemWidget, "menuitem", + (direction == GTK_TEXT_DIR_LTR) ? GTK_ARROW_RIGHT : GTK_ARROW_LEFT, + TRUE, rect->x, rect->y, rect->width, rect->height); + + return MOZ_GTK_SUCCESS; +} + +static gint +moz_gtk_check_menu_item_paint(GdkDrawable* drawable, GdkRectangle* rect, + GdkRectangle* cliprect, GtkWidgetState* state, + gboolean checked, gboolean isradio, + GtkTextDirection direction) +{ + GtkStateType state_type = ConvertGtkState(state); + GtkStyle* style; + GtkShadowType shadow_type = (checked)?GTK_SHADOW_IN:GTK_SHADOW_OUT; + gint offset; + gint indicator_size, horizontal_padding; + gint x, y; + + moz_gtk_menu_item_paint(MOZ_GTK_MENUITEM, drawable, rect, cliprect, state, + direction); + + ensure_check_menu_item_widget(); + gtk_widget_set_direction(gCheckMenuItemWidget, direction); + + gtk_widget_style_get (gCheckMenuItemWidget, + "indicator-size", &indicator_size, + "horizontal-padding", &horizontal_padding, + NULL); + + if (checked || GTK_CHECK_MENU_ITEM(gCheckMenuItemWidget)->always_show_toggle) { + style = gCheckMenuItemWidget->style; + + offset = GTK_CONTAINER(gCheckMenuItemWidget)->border_width + + gCheckMenuItemWidget->style->xthickness + 2; + + x = (direction == GTK_TEXT_DIR_RTL) ? + rect->width - indicator_size - offset - horizontal_padding: rect->x + offset + horizontal_padding; + y = rect->y + (rect->height - indicator_size) / 2; + + TSOffsetStyleGCs(style, x, y); + gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(gCheckMenuItemWidget), + checked); + + if (isradio) { + gtk_paint_option(style, drawable, state_type, shadow_type, cliprect, + gCheckMenuItemWidget, "option", + x, y, indicator_size, indicator_size); + } else { + gtk_paint_check(style, drawable, state_type, shadow_type, cliprect, + gCheckMenuItemWidget, "check", + x, y, indicator_size, indicator_size); + } + } + + return MOZ_GTK_SUCCESS; +} + +static gint +moz_gtk_window_paint(GdkDrawable* drawable, GdkRectangle* rect, + GdkRectangle* cliprect, GtkTextDirection direction) +{ + GtkStyle* style; + + ensure_window_widget(); + gtk_widget_set_direction(gProtoWindow, direction); + + style = gProtoWindow->style; + + TSOffsetStyleGCs(style, rect->x, rect->y); + gtk_style_apply_default_background(style, drawable, TRUE, + GTK_STATE_NORMAL, + cliprect, rect->x, rect->y, + rect->width, rect->height); + return MOZ_GTK_SUCCESS; +} + +gint +moz_gtk_get_widget_border(WidgetNodeType widget, gint* left, gint* top, + gint* right, gint* bottom, GtkTextDirection direction, + gboolean inhtml) +{ + GtkWidget* w; + + switch (widget) { + case MOZ_GTK_BUTTON: + case MOZ_GTK_TOOLBAR_BUTTON: + { + GtkBorder inner_border; + gboolean interior_focus; + gint focus_width, focus_pad; + + ensure_button_widget(); + *left = *top = *right = *bottom = GTK_CONTAINER(gButtonWidget)->border_width; + + /* Don't add this padding in HTML, otherwise the buttons will + become too big and stuff the layout. */ + if (!inhtml) { + moz_gtk_widget_get_focus(gButtonWidget, &interior_focus, &focus_width, &focus_pad); + moz_gtk_button_get_inner_border(gButtonWidget, &inner_border); + *left += focus_width + focus_pad + inner_border.left; + *right += focus_width + focus_pad + inner_border.right; + *top += focus_width + focus_pad + inner_border.top; + *bottom += focus_width + focus_pad + inner_border.bottom; + } + + *left += gButtonWidget->style->xthickness; + *right += gButtonWidget->style->xthickness; + *top += gButtonWidget->style->ythickness; + *bottom += gButtonWidget->style->ythickness; + return MOZ_GTK_SUCCESS; + } + case MOZ_GTK_ENTRY: + ensure_entry_widget(); + w = gEntryWidget; + break; + case MOZ_GTK_TREEVIEW: + ensure_tree_view_widget(); + w = gTreeViewWidget; + break; + case MOZ_GTK_TREE_HEADER_CELL: + { + /* A Tree Header in GTK is just a different styled button + * It must be placed in a TreeView for getting the correct style + * assigned. + * That is why the following code is the same as for MOZ_GTK_BUTTON. + * */ + + GtkBorder inner_border; + gboolean interior_focus; + gint focus_width, focus_pad; + + ensure_tree_header_cell_widget(); + *left = *top = *right = *bottom = GTK_CONTAINER(gTreeHeaderCellWidget)->border_width; + + moz_gtk_widget_get_focus(gTreeHeaderCellWidget, &interior_focus, &focus_width, &focus_pad); + moz_gtk_button_get_inner_border(gTreeHeaderCellWidget, &inner_border); + *left += focus_width + focus_pad + inner_border.left; + *right += focus_width + focus_pad + inner_border.right; + *top += focus_width + focus_pad + inner_border.top; + *bottom += focus_width + focus_pad + inner_border.bottom; + + *left += gTreeHeaderCellWidget->style->xthickness; + *right += gTreeHeaderCellWidget->style->xthickness; + *top += gTreeHeaderCellWidget->style->ythickness; + *bottom += gTreeHeaderCellWidget->style->ythickness; + return MOZ_GTK_SUCCESS; + } + case MOZ_GTK_TREE_HEADER_SORTARROW: + ensure_tree_header_cell_widget(); + w = gTreeHeaderSortArrowWidget; + break; + case MOZ_GTK_DROPDOWN_ENTRY: + ensure_combo_box_entry_widgets(); + w = gComboBoxEntryTextareaWidget; + break; + case MOZ_GTK_DROPDOWN_ARROW: + ensure_combo_box_entry_widgets(); + w = gComboBoxEntryButtonWidget; + break; + case MOZ_GTK_DROPDOWN: + { + /* We need to account for the arrow on the dropdown, so text + * doesn't come too close to the arrow, or in some cases spill + * into the arrow. */ + gboolean ignored_interior_focus, wide_separators; + gint focus_width, focus_pad, separator_width; + GtkRequisition arrow_req; + + ensure_combo_box_widgets(); + + *left = GTK_CONTAINER(gComboBoxButtonWidget)->border_width; + + if (!inhtml) { + moz_gtk_widget_get_focus(gComboBoxButtonWidget, + &ignored_interior_focus, + &focus_width, &focus_pad); + *left += focus_width + focus_pad; + } + + *top = *left + gComboBoxButtonWidget->style->ythickness; + *left += gComboBoxButtonWidget->style->xthickness; + + *right = *left; *bottom = *top; + + /* If there is no separator, don't try to count its width. */ + separator_width = 0; + if (gComboBoxSeparatorWidget) { + gtk_widget_style_get(gComboBoxSeparatorWidget, + "wide-separators", &wide_separators, + "separator-width", &separator_width, + NULL); + + if (!wide_separators) + separator_width = + XTHICKNESS(gComboBoxSeparatorWidget->style); + } + + gtk_widget_size_request(gComboBoxArrowWidget, &arrow_req); + + if (direction == GTK_TEXT_DIR_RTL) + *left += separator_width + arrow_req.width; + else + *right += separator_width + arrow_req.width; + + return MOZ_GTK_SUCCESS; + } + case MOZ_GTK_TABPANELS: + ensure_tab_widget(); + w = gTabWidget; + break; + case MOZ_GTK_PROGRESSBAR: + ensure_progress_widget(); + w = gProgressWidget; + break; + case MOZ_GTK_SPINBUTTON_ENTRY: + case MOZ_GTK_SPINBUTTON_UP: + case MOZ_GTK_SPINBUTTON_DOWN: + ensure_spin_widget(); + w = gSpinWidget; + break; + case MOZ_GTK_SCALE_HORIZONTAL: + ensure_scale_widget(); + w = gHScaleWidget; + break; + case MOZ_GTK_SCALE_VERTICAL: + ensure_scale_widget(); + w = gVScaleWidget; + break; + case MOZ_GTK_FRAME: + ensure_frame_widget(); + w = gFrameWidget; + break; + case MOZ_GTK_CHECKBUTTON_LABEL: + case MOZ_GTK_RADIOBUTTON_LABEL: + { + gboolean interior_focus; + gint focus_width, focus_pad; + + /* If the focus is interior, then the label has a border of + (focus_width + focus_pad). */ + if (widget == MOZ_GTK_CHECKBUTTON_LABEL) { + ensure_checkbox_widget(); + moz_gtk_widget_get_focus(gCheckboxWidget, &interior_focus, + &focus_width, &focus_pad); + } + else { + ensure_radiobutton_widget(); + moz_gtk_widget_get_focus(gRadiobuttonWidget, &interior_focus, + &focus_width, &focus_pad); + } + + if (interior_focus) + *left = *top = *right = *bottom = (focus_width + focus_pad); + else + *left = *top = *right = *bottom = 0; + + return MOZ_GTK_SUCCESS; + } + + case MOZ_GTK_CHECKBUTTON_CONTAINER: + case MOZ_GTK_RADIOBUTTON_CONTAINER: + { + gboolean interior_focus; + gint focus_width, focus_pad; + + /* If the focus is _not_ interior, then the container has a border + of (focus_width + focus_pad). */ + if (widget == MOZ_GTK_CHECKBUTTON_CONTAINER) { + ensure_checkbox_widget(); + moz_gtk_widget_get_focus(gCheckboxWidget, &interior_focus, + &focus_width, &focus_pad); + w = gCheckboxWidget; + } else { + ensure_radiobutton_widget(); + moz_gtk_widget_get_focus(gRadiobuttonWidget, &interior_focus, + &focus_width, &focus_pad); + w = gRadiobuttonWidget; + } + + *left = *top = *right = *bottom = GTK_CONTAINER(w)->border_width; + + if (!interior_focus) { + *left += (focus_width + focus_pad); + *right += (focus_width + focus_pad); + *top += (focus_width + focus_pad); + *bottom += (focus_width + focus_pad); + } + + return MOZ_GTK_SUCCESS; + } + case MOZ_GTK_MENUPOPUP: + ensure_menu_popup_widget(); + w = gMenuPopupWidget; + break; + case MOZ_GTK_MENUBARITEM: + // Bug 1274143 for MOZ_GTK_MENUBARITEM. + // Fall through to MOZ_GTK_MENUITEM for now. + case MOZ_GTK_MENUITEM: + ensure_menu_item_widget(); + ensure_menu_bar_item_widget(); + w = gMenuItemWidget; + break; + case MOZ_GTK_CHECKMENUITEM: + case MOZ_GTK_RADIOMENUITEM: + ensure_check_menu_item_widget(); + w = gCheckMenuItemWidget; + break; + case MOZ_GTK_TAB_TOP: + case MOZ_GTK_TAB_BOTTOM: + ensure_tab_widget(); + w = gTabWidget; + break; + case MOZ_GTK_TOOLTIP: + // In GTK 2 the spacing between box is set to 4. + *left = *top = *right = *bottom = 4; + return MOZ_GTK_SUCCESS; + /* These widgets have no borders, since they are not containers. */ + case MOZ_GTK_SPLITTER_HORIZONTAL: + case MOZ_GTK_SPLITTER_VERTICAL: + case MOZ_GTK_CHECKBUTTON: + case MOZ_GTK_RADIOBUTTON: + case MOZ_GTK_SCROLLBAR_BUTTON: + case MOZ_GTK_SCROLLBAR_HORIZONTAL: + case MOZ_GTK_SCROLLBAR_TROUGH_HORIZONTAL: + case MOZ_GTK_SCROLLBAR_VERTICAL: + case MOZ_GTK_SCROLLBAR_TROUGH_VERTICAL: + case MOZ_GTK_SCROLLBAR_THUMB_HORIZONTAL: + case MOZ_GTK_SCROLLBAR_THUMB_VERTICAL: + case MOZ_GTK_SCALE_THUMB_HORIZONTAL: + case MOZ_GTK_SCALE_THUMB_VERTICAL: + case MOZ_GTK_GRIPPER: + case MOZ_GTK_PROGRESS_CHUNK: + case MOZ_GTK_PROGRESS_CHUNK_INDETERMINATE: + case MOZ_GTK_PROGRESS_CHUNK_VERTICAL_INDETERMINATE: + case MOZ_GTK_TREEVIEW_EXPANDER: + case MOZ_GTK_TOOLBAR_SEPARATOR: + case MOZ_GTK_MENUSEPARATOR: + /* These widgets have no borders.*/ + case MOZ_GTK_SPINBUTTON: + case MOZ_GTK_WINDOW: + case MOZ_GTK_RESIZER: + case MOZ_GTK_MENUARROW: + case MOZ_GTK_TOOLBARBUTTON_ARROW: + case MOZ_GTK_TOOLBAR: + case MOZ_GTK_MENUBAR: + case MOZ_GTK_TAB_SCROLLARROW: + *left = *top = *right = *bottom = 0; + return MOZ_GTK_SUCCESS; + default: + g_warning("Unsupported widget type: %d", widget); + return MOZ_GTK_UNKNOWN_WIDGET; + } + + *right = *left = XTHICKNESS(w->style); + *bottom = *top = YTHICKNESS(w->style); + + return MOZ_GTK_SUCCESS; +} + +gint +moz_gtk_get_tab_border(gint* left, gint* top, gint* right, gint* bottom, + GtkTextDirection direction, GtkTabFlags flags, + WidgetNodeType widget) +{ + moz_gtk_get_widget_border(widget, left, top, + right, bottom, direction, + FALSE); + + // Top tabs have no bottom border, bottom tabs have no top border + if (widget == MOZ_GTK_TAB_BOTTOM) { + *top = 0; + } else { + *bottom = 0; + } + + return MOZ_GTK_SUCCESS; +} + +gint +moz_gtk_get_combo_box_entry_button_size(gint* width, gint* height) +{ + /* + * We get the requisition of the drop down button, which includes + * all padding, border and focus line widths the button uses, + * as well as the minimum arrow size and its padding + * */ + GtkRequisition requisition; + ensure_combo_box_entry_widgets(); + + gtk_widget_size_request(gComboBoxEntryButtonWidget, &requisition); + *width = requisition.width; + *height = requisition.height; + + return MOZ_GTK_SUCCESS; +} + +gint +moz_gtk_get_tab_scroll_arrow_size(gint* width, gint* height) +{ + gint arrow_size; + + ensure_tab_widget(); + gtk_widget_style_get(gTabWidget, + "scroll-arrow-hlength", &arrow_size, + NULL); + + *height = *width = arrow_size; + + return MOZ_GTK_SUCCESS; +} + +void +moz_gtk_get_arrow_size(WidgetNodeType widgetType, gint* width, gint* height) +{ + GtkWidget* widget; + switch (widgetType) { + case MOZ_GTK_DROPDOWN: + ensure_combo_box_widgets(); + widget = gComboBoxArrowWidget; + break; + default: + ensure_button_arrow_widget(); + widget = gButtonArrowWidget; + break; + } + + GtkRequisition requisition; + gtk_widget_size_request(widget, &requisition); + *width = requisition.width; + *height = requisition.height; +} + +gint +moz_gtk_get_toolbar_separator_width(gint* size) +{ + gboolean wide_separators; + gint separator_width; + GtkStyle* style; + + ensure_toolbar_widget(); + + style = gToolbarWidget->style; + + gtk_widget_style_get(gToolbarWidget, + "space-size", size, + "wide-separators", &wide_separators, + "separator-width", &separator_width, + NULL); + + /* Just in case... */ + *size = MAX(*size, (wide_separators ? separator_width : style->xthickness)); + + return MOZ_GTK_SUCCESS; +} + +gint +moz_gtk_get_expander_size(gint* size) +{ + ensure_expander_widget(); + gtk_widget_style_get(gExpanderWidget, + "expander-size", size, + NULL); + + return MOZ_GTK_SUCCESS; +} + +gint +moz_gtk_get_treeview_expander_size(gint* size) +{ + ensure_tree_view_widget(); + gtk_widget_style_get(gTreeViewWidget, + "expander-size", size, + NULL); + + return MOZ_GTK_SUCCESS; +} + +gint +moz_gtk_get_menu_separator_height(gint *size) +{ + gboolean wide_separators; + gint separator_height; + + ensure_menu_separator_widget(); + + gtk_widget_style_get(gMenuSeparatorWidget, + "wide-separators", &wide_separators, + "separator-height", &separator_height, + NULL); + + if (wide_separators) + *size = separator_height + gMenuSeparatorWidget->style->ythickness; + else + *size = gMenuSeparatorWidget->style->ythickness * 2; + + return MOZ_GTK_SUCCESS; +} + +void +moz_gtk_get_scale_metrics(GtkOrientation orient, gint* scale_width, + gint* scale_height) +{ + moz_gtk_get_scalethumb_metrics(orient, scale_width, scale_height); +} + + +gint +moz_gtk_get_scalethumb_metrics(GtkOrientation orient, gint* thumb_length, gint* thumb_height) +{ + GtkWidget* widget; + + ensure_scale_widget(); + widget = ((orient == GTK_ORIENTATION_HORIZONTAL) ? gHScaleWidget : gVScaleWidget); + + gtk_widget_style_get (widget, + "slider_length", thumb_length, + "slider_width", thumb_height, + NULL); + + return MOZ_GTK_SUCCESS; +} + +gint +moz_gtk_get_scrollbar_metrics(MozGtkScrollbarMetrics *metrics) +{ + ensure_scrollbar_widget(); + + gtk_widget_style_get (gHorizScrollbarWidget, + "slider_width", &metrics->slider_width, + "trough_border", &metrics->trough_border, + "stepper_size", &metrics->stepper_size, + "stepper_spacing", &metrics->stepper_spacing, + NULL); + + metrics->min_slider_size = + GTK_RANGE(gHorizScrollbarWidget)->min_slider_size; + + return MOZ_GTK_SUCCESS; +} +void +moz_gtk_get_widget_min_size(WidgetNodeType aGtkWidgetType, int* width, int* height) { + MOZ_ASSERT_UNREACHABLE("get_widget_min_size not available for GTK2"); +} + +gint +moz_gtk_widget_paint(WidgetNodeType widget, GdkDrawable* drawable, + GdkRectangle* rect, GdkRectangle* cliprect, + GtkWidgetState* state, gint flags, + GtkTextDirection direction) +{ + switch (widget) { + case MOZ_GTK_BUTTON: + case MOZ_GTK_TOOLBAR_BUTTON: + if (state->depressed) { + ensure_toggle_button_widget(); + return moz_gtk_button_paint(drawable, rect, cliprect, state, + (GtkReliefStyle) flags, + gToggleButtonWidget, direction); + } + ensure_button_widget(); + return moz_gtk_button_paint(drawable, rect, cliprect, state, + (GtkReliefStyle) flags, gButtonWidget, + direction); + break; + case MOZ_GTK_CHECKBUTTON: + case MOZ_GTK_RADIOBUTTON: + return moz_gtk_toggle_paint(drawable, rect, cliprect, state, + !!(flags & MOZ_GTK_WIDGET_CHECKED), + !!(flags & MOZ_GTK_WIDGET_INCONSISTENT), + (widget == MOZ_GTK_RADIOBUTTON), + direction); + break; + case MOZ_GTK_SCROLLBAR_BUTTON: + return moz_gtk_scrollbar_button_paint(drawable, rect, cliprect, state, + (GtkScrollbarButtonFlags) flags, + direction); + break; + case MOZ_GTK_SCROLLBAR_HORIZONTAL: + case MOZ_GTK_SCROLLBAR_VERTICAL: + return moz_gtk_scrollbar_trough_paint(widget, drawable, rect, + cliprect, state, direction); + break; + case MOZ_GTK_SCROLLBAR_TROUGH_HORIZONTAL: + case MOZ_GTK_SCROLLBAR_TROUGH_VERTICAL: + return MOZ_GTK_SUCCESS; + break; + case MOZ_GTK_SCROLLBAR_THUMB_HORIZONTAL: + case MOZ_GTK_SCROLLBAR_THUMB_VERTICAL: + return moz_gtk_scrollbar_thumb_paint(widget, drawable, rect, + cliprect, state, direction); + break; + case MOZ_GTK_SCALE_HORIZONTAL: + case MOZ_GTK_SCALE_VERTICAL: + return moz_gtk_scale_paint(drawable, rect, cliprect, state, + (GtkOrientation) flags, direction); + break; + case MOZ_GTK_SCALE_THUMB_HORIZONTAL: + case MOZ_GTK_SCALE_THUMB_VERTICAL: + return moz_gtk_scale_thumb_paint(drawable, rect, cliprect, state, + (GtkOrientation) flags, direction); + break; + case MOZ_GTK_SPINBUTTON: + return moz_gtk_spin_paint(drawable, rect, direction); + break; + case MOZ_GTK_SPINBUTTON_UP: + case MOZ_GTK_SPINBUTTON_DOWN: + return moz_gtk_spin_updown_paint(drawable, rect, + (widget == MOZ_GTK_SPINBUTTON_DOWN), + state, direction); + break; + case MOZ_GTK_SPINBUTTON_ENTRY: + ensure_spin_widget(); + return moz_gtk_entry_paint(drawable, rect, cliprect, state, + gSpinWidget, direction); + break; + case MOZ_GTK_GRIPPER: + return moz_gtk_gripper_paint(drawable, rect, cliprect, state, + direction); + break; + case MOZ_GTK_TREEVIEW: + return moz_gtk_treeview_paint(drawable, rect, cliprect, state, + direction); + break; + case MOZ_GTK_TREE_HEADER_CELL: + return moz_gtk_tree_header_cell_paint(drawable, rect, cliprect, state, + flags, direction); + break; + case MOZ_GTK_TREE_HEADER_SORTARROW: + return moz_gtk_tree_header_sort_arrow_paint(drawable, rect, cliprect, + state, + (GtkArrowType) flags, + direction); + break; + case MOZ_GTK_TREEVIEW_EXPANDER: + return moz_gtk_treeview_expander_paint(drawable, rect, cliprect, state, + (GtkExpanderStyle) flags, direction); + break; + case MOZ_GTK_ENTRY: + ensure_entry_widget(); + return moz_gtk_entry_paint(drawable, rect, cliprect, state, + gEntryWidget, direction); + break; + case MOZ_GTK_DROPDOWN: + return moz_gtk_combo_box_paint(drawable, rect, cliprect, state, + (gboolean) flags, direction); + break; + case MOZ_GTK_DROPDOWN_ARROW: + return moz_gtk_combo_box_entry_button_paint(drawable, rect, cliprect, + state, flags, direction); + break; + case MOZ_GTK_DROPDOWN_ENTRY: + ensure_combo_box_entry_widgets(); + return moz_gtk_entry_paint(drawable, rect, cliprect, state, + gComboBoxEntryTextareaWidget, direction); + break; + case MOZ_GTK_CHECKBUTTON_CONTAINER: + case MOZ_GTK_RADIOBUTTON_CONTAINER: + return moz_gtk_container_paint(drawable, rect, cliprect, state, + (widget == MOZ_GTK_RADIOBUTTON_CONTAINER), + direction); + break; + case MOZ_GTK_CHECKBUTTON_LABEL: + case MOZ_GTK_RADIOBUTTON_LABEL: + return moz_gtk_toggle_label_paint(drawable, rect, cliprect, state, + (widget == MOZ_GTK_RADIOBUTTON_LABEL), + direction); + break; + case MOZ_GTK_TOOLBAR: + return moz_gtk_toolbar_paint(drawable, rect, cliprect, direction); + break; + case MOZ_GTK_TOOLBAR_SEPARATOR: + return moz_gtk_toolbar_separator_paint(drawable, rect, cliprect, + direction); + break; + case MOZ_GTK_TOOLTIP: + return moz_gtk_tooltip_paint(drawable, rect, cliprect, direction); + break; + case MOZ_GTK_FRAME: + return moz_gtk_frame_paint(drawable, rect, cliprect, direction); + break; + case MOZ_GTK_RESIZER: + return moz_gtk_resizer_paint(drawable, rect, cliprect, state, + direction); + break; + case MOZ_GTK_PROGRESSBAR: + return moz_gtk_progressbar_paint(drawable, rect, cliprect, direction); + break; + case MOZ_GTK_PROGRESS_CHUNK: + case MOZ_GTK_PROGRESS_CHUNK_INDETERMINATE: + case MOZ_GTK_PROGRESS_CHUNK_VERTICAL_INDETERMINATE: + return moz_gtk_progress_chunk_paint(drawable, rect, cliprect, + direction, widget); + break; + case MOZ_GTK_TAB_TOP: + case MOZ_GTK_TAB_BOTTOM: + return moz_gtk_tab_paint(drawable, rect, cliprect, state, + (GtkTabFlags) flags, direction, widget); + break; + case MOZ_GTK_TABPANELS: + return moz_gtk_tabpanels_paint(drawable, rect, cliprect, direction); + break; + case MOZ_GTK_TAB_SCROLLARROW: + return moz_gtk_tab_scroll_arrow_paint(drawable, rect, cliprect, state, + (GtkArrowType) flags, direction); + break; + case MOZ_GTK_MENUBAR: + return moz_gtk_menu_bar_paint(drawable, rect, cliprect, direction); + break; + case MOZ_GTK_MENUPOPUP: + return moz_gtk_menu_popup_paint(drawable, rect, cliprect, direction); + break; + case MOZ_GTK_MENUSEPARATOR: + return moz_gtk_menu_separator_paint(drawable, rect, cliprect, + direction); + break; + case MOZ_GTK_MENUBARITEM: + case MOZ_GTK_MENUITEM: + return moz_gtk_menu_item_paint(widget, drawable, rect, cliprect, state, + direction); + break; + case MOZ_GTK_MENUARROW: + return moz_gtk_menu_arrow_paint(drawable, rect, cliprect, state, + direction); + break; + case MOZ_GTK_TOOLBARBUTTON_ARROW: + return moz_gtk_arrow_paint(drawable, rect, cliprect, state, + (GtkArrowType) flags, direction); + break; + case MOZ_GTK_CHECKMENUITEM: + case MOZ_GTK_RADIOMENUITEM: + return moz_gtk_check_menu_item_paint(drawable, rect, cliprect, state, + (gboolean) flags, + (widget == MOZ_GTK_RADIOMENUITEM), + direction); + break; + case MOZ_GTK_SPLITTER_HORIZONTAL: + return moz_gtk_vpaned_paint(drawable, rect, cliprect, state); + break; + case MOZ_GTK_SPLITTER_VERTICAL: + return moz_gtk_hpaned_paint(drawable, rect, cliprect, state); + break; + case MOZ_GTK_WINDOW: + return moz_gtk_window_paint(drawable, rect, cliprect, direction); + break; + default: + g_warning("Unknown widget type: %d", widget); + } + + return MOZ_GTK_UNKNOWN_WIDGET; +} + +GtkWidget* moz_gtk_get_scrollbar_widget(void) +{ + MOZ_ASSERT(is_initialized, "Forgot to call moz_gtk_init()"); + ensure_scrollbar_widget(); + return gHorizScrollbarWidget; +} + +gboolean moz_gtk_has_scrollbar_buttons(void) +{ + gboolean backward, forward, secondary_backward, secondary_forward; + MOZ_ASSERT(is_initialized, "Forgot to call moz_gtk_init()"); + ensure_scrollbar_widget(); + gtk_widget_style_get (gHorizScrollbarWidget, + "has-backward-stepper", &backward, + "has-forward-stepper", &forward, + "has-secondary-backward-stepper", &secondary_backward, + "has-secondary-forward-stepper", &secondary_forward, + NULL); + return backward | forward | secondary_forward | secondary_forward; +} + +gint +moz_gtk_shutdown() +{ + GtkWidgetClass *entry_class; + + if (gTooltipWidget) + gtk_widget_destroy(gTooltipWidget); + /* This will destroy all of our widgets */ + if (gProtoWindow) + gtk_widget_destroy(gProtoWindow); + + gProtoWindow = NULL; + gProtoLayout = NULL; + gButtonWidget = NULL; + gToggleButtonWidget = NULL; + gButtonArrowWidget = NULL; + gCheckboxWidget = NULL; + gRadiobuttonWidget = NULL; + gHorizScrollbarWidget = NULL; + gVertScrollbarWidget = NULL; + gSpinWidget = NULL; + gHScaleWidget = NULL; + gVScaleWidget = NULL; + gEntryWidget = NULL; + gComboBoxWidget = NULL; + gComboBoxButtonWidget = NULL; + gComboBoxSeparatorWidget = NULL; + gComboBoxArrowWidget = NULL; + gComboBoxEntryWidget = NULL; + gComboBoxEntryButtonWidget = NULL; + gComboBoxEntryArrowWidget = NULL; + gComboBoxEntryTextareaWidget = NULL; + gHandleBoxWidget = NULL; + gToolbarWidget = NULL; + gStatusbarWidget = NULL; + gFrameWidget = NULL; + gProgressWidget = NULL; + gTabWidget = NULL; + gTooltipWidget = NULL; + gMenuBarWidget = NULL; + gMenuBarItemWidget = NULL; + gMenuPopupWidget = NULL; + gMenuItemWidget = NULL; + gImageMenuItemWidget = NULL; + gCheckMenuItemWidget = NULL; + gTreeViewWidget = NULL; + gMiddleTreeViewColumn = NULL; + gTreeHeaderCellWidget = NULL; + gTreeHeaderSortArrowWidget = NULL; + gExpanderWidget = NULL; + gToolbarSeparatorWidget = NULL; + gMenuSeparatorWidget = NULL; + gHPanedWidget = NULL; + gVPanedWidget = NULL; + gScrolledWindowWidget = NULL; + + entry_class = g_type_class_peek(GTK_TYPE_ENTRY); + g_type_class_unref(entry_class); + + is_initialized = FALSE; + + return MOZ_GTK_SUCCESS; +} diff --git a/widget/gtk/gtk3drawing.cpp b/widget/gtk/gtk3drawing.cpp new file mode 100644 index 000000000..fb95b4cc4 --- /dev/null +++ b/widget/gtk/gtk3drawing.cpp @@ -0,0 +1,2843 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +/* + * This file contains painting functions for each of the gtk2 widgets. + * Adapted from the gtkdrawing.c, and gtk+2.0 source. + */ + +#include <gtk/gtk.h> +#include <gdk/gdkprivate.h> +#include <string.h> +#include "gtkdrawing.h" +#include "mozilla/Assertions.h" +#include "prinrval.h" +#include "WidgetStyleCache.h" + +#include <math.h> + +static style_prop_t style_prop_func; +static gboolean have_arrow_scaling; +static gboolean checkbox_check_state; +static gboolean notebook_has_tab_gap; +static gboolean is_initialized; + +#define ARROW_UP 0 +#define ARROW_DOWN G_PI +#define ARROW_RIGHT G_PI_2 +#define ARROW_LEFT (G_PI+G_PI_2) + +#if !GTK_CHECK_VERSION(3,14,0) +#define GTK_STATE_FLAG_CHECKED (1 << 11) +#endif + +static gint +moz_gtk_get_tab_thickness(GtkStyleContext *style); + +// GetStateFlagsFromGtkWidgetState() can be safely used for the specific +// GtkWidgets that set both prelight and active flags. For other widgets, +// either the GtkStateFlags or Gecko's GtkWidgetState need to be carefully +// adjusted to match GTK behavior. Although GTK sets insensitive and focus +// flags in the generic GtkWidget base class, GTK adds prelight and active +// flags only to widgets that are expected to demonstrate prelight or active +// states. This contrasts with HTML where any element may have :active and +// :hover states, and so Gecko's GtkStateFlags do not necessarily map to GTK +// flags. Failure to restrict the flags in the same way as GTK can cause +// generic CSS selectors from some themes to unintentionally match elements +// that are not expected to change appearance on hover or mouse-down. +static GtkStateFlags +GetStateFlagsFromGtkWidgetState(GtkWidgetState* state) +{ + GtkStateFlags stateFlags = GTK_STATE_FLAG_NORMAL; + + if (state->disabled) + stateFlags = GTK_STATE_FLAG_INSENSITIVE; + else { + if (state->depressed || state->active) + stateFlags = static_cast<GtkStateFlags>(stateFlags|GTK_STATE_FLAG_ACTIVE); + if (state->inHover) + stateFlags = static_cast<GtkStateFlags>(stateFlags|GTK_STATE_FLAG_PRELIGHT); + if (state->focused) + stateFlags = static_cast<GtkStateFlags>(stateFlags|GTK_STATE_FLAG_FOCUSED); + } + + return stateFlags; +} + +static GtkStateFlags +GetStateFlagsFromGtkTabFlags(GtkTabFlags flags) +{ + return ((flags & MOZ_GTK_TAB_SELECTED) == 0) ? + GTK_STATE_FLAG_NORMAL : GTK_STATE_FLAG_ACTIVE; +} + +gint +moz_gtk_enable_style_props(style_prop_t styleGetProp) +{ + style_prop_func = styleGetProp; + return MOZ_GTK_SUCCESS; +} + +gint +moz_gtk_init() +{ + if (is_initialized) + return MOZ_GTK_SUCCESS; + + is_initialized = TRUE; + have_arrow_scaling = (gtk_major_version > 2 || + (gtk_major_version == 2 && gtk_minor_version >= 12)); + if (gtk_major_version > 3 || + (gtk_major_version == 3 && gtk_minor_version >= 14)) + checkbox_check_state = GTK_STATE_FLAG_CHECKED; + else + checkbox_check_state = GTK_STATE_FLAG_ACTIVE; + + if (gtk_check_version(3, 12, 0) == nullptr && + gtk_check_version(3, 20, 0) != nullptr) + { + // Deprecated for Gtk >= 3.20+ + GtkStyleContext *style = ClaimStyleContext(MOZ_GTK_TAB_TOP); + gtk_style_context_get_style(style, + "has-tab-gap", ¬ebook_has_tab_gap, NULL); + ReleaseStyleContext(style); + } + else { + notebook_has_tab_gap = true; + } + + return MOZ_GTK_SUCCESS; +} + +gint +moz_gtk_checkbox_get_metrics(gint* indicator_size, gint* indicator_spacing) +{ + gtk_widget_style_get(GetWidget(MOZ_GTK_CHECKBUTTON_CONTAINER), + "indicator_size", indicator_size, + "indicator_spacing", indicator_spacing, + NULL); + return MOZ_GTK_SUCCESS; +} + +gint +moz_gtk_radio_get_metrics(gint* indicator_size, gint* indicator_spacing) +{ + gtk_widget_style_get(GetWidget(MOZ_GTK_RADIOBUTTON_CONTAINER), + "indicator_size", indicator_size, + "indicator_spacing", indicator_spacing, + NULL); + return MOZ_GTK_SUCCESS; +} + +static gint +moz_gtk_get_focus_outline_size(GtkStyleContext* style, + gint* focus_h_width, gint* focus_v_width) +{ + GtkBorder border; + GtkBorder padding; + gtk_style_context_get_border(style, GTK_STATE_FLAG_NORMAL, &border); + gtk_style_context_get_padding(style, GTK_STATE_FLAG_NORMAL, &padding); + *focus_h_width = border.left + padding.left; + *focus_v_width = border.top + padding.top; + return MOZ_GTK_SUCCESS; +} + +gint +moz_gtk_get_focus_outline_size(gint* focus_h_width, gint* focus_v_width) +{ + GtkStyleContext *style = ClaimStyleContext(MOZ_GTK_ENTRY); + moz_gtk_get_focus_outline_size(style, focus_h_width, focus_v_width); + ReleaseStyleContext(style); + return MOZ_GTK_SUCCESS; +} + +gint +moz_gtk_menuitem_get_horizontal_padding(gint* horizontal_padding) +{ + GtkStyleContext *style = ClaimStyleContext(MOZ_GTK_MENUITEM); + gtk_style_context_get_style(style, + "horizontal-padding", horizontal_padding, + nullptr); + ReleaseStyleContext(style); + return MOZ_GTK_SUCCESS; +} + +gint +moz_gtk_checkmenuitem_get_horizontal_padding(gint* horizontal_padding) +{ + GtkStyleContext *style = ClaimStyleContext(MOZ_GTK_CHECKMENUITEM_CONTAINER); + gtk_style_context_get_style(style, + "horizontal-padding", horizontal_padding, + nullptr); + ReleaseStyleContext(style); + return MOZ_GTK_SUCCESS; +} + +gint +moz_gtk_button_get_default_overflow(gint* border_top, gint* border_left, + gint* border_bottom, gint* border_right) +{ + GtkBorder* default_outside_border; + + GtkStyleContext *style = ClaimStyleContext(MOZ_GTK_BUTTON); + gtk_style_context_get_style(style, + "default-outside-border", &default_outside_border, + NULL); + ReleaseStyleContext(style); + + if (default_outside_border) { + *border_top = default_outside_border->top; + *border_left = default_outside_border->left; + *border_bottom = default_outside_border->bottom; + *border_right = default_outside_border->right; + gtk_border_free(default_outside_border); + } else { + *border_top = *border_left = *border_bottom = *border_right = 0; + } + return MOZ_GTK_SUCCESS; +} + +static gint +moz_gtk_button_get_default_border(gint* border_top, gint* border_left, + gint* border_bottom, gint* border_right) +{ + GtkBorder* default_border; + + GtkStyleContext *style = ClaimStyleContext(MOZ_GTK_BUTTON); + gtk_style_context_get_style(style, + "default-border", &default_border, + NULL); + ReleaseStyleContext(style); + + if (default_border) { + *border_top = default_border->top; + *border_left = default_border->left; + *border_bottom = default_border->bottom; + *border_right = default_border->right; + gtk_border_free(default_border); + } else { + /* see gtkbutton.c */ + *border_top = *border_left = *border_bottom = *border_right = 1; + } + return MOZ_GTK_SUCCESS; +} + +gint +moz_gtk_splitter_get_metrics(gint orientation, gint* size) +{ + GtkStyleContext *style; + if (orientation == GTK_ORIENTATION_HORIZONTAL) { + style = ClaimStyleContext(MOZ_GTK_SPLITTER_HORIZONTAL); + } else { + style = ClaimStyleContext(MOZ_GTK_SPLITTER_VERTICAL); + } + gtk_style_context_get_style(style, "handle_size", size, NULL); + ReleaseStyleContext(style); + return MOZ_GTK_SUCCESS; +} + +static gint +moz_gtk_window_paint(cairo_t *cr, GdkRectangle* rect, + GtkTextDirection direction) +{ + GtkStyleContext* style = ClaimStyleContext(MOZ_GTK_WINDOW, direction); + + gtk_style_context_save(style); + gtk_style_context_add_class(style, GTK_STYLE_CLASS_BACKGROUND); + gtk_render_background(style, cr, rect->x, rect->y, rect->width, rect->height); + gtk_style_context_restore(style); + + ReleaseStyleContext(style); + + return MOZ_GTK_SUCCESS; +} + +static gint +moz_gtk_button_paint(cairo_t *cr, GdkRectangle* rect, + GtkWidgetState* state, + GtkReliefStyle relief, GtkWidget* widget, + GtkTextDirection direction) +{ + GtkStateFlags state_flags = GetStateFlagsFromGtkWidgetState(state); + GtkStyleContext* style = gtk_widget_get_style_context(widget); + gint x = rect->x, y=rect->y, width=rect->width, height=rect->height; + + gtk_widget_set_direction(widget, direction); + + gtk_style_context_save(style); + gtk_style_context_set_state(style, state_flags); + + if (state->isDefault && relief == GTK_RELIEF_NORMAL) { + /* handle default borders both outside and inside the button */ + gint default_top, default_left, default_bottom, default_right; + moz_gtk_button_get_default_overflow(&default_top, &default_left, + &default_bottom, &default_right); + x -= default_left; + y -= default_top; + width += default_left + default_right; + height += default_top + default_bottom; + gtk_render_background(style, cr, x, y, width, height); + gtk_render_frame(style, cr, x, y, width, height); + moz_gtk_button_get_default_border(&default_top, &default_left, + &default_bottom, &default_right); + x += default_left; + y += default_top; + width -= (default_left + default_right); + height -= (default_top + default_bottom); + } else if (relief != GTK_RELIEF_NONE || state->depressed || + (state_flags & GTK_STATE_FLAG_PRELIGHT)) { + /* the following line can trigger an assertion (Crux theme) + file ../../gdk/gdkwindow.c: line 1846 (gdk_window_clear_area): + assertion `GDK_IS_WINDOW (window)' failed */ + gtk_render_background(style, cr, x, y, width, height); + gtk_render_frame(style, cr, x, y, width, height); + } + + if (state->focused) { + GtkBorder border; + gtk_style_context_get_border(style, state_flags, &border); + x += border.left; + y += border.top; + width -= (border.left + border.right); + height -= (border.top + border.bottom); + gtk_render_focus(style, cr, x, y, width, height); + } + gtk_style_context_restore(style); + return MOZ_GTK_SUCCESS; +} + +static gint +moz_gtk_toggle_paint(cairo_t *cr, GdkRectangle* rect, + GtkWidgetState* state, + gboolean selected, gboolean inconsistent, + gboolean isradio, GtkTextDirection direction) +{ + GtkStateFlags state_flags = GetStateFlagsFromGtkWidgetState(state); + gint indicator_size, indicator_spacing; + gint x, y, width, height; + gint focus_x, focus_y, focus_width, focus_height; + GtkStyleContext *style; + + GtkWidget *widget = GetWidget(isradio ? MOZ_GTK_RADIOBUTTON_CONTAINER : + MOZ_GTK_CHECKBUTTON_CONTAINER); + gtk_widget_style_get(widget, + "indicator_size", &indicator_size, + "indicator_spacing", &indicator_spacing, + nullptr); + + // XXX we should assert rect->height >= indicator_size too + // after bug 369581 is fixed. + MOZ_ASSERT(rect->width >= indicator_size, + "GetMinimumWidgetSize was ignored"); + + // Paint it center aligned in the rect. + x = rect->x + (rect->width - indicator_size) / 2; + y = rect->y + (rect->height - indicator_size) / 2; + width = indicator_size; + height = indicator_size; + + focus_x = x - indicator_spacing; + focus_y = y - indicator_spacing; + focus_width = width + 2 * indicator_spacing; + focus_height = height + 2 * indicator_spacing; + + if (selected) + state_flags = static_cast<GtkStateFlags>(state_flags|checkbox_check_state); + + if (inconsistent) + state_flags = static_cast<GtkStateFlags>(state_flags|GTK_STATE_FLAG_INCONSISTENT); + + style = ClaimStyleContext(isradio ? MOZ_GTK_RADIOBUTTON : + MOZ_GTK_CHECKBUTTON, + direction, state_flags); + + if (gtk_check_version(3, 20, 0) == nullptr) { + gtk_render_background(style, cr, x, y, width, height); + gtk_render_frame(style, cr, x, y, width, height); + } + + if (isradio) { + gtk_render_option(style, cr, x, y, width, height); + if (state->focused) { + gtk_render_focus(style, cr, focus_x, focus_y, + focus_width, focus_height); + } + } + else { + gtk_render_check(style, cr, x, y, width, height); + if (state->focused) { + gtk_render_focus(style, cr, + focus_x, focus_y, focus_width, focus_height); + } + } + + ReleaseStyleContext(style); + + return MOZ_GTK_SUCCESS; +} + +static gint +calculate_button_inner_rect(GtkWidget* button, GdkRectangle* rect, + GdkRectangle* inner_rect, + GtkTextDirection direction) +{ + GtkStyleContext* style; + GtkBorder border; + GtkBorder padding = {0, 0, 0, 0}; + + style = gtk_widget_get_style_context(button); + + /* This mirrors gtkbutton's child positioning */ + gtk_style_context_get_border(style, GTK_STATE_FLAG_NORMAL, &border); + gtk_style_context_get_padding(style, GTK_STATE_FLAG_NORMAL, &padding); + + inner_rect->x = rect->x + border.left + padding.left; + inner_rect->y = rect->y + padding.top + border.top; + inner_rect->width = MAX(1, rect->width - padding.left - + padding.right - border.left * 2); + inner_rect->height = MAX(1, rect->height - padding.top - + padding.bottom - border.top * 2); + + return MOZ_GTK_SUCCESS; +} + + +static gint +calculate_arrow_rect(GtkWidget* arrow, GdkRectangle* rect, + GdkRectangle* arrow_rect, GtkTextDirection direction) +{ + /* defined in gtkarrow.c */ + gfloat arrow_scaling = 0.7; + gfloat xalign, xpad; + gint extent; + gint mxpad, mypad; + gfloat mxalign, myalign; + GtkMisc* misc = GTK_MISC(arrow); + + if (have_arrow_scaling) + gtk_style_context_get_style(gtk_widget_get_style_context(arrow), + "arrow_scaling", &arrow_scaling, NULL); + + gtk_misc_get_padding(misc, &mxpad, &mypad); + extent = MIN((rect->width - mxpad * 2), + (rect->height - mypad * 2)) * arrow_scaling; + + gtk_misc_get_alignment(misc, &mxalign, &myalign); + + xalign = direction == GTK_TEXT_DIR_LTR ? mxalign : 1.0 - mxalign; + xpad = mxpad + (rect->width - extent) * xalign; + + arrow_rect->x = direction == GTK_TEXT_DIR_LTR ? + floor(rect->x + xpad) : ceil(rect->x + xpad); + arrow_rect->y = floor(rect->y + mypad + + ((rect->height - extent) * myalign)); + + arrow_rect->width = arrow_rect->height = extent; + + return MOZ_GTK_SUCCESS; +} + +void +moz_gtk_get_widget_min_size(WidgetNodeType aGtkWidgetType, int* width, + int* height) +{ + GtkStyleContext* style = ClaimStyleContext(aGtkWidgetType); + GtkStateFlags state_flags = gtk_style_context_get_state(style); + gtk_style_context_get(style, state_flags, + "min-height", height, + "min-width", width, + nullptr); + + GtkBorder border, padding, margin; + gtk_style_context_get_border(style, state_flags, &border); + gtk_style_context_get_padding(style, state_flags, &padding); + gtk_style_context_get_margin(style, state_flags, &margin); + ReleaseStyleContext(style); + + *width += border.left + border.right + margin.left + margin.right + + padding.left + padding.right; + *height += border.top + border.bottom + margin.top + margin.bottom + + padding.top + padding.bottom; +} + +static void +moz_gtk_rectangle_inset(GdkRectangle* rect, GtkBorder& aBorder) +{ + MOZ_ASSERT(rect); + rect->x += aBorder.left; + rect->y += aBorder.top; + rect->width -= aBorder.left + aBorder.right; + rect->height -= aBorder.top + aBorder.bottom; +} + +/* Subtracting margin is used to inset drawing of element which can have margins, + * like scrollbar, scrollbar's trough, thumb and scrollbar's button */ +static void +moz_gtk_subtract_margin(GtkStyleContext* style, GdkRectangle* rect) +{ + MOZ_ASSERT(rect); + GtkBorder margin; + + gtk_style_context_get_margin(style, gtk_style_context_get_state(style), + &margin); + moz_gtk_rectangle_inset(rect, margin); +} + +static gint +moz_gtk_scrollbar_button_paint(cairo_t *cr, const GdkRectangle* aRect, + GtkWidgetState* state, + GtkScrollbarButtonFlags flags, + GtkTextDirection direction) +{ + GtkStateFlags state_flags = GetStateFlagsFromGtkWidgetState(state); + GdkRectangle arrow_rect; + gdouble arrow_angle; + GtkStyleContext* style; + gint arrow_displacement_x, arrow_displacement_y; + + GtkWidget *scrollbar = + GetWidget(flags & MOZ_GTK_STEPPER_VERTICAL ? + MOZ_GTK_SCROLLBAR_VERTICAL : MOZ_GTK_SCROLLBAR_HORIZONTAL); + + gtk_widget_set_direction(scrollbar, direction); + + if (flags & MOZ_GTK_STEPPER_VERTICAL) { + arrow_angle = (flags & MOZ_GTK_STEPPER_DOWN) ? ARROW_DOWN : ARROW_UP; + } else { + arrow_angle = (flags & MOZ_GTK_STEPPER_DOWN) ? ARROW_RIGHT : ARROW_LEFT; + } + + style = gtk_widget_get_style_context(scrollbar); + + gtk_style_context_save(style); + gtk_style_context_add_class(style, GTK_STYLE_CLASS_BUTTON); + gtk_style_context_set_state(style, state_flags); + if (arrow_angle == ARROW_RIGHT) { + gtk_style_context_add_class(style, GTK_STYLE_CLASS_RIGHT); + } else if (arrow_angle == ARROW_DOWN) { + gtk_style_context_add_class(style, GTK_STYLE_CLASS_BOTTOM); + } else if (arrow_angle == ARROW_LEFT) { + gtk_style_context_add_class(style, GTK_STYLE_CLASS_LEFT); + } else { + gtk_style_context_add_class(style, GTK_STYLE_CLASS_TOP); + } + + GdkRectangle rect = *aRect; + if (gtk_check_version(3,20,0) == nullptr) { + // The "trough-border" is not used since GTK 3.20. The stepper margin + // box occupies the full width of the "contents" gadget content box. + moz_gtk_subtract_margin(style, &rect); + } else { + // Scrollbar button has to be inset by trough_border because its DOM + // element is filling width of vertical scrollbar's track (or height + // in case of horizontal scrollbars). + MozGtkScrollbarMetrics metrics; + moz_gtk_get_scrollbar_metrics(&metrics); + if (flags & MOZ_GTK_STEPPER_VERTICAL) { + rect.x += metrics.trough_border; + rect.width = metrics.slider_width; + } else { + rect.y += metrics.trough_border; + rect.height = metrics.slider_width; + } + } + + gtk_render_background(style, cr, rect.x, rect.y, rect.width, rect.height); + gtk_render_frame(style, cr, rect.x, rect.y, rect.width, rect.height); + + arrow_rect.width = rect.width / 2; + arrow_rect.height = rect.height / 2; + + gfloat arrow_scaling; + gtk_style_context_get_style(style, "arrow-scaling", &arrow_scaling, NULL); + + gdouble arrow_size = MIN(rect.width, rect.height) * arrow_scaling; + arrow_rect.x = rect.x + (rect.width - arrow_size) / 2; + arrow_rect.y = rect.y + (rect.height - arrow_size) / 2; + + if (state_flags & GTK_STATE_FLAG_ACTIVE) { + gtk_style_context_get_style(style, + "arrow-displacement-x", &arrow_displacement_x, + "arrow-displacement-y", &arrow_displacement_y, + NULL); + + arrow_rect.x += arrow_displacement_x; + arrow_rect.y += arrow_displacement_y; + } + + gtk_render_arrow(style, cr, arrow_angle, + arrow_rect.x, + arrow_rect.y, + arrow_size); + + gtk_style_context_restore(style); + + return MOZ_GTK_SUCCESS; +} + +static void +moz_gtk_update_scrollbar_style(GtkStyleContext* style, + WidgetNodeType widget, + GtkTextDirection direction) +{ + if (widget == MOZ_GTK_SCROLLBAR_HORIZONTAL) { + gtk_style_context_add_class(style, GTK_STYLE_CLASS_BOTTOM); + } else { + if (direction == GTK_TEXT_DIR_LTR) { + gtk_style_context_add_class(style, GTK_STYLE_CLASS_RIGHT); + gtk_style_context_remove_class(style, GTK_STYLE_CLASS_LEFT); + } else { + gtk_style_context_add_class(style, GTK_STYLE_CLASS_LEFT); + gtk_style_context_remove_class(style, GTK_STYLE_CLASS_RIGHT); + } + } +} + +static void +moz_gtk_draw_styled_frame(GtkStyleContext* style, cairo_t *cr, + const GdkRectangle* aRect, bool drawFocus) +{ + GdkRectangle rect = *aRect; + if (gtk_check_version(3, 6, 0) == nullptr) { + moz_gtk_subtract_margin(style, &rect); + } + gtk_render_background(style, cr, rect.x, rect.y, rect.width, rect.height); + gtk_render_frame(style, cr, rect.x, rect.y, rect.width, rect.height); + if (drawFocus) { + gtk_render_focus(style, cr, + rect.x, rect.y, rect.width, rect.height); + } +} + +static gint +moz_gtk_scrollbar_trough_paint(WidgetNodeType widget, + cairo_t *cr, const GdkRectangle* rect, + GtkWidgetState* state, + GtkTextDirection direction) +{ + GtkStyleContext* style = ClaimStyleContext(widget, direction); + moz_gtk_draw_styled_frame(style, cr, rect, state->focused); + ReleaseStyleContext(style); + + return MOZ_GTK_SUCCESS; +} + +static gint +moz_gtk_scrollbar_paint(WidgetNodeType widget, + cairo_t *cr, const GdkRectangle* rect, + GtkWidgetState* state, + GtkTextDirection direction) +{ + GtkStyleContext* style = ClaimStyleContext(widget, direction); + moz_gtk_update_scrollbar_style(style, widget, direction); + + moz_gtk_draw_styled_frame(style, cr, rect, state->focused); + + ReleaseStyleContext(style); + style = ClaimStyleContext((widget == MOZ_GTK_SCROLLBAR_HORIZONTAL) ? + MOZ_GTK_SCROLLBAR_CONTENTS_HORIZONTAL : + MOZ_GTK_SCROLLBAR_CONTENTS_VERTICAL, + direction); + moz_gtk_draw_styled_frame(style, cr, rect, state->focused); + ReleaseStyleContext(style); + + return MOZ_GTK_SUCCESS; +} + +static gint +moz_gtk_scrollbar_thumb_paint(WidgetNodeType widget, + cairo_t *cr, const GdkRectangle* aRect, + GtkWidgetState* state, + GtkTextDirection direction) +{ + GtkStateFlags state_flags = GetStateFlagsFromGtkWidgetState(state); + + GdkRectangle rect = *aRect; + GtkStyleContext* style = ClaimStyleContext(widget, direction, state_flags); + moz_gtk_subtract_margin(style, &rect); + + gtk_render_slider(style, cr, + rect.x, + rect.y, + rect.width, + rect.height, + (widget == MOZ_GTK_SCROLLBAR_THUMB_HORIZONTAL) ? + GTK_ORIENTATION_HORIZONTAL : GTK_ORIENTATION_VERTICAL); + + ReleaseStyleContext(style); + + return MOZ_GTK_SUCCESS; +} + +static gint +moz_gtk_spin_paint(cairo_t *cr, GdkRectangle* rect, + GtkTextDirection direction) +{ + GtkStyleContext* style = ClaimStyleContext(MOZ_GTK_SPINBUTTON, direction); + gtk_render_background(style, cr, rect->x, rect->y, rect->width, rect->height); + gtk_render_frame(style, cr, rect->x, rect->y, rect->width, rect->height); + ReleaseStyleContext(style); + return MOZ_GTK_SUCCESS; +} + +static gint +moz_gtk_spin_updown_paint(cairo_t *cr, GdkRectangle* rect, + gboolean isDown, GtkWidgetState* state, + GtkTextDirection direction) +{ + GtkStyleContext* style = ClaimStyleContext(MOZ_GTK_SPINBUTTON, direction, + GetStateFlagsFromGtkWidgetState(state)); + + gtk_render_background(style, cr, rect->x, rect->y, rect->width, rect->height); + gtk_render_frame(style, cr, rect->x, rect->y, rect->width, rect->height); + + /* hard code these values */ + GdkRectangle arrow_rect; + arrow_rect.width = 6; + arrow_rect.height = 6; + arrow_rect.x = rect->x + (rect->width - arrow_rect.width) / 2; + arrow_rect.y = rect->y + (rect->height - arrow_rect.height) / 2; + arrow_rect.y += isDown ? -1 : 1; + + gtk_render_arrow(style, cr, + isDown ? ARROW_DOWN : ARROW_UP, + arrow_rect.x, arrow_rect.y, + arrow_rect.width); + + ReleaseStyleContext(style); + return MOZ_GTK_SUCCESS; +} + +/* See gtk_range_draw() for reference. +*/ +static gint +moz_gtk_scale_paint(cairo_t *cr, GdkRectangle* rect, + GtkWidgetState* state, + GtkOrientation flags, GtkTextDirection direction) +{ + GtkStateFlags state_flags = GetStateFlagsFromGtkWidgetState(state); + gint x, y, width, height, min_width, min_height; + GtkStyleContext* style; + GtkBorder margin; + + moz_gtk_get_scale_metrics(flags, &min_width, &min_height); + + WidgetNodeType widget = (flags == GTK_ORIENTATION_HORIZONTAL) ? + MOZ_GTK_SCALE_TROUGH_HORIZONTAL : + MOZ_GTK_SCALE_TROUGH_VERTICAL; + style = ClaimStyleContext(widget, direction, state_flags); + gtk_style_context_get_margin(style, state_flags, &margin); + + // Clamp the dimension perpendicular to the direction that the slider crosses + // to the minimum size. + if (flags == GTK_ORIENTATION_HORIZONTAL) { + width = rect->width - (margin.left + margin.right); + height = min_height - (margin.top + margin.bottom); + x = rect->x + margin.left; + y = rect->y + (rect->height - height)/2; + } else { + width = min_width - (margin.left + margin.right); + height = rect->height - (margin.top + margin.bottom); + x = rect->x + (rect->width - width)/2; + y = rect->y + margin.top; + } + + gtk_render_background(style, cr, x, y, width, height); + gtk_render_frame(style, cr, x, y, width, height); + + if (state->focused) + gtk_render_focus(style, cr, + rect->x, rect->y, rect->width, rect->height); + + ReleaseStyleContext(style); + return MOZ_GTK_SUCCESS; +} + +static gint +moz_gtk_scale_thumb_paint(cairo_t *cr, GdkRectangle* rect, + GtkWidgetState* state, + GtkOrientation flags, GtkTextDirection direction) +{ + GtkStateFlags state_flags = GetStateFlagsFromGtkWidgetState(state); + GtkStyleContext* style; + gint thumb_width, thumb_height, x, y; + + /* determine the thumb size, and position the thumb in the center in the opposite axis + */ + if (flags == GTK_ORIENTATION_HORIZONTAL) { + moz_gtk_get_scalethumb_metrics(GTK_ORIENTATION_HORIZONTAL, &thumb_width, &thumb_height); + x = rect->x; + y = rect->y + (rect->height - thumb_height) / 2; + } + else { + moz_gtk_get_scalethumb_metrics(GTK_ORIENTATION_VERTICAL, &thumb_height, &thumb_width); + x = rect->x + (rect->width - thumb_width) / 2; + y = rect->y; + } + + WidgetNodeType widget = (flags == GTK_ORIENTATION_HORIZONTAL) ? + MOZ_GTK_SCALE_THUMB_HORIZONTAL : + MOZ_GTK_SCALE_THUMB_VERTICAL; + style = ClaimStyleContext(widget, direction, state_flags); + gtk_render_slider(style, cr, x, y, thumb_width, thumb_height, flags); + ReleaseStyleContext(style); + + return MOZ_GTK_SUCCESS; +} + +static gint +moz_gtk_gripper_paint(cairo_t *cr, GdkRectangle* rect, + GtkWidgetState* state, + GtkTextDirection direction) +{ + GtkStyleContext* style = + ClaimStyleContext(MOZ_GTK_GRIPPER, direction, + GetStateFlagsFromGtkWidgetState(state)); + gtk_render_background(style, cr, rect->x, rect->y, rect->width, rect->height); + gtk_render_frame(style, cr, rect->x, rect->y, rect->width, rect->height); + ReleaseStyleContext(style); + return MOZ_GTK_SUCCESS; +} + +static gint +moz_gtk_hpaned_paint(cairo_t *cr, GdkRectangle* rect, + GtkWidgetState* state) +{ + GtkStyleContext* style = + ClaimStyleContext(MOZ_GTK_SPLITTER_SEPARATOR_HORIZONTAL, + GTK_TEXT_DIR_LTR, + GetStateFlagsFromGtkWidgetState(state)); + gtk_render_handle(style, cr, + rect->x, rect->y, rect->width, rect->height); + ReleaseStyleContext(style); + return MOZ_GTK_SUCCESS; +} + +static gint +moz_gtk_vpaned_paint(cairo_t *cr, GdkRectangle* rect, + GtkWidgetState* state) +{ + GtkStyleContext* style = + ClaimStyleContext(MOZ_GTK_SPLITTER_SEPARATOR_VERTICAL, + GTK_TEXT_DIR_LTR, + GetStateFlagsFromGtkWidgetState(state)); + gtk_render_handle(style, cr, + rect->x, rect->y, rect->width, rect->height); + ReleaseStyleContext(style); + return MOZ_GTK_SUCCESS; +} + +// See gtk_entry_draw() for reference. +static gint +moz_gtk_entry_paint(cairo_t *cr, GdkRectangle* rect, + GtkWidgetState* state, + GtkStyleContext* style) +{ + gint x = rect->x, y = rect->y, width = rect->width, height = rect->height; + int draw_focus_outline_only = state->depressed; // NS_THEME_FOCUS_OUTLINE + + if (draw_focus_outline_only) { + // Inflate the given 'rect' with the focus outline size. + gint h, v; + moz_gtk_get_focus_outline_size(style, &h, &v); + rect->x -= h; + rect->width += 2 * h; + rect->y -= v; + rect->height += 2 * v; + width = rect->width; + height = rect->height; + } else { + gtk_render_background(style, cr, x, y, width, height); + } + gtk_render_frame(style, cr, x, y, width, height); + + return MOZ_GTK_SUCCESS; +} + +static gint +moz_gtk_text_view_paint(cairo_t *cr, GdkRectangle* rect, + GtkWidgetState* state, + GtkTextDirection direction) +{ + // GtkTextView and GtkScrolledWindow do not set active and prelight flags. + // The use of focus with MOZ_GTK_SCROLLED_WINDOW here is questionable + // because a parent widget will not have focus when its child GtkTextView + // has focus, but perhaps this may help identify a focused textarea with + // some themes as GtkTextView backgrounds do not typically render + // differently with focus. + GtkStateFlags state_flags = + state->disabled ? GTK_STATE_FLAG_INSENSITIVE : + state->focused ? GTK_STATE_FLAG_FOCUSED : + GTK_STATE_FLAG_NORMAL; + + GtkStyleContext* style_frame = + ClaimStyleContext(MOZ_GTK_SCROLLED_WINDOW, direction, state_flags); + gtk_render_frame(style_frame, cr, rect->x, rect->y, rect->width, rect->height); + + GtkBorder border, padding; + gtk_style_context_get_border(style_frame, state_flags, &border); + gtk_style_context_get_padding(style_frame, state_flags, &padding); + ReleaseStyleContext(style_frame); + + GtkStyleContext* style = + ClaimStyleContext(MOZ_GTK_TEXT_VIEW, direction, state_flags); + + gint xthickness = border.left + padding.left; + gint ythickness = border.top + padding.top; + + gtk_render_background(style, cr, + rect->x + xthickness, rect->y + ythickness, + rect->width - 2 * xthickness, + rect->height - 2 * ythickness); + + ReleaseStyleContext(style); + + return MOZ_GTK_SUCCESS; +} + +static gint +moz_gtk_treeview_paint(cairo_t *cr, GdkRectangle* rect, + GtkWidgetState* state, + GtkTextDirection direction) +{ + gint xthickness, ythickness; + GtkStyleContext *style; + GtkStyleContext *style_tree; + GtkStateFlags state_flags; + GtkBorder border; + + /* only handle disabled and normal states, otherwise the whole background + * area will be painted differently with other states */ + state_flags = state->disabled ? GTK_STATE_FLAG_INSENSITIVE : GTK_STATE_FLAG_NORMAL; + + style = ClaimStyleContext(MOZ_GTK_SCROLLED_WINDOW, direction); + gtk_style_context_get_border(style, state_flags, &border); + xthickness = border.left; + ythickness = border.top; + ReleaseStyleContext(style); + + style_tree = ClaimStyleContext(MOZ_GTK_TREEVIEW_VIEW, direction); + gtk_render_background(style_tree, cr, + rect->x + xthickness, rect->y + ythickness, + rect->width - 2 * xthickness, + rect->height - 2 * ythickness); + ReleaseStyleContext(style_tree); + + style = ClaimStyleContext(MOZ_GTK_SCROLLED_WINDOW, direction); + gtk_render_frame(style, cr, + rect->x, rect->y, rect->width, rect->height); + ReleaseStyleContext(style); + return MOZ_GTK_SUCCESS; +} + +static gint +moz_gtk_tree_header_cell_paint(cairo_t *cr, GdkRectangle* rect, + GtkWidgetState* state, + gboolean isSorted, GtkTextDirection direction) +{ + moz_gtk_button_paint(cr, rect, state, GTK_RELIEF_NORMAL, + GetWidget(MOZ_GTK_TREE_HEADER_CELL), direction); + return MOZ_GTK_SUCCESS; +} + +static gint +moz_gtk_tree_header_sort_arrow_paint(cairo_t *cr, GdkRectangle* rect, + GtkWidgetState* state, GtkArrowType arrow_type, + GtkTextDirection direction) +{ + GdkRectangle arrow_rect; + gdouble arrow_angle; + GtkStyleContext* style; + + /* hard code these values */ + arrow_rect.width = 11; + arrow_rect.height = 11; + arrow_rect.x = rect->x + (rect->width - arrow_rect.width) / 2; + arrow_rect.y = rect->y + (rect->height - arrow_rect.height) / 2; + style = ClaimStyleContext(MOZ_GTK_TREE_HEADER_SORTARROW, direction, + GetStateFlagsFromGtkWidgetState(state)); + switch (arrow_type) { + case GTK_ARROW_LEFT: + arrow_angle = ARROW_LEFT; + break; + case GTK_ARROW_RIGHT: + arrow_angle = ARROW_RIGHT; + break; + case GTK_ARROW_DOWN: + arrow_angle = ARROW_DOWN; + break; + default: + arrow_angle = ARROW_UP; + break; + } + if (arrow_type != GTK_ARROW_NONE) + gtk_render_arrow(style, cr, arrow_angle, + arrow_rect.x, arrow_rect.y, + arrow_rect.width); + ReleaseStyleContext(style); + return MOZ_GTK_SUCCESS; +} + +/* See gtk_expander_paint() for reference. + */ +static gint +moz_gtk_treeview_expander_paint(cairo_t *cr, GdkRectangle* rect, + GtkWidgetState* state, + GtkExpanderStyle expander_state, + GtkTextDirection direction) +{ + /* Because the frame we get is of the entire treeview, we can't get the precise + * event state of one expander, thus rendering hover and active feedback useless. */ + GtkStateFlags state_flags = state->disabled ? GTK_STATE_FLAG_INSENSITIVE : + GTK_STATE_FLAG_NORMAL; + + /* GTK_STATE_FLAG_ACTIVE controls expanded/colapsed state rendering + * in gtk_render_expander() + */ + if (expander_state == GTK_EXPANDER_EXPANDED) + state_flags = static_cast<GtkStateFlags>(state_flags|checkbox_check_state); + else + state_flags = static_cast<GtkStateFlags>(state_flags&~(checkbox_check_state)); + + GtkStyleContext *style = ClaimStyleContext(MOZ_GTK_TREEVIEW_EXPANDER, + direction, state_flags); + gtk_render_expander(style, cr, + rect->x, + rect->y, + rect->width, + rect->height); + + ReleaseStyleContext(style); + return MOZ_GTK_SUCCESS; +} + +/* See gtk_separator_draw() for reference. +*/ +static gint +moz_gtk_combo_box_paint(cairo_t *cr, GdkRectangle* rect, + GtkWidgetState* state, + GtkTextDirection direction) +{ + GdkRectangle arrow_rect, real_arrow_rect; + gint separator_width; + gboolean wide_separators; + GtkStyleContext* style; + GtkRequisition arrow_req; + + GtkWidget* comboBoxButton = GetWidget(MOZ_GTK_COMBOBOX_BUTTON); + GtkWidget* comboBoxArrow = GetWidget(MOZ_GTK_COMBOBOX_ARROW); + + /* Also sets the direction on gComboBoxButtonWidget, which is then + * inherited by the separator and arrow */ + moz_gtk_button_paint(cr, rect, state, GTK_RELIEF_NORMAL, + comboBoxButton, direction); + + calculate_button_inner_rect(comboBoxButton, rect, &arrow_rect, direction); + /* Now arrow_rect contains the inner rect ; we want to correct the width + * to what the arrow needs (see gtk_combo_box_size_allocate) */ + gtk_widget_get_preferred_size(comboBoxArrow, NULL, &arrow_req); + + if (direction == GTK_TEXT_DIR_LTR) + arrow_rect.x += arrow_rect.width - arrow_req.width; + arrow_rect.width = arrow_req.width; + + calculate_arrow_rect(comboBoxArrow, + &arrow_rect, &real_arrow_rect, direction); + + style = ClaimStyleContext(MOZ_GTK_COMBOBOX_ARROW); + gtk_render_arrow(style, cr, ARROW_DOWN, + real_arrow_rect.x, real_arrow_rect.y, + real_arrow_rect.width); + ReleaseStyleContext(style); + + /* If there is no separator in the theme, there's nothing left to do. */ + GtkWidget* widget = GetWidget(MOZ_GTK_COMBOBOX_SEPARATOR); + if (!widget) + return MOZ_GTK_SUCCESS; + style = gtk_widget_get_style_context(widget); + gtk_style_context_get_style(style, + "wide-separators", &wide_separators, + "separator-width", &separator_width, + NULL); + + if (wide_separators) { + if (direction == GTK_TEXT_DIR_LTR) + arrow_rect.x -= separator_width; + else + arrow_rect.x += arrow_rect.width; + + gtk_render_frame(style, cr, arrow_rect.x, arrow_rect.y, separator_width, arrow_rect.height); + } else { + if (direction == GTK_TEXT_DIR_LTR) { + GtkBorder padding; + GtkStateFlags state_flags = GetStateFlagsFromGtkWidgetState(state); + gtk_style_context_get_padding(style, state_flags, &padding); + arrow_rect.x -= padding.left; + } + else + arrow_rect.x += arrow_rect.width; + + gtk_render_line(style, cr, + arrow_rect.x, arrow_rect.y, + arrow_rect.x, arrow_rect.y + arrow_rect.height); + } + return MOZ_GTK_SUCCESS; +} + +static gint +moz_gtk_arrow_paint(cairo_t *cr, GdkRectangle* rect, + GtkWidgetState* state, + GtkArrowType arrow_type, GtkTextDirection direction) +{ + GdkRectangle arrow_rect; + gdouble arrow_angle; + + if (direction == GTK_TEXT_DIR_RTL) { + if (arrow_type == GTK_ARROW_LEFT) { + arrow_type = GTK_ARROW_RIGHT; + } else if (arrow_type == GTK_ARROW_RIGHT) { + arrow_type = GTK_ARROW_LEFT; + } + } + switch (arrow_type) { + case GTK_ARROW_LEFT: + arrow_angle = ARROW_LEFT; + break; + case GTK_ARROW_RIGHT: + arrow_angle = ARROW_RIGHT; + break; + case GTK_ARROW_DOWN: + arrow_angle = ARROW_DOWN; + break; + default: + arrow_angle = ARROW_UP; + break; + } + if (arrow_type == GTK_ARROW_NONE) + return MOZ_GTK_SUCCESS; + + calculate_arrow_rect(GetWidget(MOZ_GTK_BUTTON_ARROW), rect, &arrow_rect, + direction); + GtkStateFlags state_flags = GetStateFlagsFromGtkWidgetState(state); + GtkStyleContext* style = ClaimStyleContext(MOZ_GTK_BUTTON_ARROW, + direction, state_flags); + gtk_render_arrow(style, cr, arrow_angle, + arrow_rect.x, arrow_rect.y, arrow_rect.width); + ReleaseStyleContext(style); + return MOZ_GTK_SUCCESS; +} + +static gint +moz_gtk_combo_box_entry_button_paint(cairo_t *cr, GdkRectangle* rect, + GtkWidgetState* state, + gboolean input_focus, + GtkTextDirection direction) +{ + gint x_displacement, y_displacement; + GdkRectangle arrow_rect, real_arrow_rect; + GtkStateFlags state_flags = GetStateFlagsFromGtkWidgetState(state); + GtkStyleContext* style; + + GtkWidget* comboBoxEntry = GetWidget(MOZ_GTK_COMBOBOX_ENTRY_BUTTON); + moz_gtk_button_paint(cr, rect, state, GTK_RELIEF_NORMAL, + comboBoxEntry, direction); + calculate_button_inner_rect(comboBoxEntry, rect, &arrow_rect, direction); + + if (state_flags & GTK_STATE_FLAG_ACTIVE) { + style = gtk_widget_get_style_context(comboBoxEntry); + gtk_style_context_get_style(style, + "child-displacement-x", &x_displacement, + "child-displacement-y", &y_displacement, + NULL); + arrow_rect.x += x_displacement; + arrow_rect.y += y_displacement; + } + + calculate_arrow_rect(GetWidget(MOZ_GTK_COMBOBOX_ENTRY_ARROW), + &arrow_rect, &real_arrow_rect, direction); + + style = ClaimStyleContext(MOZ_GTK_COMBOBOX_ENTRY_ARROW); + gtk_render_arrow(style, cr, ARROW_DOWN, + real_arrow_rect.x, real_arrow_rect.y, + real_arrow_rect.width); + ReleaseStyleContext(style); + return MOZ_GTK_SUCCESS; +} + +static gint +moz_gtk_container_paint(cairo_t *cr, GdkRectangle* rect, + GtkWidgetState* state, + WidgetNodeType widget_type, + GtkTextDirection direction) +{ + GtkStateFlags state_flags = GetStateFlagsFromGtkWidgetState(state); + GtkStyleContext* style = ClaimStyleContext(widget_type, direction, + state_flags); + /* this is for drawing a prelight box */ + if (state_flags & GTK_STATE_FLAG_PRELIGHT) { + gtk_render_background(style, cr, + rect->x, rect->y, rect->width, rect->height); + } + + ReleaseStyleContext(style); + return MOZ_GTK_SUCCESS; +} + +static gint +moz_gtk_toggle_label_paint(cairo_t *cr, GdkRectangle* rect, + GtkWidgetState* state, + gboolean isradio, GtkTextDirection direction) +{ + if (!state->focused) + return MOZ_GTK_SUCCESS; + + GtkStyleContext *style = + ClaimStyleContext(isradio ? MOZ_GTK_RADIOBUTTON_CONTAINER : + MOZ_GTK_CHECKBUTTON_CONTAINER, + direction, + GetStateFlagsFromGtkWidgetState(state)); + gtk_render_focus(style, cr, + rect->x, rect->y, rect->width, rect->height); + + ReleaseStyleContext(style); + + return MOZ_GTK_SUCCESS; +} + +static gint +moz_gtk_toolbar_paint(cairo_t *cr, GdkRectangle* rect, + GtkTextDirection direction) +{ + GtkStyleContext* style = ClaimStyleContext(MOZ_GTK_TOOLBAR, direction); + gtk_render_background(style, cr, rect->x, rect->y, rect->width, rect->height); + gtk_render_frame(style, cr, rect->x, rect->y, rect->width, rect->height); + ReleaseStyleContext(style); + return MOZ_GTK_SUCCESS; +} + +/* See _gtk_toolbar_paint_space_line() for reference. +*/ +static gint +moz_gtk_toolbar_separator_paint(cairo_t *cr, GdkRectangle* rect, + GtkTextDirection direction) +{ + gint separator_width; + gint paint_width; + gboolean wide_separators; + + /* Defined as constants in GTK+ 2.10.14 */ + const double start_fraction = 0.2; + const double end_fraction = 0.8; + + GtkStyleContext* style = ClaimStyleContext(MOZ_GTK_TOOLBAR); + gtk_style_context_get_style(style, + "wide-separators", &wide_separators, + "separator-width", &separator_width, + NULL); + ReleaseStyleContext(style); + + style = ClaimStyleContext(MOZ_GTK_TOOLBAR_SEPARATOR, direction); + if (wide_separators) { + if (separator_width > rect->width) + separator_width = rect->width; + + gtk_render_frame(style, cr, + rect->x + (rect->width - separator_width) / 2, + rect->y + rect->height * start_fraction, + separator_width, + rect->height * (end_fraction - start_fraction)); + } else { + GtkBorder padding; + gtk_style_context_get_padding(style, GTK_STATE_FLAG_NORMAL, &padding); + + paint_width = padding.left; + if (paint_width > rect->width) + paint_width = rect->width; + + gtk_render_line(style, cr, + rect->x + (rect->width - paint_width) / 2, + rect->y + rect->height * start_fraction, + rect->x + (rect->width - paint_width) / 2, + rect->y + rect->height * end_fraction); + } + ReleaseStyleContext(style); + return MOZ_GTK_SUCCESS; +} + +static gint +moz_gtk_tooltip_paint(cairo_t *cr, const GdkRectangle* aRect, + GtkTextDirection direction) +{ + // Tooltip widget is made in GTK3 as following tree: + // Tooltip window + // Horizontal Box + // Icon (not supported by Firefox) + // Label + // Each element can be fully styled by CSS of GTK theme. + // We have to draw all elements with appropriate offset and right dimensions. + + // Tooltip drawing + GtkStyleContext* style = ClaimStyleContext(MOZ_GTK_TOOLTIP, direction); + GdkRectangle rect = *aRect; + gtk_render_background(style, cr, rect.x, rect.y, rect.width, rect.height); + gtk_render_frame(style, cr, rect.x, rect.y, rect.width, rect.height); + + // Horizontal Box drawing + // + // The box element has hard-coded 6px margin-* GtkWidget properties, which + // are added between the window dimensions and the CSS margin box of the + // horizontal box. The frame of the tooltip window is drawn in the + // 6px margin. + // For drawing Horizontal Box we have to inset drawing area by that 6px + // plus its CSS margin. + GtkStyleContext* boxStyle = + CreateStyleForWidget(gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0), style); + + rect.x += 6; + rect.y += 6; + rect.width -= 12; + rect.height -= 12; + + moz_gtk_subtract_margin(boxStyle, &rect); + gtk_render_background(boxStyle, cr, rect.x, rect.y, rect.width, rect.height); + gtk_render_frame(boxStyle, cr, rect.x, rect.y, rect.width, rect.height); + + // Label drawing + GtkBorder padding, border; + gtk_style_context_get_padding(boxStyle, GTK_STATE_FLAG_NORMAL, &padding); + moz_gtk_rectangle_inset(&rect, padding); + gtk_style_context_get_border(boxStyle, GTK_STATE_FLAG_NORMAL, &border); + moz_gtk_rectangle_inset(&rect, border); + + GtkStyleContext* labelStyle = + CreateStyleForWidget(gtk_label_new(nullptr), boxStyle); + moz_gtk_draw_styled_frame(labelStyle, cr, &rect, false); + g_object_unref(labelStyle); + + g_object_unref(boxStyle); + + ReleaseStyleContext(style); + return MOZ_GTK_SUCCESS; +} + +static gint +moz_gtk_resizer_paint(cairo_t *cr, GdkRectangle* rect, + GtkWidgetState* state, + GtkTextDirection direction) +{ + GtkStyleContext* style; + + // gtk_render_handle() draws a background, so use GtkTextView and its + // GTK_STYLE_CLASS_VIEW to match the background with textarea elements. + // The resizer is drawn with shaded variants of the background color, and + // so a transparent background would lead to a transparent resizer. + style = ClaimStyleContext(MOZ_GTK_TEXT_VIEW, GTK_TEXT_DIR_LTR, + GetStateFlagsFromGtkWidgetState(state)); + // TODO - we need to save/restore style when gtk 3.20 CSS node path + // is used + gtk_style_context_add_class(style, GTK_STYLE_CLASS_GRIP); + + // Workaround unico not respecting the text direction for resizers. + // See bug 1174248. + cairo_save(cr); + if (direction == GTK_TEXT_DIR_RTL) { + cairo_matrix_t mat; + cairo_matrix_init_translate(&mat, 2 * rect->x + rect->width, 0); + cairo_matrix_scale(&mat, -1, 1); + cairo_transform(cr, &mat); + } + + gtk_render_handle(style, cr, rect->x, rect->y, rect->width, rect->height); + cairo_restore(cr); + ReleaseStyleContext(style); + + return MOZ_GTK_SUCCESS; +} + +static gint +moz_gtk_frame_paint(cairo_t *cr, GdkRectangle* rect, + GtkTextDirection direction) +{ + GtkStyleContext* style = ClaimStyleContext(MOZ_GTK_FRAME, direction); + gtk_render_frame(style, cr, rect->x, rect->y, rect->width, rect->height); + ReleaseStyleContext(style); + return MOZ_GTK_SUCCESS; +} + +static gint +moz_gtk_progressbar_paint(cairo_t *cr, GdkRectangle* rect, + GtkTextDirection direction) +{ + GtkStyleContext* style = ClaimStyleContext(MOZ_GTK_PROGRESS_TROUGH, + direction); + gtk_render_background(style, cr, rect->x, rect->y, rect->width, rect->height); + gtk_render_frame(style, cr, rect->x, rect->y, rect->width, rect->height); + ReleaseStyleContext(style); + + return MOZ_GTK_SUCCESS; +} + +static gint +moz_gtk_progress_chunk_paint(cairo_t *cr, GdkRectangle* rect, + GtkTextDirection direction, + WidgetNodeType widget) +{ + GtkStyleContext* style; + + if (gtk_check_version(3, 20, 0) != nullptr) { + /* Ask for MOZ_GTK_PROGRESS_TROUGH instead of MOZ_GTK_PROGRESSBAR + * because ClaimStyleContext() saves/restores that style */ + style = ClaimStyleContext(MOZ_GTK_PROGRESS_TROUGH, direction); + gtk_style_context_remove_class(style, GTK_STYLE_CLASS_TROUGH); + gtk_style_context_add_class(style, GTK_STYLE_CLASS_PROGRESSBAR); + } else { + style = ClaimStyleContext(MOZ_GTK_PROGRESS_CHUNK, direction); + } + + if (widget == MOZ_GTK_PROGRESS_CHUNK_INDETERMINATE || + widget == MOZ_GTK_PROGRESS_CHUNK_VERTICAL_INDETERMINATE) { + /** + * The bar's size and the bar speed are set depending of the progress' + * size. These could also be constant for all progress bars easily. + */ + gboolean vertical = (widget == MOZ_GTK_PROGRESS_CHUNK_VERTICAL_INDETERMINATE); + + /* The size of the dimension we are going to use for the animation. */ + const gint progressSize = vertical ? rect->height : rect->width; + + /* The bar is using a fifth of the element size, based on GtkProgressBar + * activity-blocks property. */ + const gint barSize = MAX(1, progressSize / 5); + + /* Represents the travel that has to be done for a complete cycle. */ + const gint travel = 2 * (progressSize - barSize); + + /* period equals to travel / pixelsPerMillisecond + * where pixelsPerMillisecond equals progressSize / 1000.0. + * This is equivalent to 1600. */ + static const guint period = 1600; + const gint t = PR_IntervalToMilliseconds(PR_IntervalNow()) % period; + const gint dx = travel * t / period; + + if (vertical) { + rect->y += (dx < travel / 2) ? dx : travel - dx; + rect->height = barSize; + } else { + rect->x += (dx < travel / 2) ? dx : travel - dx; + rect->width = barSize; + } + } + + // gtk_render_activity was used to render progress chunks on GTK versions + // before 3.13.7, see bug 1173907. + if (!gtk_check_version(3, 13, 7)) { + gtk_render_background(style, cr, rect->x, rect->y, rect->width, rect->height); + gtk_render_frame(style, cr, rect->x, rect->y, rect->width, rect->height); + } else { + gtk_render_activity(style, cr, rect->x, rect->y, rect->width, rect->height); + } + ReleaseStyleContext(style); + + return MOZ_GTK_SUCCESS; +} + +static gint +moz_gtk_get_tab_thickness(GtkStyleContext *style) +{ + if (!notebook_has_tab_gap) + return 0; /* tabs do not overdraw the tabpanel border with "no gap" style */ + + GtkBorder border; + gtk_style_context_get_border(style, GTK_STATE_FLAG_NORMAL, &border); + if (border.top < 2) + return 2; /* some themes don't set ythickness correctly */ + + return border.top; +} + +gint +moz_gtk_get_tab_thickness(WidgetNodeType aNodeType) +{ + GtkStyleContext *style = ClaimStyleContext(aNodeType); + int thickness = moz_gtk_get_tab_thickness(style); + ReleaseStyleContext(style); + return thickness; +} + +/* actual small tabs */ +static gint +moz_gtk_tab_paint(cairo_t *cr, GdkRectangle* rect, + GtkWidgetState* state, + GtkTabFlags flags, GtkTextDirection direction, + WidgetNodeType widget) +{ + /* When the tab isn't selected, we just draw a notebook extension. + * When it is selected, we overwrite the adjacent border of the tabpanel + * touching the tab with a pierced border (called "the gap") to make the + * tab appear physically attached to the tabpanel; see details below. */ + + GtkStyleContext* style; + GdkRectangle tabRect; + GdkRectangle focusRect; + GdkRectangle backRect; + int initial_gap = 0; + bool isBottomTab = (widget == MOZ_GTK_TAB_BOTTOM); + + style = ClaimStyleContext(widget, direction, + GetStateFlagsFromGtkTabFlags(flags)); + tabRect = *rect; + + if (flags & MOZ_GTK_TAB_FIRST) { + gtk_style_context_get_style(style, "initial-gap", &initial_gap, NULL); + tabRect.width -= initial_gap; + + if (direction != GTK_TEXT_DIR_RTL) { + tabRect.x += initial_gap; + } + } + + focusRect = backRect = tabRect; + + if (notebook_has_tab_gap) { + if ((flags & MOZ_GTK_TAB_SELECTED) == 0) { + /* Only draw the tab */ + gtk_render_extension(style, cr, + tabRect.x, tabRect.y, tabRect.width, tabRect.height, + isBottomTab ? GTK_POS_TOP : GTK_POS_BOTTOM ); + } else { + /* Draw the tab and the gap + * We want the gap to be positioned exactly on the tabpanel top + * border; since tabbox.css may set a negative margin so that the tab + * frame rect already overlaps the tabpanel frame rect, we need to take + * that into account when drawing. To that effect, nsNativeThemeGTK + * passes us this negative margin (bmargin in the graphic below) in the + * lowest bits of |flags|. We use it to set gap_voffset, the distance + * between the top of the gap and the bottom of the tab (resp. the + * bottom of the gap and the top of the tab when we draw a bottom tab), + * while ensuring that the gap always touches the border of the tab, + * i.e. 0 <= gap_voffset <= gap_height, to avoid surprinsing results + * with big negative or positive margins. + * Here is a graphical explanation in the case of top tabs: + * ___________________________ + * / \ + * | T A B | + * ----------|. . . . . . . . . . . . . . .|----- top of tabpanel + * : ^ bmargin : ^ + * : | (-negative margin, : | + * bottom : v passed in flags) : | gap_height + * of -> :.............................: | (the size of the + * the tab . part of the gap . | tabpanel top border) + * . outside of the tab . v + * ---------------------------------------------- + * + * To draw the gap, we use gtk_paint_box_gap(), see comment in + * moz_gtk_tabpanels_paint(). This box_gap is made 3 * gap_height tall, + * which should suffice to ensure that the only visible border is the + * pierced one. If the tab is in the middle, we make the box_gap begin + * a bit to the left of the tab and end a bit to the right, adjusting + * the gap position so it still is under the tab, because we want the + * rendering of a gap in the middle of a tabpanel. This is the role of + * the gints gap_{l,r}_offset. On the contrary, if the tab is the + * first, we align the start border of the box_gap with the start + * border of the tab (left if LTR, right if RTL), by setting the + * appropriate offset to 0.*/ + gint gap_loffset, gap_roffset, gap_voffset, gap_height; + + /* Get height needed by the gap */ + gap_height = moz_gtk_get_tab_thickness(style); + + /* Extract gap_voffset from the first bits of flags */ + gap_voffset = flags & MOZ_GTK_TAB_MARGIN_MASK; + if (gap_voffset > gap_height) + gap_voffset = gap_height; + + /* Set gap_{l,r}_offset to appropriate values */ + gap_loffset = gap_roffset = 20; /* should be enough */ + if (flags & MOZ_GTK_TAB_FIRST) { + if (direction == GTK_TEXT_DIR_RTL) + gap_roffset = initial_gap; + else + gap_loffset = initial_gap; + } + + if (isBottomTab) { + /* Draw the tab on bottom */ + focusRect.y += gap_voffset; + focusRect.height -= gap_voffset; + + gtk_render_extension(style, cr, + tabRect.x, tabRect.y + gap_voffset, tabRect.width, + tabRect.height - gap_voffset, GTK_POS_TOP); + + gtk_style_context_remove_region(style, GTK_STYLE_REGION_TAB); + + backRect.y += (gap_voffset - gap_height); + backRect.height = gap_height; + + /* Draw the gap; erase with background color before painting in + * case theme does not */ + gtk_render_background(style, cr, backRect.x, backRect.y, + backRect.width, backRect.height); + cairo_save(cr); + cairo_rectangle(cr, backRect.x, backRect.y, backRect.width, backRect.height); + cairo_clip(cr); + + gtk_render_frame_gap(style, cr, + tabRect.x - gap_loffset, + tabRect.y + gap_voffset - 3 * gap_height, + tabRect.width + gap_loffset + gap_roffset, + 3 * gap_height, GTK_POS_BOTTOM, + gap_loffset, gap_loffset + tabRect.width); + cairo_restore(cr); + } else { + /* Draw the tab on top */ + focusRect.height -= gap_voffset; + gtk_render_extension(style, cr, + tabRect.x, tabRect.y, tabRect.width, + tabRect.height - gap_voffset, GTK_POS_BOTTOM); + + gtk_style_context_remove_region(style, GTK_STYLE_REGION_TAB); + + backRect.y += (tabRect.height - gap_voffset); + backRect.height = gap_height; + + /* Draw the gap; erase with background color before painting in + * case theme does not */ + gtk_render_background(style, cr, backRect.x, backRect.y, + backRect.width, backRect.height); + + cairo_save(cr); + cairo_rectangle(cr, backRect.x, backRect.y, backRect.width, backRect.height); + cairo_clip(cr); + + gtk_render_frame_gap(style, cr, + tabRect.x - gap_loffset, + tabRect.y + tabRect.height - gap_voffset, + tabRect.width + gap_loffset + gap_roffset, + 3 * gap_height, GTK_POS_TOP, + gap_loffset, gap_loffset + tabRect.width); + cairo_restore(cr); + } + } + } else { + gtk_render_background(style, cr, tabRect.x, tabRect.y, tabRect.width, tabRect.height); + gtk_render_frame(style, cr, tabRect.x, tabRect.y, tabRect.width, tabRect.height); + } + + if (state->focused) { + /* Paint the focus ring */ + GtkBorder padding; + gtk_style_context_get_padding(style, GetStateFlagsFromGtkWidgetState(state), &padding); + + focusRect.x += padding.left; + focusRect.width -= (padding.left + padding.right); + focusRect.y += padding.top; + focusRect.height -= (padding.top + padding.bottom); + + gtk_render_focus(style, cr, + focusRect.x, focusRect.y, focusRect.width, focusRect.height); + } + ReleaseStyleContext(style); + + return MOZ_GTK_SUCCESS; +} + +/* tab area*/ +static gint +moz_gtk_tabpanels_paint(cairo_t *cr, GdkRectangle* rect, + GtkTextDirection direction) +{ + GtkStyleContext* style = ClaimStyleContext(MOZ_GTK_TABPANELS, direction); + gtk_render_background(style, cr, rect->x, rect->y, + rect->width, rect->height); + /* + * The gap size is not needed in moz_gtk_tabpanels_paint because + * the gap will be painted with the foreground tab in moz_gtk_tab_paint. + * + * However, if moz_gtk_tabpanels_paint just uses gtk_render_frame(), + * the theme will think that there are no tabs and may draw something + * different.Hence the trick of using two clip regions, and drawing the + * gap outside each clip region, to get the correct frame for + * a tabpanel with tabs. + */ + /* left side */ + cairo_save(cr); + cairo_rectangle(cr, rect->x, rect->y, + rect->x + rect->width / 2, + rect->y + rect->height); + cairo_clip(cr); + gtk_render_frame_gap(style, cr, + rect->x, rect->y, + rect->width, rect->height, + GTK_POS_TOP, rect->width - 1, rect->width); + cairo_restore(cr); + + /* right side */ + cairo_save(cr); + cairo_rectangle(cr, rect->x + rect->width / 2, rect->y, + rect->x + rect->width, + rect->y + rect->height); + cairo_clip(cr); + gtk_render_frame_gap(style, cr, + rect->x, rect->y, + rect->width, rect->height, + GTK_POS_TOP, 0, 1); + cairo_restore(cr); + + ReleaseStyleContext(style); + return MOZ_GTK_SUCCESS; +} + +static gint +moz_gtk_tab_scroll_arrow_paint(cairo_t *cr, GdkRectangle* rect, + GtkWidgetState* state, + GtkArrowType arrow_type, + GtkTextDirection direction) +{ + GtkStyleContext* style; + gdouble arrow_angle; + gint arrow_size = MIN(rect->width, rect->height); + gint x = rect->x + (rect->width - arrow_size) / 2; + gint y = rect->y + (rect->height - arrow_size) / 2; + + if (direction == GTK_TEXT_DIR_RTL) { + arrow_type = (arrow_type == GTK_ARROW_LEFT) ? + GTK_ARROW_RIGHT : GTK_ARROW_LEFT; + } + switch (arrow_type) { + case GTK_ARROW_LEFT: + arrow_angle = ARROW_LEFT; + break; + case GTK_ARROW_RIGHT: + arrow_angle = ARROW_RIGHT; + break; + case GTK_ARROW_DOWN: + arrow_angle = ARROW_DOWN; + break; + default: + arrow_angle = ARROW_UP; + break; + } + if (arrow_type != GTK_ARROW_NONE) { + style = ClaimStyleContext(MOZ_GTK_TAB_SCROLLARROW, direction, + GetStateFlagsFromGtkWidgetState(state)); + gtk_render_arrow(style, cr, arrow_angle, + x, y, arrow_size); + ReleaseStyleContext(style); + } + return MOZ_GTK_SUCCESS; +} + +static gint +moz_gtk_menu_bar_paint(cairo_t *cr, GdkRectangle* rect, + GtkTextDirection direction) +{ + GtkStyleContext* style; + + GtkWidget* widget = GetWidget(MOZ_GTK_MENUBAR); + gtk_widget_set_direction(widget, direction); + + style = gtk_widget_get_style_context(widget); + gtk_style_context_save(style); + gtk_style_context_add_class(style, GTK_STYLE_CLASS_MENUBAR); + gtk_render_background(style, cr, rect->x, rect->y, rect->width, rect->height); + gtk_render_frame(style, cr, rect->x, rect->y, rect->width, rect->height); + gtk_style_context_restore(style); + + return MOZ_GTK_SUCCESS; +} + +static gint +moz_gtk_menu_popup_paint(cairo_t *cr, GdkRectangle* rect, + GtkTextDirection direction) +{ + GtkStyleContext* style; + + GtkWidget* widget = GetWidget(MOZ_GTK_MENUPOPUP); + gtk_widget_set_direction(widget, direction); + + // Draw a backing toplevel. This fixes themes that don't provide a menu + // background, and depend on the GtkMenu's implementation window to provide it. + moz_gtk_window_paint(cr, rect, direction); + + style = gtk_widget_get_style_context(widget); + gtk_style_context_save(style); + gtk_style_context_add_class(style, GTK_STYLE_CLASS_MENU); + + gtk_render_background(style, cr, rect->x, rect->y, rect->width, rect->height); + gtk_render_frame(style, cr, rect->x, rect->y, rect->width, rect->height); + gtk_style_context_restore(style); + + return MOZ_GTK_SUCCESS; +} + +// See gtk_menu_item_draw() for reference. +static gint +moz_gtk_menu_separator_paint(cairo_t *cr, GdkRectangle* rect, + GtkTextDirection direction) +{ + GtkStyleContext* style; + gboolean wide_separators; + gint separator_height; + gint x, y, w; + GtkBorder padding; + + style = ClaimStyleContext(MOZ_GTK_MENUSEPARATOR, direction); + gtk_style_context_get_padding(style, GTK_STATE_FLAG_NORMAL, &padding); + + x = rect->x; + y = rect->y; + w = rect->width; + + gtk_style_context_save(style); + gtk_style_context_add_class(style, GTK_STYLE_CLASS_SEPARATOR); + + gtk_style_context_get_style(style, + "wide-separators", &wide_separators, + "separator-height", &separator_height, + NULL); + + if (wide_separators) { + gtk_render_frame(style, cr, + x + padding.left, + y + padding.top, + w - padding.left - padding.right, + separator_height); + } else { + gtk_render_line(style, cr, + x + padding.left, + y + padding.top, + x + w - padding.right - 1, + y + padding.top); + } + + gtk_style_context_restore(style); + ReleaseStyleContext(style); + + return MOZ_GTK_SUCCESS; +} + +// See gtk_menu_item_draw() for reference. +static gint +moz_gtk_menu_item_paint(WidgetNodeType widget, cairo_t *cr, GdkRectangle* rect, + GtkWidgetState* state, GtkTextDirection direction) +{ + gint x, y, w, h; + + if (state->inHover && !state->disabled) { + GtkStateFlags state_flags = GetStateFlagsFromGtkWidgetState(state); + GtkStyleContext* style = + ClaimStyleContext(widget, direction, state_flags); + + bool pre_3_6 = gtk_check_version(3, 6, 0) != nullptr; + if (pre_3_6) { + // GTK+ 3.4 saves the style context and adds the menubar class to + // menubar children, but does each of these only when drawing, not + // during layout. + gtk_style_context_save(style); + if (widget == MOZ_GTK_MENUBARITEM) { + gtk_style_context_add_class(style, GTK_STYLE_CLASS_MENUBAR); + } + } + + x = rect->x; + y = rect->y; + w = rect->width; + h = rect->height; + + gtk_render_background(style, cr, x, y, w, h); + gtk_render_frame(style, cr, x, y, w, h); + + if (pre_3_6) { + gtk_style_context_restore(style); + } + ReleaseStyleContext(style); + } + + return MOZ_GTK_SUCCESS; +} + +static gint +moz_gtk_menu_arrow_paint(cairo_t *cr, GdkRectangle* rect, + GtkWidgetState* state, + GtkTextDirection direction) +{ + GtkStateFlags state_flags = GetStateFlagsFromGtkWidgetState(state); + GtkStyleContext* style = ClaimStyleContext(MOZ_GTK_MENUITEM, + direction, state_flags); + gtk_render_arrow(style, cr, + (direction == GTK_TEXT_DIR_LTR) ? ARROW_RIGHT : ARROW_LEFT, + rect->x, rect->y, rect->width); + ReleaseStyleContext(style); + return MOZ_GTK_SUCCESS; +} + +// See gtk_real_check_menu_item_draw_indicator() for reference. +static gint +moz_gtk_check_menu_item_paint(cairo_t *cr, GdkRectangle* rect, + GtkWidgetState* state, + gboolean checked, gboolean isradio, + GtkTextDirection direction) +{ + GtkStateFlags state_flags = GetStateFlagsFromGtkWidgetState(state); + GtkStyleContext* style; + GtkBorder padding; + gint indicator_size, horizontal_padding; + gint x, y; + + moz_gtk_menu_item_paint(MOZ_GTK_MENUITEM, cr, rect, state, direction); + + if (checked) { + state_flags = static_cast<GtkStateFlags>(state_flags|checkbox_check_state); + } + + style = ClaimStyleContext(isradio ? MOZ_GTK_RADIOMENUITEM_CONTAINER : + MOZ_GTK_CHECKMENUITEM_CONTAINER, + direction); + gtk_style_context_get_style(style, + "indicator-size", &indicator_size, + "horizontal-padding", &horizontal_padding, + NULL); + ReleaseStyleContext(style); + + style = ClaimStyleContext(isradio ? MOZ_GTK_RADIOMENUITEM : + MOZ_GTK_CHECKMENUITEM, + direction, state_flags); + gtk_style_context_get_padding(style, state_flags, &padding); + gint offset = padding.left + 2; + + if (direction == GTK_TEXT_DIR_RTL) { + x = rect->width - indicator_size - offset - horizontal_padding; + } + else { + x = rect->x + offset + horizontal_padding; + } + y = rect->y + (rect->height - indicator_size) / 2; + + if (gtk_check_version(3, 20, 0) == nullptr) { + gtk_render_background(style, cr, x, y, indicator_size, indicator_size); + gtk_render_frame(style, cr, x, y, indicator_size, indicator_size); + } + + if (isradio) { + gtk_render_option(style, cr, x, y, indicator_size, indicator_size); + } else { + gtk_render_check(style, cr, x, y, indicator_size, indicator_size); + } + ReleaseStyleContext(style); + + return MOZ_GTK_SUCCESS; +} + +static gint +moz_gtk_info_bar_paint(cairo_t *cr, GdkRectangle* rect, + GtkWidgetState* state) +{ + GtkStyleContext *style = + ClaimStyleContext(MOZ_GTK_INFO_BAR, GTK_TEXT_DIR_LTR, + GetStateFlagsFromGtkWidgetState(state)); + gtk_render_background(style, cr, rect->x, rect->y, rect->width, + rect->height); + gtk_render_frame(style, cr, rect->x, rect->y, rect->width, rect->height); + ReleaseStyleContext(style); + + return MOZ_GTK_SUCCESS; +} + +static void +moz_gtk_add_style_margin(GtkStyleContext* style, + gint* left, gint* top, gint* right, gint* bottom) +{ + GtkBorder margin; + + gtk_style_context_get_margin(style, GTK_STATE_FLAG_NORMAL, &margin); + + *left += margin.left; + *right += margin.right; + *top += margin.top; + *bottom += margin.bottom; +} + +static void +moz_gtk_add_style_border(GtkStyleContext* style, + gint* left, gint* top, gint* right, gint* bottom) +{ + GtkBorder border; + + gtk_style_context_get_border(style, GTK_STATE_FLAG_NORMAL, &border); + + *left += border.left; + *right += border.right; + *top += border.top; + *bottom += border.bottom; +} + +static void +moz_gtk_add_style_padding(GtkStyleContext* style, + gint* left, gint* top, gint* right, gint* bottom) +{ + GtkBorder padding; + + gtk_style_context_get_padding(style, GTK_STATE_FLAG_NORMAL, &padding); + + *left += padding.left; + *right += padding.right; + *top += padding.top; + *bottom += padding.bottom; +} + +static void moz_gtk_add_margin_border_padding(GtkStyleContext *style, + gint* left, gint* top, + gint* right, gint* bottom) +{ + moz_gtk_add_style_margin(style, left, top, right, bottom); + moz_gtk_add_style_border(style, left, top, right, bottom); + moz_gtk_add_style_padding(style, left, top, right, bottom); +} + +gint +moz_gtk_get_widget_border(WidgetNodeType widget, gint* left, gint* top, + gint* right, gint* bottom, GtkTextDirection direction, + gboolean inhtml) +{ + GtkWidget* w; + GtkStyleContext* style; + *left = *top = *right = *bottom = 0; + + switch (widget) { + case MOZ_GTK_BUTTON: + case MOZ_GTK_TOOLBAR_BUTTON: + { + style = ClaimStyleContext(MOZ_GTK_BUTTON); + + *left = *top = *right = *bottom = + gtk_container_get_border_width(GTK_CONTAINER(GetWidget(MOZ_GTK_BUTTON))); + + if (widget == MOZ_GTK_TOOLBAR_BUTTON) { + gtk_style_context_save(style); + gtk_style_context_add_class(style, "image-button"); + } + + moz_gtk_add_style_padding(style, left, top, right, bottom); + + if (widget == MOZ_GTK_TOOLBAR_BUTTON) + gtk_style_context_restore(style); + + // XXX: Subtract 1 pixel from the border to account for the added + // -moz-focus-inner border (Bug 1228281). + *left -= 1; *top -= 1; *right -= 1; *bottom -= 1; + moz_gtk_add_style_border(style, left, top, right, bottom); + + ReleaseStyleContext(style); + return MOZ_GTK_SUCCESS; + } + case MOZ_GTK_ENTRY: + { + style = ClaimStyleContext(MOZ_GTK_ENTRY); + + // XXX: Subtract 1 pixel from the padding to account for the default + // padding in forms.css. See bug 1187385. + *left = *top = *right = *bottom = -1; + moz_gtk_add_style_padding(style, left, top, right, bottom); + moz_gtk_add_style_border(style, left, top, right, bottom); + + ReleaseStyleContext(style); + return MOZ_GTK_SUCCESS; + } + case MOZ_GTK_TEXT_VIEW: + case MOZ_GTK_TREEVIEW: + { + style = ClaimStyleContext(MOZ_GTK_SCROLLED_WINDOW); + moz_gtk_add_style_border(style, left, top, right, bottom); + ReleaseStyleContext(style); + return MOZ_GTK_SUCCESS; + } + case MOZ_GTK_TREE_HEADER_CELL: + { + /* A Tree Header in GTK is just a different styled button + * It must be placed in a TreeView for getting the correct style + * assigned. + * That is why the following code is the same as for MOZ_GTK_BUTTON. + * */ + *left = *top = *right = *bottom = + gtk_container_get_border_width(GTK_CONTAINER( + GetWidget(MOZ_GTK_TREE_HEADER_CELL))); + + style = ClaimStyleContext(MOZ_GTK_TREE_HEADER_CELL); + moz_gtk_add_style_border(style, left, top, right, bottom); + moz_gtk_add_style_padding(style, left, top, right, bottom); + ReleaseStyleContext(style); + return MOZ_GTK_SUCCESS; + } + case MOZ_GTK_TREE_HEADER_SORTARROW: + w = GetWidget(MOZ_GTK_TREE_HEADER_SORTARROW); + break; + case MOZ_GTK_DROPDOWN_ENTRY: + w = GetWidget(MOZ_GTK_COMBOBOX_ENTRY_TEXTAREA); + break; + case MOZ_GTK_DROPDOWN_ARROW: + w = GetWidget(MOZ_GTK_COMBOBOX_ENTRY_BUTTON); + break; + case MOZ_GTK_DROPDOWN: + { + /* We need to account for the arrow on the dropdown, so text + * doesn't come too close to the arrow, or in some cases spill + * into the arrow. */ + gboolean wide_separators; + gint separator_width; + GtkRequisition arrow_req; + GtkBorder border; + + *left = *top = *right = *bottom = + gtk_container_get_border_width(GTK_CONTAINER( + GetWidget(MOZ_GTK_COMBOBOX_BUTTON))); + style = ClaimStyleContext(MOZ_GTK_COMBOBOX_BUTTON); + moz_gtk_add_style_padding(style, left, top, right, bottom); + moz_gtk_add_style_border(style, left, top, right, bottom); + ReleaseStyleContext(style); + + /* If there is no separator, don't try to count its width. */ + separator_width = 0; + GtkWidget* comboBoxSeparator = GetWidget(MOZ_GTK_COMBOBOX_SEPARATOR); + if (comboBoxSeparator) { + style = gtk_widget_get_style_context(comboBoxSeparator); + gtk_style_context_get_style(style, + "wide-separators", &wide_separators, + "separator-width", &separator_width, + NULL); + + if (!wide_separators) { + gtk_style_context_get_border(style, GTK_STATE_FLAG_NORMAL, + &border); + separator_width = border.left; + } + } + + gtk_widget_get_preferred_size(GetWidget(MOZ_GTK_COMBOBOX_ARROW), + NULL, &arrow_req); + + if (direction == GTK_TEXT_DIR_RTL) + *left += separator_width + arrow_req.width; + else + *right += separator_width + arrow_req.width; + + return MOZ_GTK_SUCCESS; + } + case MOZ_GTK_TABPANELS: + w = GetWidget(MOZ_GTK_TABPANELS); + break; + case MOZ_GTK_PROGRESSBAR: + w = GetWidget(MOZ_GTK_PROGRESSBAR); + break; + case MOZ_GTK_SPINBUTTON_ENTRY: + case MOZ_GTK_SPINBUTTON_UP: + case MOZ_GTK_SPINBUTTON_DOWN: + w = GetWidget(MOZ_GTK_SPINBUTTON); + break; + case MOZ_GTK_SCALE_HORIZONTAL: + case MOZ_GTK_SCALE_VERTICAL: + w = GetWidget(widget); + break; + case MOZ_GTK_FRAME: + w = GetWidget(MOZ_GTK_FRAME); + break; + case MOZ_GTK_CHECKBUTTON_CONTAINER: + case MOZ_GTK_RADIOBUTTON_CONTAINER: + { + w = GetWidget(widget); + style = gtk_widget_get_style_context(w); + + *left = *top = *right = *bottom = gtk_container_get_border_width(GTK_CONTAINER(w)); + moz_gtk_add_style_border(style, + left, top, right, bottom); + moz_gtk_add_style_padding(style, + left, top, right, bottom); + return MOZ_GTK_SUCCESS; + } + case MOZ_GTK_MENUPOPUP: + w = GetWidget(MOZ_GTK_MENUPOPUP); + break; + case MOZ_GTK_MENUBARITEM: + case MOZ_GTK_MENUITEM: + case MOZ_GTK_CHECKMENUITEM: + case MOZ_GTK_RADIOMENUITEM: + { + // Bug 1274143 for MOZ_GTK_MENUBARITEM + WidgetNodeType type = + widget == MOZ_GTK_MENUBARITEM || widget == MOZ_GTK_MENUITEM ? + MOZ_GTK_MENUITEM : MOZ_GTK_CHECKMENUITEM_CONTAINER; + style = ClaimStyleContext(type); + + moz_gtk_add_style_padding(style, left, top, right, bottom); + + ReleaseStyleContext(style); + return MOZ_GTK_SUCCESS; + } + case MOZ_GTK_INFO_BAR: + w = GetWidget(MOZ_GTK_INFO_BAR); + break; + case MOZ_GTK_TOOLTIP: + { + style = ClaimStyleContext(MOZ_GTK_TOOLTIP); + // In GTK 3 there are 6 pixels of additional margin around the box. + // See details there: + // https://github.com/GNOME/gtk/blob/5ea69a136bd7e4970b3a800390e20314665aaed2/gtk/ui/gtktooltipwindow.ui#L11 + *left = *right = *top = *bottom = 6; + + // We also need to add margin/padding/borders from Tooltip content. + // Tooltip contains horizontal box, where icon and label is put. + // We ignore icon as long as we don't have support for it. + GtkStyleContext* boxStyle = + CreateStyleForWidget(gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0), + style); + moz_gtk_add_margin_border_padding(boxStyle, + left, top, right, bottom); + + GtkStyleContext* labelStyle = + CreateStyleForWidget(gtk_label_new(nullptr), boxStyle); + moz_gtk_add_margin_border_padding(labelStyle, + left, top, right, bottom); + + g_object_unref(labelStyle); + g_object_unref(boxStyle); + + ReleaseStyleContext(style); + return MOZ_GTK_SUCCESS; + } + case MOZ_GTK_SCROLLBAR_VERTICAL: + case MOZ_GTK_SCROLLBAR_TROUGH_HORIZONTAL: + { + if (gtk_check_version(3,20,0) == nullptr) { + style = ClaimStyleContext(widget); + moz_gtk_add_margin_border_padding(style, left, top, right, bottom); + ReleaseStyleContext(style); + if (widget == MOZ_GTK_SCROLLBAR_VERTICAL) { + style = ClaimStyleContext(MOZ_GTK_SCROLLBAR_CONTENTS_VERTICAL); + moz_gtk_add_margin_border_padding(style, left, top, right, bottom); + ReleaseStyleContext(style); + } + } else { + MozGtkScrollbarMetrics metrics; + moz_gtk_get_scrollbar_metrics(&metrics); + /* Top and bottom border for whole vertical scrollbar, top and bottom + * border for horizontal track - to correctly position thumb element */ + *top = *bottom = metrics.trough_border; + } + return MOZ_GTK_SUCCESS; + } + break; + + case MOZ_GTK_SCROLLBAR_HORIZONTAL: + case MOZ_GTK_SCROLLBAR_TROUGH_VERTICAL: + { + if (gtk_check_version(3,20,0) == nullptr) { + style = ClaimStyleContext(widget); + moz_gtk_add_margin_border_padding(style, left, top, right, bottom); + ReleaseStyleContext(style); + if (widget == MOZ_GTK_SCROLLBAR_HORIZONTAL) { + style = ClaimStyleContext(MOZ_GTK_SCROLLBAR_CONTENTS_HORIZONTAL); + moz_gtk_add_margin_border_padding(style, left, top, right, bottom); + ReleaseStyleContext(style); + } + } else { + MozGtkScrollbarMetrics metrics; + moz_gtk_get_scrollbar_metrics(&metrics); + *left = *right = metrics.trough_border; + } + return MOZ_GTK_SUCCESS; + } + break; + + case MOZ_GTK_SCROLLBAR_THUMB_HORIZONTAL: + case MOZ_GTK_SCROLLBAR_THUMB_VERTICAL: + { + if (gtk_check_version(3,20,0) == nullptr) { + style = ClaimStyleContext(widget); + moz_gtk_add_margin_border_padding(style, left, top, right, bottom); + ReleaseStyleContext(style); + } + return MOZ_GTK_SUCCESS; + } + break; + /* These widgets have no borders, since they are not containers. */ + case MOZ_GTK_CHECKBUTTON_LABEL: + case MOZ_GTK_RADIOBUTTON_LABEL: + case MOZ_GTK_SPLITTER_HORIZONTAL: + case MOZ_GTK_SPLITTER_VERTICAL: + case MOZ_GTK_CHECKBUTTON: + case MOZ_GTK_RADIOBUTTON: + case MOZ_GTK_SCROLLBAR_BUTTON: + case MOZ_GTK_SCALE_THUMB_HORIZONTAL: + case MOZ_GTK_SCALE_THUMB_VERTICAL: + case MOZ_GTK_GRIPPER: + case MOZ_GTK_PROGRESS_CHUNK: + case MOZ_GTK_PROGRESS_CHUNK_INDETERMINATE: + case MOZ_GTK_PROGRESS_CHUNK_VERTICAL_INDETERMINATE: + case MOZ_GTK_TREEVIEW_EXPANDER: + case MOZ_GTK_TOOLBAR_SEPARATOR: + case MOZ_GTK_MENUSEPARATOR: + /* These widgets have no borders.*/ + case MOZ_GTK_SPINBUTTON: + case MOZ_GTK_WINDOW: + case MOZ_GTK_RESIZER: + case MOZ_GTK_MENUARROW: + case MOZ_GTK_TOOLBARBUTTON_ARROW: + case MOZ_GTK_TOOLBAR: + case MOZ_GTK_MENUBAR: + case MOZ_GTK_TAB_SCROLLARROW: + return MOZ_GTK_SUCCESS; + default: + g_warning("Unsupported widget type: %d", widget); + return MOZ_GTK_UNKNOWN_WIDGET; + } + /* TODO - we're still missing some widget implementations */ + if (w) { + moz_gtk_add_style_border(gtk_widget_get_style_context(w), + left, top, right, bottom); + } + return MOZ_GTK_SUCCESS; +} + +gint +moz_gtk_get_tab_border(gint* left, gint* top, gint* right, gint* bottom, + GtkTextDirection direction, GtkTabFlags flags, + WidgetNodeType widget) +{ + GtkStyleContext* style = ClaimStyleContext(widget, direction, + GetStateFlagsFromGtkTabFlags(flags)); + + *left = *top = *right = *bottom = 0; + moz_gtk_add_style_padding(style, left, top, right, bottom); + + // Gtk >= 3.20 does not use those styles + if (gtk_check_version(3, 20, 0) != nullptr) { + int tab_curvature; + + gtk_style_context_get_style(style, "tab-curvature", &tab_curvature, NULL); + *left += tab_curvature; + *right += tab_curvature; + + if (flags & MOZ_GTK_TAB_FIRST) { + int initial_gap = 0; + gtk_style_context_get_style(style, "initial-gap", &initial_gap, NULL); + if (direction == GTK_TEXT_DIR_RTL) + *right += initial_gap; + else + *left += initial_gap; + } + } else { + GtkBorder margin; + + gtk_style_context_get_margin(style, GTK_STATE_FLAG_NORMAL, &margin); + *left += margin.left; + *right += margin.right; + + if (flags & MOZ_GTK_TAB_FIRST) { + ReleaseStyleContext(style); + style = ClaimStyleContext(MOZ_GTK_NOTEBOOK_HEADER, direction); + gtk_style_context_get_margin(style, GTK_STATE_FLAG_NORMAL, &margin); + *left += margin.left; + *right += margin.right; + } + } + + ReleaseStyleContext(style); + return MOZ_GTK_SUCCESS; +} + +gint +moz_gtk_get_combo_box_entry_button_size(gint* width, gint* height) +{ + /* + * We get the requisition of the drop down button, which includes + * all padding, border and focus line widths the button uses, + * as well as the minimum arrow size and its padding + * */ + GtkRequisition requisition; + + gtk_widget_get_preferred_size(GetWidget(MOZ_GTK_COMBOBOX_ENTRY_BUTTON), + NULL, &requisition); + *width = requisition.width; + *height = requisition.height; + + return MOZ_GTK_SUCCESS; +} + +gint +moz_gtk_get_tab_scroll_arrow_size(gint* width, gint* height) +{ + gint arrow_size; + + GtkStyleContext *style = ClaimStyleContext(MOZ_GTK_TABPANELS); + gtk_style_context_get_style(style, + "scroll-arrow-hlength", &arrow_size, + NULL); + ReleaseStyleContext(style); + + *height = *width = arrow_size; + + return MOZ_GTK_SUCCESS; +} + +void +moz_gtk_get_arrow_size(WidgetNodeType widgetType, gint* width, gint* height) +{ + GtkWidget* widget; + switch (widgetType) { + case MOZ_GTK_DROPDOWN: + widget = GetWidget(MOZ_GTK_COMBOBOX_ARROW); + break; + default: + widget = GetWidget(MOZ_GTK_BUTTON_ARROW); + break; + } + + GtkRequisition requisition; + gtk_widget_get_preferred_size(widget, NULL, &requisition); + *width = requisition.width; + *height = requisition.height; +} + +gint +moz_gtk_get_toolbar_separator_width(gint* size) +{ + gboolean wide_separators; + gint separator_width; + GtkBorder border; + + GtkStyleContext* style = ClaimStyleContext(MOZ_GTK_TOOLBAR); + gtk_style_context_get_style(style, + "space-size", size, + "wide-separators", &wide_separators, + "separator-width", &separator_width, + NULL); + /* Just in case... */ + gtk_style_context_get_border(style, GTK_STATE_FLAG_NORMAL, &border); + *size = MAX(*size, (wide_separators ? separator_width : border.left)); + ReleaseStyleContext(style); + return MOZ_GTK_SUCCESS; +} + +gint +moz_gtk_get_expander_size(gint* size) +{ + GtkStyleContext* style = ClaimStyleContext(MOZ_GTK_EXPANDER); + gtk_style_context_get_style(style, + "expander-size", size, + NULL); + ReleaseStyleContext(style); + return MOZ_GTK_SUCCESS; +} + +gint +moz_gtk_get_treeview_expander_size(gint* size) +{ + GtkStyleContext* style = ClaimStyleContext(MOZ_GTK_TREEVIEW); + gtk_style_context_get_style(style, "expander-size", size, NULL); + ReleaseStyleContext(style); + return MOZ_GTK_SUCCESS; +} + +// See gtk_menu_item_draw() for reference. +gint +moz_gtk_get_menu_separator_height(gint *size) +{ + gboolean wide_separators; + gint separator_height; + GtkBorder padding; + GtkStyleContext* style = ClaimStyleContext(MOZ_GTK_MENUSEPARATOR); + gtk_style_context_get_padding(style, GTK_STATE_FLAG_NORMAL, &padding); + + gtk_style_context_save(style); + gtk_style_context_add_class(style, GTK_STYLE_CLASS_SEPARATOR); + + gtk_style_context_get_style(style, + "wide-separators", &wide_separators, + "separator-height", &separator_height, + NULL); + + gtk_style_context_restore(style); + ReleaseStyleContext(style); + + *size = padding.top + padding.bottom; + *size += (wide_separators) ? separator_height : 1; + + return MOZ_GTK_SUCCESS; +} + +void +moz_gtk_get_entry_min_height(gint* height) +{ + GtkStyleContext* style = ClaimStyleContext(MOZ_GTK_ENTRY); + if (!gtk_check_version(3, 20, 0)) { + gtk_style_context_get(style, gtk_style_context_get_state(style), + "min-height", height, + nullptr); + } else { + *height = 0; + } + + GtkBorder border; + GtkBorder padding; + gtk_style_context_get_border(style, GTK_STATE_FLAG_NORMAL, &border); + gtk_style_context_get_padding(style, GTK_STATE_FLAG_NORMAL, &padding); + + *height += (border.top + border.bottom + padding.top + padding.bottom); + ReleaseStyleContext(style); +} + +void +moz_gtk_get_scale_metrics(GtkOrientation orient, gint* scale_width, + gint* scale_height) +{ + WidgetNodeType widget = (orient == GTK_ORIENTATION_HORIZONTAL) ? + MOZ_GTK_SCALE_HORIZONTAL : + MOZ_GTK_SCALE_VERTICAL; + + if (gtk_check_version(3, 20, 0) != nullptr) { + gint thumb_length, thumb_height, trough_border; + moz_gtk_get_scalethumb_metrics(orient, &thumb_length, &thumb_height); + + GtkStyleContext* style = ClaimStyleContext(widget); + gtk_style_context_get_style(style, "trough-border", &trough_border, NULL); + + if (orient == GTK_ORIENTATION_HORIZONTAL) { + *scale_width = thumb_length + trough_border * 2; + *scale_height = thumb_height + trough_border * 2; + } else { + *scale_width = thumb_height + trough_border * 2; + *scale_height = thumb_length + trough_border * 2; + } + ReleaseStyleContext(style); + } else { + GtkStyleContext* style = ClaimStyleContext(widget); + gtk_style_context_get(style, gtk_style_context_get_state(style), + "min-width", scale_width, + "min-height", scale_height, + nullptr); + ReleaseStyleContext(style); + } +} + +gint +moz_gtk_get_scalethumb_metrics(GtkOrientation orient, gint* thumb_length, gint* thumb_height) +{ + + if (gtk_check_version(3, 20, 0) != nullptr) { + WidgetNodeType widget = (orient == GTK_ORIENTATION_HORIZONTAL) ? + MOZ_GTK_SCALE_HORIZONTAL: + MOZ_GTK_SCALE_VERTICAL; + GtkStyleContext* style = ClaimStyleContext(widget); + gtk_style_context_get_style(style, + "slider_length", thumb_length, + "slider_width", thumb_height, + NULL); + ReleaseStyleContext(style); + } else { + WidgetNodeType widget = (orient == GTK_ORIENTATION_HORIZONTAL) ? + MOZ_GTK_SCALE_THUMB_HORIZONTAL: + MOZ_GTK_SCALE_THUMB_VERTICAL; + GtkStyleContext* style = ClaimStyleContext(widget); + gtk_style_context_get(style, gtk_style_context_get_state(style), + "min-width", thumb_length, + "min-height", thumb_height, + nullptr); + ReleaseStyleContext(style); + } + + return MOZ_GTK_SUCCESS; +} + +gint +moz_gtk_get_scrollbar_metrics(MozGtkScrollbarMetrics *metrics) +{ + // For Gtk >= 3.20 scrollbar metrics are ignored + MOZ_ASSERT(gtk_check_version(3, 20, 0) != nullptr); + + GtkStyleContext* style = ClaimStyleContext(MOZ_GTK_SCROLLBAR_VERTICAL); + gtk_style_context_get_style(style, + "slider_width", &metrics->slider_width, + "trough_border", &metrics->trough_border, + "stepper_size", &metrics->stepper_size, + "stepper_spacing", &metrics->stepper_spacing, + "min-slider-length", &metrics->min_slider_size, + nullptr); + ReleaseStyleContext(style); + + return MOZ_GTK_SUCCESS; +} + +/* cairo_t *cr argument has to be a system-cairo. */ +gint +moz_gtk_widget_paint(WidgetNodeType widget, cairo_t *cr, + GdkRectangle* rect, + GtkWidgetState* state, gint flags, + GtkTextDirection direction) +{ + /* A workaround for https://bugzilla.gnome.org/show_bug.cgi?id=694086 + */ + cairo_new_path(cr); + + switch (widget) { + case MOZ_GTK_BUTTON: + case MOZ_GTK_TOOLBAR_BUTTON: + if (state->depressed) { + return moz_gtk_button_paint(cr, rect, state, + (GtkReliefStyle) flags, + GetWidget(MOZ_GTK_TOGGLE_BUTTON), + direction); + } + return moz_gtk_button_paint(cr, rect, state, + (GtkReliefStyle) flags, + GetWidget(MOZ_GTK_BUTTON), + direction); + break; + case MOZ_GTK_CHECKBUTTON: + case MOZ_GTK_RADIOBUTTON: + return moz_gtk_toggle_paint(cr, rect, state, + !!(flags & MOZ_GTK_WIDGET_CHECKED), + !!(flags & MOZ_GTK_WIDGET_INCONSISTENT), + (widget == MOZ_GTK_RADIOBUTTON), + direction); + break; + case MOZ_GTK_SCROLLBAR_BUTTON: + return moz_gtk_scrollbar_button_paint(cr, rect, state, + (GtkScrollbarButtonFlags) flags, + direction); + break; + case MOZ_GTK_SCROLLBAR_HORIZONTAL: + case MOZ_GTK_SCROLLBAR_VERTICAL: + if (flags & MOZ_GTK_TRACK_OPAQUE) { + GtkStyleContext* style = + ClaimStyleContext(MOZ_GTK_WINDOW, direction); + gtk_render_background(style, cr, + rect->x, rect->y, rect->width, rect->height); + ReleaseStyleContext(style); + } + if (gtk_check_version(3,20,0) == nullptr) { + return moz_gtk_scrollbar_paint(widget, cr, rect, state, direction); + } else { + WidgetNodeType trough_widget = (widget == MOZ_GTK_SCROLLBAR_HORIZONTAL) ? + MOZ_GTK_SCROLLBAR_TROUGH_HORIZONTAL : MOZ_GTK_SCROLLBAR_TROUGH_VERTICAL; + return moz_gtk_scrollbar_trough_paint(trough_widget, cr, rect, + state, direction); + } + break; + case MOZ_GTK_SCROLLBAR_TROUGH_HORIZONTAL: + case MOZ_GTK_SCROLLBAR_TROUGH_VERTICAL: + if (gtk_check_version(3,20,0) == nullptr) { + return moz_gtk_scrollbar_trough_paint(widget, cr, rect, + state, direction); + } + break; + case MOZ_GTK_SCROLLBAR_THUMB_HORIZONTAL: + case MOZ_GTK_SCROLLBAR_THUMB_VERTICAL: + return moz_gtk_scrollbar_thumb_paint(widget, cr, rect, + state, direction); + break; + case MOZ_GTK_SCALE_HORIZONTAL: + case MOZ_GTK_SCALE_VERTICAL: + return moz_gtk_scale_paint(cr, rect, state, + (GtkOrientation) flags, direction); + break; + case MOZ_GTK_SCALE_THUMB_HORIZONTAL: + case MOZ_GTK_SCALE_THUMB_VERTICAL: + return moz_gtk_scale_thumb_paint(cr, rect, state, + (GtkOrientation) flags, direction); + break; + case MOZ_GTK_SPINBUTTON: + return moz_gtk_spin_paint(cr, rect, direction); + break; + case MOZ_GTK_SPINBUTTON_UP: + case MOZ_GTK_SPINBUTTON_DOWN: + return moz_gtk_spin_updown_paint(cr, rect, + (widget == MOZ_GTK_SPINBUTTON_DOWN), + state, direction); + break; + case MOZ_GTK_SPINBUTTON_ENTRY: + { + GtkStyleContext* style = ClaimStyleContext(MOZ_GTK_SPINBUTTON_ENTRY, + direction, GetStateFlagsFromGtkWidgetState(state)); + gint ret = moz_gtk_entry_paint(cr, rect, state, style); + ReleaseStyleContext(style); + return ret; + } + break; + case MOZ_GTK_GRIPPER: + return moz_gtk_gripper_paint(cr, rect, state, + direction); + break; + case MOZ_GTK_TREEVIEW: + return moz_gtk_treeview_paint(cr, rect, state, + direction); + break; + case MOZ_GTK_TREE_HEADER_CELL: + return moz_gtk_tree_header_cell_paint(cr, rect, state, + flags, direction); + break; + case MOZ_GTK_TREE_HEADER_SORTARROW: + return moz_gtk_tree_header_sort_arrow_paint(cr, rect, + state, + (GtkArrowType) flags, + direction); + break; + case MOZ_GTK_TREEVIEW_EXPANDER: + return moz_gtk_treeview_expander_paint(cr, rect, state, + (GtkExpanderStyle) flags, direction); + break; + case MOZ_GTK_ENTRY: + { + GtkStyleContext* style = ClaimStyleContext(MOZ_GTK_ENTRY, + direction, GetStateFlagsFromGtkWidgetState(state)); + gint ret = moz_gtk_entry_paint(cr, rect, state, style); + ReleaseStyleContext(style); + return ret; + } + case MOZ_GTK_TEXT_VIEW: + return moz_gtk_text_view_paint(cr, rect, state, direction); + break; + case MOZ_GTK_DROPDOWN: + return moz_gtk_combo_box_paint(cr, rect, state, direction); + break; + case MOZ_GTK_DROPDOWN_ARROW: + return moz_gtk_combo_box_entry_button_paint(cr, rect, + state, flags, direction); + break; + case MOZ_GTK_DROPDOWN_ENTRY: + { + GtkStyleContext* style = ClaimStyleContext(MOZ_GTK_COMBOBOX_ENTRY_TEXTAREA, + direction, GetStateFlagsFromGtkWidgetState(state)); + gint ret = moz_gtk_entry_paint(cr, rect, state, style); + ReleaseStyleContext(style); + return ret; + } + break; + case MOZ_GTK_CHECKBUTTON_CONTAINER: + case MOZ_GTK_RADIOBUTTON_CONTAINER: + return moz_gtk_container_paint(cr, rect, state, widget, direction); + break; + case MOZ_GTK_CHECKBUTTON_LABEL: + case MOZ_GTK_RADIOBUTTON_LABEL: + return moz_gtk_toggle_label_paint(cr, rect, state, + (widget == MOZ_GTK_RADIOBUTTON_LABEL), + direction); + break; + case MOZ_GTK_TOOLBAR: + return moz_gtk_toolbar_paint(cr, rect, direction); + break; + case MOZ_GTK_TOOLBAR_SEPARATOR: + return moz_gtk_toolbar_separator_paint(cr, rect, + direction); + break; + case MOZ_GTK_TOOLTIP: + return moz_gtk_tooltip_paint(cr, rect, direction); + break; + case MOZ_GTK_FRAME: + return moz_gtk_frame_paint(cr, rect, direction); + break; + case MOZ_GTK_RESIZER: + return moz_gtk_resizer_paint(cr, rect, state, + direction); + break; + case MOZ_GTK_PROGRESSBAR: + return moz_gtk_progressbar_paint(cr, rect, direction); + break; + case MOZ_GTK_PROGRESS_CHUNK: + case MOZ_GTK_PROGRESS_CHUNK_INDETERMINATE: + case MOZ_GTK_PROGRESS_CHUNK_VERTICAL_INDETERMINATE: + return moz_gtk_progress_chunk_paint(cr, rect, + direction, widget); + break; + case MOZ_GTK_TAB_TOP: + case MOZ_GTK_TAB_BOTTOM: + return moz_gtk_tab_paint(cr, rect, state, + (GtkTabFlags) flags, direction, widget); + break; + case MOZ_GTK_TABPANELS: + return moz_gtk_tabpanels_paint(cr, rect, direction); + break; + case MOZ_GTK_TAB_SCROLLARROW: + return moz_gtk_tab_scroll_arrow_paint(cr, rect, state, + (GtkArrowType) flags, direction); + break; + case MOZ_GTK_MENUBAR: + return moz_gtk_menu_bar_paint(cr, rect, direction); + break; + case MOZ_GTK_MENUPOPUP: + return moz_gtk_menu_popup_paint(cr, rect, direction); + break; + case MOZ_GTK_MENUSEPARATOR: + return moz_gtk_menu_separator_paint(cr, rect, + direction); + break; + case MOZ_GTK_MENUBARITEM: + case MOZ_GTK_MENUITEM: + return moz_gtk_menu_item_paint(widget, cr, rect, state, direction); + break; + case MOZ_GTK_MENUARROW: + return moz_gtk_menu_arrow_paint(cr, rect, state, + direction); + break; + case MOZ_GTK_TOOLBARBUTTON_ARROW: + return moz_gtk_arrow_paint(cr, rect, state, + (GtkArrowType) flags, direction); + break; + case MOZ_GTK_CHECKMENUITEM: + case MOZ_GTK_RADIOMENUITEM: + return moz_gtk_check_menu_item_paint(cr, rect, state, + (gboolean) flags, + (widget == MOZ_GTK_RADIOMENUITEM), + direction); + break; + case MOZ_GTK_SPLITTER_HORIZONTAL: + return moz_gtk_vpaned_paint(cr, rect, state); + break; + case MOZ_GTK_SPLITTER_VERTICAL: + return moz_gtk_hpaned_paint(cr, rect, state); + break; + case MOZ_GTK_WINDOW: + return moz_gtk_window_paint(cr, rect, direction); + break; + case MOZ_GTK_INFO_BAR: + return moz_gtk_info_bar_paint(cr, rect, state); + break; + default: + g_warning("Unknown widget type: %d", widget); + } + + return MOZ_GTK_UNKNOWN_WIDGET; +} + +GtkWidget* moz_gtk_get_scrollbar_widget(void) +{ + return GetWidget(MOZ_GTK_SCROLLBAR_HORIZONTAL); +} + +gboolean moz_gtk_has_scrollbar_buttons(void) +{ + gboolean backward, forward, secondary_backward, secondary_forward; + MOZ_ASSERT(is_initialized, "Forgot to call moz_gtk_init()"); + GtkStyleContext* style = ClaimStyleContext(MOZ_GTK_SCROLLBAR_VERTICAL); + gtk_style_context_get_style(style, + "has-backward-stepper", &backward, + "has-forward-stepper", &forward, + "has-secondary-backward-stepper", &secondary_backward, + "has-secondary-forward-stepper", &secondary_forward, + NULL); + ReleaseStyleContext(style); + + return backward | forward | secondary_forward | secondary_forward; +} + +gint +moz_gtk_shutdown() +{ + /* This will destroy all of our widgets */ + ResetWidgetCache(); + + is_initialized = FALSE; + + return MOZ_GTK_SUCCESS; +} diff --git a/widget/gtk/gtkdrawing.h b/widget/gtk/gtkdrawing.h new file mode 100644 index 000000000..9bbfdefe9 --- /dev/null +++ b/widget/gtk/gtkdrawing.h @@ -0,0 +1,542 @@ +/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +/** + * gtkdrawing.h: GTK widget rendering utilities + * + * gtkdrawing provides an API for rendering GTK widgets in the + * current theme to a pixmap or window, without requiring an actual + * widget instantiation, similar to the Macintosh Appearance Manager + * or Windows XP's DrawThemeBackground() API. + */ + +#ifndef _GTK_DRAWING_H_ +#define _GTK_DRAWING_H_ + +#include <gdk/gdk.h> +#include <gtk/gtk.h> + +#if (MOZ_WIDGET_GTK == 2) +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ +#endif + +/*** type definitions ***/ +typedef struct { + guint8 active; + guint8 focused; + guint8 inHover; + guint8 disabled; + guint8 isDefault; + guint8 canDefault; + /* The depressed state is for buttons which remain active for a longer period: + * activated toggle buttons or buttons showing a popup menu. */ + guint8 depressed; + gint32 curpos; /* curpos and maxpos are used for scrollbars */ + gint32 maxpos; +} GtkWidgetState; + +typedef struct { + gint slider_width; + gint trough_border; + gint stepper_size; + gint stepper_spacing; + gint min_slider_size; +} MozGtkScrollbarMetrics; + +typedef enum { + MOZ_GTK_STEPPER_DOWN = 1 << 0, + MOZ_GTK_STEPPER_BOTTOM = 1 << 1, + MOZ_GTK_STEPPER_VERTICAL = 1 << 2 +} GtkScrollbarButtonFlags; + +typedef enum { + MOZ_GTK_TRACK_OPAQUE = 1 << 0 +} GtkScrollbarTrackFlags; + +/** flags for tab state **/ +typedef enum { + /* first eight bits are used to pass a margin */ + MOZ_GTK_TAB_MARGIN_MASK = 0xFF, + /* the first tab in the group */ + MOZ_GTK_TAB_FIRST = 1 << 9, + /* the selected tab */ + MOZ_GTK_TAB_SELECTED = 1 << 10 +} GtkTabFlags; + +/* function type for moz_gtk_enable_style_props */ +typedef gint (*style_prop_t)(GtkStyle*, const gchar*, gint); + +/*** result/error codes ***/ +#define MOZ_GTK_SUCCESS 0 +#define MOZ_GTK_UNKNOWN_WIDGET -1 +#define MOZ_GTK_UNSAFE_THEME -2 + +/*** checkbox/radio flags ***/ +#define MOZ_GTK_WIDGET_CHECKED 1 +#define MOZ_GTK_WIDGET_INCONSISTENT (1 << 1) + +/*** widget type constants ***/ +typedef enum { + /* Paints a GtkButton. flags is a GtkReliefStyle. */ + MOZ_GTK_BUTTON, + /* Paints a button with image and no text */ + MOZ_GTK_TOOLBAR_BUTTON, + /* Paints a toggle button */ + MOZ_GTK_TOGGLE_BUTTON, + /* Paints a button arrow */ + MOZ_GTK_BUTTON_ARROW, + + /* Paints the container part of a GtkCheckButton. */ + MOZ_GTK_CHECKBUTTON_CONTAINER, + /* Paints a GtkCheckButton. flags is a boolean, 1=checked, 0=not checked. */ + MOZ_GTK_CHECKBUTTON, + /* Paints the label of a GtkCheckButton (focus outline) */ + MOZ_GTK_CHECKBUTTON_LABEL, + + /* Paints the container part of a GtkRadioButton. */ + MOZ_GTK_RADIOBUTTON_CONTAINER, + /* Paints a GtkRadioButton. flags is a boolean, 1=checked, 0=not checked. */ + MOZ_GTK_RADIOBUTTON, + /* Paints the label of a GtkRadioButton (focus outline) */ + MOZ_GTK_RADIOBUTTON_LABEL, + /** + * Paints the button of a GtkScrollbar. flags is a GtkArrowType giving + * the arrow direction. + */ + MOZ_GTK_SCROLLBAR_BUTTON, + + /* Horizontal GtkScrollbar counterparts */ + MOZ_GTK_SCROLLBAR_HORIZONTAL, + MOZ_GTK_SCROLLBAR_CONTENTS_HORIZONTAL, + /* Paints the trough (track) of a GtkScrollbar. */ + MOZ_GTK_SCROLLBAR_TROUGH_HORIZONTAL, + /* Paints the slider (thumb) of a GtkScrollbar. */ + MOZ_GTK_SCROLLBAR_THUMB_HORIZONTAL, + + /* Vertical GtkScrollbar counterparts */ + MOZ_GTK_SCROLLBAR_VERTICAL, + MOZ_GTK_SCROLLBAR_CONTENTS_VERTICAL, + MOZ_GTK_SCROLLBAR_TROUGH_VERTICAL, + MOZ_GTK_SCROLLBAR_THUMB_VERTICAL, + + /* Paints a GtkScale. */ + MOZ_GTK_SCALE_HORIZONTAL, + MOZ_GTK_SCALE_VERTICAL, + /* Paints a GtkScale trough. */ + MOZ_GTK_SCALE_CONTENTS_HORIZONTAL, + MOZ_GTK_SCALE_CONTENTS_VERTICAL, + MOZ_GTK_SCALE_TROUGH_HORIZONTAL, + MOZ_GTK_SCALE_TROUGH_VERTICAL, + /* Paints a GtkScale thumb. */ + MOZ_GTK_SCALE_THUMB_HORIZONTAL, + MOZ_GTK_SCALE_THUMB_VERTICAL, + /* Paints a GtkSpinButton */ + MOZ_GTK_SPINBUTTON, + MOZ_GTK_SPINBUTTON_UP, + MOZ_GTK_SPINBUTTON_DOWN, + MOZ_GTK_SPINBUTTON_ENTRY, + /* Paints the gripper of a GtkHandleBox. */ + MOZ_GTK_GRIPPER, + /* Paints a GtkEntry. */ + MOZ_GTK_ENTRY, + /* Paints a GtkExpander. */ + MOZ_GTK_EXPANDER, + /* Paints a GtkTextView. */ + MOZ_GTK_TEXT_VIEW, + /* Paints a GtkOptionMenu. */ + MOZ_GTK_DROPDOWN, + /* Paints a dropdown arrow (a GtkButton containing a down GtkArrow). */ + MOZ_GTK_DROPDOWN_ARROW, + /* Paints an entry in an editable option menu */ + MOZ_GTK_DROPDOWN_ENTRY, + + /* Paints the background of a GtkHandleBox. */ + MOZ_GTK_TOOLBAR, + /* Paints a toolbar separator */ + MOZ_GTK_TOOLBAR_SEPARATOR, + /* Paints a GtkToolTip */ + MOZ_GTK_TOOLTIP, + /* Paints a GtkFrame (e.g. a status bar panel). */ + MOZ_GTK_FRAME, + /* Paints the border of a GtkFrame */ + MOZ_GTK_FRAME_BORDER, + /* Paints a resize grip for a GtkWindow */ + MOZ_GTK_RESIZER, + /* Paints a GtkProgressBar. */ + MOZ_GTK_PROGRESSBAR, + /* Paints a trough (track) of a GtkProgressBar */ + MOZ_GTK_PROGRESS_TROUGH, + /* Paints a progress chunk of a GtkProgressBar. */ + MOZ_GTK_PROGRESS_CHUNK, + /* Paints a progress chunk of an indeterminated GtkProgressBar. */ + MOZ_GTK_PROGRESS_CHUNK_INDETERMINATE, + /* Paints a progress chunk of a vertical indeterminated GtkProgressBar. */ + MOZ_GTK_PROGRESS_CHUNK_VERTICAL_INDETERMINATE, + /* Used as root style of whole GtkNotebook widget */ + MOZ_GTK_NOTEBOOK, + /* Used as root style of active GtkNotebook area which contains tabs and arrows. */ + MOZ_GTK_NOTEBOOK_HEADER, + /* Paints a tab of a GtkNotebook. flags is a GtkTabFlags, defined above. */ + MOZ_GTK_TAB_TOP, + /* Paints a tab of a GtkNotebook. flags is a GtkTabFlags, defined above. */ + MOZ_GTK_TAB_BOTTOM, + /* Paints the background and border of a GtkNotebook. */ + MOZ_GTK_TABPANELS, + /* Paints a GtkArrow for a GtkNotebook. flags is a GtkArrowType. */ + MOZ_GTK_TAB_SCROLLARROW, + /* Paints the expander and border of a GtkTreeView */ + MOZ_GTK_TREEVIEW, + /* Paints the border of a GtkTreeView */ + MOZ_GTK_TREEVIEW_VIEW, + /* Paints treeheader cells */ + MOZ_GTK_TREE_HEADER_CELL, + /* Paints sort arrows in treeheader cells */ + MOZ_GTK_TREE_HEADER_SORTARROW, + /* Paints an expander for a GtkTreeView */ + MOZ_GTK_TREEVIEW_EXPANDER, + /* Paints the background of the menu bar. */ + MOZ_GTK_MENUBAR, + /* Paints the background of menus, context menus. */ + MOZ_GTK_MENUPOPUP, + /* Paints the arrow of menuitems that contain submenus */ + MOZ_GTK_MENUARROW, + /* Paints an arrow in a toolbar button. flags is a GtkArrowType. */ + MOZ_GTK_TOOLBARBUTTON_ARROW, + /* Paints items of menubar. */ + MOZ_GTK_MENUBARITEM, + /* Paints items of popup menus. */ + MOZ_GTK_MENUITEM, + MOZ_GTK_IMAGEMENUITEM, + MOZ_GTK_CHECKMENUITEM_CONTAINER, + MOZ_GTK_RADIOMENUITEM_CONTAINER, + MOZ_GTK_CHECKMENUITEM, + MOZ_GTK_RADIOMENUITEM, + MOZ_GTK_MENUSEPARATOR, + /* GtkVPaned base class */ + MOZ_GTK_SPLITTER_HORIZONTAL, + /* GtkHPaned base class */ + MOZ_GTK_SPLITTER_VERTICAL, + /* Paints a GtkVPaned separator */ + MOZ_GTK_SPLITTER_SEPARATOR_HORIZONTAL, + /* Paints a GtkHPaned separator */ + MOZ_GTK_SPLITTER_SEPARATOR_VERTICAL, + /* Paints the background of a window, dialog or page. */ + MOZ_GTK_WINDOW, + /* Window container for all widgets */ + MOZ_GTK_WINDOW_CONTAINER, + /* Paints a GtkInfoBar, for notifications. */ + MOZ_GTK_INFO_BAR, + /* Used for widget tree construction. */ + MOZ_GTK_COMBOBOX, + /* Paints a GtkComboBox button widget. */ + MOZ_GTK_COMBOBOX_BUTTON, + /* Paints a GtkComboBox arrow widget. */ + MOZ_GTK_COMBOBOX_ARROW, + /* Paints a GtkComboBox separator widget. */ + MOZ_GTK_COMBOBOX_SEPARATOR, + /* Used for widget tree construction. */ + MOZ_GTK_COMBOBOX_ENTRY, + /* Paints a GtkComboBox entry widget. */ + MOZ_GTK_COMBOBOX_ENTRY_TEXTAREA, + /* Paints a GtkComboBox entry button widget. */ + MOZ_GTK_COMBOBOX_ENTRY_BUTTON, + /* Paints a GtkComboBox entry arrow widget. */ + MOZ_GTK_COMBOBOX_ENTRY_ARROW, + /* Used for scrolled window shell. */ + MOZ_GTK_SCROLLED_WINDOW, + + MOZ_GTK_WIDGET_NODE_COUNT +} WidgetNodeType; + +/*** General library functions ***/ +/** + * Initializes the drawing library. You must call this function + * prior to using any other functionality. + * returns: MOZ_GTK_SUCCESS if there were no errors + * MOZ_GTK_UNSAFE_THEME if the current theme engine is known + * to crash with gtkdrawing. + */ +gint moz_gtk_init(); + +/** + * Enable GTK+ 1.2.9+ theme enhancements. You must provide a pointer + * to the GTK+ 1.2.9+ function "gtk_style_get_prop_experimental". + * styleGetProp: pointer to gtk_style_get_prop_experimental + * + * returns: MOZ_GTK_SUCCESS if there was no error, an error code otherwise + */ +gint moz_gtk_enable_style_props(style_prop_t styleGetProp); + +/** + * Perform cleanup of the drawing library. You should call this function + * when your program exits, or you no longer need the library. + * + * returns: MOZ_GTK_SUCCESS if there was no error, an error code otherwise + */ +gint moz_gtk_shutdown(); + +#if (MOZ_WIDGET_GTK == 2) +/** + * Retrieves the colormap to use for drawables passed to moz_gtk_widget_paint. + */ +GdkColormap* moz_gtk_widget_get_colormap(); +#endif + +/*** Widget drawing ***/ +#if (MOZ_WIDGET_GTK == 2) +/** + * Paint a widget in the current theme. + * widget: a constant giving the widget to paint + * drawable: the drawable to paint to; + * it's colormap must be moz_gtk_widget_get_colormap(). + * rect: the bounding rectangle for the widget + * cliprect: a clipprect rectangle for this painting operation + * state: the state of the widget. ignored for some widgets. + * flags: widget-dependant flags; see the WidgetNodeType definition. + * direction: the text direction, to draw the widget correctly LTR and RTL. + */ +gint +moz_gtk_widget_paint(WidgetNodeType widget, GdkDrawable* drawable, + GdkRectangle* rect, GdkRectangle* cliprect, + GtkWidgetState* state, gint flags, + GtkTextDirection direction); +#else +gint +moz_gtk_widget_paint(WidgetNodeType widget, cairo_t *cr, + GdkRectangle* rect, + GtkWidgetState* state, gint flags, + GtkTextDirection direction); +#endif + + +/*** Widget metrics ***/ +/** + * Get the border size of a widget + * left/right: [OUT] the widget's left/right border + * top/bottom: [OUT] the widget's top/bottom border + * direction: the text direction for the widget + * inhtml: boolean indicating whether this widget will be drawn as a HTML form control, + * in order to workaround a size issue (MOZ_GTK_BUTTON only, ignored otherwise) + * + * returns: MOZ_GTK_SUCCESS if there was no error, an error code otherwise + */ +gint moz_gtk_get_widget_border(WidgetNodeType widget, gint* left, gint* top, + gint* right, gint* bottom, GtkTextDirection direction, + gboolean inhtml); + +/** + * Get the border size of a notebook tab + * left/right: [OUT] the tab's left/right border + * top/bottom: [OUT] the tab's top/bottom border + * direction: the text direction for the widget + * flags: tab-dependant flags; see the GtkTabFlags definition. + * widget: tab widget + * + * returns: MOZ_GTK_SUCCESS if there was no error, an error code otherwise + */ +gint +moz_gtk_get_tab_border(gint* left, gint* top, gint* right, gint* bottom, + GtkTextDirection direction, GtkTabFlags flags, + WidgetNodeType widget); + +/** + * Get the desired size of a GtkCheckButton + * indicator_size: [OUT] the indicator size + * indicator_spacing: [OUT] the spacing between the indicator and its + * container + * + * returns: MOZ_GTK_SUCCESS if there was no error, an error code otherwise + */ +gint +moz_gtk_checkbox_get_metrics(gint* indicator_size, gint* indicator_spacing); + +/** + * Get the desired size of a GtkRadioButton + * indicator_size: [OUT] the indicator size + * indicator_spacing: [OUT] the spacing between the indicator and its + * container + * + * returns: MOZ_GTK_SUCCESS if there was no error, an error code otherwise + */ +gint +moz_gtk_radio_get_metrics(gint* indicator_size, gint* indicator_spacing); + +/** Get the extra size for the focus ring for outline:auto. + * widget: [IN] the widget to get the focus metrics for + * focus_h_width: [OUT] the horizontal width + * focus_v_width: [OUT] the vertical width + * + * returns: MOZ_GTK_SUCCESS + */ +gint +moz_gtk_get_focus_outline_size(gint* focus_h_width, gint* focus_v_width); + +/** Get the horizontal padding for the menuitem widget or checkmenuitem widget. + * horizontal_padding: [OUT] The left and right padding of the menuitem or checkmenuitem + * + * returns: MOZ_GTK_SUCCESS if there was no error, an error code otherwise + */ +gint +moz_gtk_menuitem_get_horizontal_padding(gint* horizontal_padding); + +gint +moz_gtk_checkmenuitem_get_horizontal_padding(gint* horizontal_padding); + +/** + * Some GTK themes draw their indication for the default button outside + * the button (e.g. the glow in New Wave). This gets the extra space necessary. + * + * border_top: [OUT] extra space to add above + * border_left: [OUT] extra space to add to the left + * border_bottom: [OUT] extra space to add underneath + * border_right: [OUT] extra space to add to the right + * + * returns: MOZ_GTK_SUCCESS if there was no error, an error code otherwise + */ +gint +moz_gtk_button_get_default_overflow(gint* border_top, gint* border_left, + gint* border_bottom, gint* border_right); + +/** + * Gets the minimum size of a GtkScale. + * orient: [IN] the scale orientation + * scale_width: [OUT] the width of the scale + * scale_height: [OUT] the height of the scale + */ +void +moz_gtk_get_scale_metrics(GtkOrientation orient, gint* scale_width, + gint* scale_height); + +/** + * Get the desired size of a GtkScale thumb + * orient: [IN] the scale orientation + * thumb_length: [OUT] the length of the thumb + * thumb_height: [OUT] the height of the thumb + * + * returns: MOZ_GTK_SUCCESS if there was no error, an error code otherwise + */ +gint +moz_gtk_get_scalethumb_metrics(GtkOrientation orient, gint* thumb_length, gint* thumb_height); + +/** + * Get the desired metrics for a GtkScrollbar + * metrics: [IN] struct which will contain the metrics + * + * returns: MOZ_GTK_SUCCESS if there was no error, an error code otherwise + */ +gint +moz_gtk_get_scrollbar_metrics(MozGtkScrollbarMetrics* metrics); + +/** + * Get the desired size of a dropdown arrow button + * width: [OUT] the desired width + * height: [OUT] the desired height + * + * returns: MOZ_GTK_SUCCESS if there was no error, an error code otherwise + */ +gint moz_gtk_get_combo_box_entry_button_size(gint* width, gint* height); + +/** + * Get the desired size of a scroll arrow widget + * width: [OUT] the desired width + * height: [OUT] the desired height + * + * returns: MOZ_GTK_SUCCESS if there was no error, an error code otherwise + */ +gint moz_gtk_get_tab_scroll_arrow_size(gint* width, gint* height); + +/** + * Get the desired size of an arrow in a button + * + * widgetType: [IN] the widget for which to get the arrow size + * width: [OUT] the desired width + * height: [OUT] the desired height + */ +void +moz_gtk_get_arrow_size(WidgetNodeType widgetType, + gint* width, gint* height); + +/** + * Get the minimum height of a entry widget + * size: [OUT] the minimum height + * + */ +void moz_gtk_get_entry_min_height(gint* height); + +/** + * Get the desired size of a toolbar separator + * size: [OUT] the desired width + * + * returns: MOZ_GTK_SUCCESS if there was no error, an error code otherwise + */ +gint moz_gtk_get_toolbar_separator_width(gint* size); + +/** + * Get the size of a regular GTK expander that shows/hides content + * size: [OUT] the size of the GTK expander, size = width = height. + * + * returns: MOZ_GTK_SUCCESS if there was no error, an error code otherwise + */ +gint moz_gtk_get_expander_size(gint* size); + +/** + * Get the size of a treeview's expander (we call them twisties) + * size: [OUT] the size of the GTK expander, size = width = height. + * + * returns: MOZ_GTK_SUCCESS if there was no error, an error code otherwise + */ +gint moz_gtk_get_treeview_expander_size(gint* size); + +/** + * Get the desired height of a menu separator + * size: [OUT] the desired height + * + * returns: MOZ_GTK_SUCCESS if there was no error, an error code otherwise + */ +gint moz_gtk_get_menu_separator_height(gint* size); + +/** + * Get the desired size of a splitter + * orientation: [IN] GTK_ORIENTATION_HORIZONTAL or GTK_ORIENTATION_VERTICAL + * size: [OUT] width or height of the splitter handle + * + * returns: MOZ_GTK_SUCCESS if there was no error, an error code otherwise + */ +gint moz_gtk_splitter_get_metrics(gint orientation, gint* size); + +/** + * Retrieve an actual GTK scrollbar widget for style analysis. It will not + * be modified. + */ +GtkWidget* moz_gtk_get_scrollbar_widget(void); + +/** + * Get the YTHICKNESS of a tab (notebook extension). + */ +gint +moz_gtk_get_tab_thickness(WidgetNodeType aNodeType); + +/** + * Get a boolean which indicates whether the theme draws scrollbar buttons. + * If TRUE, draw scrollbar buttons. + */ +gboolean moz_gtk_has_scrollbar_buttons(void); + +/** + * Get minimum widget size as sum of margin, padding, border and min-width, + * min-height. + */ +void moz_gtk_get_widget_min_size(WidgetNodeType aGtkWidgetType, int* width, + int* height); + +#if (MOZ_WIDGET_GTK == 2) +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif + +#endif diff --git a/widget/gtk/maiRedundantObjectFactory.c b/widget/gtk/maiRedundantObjectFactory.c new file mode 100644 index 000000000..3db26c8bb --- /dev/null +++ b/widget/gtk/maiRedundantObjectFactory.c @@ -0,0 +1,93 @@ +/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:expandtab:shiftwidth=2:tabstop=2: + */ +/* 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 <atk/atk.h> +#include "maiRedundantObjectFactory.h" + +static void mai_redundant_object_factory_class_init ( + maiRedundantObjectFactoryClass *klass); + +static AtkObject* mai_redundant_object_factory_create_accessible ( + GObject *obj); +static GType mai_redundant_object_factory_get_accessible_type (void); + +GType +mai_redundant_object_factory_get_type (void) +{ + static GType type = 0; + + if (!type) + { + static const GTypeInfo tinfo = + { + sizeof (maiRedundantObjectFactoryClass), + (GBaseInitFunc) NULL, /* base init */ + (GBaseFinalizeFunc) NULL, /* base finalize */ + (GClassInitFunc) mai_redundant_object_factory_class_init, /* class init */ + (GClassFinalizeFunc) NULL, /* class finalize */ + NULL, /* class data */ + sizeof (maiRedundantObjectFactory), /* instance size */ + 0, /* nb preallocs */ + (GInstanceInitFunc) NULL, /* instance init */ + NULL /* value table */ + }; + type = g_type_register_static ( + ATK_TYPE_OBJECT_FACTORY, + "MaiRedundantObjectFactory" , &tinfo, 0); + } + + return type; +} + +static void +mai_redundant_object_factory_class_init (maiRedundantObjectFactoryClass *klass) +{ + AtkObjectFactoryClass *class = ATK_OBJECT_FACTORY_CLASS (klass); + + class->create_accessible = mai_redundant_object_factory_create_accessible; + class->get_accessible_type = mai_redundant_object_factory_get_accessible_type; +} + +/** + * mai_redundant_object_factory_new: + * + * Creates an instance of an #AtkObjectFactory which generates primitive + * (non-functioning) #AtkObjects. + * + * Returns: an instance of an #AtkObjectFactory + **/ +AtkObjectFactory* +mai_redundant_object_factory_new () +{ + GObject *factory; + + factory = g_object_new (mai_redundant_object_factory_get_type(), NULL); + + g_return_val_if_fail (factory != NULL, NULL); + return ATK_OBJECT_FACTORY (factory); +} + +static AtkObject* +mai_redundant_object_factory_create_accessible (GObject *obj) +{ + AtkObject *accessible; + + g_return_val_if_fail (obj != NULL, NULL); + + accessible = g_object_new (ATK_TYPE_OBJECT, NULL); + g_return_val_if_fail (accessible != NULL, NULL); + + accessible->role = ATK_ROLE_REDUNDANT_OBJECT; + + return accessible; +} + +static GType +mai_redundant_object_factory_get_accessible_type () +{ + return mai_redundant_object_factory_get_type(); +} diff --git a/widget/gtk/maiRedundantObjectFactory.h b/widget/gtk/maiRedundantObjectFactory.h new file mode 100644 index 000000000..809af23ce --- /dev/null +++ b/widget/gtk/maiRedundantObjectFactory.h @@ -0,0 +1,32 @@ +/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:expandtab:shiftwidth=2:tabstop=2: + */ +/* 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/. */ + +#ifndef __MAI_REDUNDANT_OBJECT_FACTORY_H__ +#define __MAI_REDUNDANT_OBJECT_FACTORY_H__ + +G_BEGIN_DECLS + +typedef struct _maiRedundantObjectFactory maiRedundantObjectFactory; +typedef struct _maiRedundantObjectFactoryClass maiRedundantObjectFactoryClass; + +struct _maiRedundantObjectFactory +{ + AtkObjectFactory parent; +}; + +struct _maiRedundantObjectFactoryClass +{ + AtkObjectFactoryClass parent_class; +}; + +GType mai_redundant_object_factory_get_type(); + +AtkObjectFactory *mai_redundant_object_factory_new(); + +G_END_DECLS + +#endif /* __NS_MAI_REDUNDANT_OBJECT_FACTORY_H__ */ diff --git a/widget/gtk/moz.build b/widget/gtk/moz.build new file mode 100644 index 000000000..baccb6ccd --- /dev/null +++ b/widget/gtk/moz.build @@ -0,0 +1,141 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gtk3': + DIRS += ['mozgtk'] + +EXPORTS += [ + 'mozcontainer.h', + 'nsGTKToolkit.h', + 'nsIImageToPixbuf.h', +] + +EXPORTS.mozilla += [ + 'WidgetUtilsGtk.h' +] + +UNIFIED_SOURCES += [ + 'IMContextWrapper.cpp', + 'mozcontainer.c', + 'NativeKeyBindings.cpp', + 'nsAppShell.cpp', + 'nsBidiKeyboard.cpp', + 'nsColorPicker.cpp', + 'nsFilePicker.cpp', + 'nsGtkKeyUtils.cpp', + 'nsImageToPixbuf.cpp', + 'nsLookAndFeel.cpp', + 'nsNativeThemeGTK.cpp', + 'nsScreenGtk.cpp', + 'nsScreenManagerGtk.cpp', + 'nsSound.cpp', + 'nsToolkit.cpp', + 'nsWidgetFactory.cpp', + 'WakeLockListener.cpp', + 'WidgetTraceEvent.cpp', + 'WidgetUtilsGtk.cpp', +] + +SOURCES += [ + 'nsWindow.cpp', # conflicts with X11 headers +] + +if CONFIG['MOZ_X11']: + UNIFIED_SOURCES += [ + 'CompositorWidgetChild.cpp', + 'CompositorWidgetParent.cpp', + 'InProcessX11CompositorWidget.cpp', + 'nsIdleServiceGTK.cpp', + 'X11CompositorWidget.cpp', + ] + EXPORTS.mozilla.widget += [ + 'CompositorWidgetChild.h', + 'CompositorWidgetParent.h', + 'InProcessX11CompositorWidget.h', + 'X11CompositorWidget.h', + ] + +if CONFIG['NS_PRINTING']: + UNIFIED_SOURCES += [ + 'nsCUPSShim.cpp', + 'nsDeviceContextSpecG.cpp', + 'nsPaperPS.cpp', + 'nsPrintDialogGTK.cpp', + 'nsPrintOptionsGTK.cpp', + 'nsPrintSettingsGTK.cpp', + 'nsPSPrinters.cpp', + ] + +if CONFIG['MOZ_X11']: + UNIFIED_SOURCES += [ + 'nsClipboard.cpp', + 'nsDragService.cpp', + 'WindowSurfaceProvider.cpp', + 'WindowSurfaceX11.cpp', + 'WindowSurfaceX11Image.cpp', + 'WindowSurfaceXRender.cpp', + ] + EXPORTS.mozilla.widget += [ + 'WindowSurfaceProvider.h', + ] + +if CONFIG['ACCESSIBILITY']: + UNIFIED_SOURCES += [ + 'maiRedundantObjectFactory.c', + ] + +if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gtk2': + UNIFIED_SOURCES += [ + 'gtk2drawing.c', + ] +else: + UNIFIED_SOURCES += [ + 'gtk3drawing.cpp', + 'nsApplicationChooser.cpp', + 'WidgetStyleCache.cpp', + ] + +include('/ipc/chromium/chromium-config.mozbuild') + +FINAL_LIBRARY = 'xul' + +LOCAL_INCLUDES += [ + '/layout/generic', + '/layout/xul', + '/other-licenses/atk-1.0', + '/widget', +] + +if CONFIG['MOZ_X11']: + LOCAL_INCLUDES += [ + '/widget/x11', + ] + +DEFINES['CAIRO_GFX'] = True + +DEFINES['MOZ_APP_NAME'] = '"%s"' % CONFIG['MOZ_APP_NAME'] + +CFLAGS += CONFIG['MOZ_STARTUP_NOTIFICATION_CFLAGS'] + +# When building with GTK3, the widget code always needs to use +# system Cairo headers, regardless of whether we are also linked +# against and using in-tree Cairo. By not using in-tree Cairo +# headers, we avoid picking up our renamed symbols, and instead +# use only system Cairo symbols that GTK3 uses. This allows that +# any Cairo objects created can be freely passed back and forth +# between the widget code and GTK3. +if not (CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gtk3' and CONFIG['MOZ_TREE_CAIRO']): + CXXFLAGS += CONFIG['MOZ_CAIRO_CFLAGS'] + +CXXFLAGS += CONFIG['MOZ_STARTUP_NOTIFICATION_CFLAGS'] + +CFLAGS += CONFIG['TK_CFLAGS'] +CXXFLAGS += CONFIG['TK_CFLAGS'] + +if CONFIG['MOZ_ENABLE_DBUS']: + CXXFLAGS += CONFIG['MOZ_DBUS_GLIB_CFLAGS'] + +CXXFLAGS += ['-Wno-error=shadow'] diff --git a/widget/gtk/mozcontainer.c b/widget/gtk/mozcontainer.c new file mode 100644 index 000000000..9b596e4fb --- /dev/null +++ b/widget/gtk/mozcontainer.c @@ -0,0 +1,418 @@ +/* -*- 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 "mozcontainer.h" +#include <gtk/gtk.h> +#include <stdio.h> + +#ifdef ACCESSIBILITY +#include <atk/atk.h> +#include "maiRedundantObjectFactory.h" +#endif + +/* init methods */ +static void moz_container_class_init (MozContainerClass *klass); +static void moz_container_init (MozContainer *container); + +/* widget class methods */ +static void moz_container_map (GtkWidget *widget); +static void moz_container_unmap (GtkWidget *widget); +static void moz_container_realize (GtkWidget *widget); +static void moz_container_size_allocate (GtkWidget *widget, + GtkAllocation *allocation); + +/* container class methods */ +static void moz_container_remove (GtkContainer *container, + GtkWidget *child_widget); +static void moz_container_forall (GtkContainer *container, + gboolean include_internals, + GtkCallback callback, + gpointer callback_data); +static void moz_container_add (GtkContainer *container, + GtkWidget *widget); + +typedef struct _MozContainerChild MozContainerChild; + +struct _MozContainerChild { + GtkWidget *widget; + gint x; + gint y; +}; + +static void moz_container_allocate_child (MozContainer *container, + MozContainerChild *child); +static MozContainerChild * +moz_container_get_child (MozContainer *container, GtkWidget *child); + +/* public methods */ + +GType +moz_container_get_type(void) +{ + static GType moz_container_type = 0; + + if (!moz_container_type) { + static GTypeInfo moz_container_info = { + sizeof(MozContainerClass), /* class_size */ + NULL, /* base_init */ + NULL, /* base_finalize */ + (GClassInitFunc) moz_container_class_init, /* class_init */ + NULL, /* class_destroy */ + NULL, /* class_data */ + sizeof(MozContainer), /* instance_size */ + 0, /* n_preallocs */ + (GInstanceInitFunc) moz_container_init, /* instance_init */ + NULL, /* value_table */ + }; + + moz_container_type = g_type_register_static (GTK_TYPE_CONTAINER, + "MozContainer", + &moz_container_info, 0); +#ifdef ACCESSIBILITY + /* Set a factory to return accessible object with ROLE_REDUNDANT for + * MozContainer, so that gail won't send focus notification for it */ + atk_registry_set_factory_type(atk_get_default_registry(), + moz_container_type, + mai_redundant_object_factory_get_type()); +#endif + } + + return moz_container_type; +} + +GtkWidget * +moz_container_new (void) +{ + MozContainer *container; + + container = g_object_new (MOZ_CONTAINER_TYPE, NULL); + + return GTK_WIDGET(container); +} + +void +moz_container_put (MozContainer *container, GtkWidget *child_widget, + gint x, gint y) +{ + MozContainerChild *child; + + child = g_new (MozContainerChild, 1); + + child->widget = child_widget; + child->x = x; + child->y = y; + + /* printf("moz_container_put %p %p %d %d\n", (void *)container, + (void *)child_widget, x, y); */ + + container->children = g_list_append (container->children, child); + + /* we assume that the caller of this function will have already set + the parent GdkWindow because we can have many anonymous children. */ + gtk_widget_set_parent(child_widget, GTK_WIDGET(container)); +} + +void +moz_container_move (MozContainer *container, GtkWidget *child_widget, + gint x, gint y, gint width, gint height) +{ + MozContainerChild *child; + GtkAllocation new_allocation; + + child = moz_container_get_child (container, child_widget); + + child->x = x; + child->y = y; + + new_allocation.x = x; + new_allocation.y = y; + new_allocation.width = width; + new_allocation.height = height; + + /* printf("moz_container_move %p %p will allocate to %d %d %d %d\n", + (void *)container, (void *)child_widget, + new_allocation.x, new_allocation.y, + new_allocation.width, new_allocation.height); */ + + gtk_widget_size_allocate(child_widget, &new_allocation); +} + +/* static methods */ + +void +moz_container_class_init (MozContainerClass *klass) +{ + /*GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + GtkObjectClass *object_class = GTK_OBJECT_CLASS (klass); */ + GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + widget_class->map = moz_container_map; + widget_class->unmap = moz_container_unmap; + widget_class->realize = moz_container_realize; + widget_class->size_allocate = moz_container_size_allocate; + + container_class->remove = moz_container_remove; + container_class->forall = moz_container_forall; + container_class->add = moz_container_add; +} + +void +moz_container_init (MozContainer *container) +{ + gtk_widget_set_can_focus(GTK_WIDGET(container), TRUE); + gtk_container_set_resize_mode(GTK_CONTAINER(container), GTK_RESIZE_IMMEDIATE); + gtk_widget_set_redraw_on_allocate(GTK_WIDGET(container), FALSE); +} + +void +moz_container_map (GtkWidget *widget) +{ + MozContainer *container; + GList *tmp_list; + GtkWidget *tmp_child; + + g_return_if_fail (IS_MOZ_CONTAINER(widget)); + container = MOZ_CONTAINER (widget); + + gtk_widget_set_mapped(widget, TRUE); + + tmp_list = container->children; + while (tmp_list) { + tmp_child = ((MozContainerChild *)tmp_list->data)->widget; + + if (gtk_widget_get_visible(tmp_child)) { + if (!gtk_widget_get_mapped(tmp_child)) + gtk_widget_map(tmp_child); + } + tmp_list = tmp_list->next; + } + + if (gtk_widget_get_has_window (widget)) { + gdk_window_show (gtk_widget_get_window(widget)); + } +} + +void +moz_container_unmap (GtkWidget *widget) +{ + g_return_if_fail (IS_MOZ_CONTAINER (widget)); + + gtk_widget_set_mapped(widget, FALSE); + + if (gtk_widget_get_has_window (widget)) { + gdk_window_hide (gtk_widget_get_window(widget)); + } +} + +void +moz_container_realize (GtkWidget *widget) +{ + GdkWindow *parent = gtk_widget_get_parent_window (widget); + GdkWindow *window; + + gtk_widget_set_realized(widget, TRUE); + + if (gtk_widget_get_has_window (widget)) { + GdkWindowAttr attributes; + gint attributes_mask = GDK_WA_VISUAL | GDK_WA_X | GDK_WA_Y; + GtkAllocation allocation; + + gtk_widget_get_allocation (widget, &allocation); + attributes.event_mask = gtk_widget_get_events (widget); + attributes.x = allocation.x; + attributes.y = allocation.y; + attributes.width = allocation.width; + attributes.height = allocation.height; + attributes.wclass = GDK_INPUT_OUTPUT; + attributes.visual = gtk_widget_get_visual (widget); + attributes.window_type = GDK_WINDOW_CHILD; + +#if (MOZ_WIDGET_GTK == 2) + attributes.colormap = gtk_widget_get_colormap (widget); + attributes_mask |= GDK_WA_COLORMAP; +#endif + + window = gdk_window_new (parent, &attributes, attributes_mask); + gdk_window_set_user_data (window, widget); +#if (MOZ_WIDGET_GTK == 2) + /* TODO GTK3? */ + /* set the back 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 + } else { + window = parent; + g_object_ref (window); + } + + gtk_widget_set_window (widget, window); + +#if (MOZ_WIDGET_GTK == 2) + widget->style = gtk_style_attach (widget->style, widget->window); +#endif +} + +void +moz_container_size_allocate (GtkWidget *widget, + GtkAllocation *allocation) +{ + MozContainer *container; + GList *tmp_list; + GtkAllocation tmp_allocation; + + g_return_if_fail (IS_MOZ_CONTAINER (widget)); + + /* printf("moz_container_size_allocate %p %d %d %d %d\n", + (void *)widget, + allocation->x, + allocation->y, + allocation->width, + allocation->height); */ + + /* short circuit if you can */ + container = MOZ_CONTAINER (widget); + gtk_widget_get_allocation(widget, &tmp_allocation); + if (!container->children && + tmp_allocation.x == allocation->x && + tmp_allocation.y == allocation->y && + tmp_allocation.width == allocation->width && + tmp_allocation.height == allocation->height) { + return; + } + + gtk_widget_set_allocation(widget, allocation); + + tmp_list = container->children; + + while (tmp_list) { + MozContainerChild *child = tmp_list->data; + + moz_container_allocate_child (container, child); + + tmp_list = tmp_list->next; + } + + if (gtk_widget_get_has_window (widget) && + gtk_widget_get_realized (widget)) { + + gdk_window_move_resize(gtk_widget_get_window(widget), + allocation->x, + allocation->y, + allocation->width, + allocation->height); + } +} + +void +moz_container_remove (GtkContainer *container, GtkWidget *child_widget) +{ + MozContainerChild *child; + MozContainer *moz_container; + GdkWindow* parent_window; + + g_return_if_fail (IS_MOZ_CONTAINER(container)); + g_return_if_fail (GTK_IS_WIDGET(child_widget)); + + moz_container = MOZ_CONTAINER(container); + + child = moz_container_get_child (moz_container, child_widget); + g_return_if_fail (child); + + /* gtk_widget_unparent will remove the parent window (as well as the + * parent widget), but, in Mozilla's window hierarchy, the parent window + * may need to be kept because it may be part of a GdkWindow sub-hierarchy + * that is being moved to another MozContainer. + * + * (In a conventional GtkWidget hierarchy, GdkWindows being reparented + * would have their own GtkWidget and that widget would be the one being + * reparented. In Mozilla's hierarchy, the parent_window needs to be + * retained so that the GdkWindow sub-hierarchy is maintained.) + */ + parent_window = gtk_widget_get_parent_window(child_widget); + if (parent_window) + g_object_ref(parent_window); + + gtk_widget_unparent(child_widget); + + if (parent_window) { + /* The child_widget will always still exist because g_signal_emit, + * which invokes this function, holds a reference. + * + * If parent_window is the container's root window then it will not be + * the parent_window if the child_widget is placed in another + * container. + */ + if (parent_window != gtk_widget_get_window(GTK_WIDGET(container))) + gtk_widget_set_parent_window(child_widget, parent_window); + + g_object_unref(parent_window); + } + + moz_container->children = g_list_remove(moz_container->children, child); + g_free(child); +} + +void +moz_container_forall (GtkContainer *container, gboolean include_internals, + GtkCallback callback, gpointer callback_data) +{ + MozContainer *moz_container; + GList *tmp_list; + + g_return_if_fail (IS_MOZ_CONTAINER(container)); + g_return_if_fail (callback != NULL); + + moz_container = MOZ_CONTAINER(container); + + tmp_list = moz_container->children; + while (tmp_list) { + MozContainerChild *child; + child = tmp_list->data; + tmp_list = tmp_list->next; + (* callback) (child->widget, callback_data); + } +} + +static void +moz_container_allocate_child (MozContainer *container, + MozContainerChild *child) +{ + GtkAllocation allocation; + + gtk_widget_get_allocation (child->widget, &allocation); + allocation.x = child->x; + allocation.y = child->y; + + gtk_widget_size_allocate (child->widget, &allocation); +} + +MozContainerChild * +moz_container_get_child (MozContainer *container, GtkWidget *child_widget) +{ + GList *tmp_list; + + tmp_list = container->children; + while (tmp_list) { + MozContainerChild *child; + + child = tmp_list->data; + tmp_list = tmp_list->next; + + if (child->widget == child_widget) + return child; + } + + return NULL; +} + +static void +moz_container_add(GtkContainer *container, GtkWidget *widget) +{ + moz_container_put(MOZ_CONTAINER(container), widget, 0, 0); +} + diff --git a/widget/gtk/mozcontainer.h b/widget/gtk/mozcontainer.h new file mode 100644 index 000000000..23e17f7b3 --- /dev/null +++ b/widget/gtk/mozcontainer.h @@ -0,0 +1,86 @@ +/* -*- 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/. */ + +#ifndef __MOZ_CONTAINER_H__ +#define __MOZ_CONTAINER_H__ + +#include <gtk/gtk.h> + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/* + * MozContainer + * + * This class serves two purposes in the nsIWidget implementation. + * + * - It provides objects to receive signals from GTK for events on native + * windows. + * + * - It provides a container parent for GtkWidgets. The only GtkWidgets + * that need this in Mozilla are the GtkSockets for windowed plugins (Xt + * and XEmbed). + * + * Note that the window hierarchy in Mozilla differs from conventional + * GtkWidget hierarchies. + * + * Mozilla's hierarchy exists through the GdkWindow hierarchy, and all child + * GdkWindows (within a child nsIWidget hierarchy) belong to one MozContainer + * GtkWidget. If the MozContainer is unrealized or its GdkWindows are + * destroyed for some other reason, then the hierarchy no longer exists. (In + * conventional GTK clients, the hierarchy is recorded by the GtkWidgets, and + * so can be re-established after destruction of the GdkWindows.) + * + * One consequence of this is that the MozContainer does not know which of its + * GdkWindows should parent child GtkWidgets. (Conventional GtkContainers + * determine which GdkWindow to assign child GtkWidgets.) + * + * Therefore, when adding a child GtkWidget to a MozContainer, + * gtk_widget_set_parent_window should be called on the child GtkWidget before + * it is realized. + */ + +#define MOZ_CONTAINER_TYPE (moz_container_get_type()) +#define MOZ_CONTAINER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MOZ_CONTAINER_TYPE, MozContainer)) +#define MOZ_CONTAINER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MOZ_CONTAINER_TYPE, MozContainerClass)) +#define IS_MOZ_CONTAINER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MOZ_CONTAINER_TYPE)) +#define IS_MOZ_CONTAINER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MOZ_CONTAINER_TYPE)) +#define MOZ_CONAINTER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MOZ_CONTAINER_TYPE, MozContainerClass)) + +typedef struct _MozContainer MozContainer; +typedef struct _MozContainerClass MozContainerClass; + +struct _MozContainer +{ + GtkContainer container; + GList *children; +}; + +struct _MozContainerClass +{ + GtkContainerClass parent_class; +}; + +GType moz_container_get_type (void); +GtkWidget *moz_container_new (void); +void moz_container_put (MozContainer *container, + GtkWidget *child_widget, + gint x, + gint y); +void moz_container_move (MozContainer *container, + GtkWidget *child_widget, + gint x, + gint y, + gint width, + gint height); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __MOZ_CONTAINER_H__ */ diff --git a/widget/gtk/mozgtk/gtk2/moz.build b/widget/gtk/mozgtk/gtk2/moz.build new file mode 100644 index 000000000..d07fd06df --- /dev/null +++ b/widget/gtk/mozgtk/gtk2/moz.build @@ -0,0 +1,40 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +SOURCES += [ + '../mozgtk.c', +] + +DEFINES['GTK3_SYMBOLS'] = True + +SharedLibrary('mozgtk2') + +SHARED_LIBRARY_NAME = 'mozgtk' + +FINAL_TARGET = 'dist/bin/gtk2' + +# If LDFLAGS contains -Wl,--as-needed or if it's the default for the toolchain, +# we need to add -Wl,--no-as-needed before the gtk libraries, otherwise the +# linker will drop those dependencies because no symbols are used from them. +# But those dependencies need to be kept for things to work properly. +# Ideally, we'd only add -Wl,--no-as-needed if necessary, but it's just simpler +# to add it unconditionally. This library is also simple enough that forcing +# -Wl,--as-needed after the gtk libraries is not going to make a significant +# difference. +if CONFIG['GCC_USE_GNU_LD']: + no_as_needed = ['-Wl,--no-as-needed'] + as_needed = ['-Wl,--as-needed'] +else: + no_as_needed = [] + as_needed = [] + +OS_LIBS += [f for f in CONFIG['MOZ_GTK2_LIBS'] if f.startswith('-L')] +OS_LIBS += no_as_needed +OS_LIBS += [ + 'gtk-x11-2.0', + 'gdk-x11-2.0', +] +OS_LIBS += as_needed diff --git a/widget/gtk/mozgtk/gtk3/moz.build b/widget/gtk/mozgtk/gtk3/moz.build new file mode 100644 index 000000000..4e9379565 --- /dev/null +++ b/widget/gtk/mozgtk/gtk3/moz.build @@ -0,0 +1,38 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +SOURCES += [ + '../mozgtk.c', +] + +DEFINES['GTK2_SYMBOLS'] = True + +SharedLibrary('mozgtk') + +SONAME = 'mozgtk' + +# If LDFLAGS contains -Wl,--as-needed or if it's the default for the toolchain, +# we need to add -Wl,--no-as-needed before the gtk libraries, otherwise the +# linker will drop those dependencies because no symbols are used from them. +# But those dependencies need to be kept for things to work properly. +# Ideally, we'd only add -Wl,--no-as-needed if necessary, but it's just simpler +# to add it unconditionally. This library is also simple enough that forcing +# -Wl,--as-needed after the gtk libraries is not going to make a significant +# difference. +if CONFIG['GCC_USE_GNU_LD']: + no_as_needed = ['-Wl,--no-as-needed'] + as_needed = ['-Wl,--as-needed'] +else: + no_as_needed = [] + as_needed = [] + +OS_LIBS += [f for f in CONFIG['MOZ_GTK3_LIBS'] if f.startswith('-L')] +OS_LIBS += no_as_needed +OS_LIBS += [ + 'gtk-3', + 'gdk-3', +] +OS_LIBS += as_needed diff --git a/widget/gtk/mozgtk/moz.build b/widget/gtk/mozgtk/moz.build new file mode 100644 index 000000000..528e2e9d0 --- /dev/null +++ b/widget/gtk/mozgtk/moz.build @@ -0,0 +1,7 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +DIRS += ['stub', 'gtk2', 'gtk3'] diff --git a/widget/gtk/mozgtk/mozgtk.c b/widget/gtk/mozgtk/mozgtk.c new file mode 100644 index 000000000..d9fb9385d --- /dev/null +++ b/widget/gtk/mozgtk/mozgtk.c @@ -0,0 +1,634 @@ +#include "mozilla/Types.h" +#include "mozilla/Assertions.h" + +#define STUB(symbol) MOZ_EXPORT void symbol (void) { MOZ_CRASH(); } + +#ifdef COMMON_SYMBOLS +STUB(gdk_atom_intern) +STUB(gdk_atom_name) +STUB(gdk_beep) +STUB(gdk_cairo_create) +STUB(gdk_color_free) +STUB(gdk_color_parse) +STUB(gdk_cursor_new_for_display) +STUB(gdk_cursor_new_from_name) +STUB(gdk_cursor_new_from_pixbuf) +STUB(gdk_display_close) +STUB(gdk_display_get_default) +STUB(gdk_display_get_default_screen) +STUB(gdk_display_get_pointer) +STUB(gdk_display_get_window_at_pointer) +STUB(gdk_display_manager_get) +STUB(gdk_display_manager_set_default_display) +STUB(gdk_display_open) +STUB(gdk_display_sync) +STUB(gdk_display_warp_pointer) +STUB(gdk_drag_context_get_actions) +STUB(gdk_drag_context_get_dest_window) +STUB(gdk_drag_context_list_targets) +STUB(gdk_drag_status) +STUB(gdk_error_trap_pop) +STUB(gdk_error_trap_push) +STUB(gdk_event_copy) +STUB(gdk_event_free) +STUB(gdk_event_get_axis) +STUB(gdk_event_get_time) +STUB(gdk_event_handler_set) +STUB(gdk_event_peek) +STUB(gdk_event_put) +STUB(gdk_flush) +STUB(gdk_get_default_root_window) +STUB(gdk_get_display) +STUB(gdk_get_display_arg_name) +STUB(gdk_get_program_class) +STUB(gdk_keymap_get_default) +STUB(gdk_keymap_get_direction) +STUB(gdk_keymap_get_entries_for_keyval) +STUB(gdk_keymap_get_for_display) +STUB(gdk_keymap_have_bidi_layouts) +STUB(gdk_keymap_translate_keyboard_state) +STUB(gdk_keyval_name) +STUB(gdk_keyval_to_unicode) +STUB(gdk_pango_context_get) +STUB(gdk_pointer_grab) +STUB(gdk_pointer_ungrab) +STUB(gdk_property_get) +STUB(gdk_screen_get_default) +STUB(gdk_screen_get_display) +STUB(gdk_screen_get_font_options) +STUB(gdk_screen_get_height) +STUB(gdk_screen_get_height_mm) +STUB(gdk_screen_get_monitor_at_window) +STUB(gdk_screen_get_monitor_geometry) +STUB(gdk_screen_get_number) +STUB(gdk_screen_get_resolution) +STUB(gdk_screen_get_rgba_visual) +STUB(gdk_screen_get_root_window) +STUB(gdk_screen_get_system_visual) +STUB(gdk_screen_get_width) +STUB(gdk_screen_height) +STUB(gdk_screen_is_composited) +STUB(gdk_screen_width) +STUB(gdk_unicode_to_keyval) +STUB(gdk_visual_get_depth) +STUB(gdk_visual_get_system) +STUB(gdk_window_add_filter) +STUB(gdk_window_begin_move_drag) +STUB(gdk_window_begin_resize_drag) +STUB(gdk_window_destroy) +STUB(gdk_window_focus) +STUB(gdk_window_get_children) +STUB(gdk_window_get_display) +STUB(gdk_window_get_events) +STUB(gdk_window_get_geometry) +STUB(gdk_window_get_height) +STUB(gdk_window_get_origin) +STUB(gdk_window_get_parent) +STUB(gdk_window_get_position) +STUB(gdk_window_get_root_origin) +STUB(gdk_window_get_screen) +STUB(gdk_window_get_state) +STUB(gdk_window_get_toplevel) +STUB(gdk_window_get_update_area) +STUB(gdk_window_get_user_data) +STUB(gdk_window_get_visual) +STUB(gdk_window_get_width) +STUB(gdk_window_hide) +STUB(gdk_window_input_shape_combine_region) +STUB(gdk_window_invalidate_rect) +STUB(gdk_window_invalidate_region) +STUB(gdk_window_is_destroyed) +STUB(gdk_window_is_visible) +STUB(gdk_window_lower) +STUB(gdk_window_move) +STUB(gdk_window_move_resize) +STUB(gdk_window_new) +STUB(gdk_window_peek_children) +STUB(gdk_window_process_updates) +STUB(gdk_window_raise) +STUB(gdk_window_remove_filter) +STUB(gdk_window_reparent) +STUB(gdk_window_resize) +STUB(gdk_window_set_cursor) +STUB(gdk_window_set_debug_updates) +STUB(gdk_window_set_decorations) +STUB(gdk_window_set_events) +STUB(gdk_window_set_role) +STUB(gdk_window_set_urgency_hint) +STUB(gdk_window_set_user_data) +STUB(gdk_window_shape_combine_region) +STUB(gdk_window_show) +STUB(gdk_window_show_unraised) +STUB(gdk_x11_atom_to_xatom) +STUB(gdk_x11_display_get_user_time) +STUB(gdk_x11_display_get_xdisplay) +STUB(gdk_x11_get_default_root_xwindow) +STUB(gdk_x11_get_default_xdisplay) +STUB(gdk_x11_get_server_time) +STUB(gdk_x11_get_xatom_by_name) +STUB(gdk_x11_get_xatom_by_name_for_display) +STUB(gdk_x11_lookup_xdisplay) +STUB(gdk_x11_screen_get_xscreen) +STUB(gdk_x11_screen_supports_net_wm_hint) +STUB(gdk_x11_visual_get_xvisual) +STUB(gdk_x11_window_foreign_new_for_display) +STUB(gdk_x11_window_lookup_for_display) +STUB(gdk_x11_window_set_user_time) +STUB(gdk_x11_xatom_to_atom) +STUB(gdk_x11_set_sm_client_id) +STUB(gtk_accel_label_new) +STUB(gtk_alignment_get_type) +STUB(gtk_alignment_new) +STUB(gtk_alignment_set_padding) +STUB(gtk_arrow_get_type) +STUB(gtk_arrow_new) +STUB(gtk_bindings_activate) +STUB(gtk_bin_get_child) +STUB(gtk_bin_get_type) +STUB(gtk_border_free) +STUB(gtk_box_get_type) +STUB(gtk_box_pack_start) +STUB(gtk_button_new) +STUB(gtk_button_new_with_label) +STUB(gtk_check_button_new_with_label) +STUB(gtk_check_button_new_with_mnemonic) +STUB(gtk_check_menu_item_new) +STUB(gtk_check_version) +STUB(gtk_clipboard_clear) +STUB(gtk_clipboard_get) +STUB(gtk_clipboard_request_contents) +STUB(gtk_clipboard_request_text) +STUB(gtk_clipboard_set_can_store) +STUB(gtk_clipboard_set_with_data) +STUB(gtk_clipboard_store) +STUB(gtk_color_selection_dialog_get_color_selection) +STUB(gtk_color_selection_dialog_get_type) +STUB(gtk_color_selection_dialog_new) +STUB(gtk_color_selection_get_current_color) +STUB(gtk_color_selection_get_type) +STUB(gtk_color_selection_set_current_color) +STUB(gtk_combo_box_get_active) +STUB(gtk_combo_box_get_type) +STUB(gtk_combo_box_new) +STUB(gtk_combo_box_new_with_entry) +STUB(gtk_combo_box_set_active) +STUB(gtk_combo_box_text_get_type) +STUB(gtk_combo_box_text_new) +STUB(gtk_container_add) +STUB(gtk_container_forall) +STUB(gtk_container_get_border_width) +STUB(gtk_container_get_type) +STUB(gtk_container_set_border_width) +STUB(gtk_container_set_resize_mode) +STUB(gtk_dialog_get_content_area) +STUB(gtk_dialog_get_type) +STUB(gtk_dialog_new_with_buttons) +STUB(gtk_dialog_run) +STUB(gtk_dialog_set_alternative_button_order) +STUB(gtk_dialog_set_default_response) +STUB(gtk_drag_begin) +STUB(gtk_drag_dest_set) +STUB(gtk_drag_finish) +STUB(gtk_drag_get_data) +STUB(gtk_drag_get_source_widget) +STUB(gtk_drag_set_icon_pixbuf) +STUB(gtk_drag_set_icon_widget) +STUB(gtk_editable_get_type) +STUB(gtk_editable_select_region) +STUB(gtk_entry_get_text) +STUB(gtk_entry_get_type) +STUB(gtk_entry_new) +STUB(gtk_entry_set_activates_default) +STUB(gtk_entry_set_text) +STUB(gtk_enumerate_printers) +STUB(gtk_expander_new) +STUB(gtk_file_chooser_add_filter) +STUB(gtk_file_chooser_dialog_new) +STUB(gtk_file_chooser_get_filenames) +STUB(gtk_file_chooser_get_filter) +STUB(gtk_file_chooser_get_preview_filename) +STUB(gtk_file_chooser_get_type) +STUB(gtk_file_chooser_get_uri) +STUB(gtk_file_chooser_list_filters) +STUB(gtk_file_chooser_set_current_folder) +STUB(gtk_file_chooser_set_current_name) +STUB(gtk_file_chooser_set_do_overwrite_confirmation) +STUB(gtk_file_chooser_set_filename) +STUB(gtk_file_chooser_set_filter) +STUB(gtk_file_chooser_set_local_only) +STUB(gtk_file_chooser_set_preview_widget) +STUB(gtk_file_chooser_set_preview_widget_active) +STUB(gtk_file_chooser_set_select_multiple) +STUB(gtk_file_chooser_widget_get_type) +STUB(gtk_file_filter_add_pattern) +STUB(gtk_file_filter_new) +STUB(gtk_file_filter_set_name) +STUB(gtk_fixed_new) +STUB(gtk_frame_new) +STUB(gtk_grab_add) +STUB(gtk_grab_remove) +STUB(gtk_handle_box_new) +STUB(gtk_hbox_new) +STUB(gtk_icon_info_free) +STUB(gtk_icon_info_load_icon) +STUB(gtk_icon_set_add_source) +STUB(gtk_icon_set_new) +STUB(gtk_icon_set_render_icon) +STUB(gtk_icon_set_unref) +STUB(gtk_icon_size_lookup) +STUB(gtk_icon_source_free) +STUB(gtk_icon_source_new) +STUB(gtk_icon_source_set_icon_name) +STUB(gtk_icon_theme_add_builtin_icon) +STUB(gtk_icon_theme_get_default) +STUB(gtk_icon_theme_get_icon_sizes) +STUB(gtk_icon_theme_lookup_by_gicon) +STUB(gtk_icon_theme_lookup_icon) +STUB(gtk_image_get_type) +STUB(gtk_image_menu_item_new) +STUB(gtk_image_new) +STUB(gtk_image_new_from_stock) +STUB(gtk_image_set_from_pixbuf) +STUB(gtk_im_context_filter_keypress) +STUB(gtk_im_context_focus_in) +STUB(gtk_im_context_focus_out) +STUB(gtk_im_context_get_preedit_string) +STUB(gtk_im_context_reset) +STUB(gtk_im_context_set_client_window) +STUB(gtk_im_context_set_cursor_location) +STUB(gtk_im_context_set_surrounding) +STUB(gtk_im_context_simple_new) +STUB(gtk_im_multicontext_get_type) +STUB(gtk_im_multicontext_new) +STUB(gtk_info_bar_get_type) +STUB(gtk_info_bar_get_content_area) +STUB(gtk_info_bar_new) +STUB(gtk_init) +STUB(gtk_invisible_new) +STUB(gtk_key_snooper_install) +STUB(gtk_key_snooper_remove) +STUB(gtk_label_get_type) +STUB(gtk_label_new) +STUB(gtk_label_set_markup) +STUB(gtk_link_button_new) +STUB(gtk_main_do_event) +STUB(gtk_main_iteration) +STUB(gtk_menu_attach_to_widget) +STUB(gtk_menu_bar_new) +STUB(gtk_menu_get_type) +STUB(gtk_menu_item_get_type) +STUB(gtk_menu_item_new) +STUB(gtk_menu_item_set_submenu) +STUB(gtk_menu_new) +STUB(gtk_menu_shell_append) +STUB(gtk_menu_shell_get_type) +STUB(gtk_misc_get_alignment) +STUB(gtk_misc_get_padding) +STUB(gtk_misc_get_type) +STUB(gtk_misc_set_alignment) +STUB(gtk_misc_set_padding) +STUB(gtk_notebook_new) +STUB(gtk_page_setup_copy) +STUB(gtk_page_setup_get_bottom_margin) +STUB(gtk_page_setup_get_left_margin) +STUB(gtk_page_setup_get_orientation) +STUB(gtk_page_setup_get_paper_size) +STUB(gtk_page_setup_get_right_margin) +STUB(gtk_page_setup_get_top_margin) +STUB(gtk_page_setup_new) +STUB(gtk_page_setup_set_bottom_margin) +STUB(gtk_page_setup_set_left_margin) +STUB(gtk_page_setup_set_orientation) +STUB(gtk_page_setup_set_paper_size) +STUB(gtk_page_setup_set_paper_size_and_default_margins) +STUB(gtk_page_setup_set_right_margin) +STUB(gtk_page_setup_set_top_margin) +STUB(gtk_paper_size_free) +STUB(gtk_paper_size_get_display_name) +STUB(gtk_paper_size_get_height) +STUB(gtk_paper_size_get_name) +STUB(gtk_paper_size_get_width) +STUB(gtk_paper_size_is_custom) +STUB(gtk_paper_size_is_equal) +STUB(gtk_paper_size_new) +STUB(gtk_paper_size_new_custom) +STUB(gtk_paper_size_set_size) +STUB(gtk_parse_args) +STUB(gtk_plug_get_socket_window) +STUB(gtk_plug_get_type) +STUB(gtk_printer_accepts_pdf) +STUB(gtk_printer_get_name) +STUB(gtk_printer_get_type) +STUB(gtk_printer_is_default) +STUB(gtk_print_job_new) +STUB(gtk_print_job_send) +STUB(gtk_print_job_set_source_file) +STUB(gtk_print_run_page_setup_dialog) +STUB(gtk_print_settings_copy) +STUB(gtk_print_settings_foreach) +STUB(gtk_print_settings_get) +STUB(gtk_print_settings_get_duplex) +STUB(gtk_print_settings_get_n_copies) +STUB(gtk_print_settings_get_page_ranges) +STUB(gtk_print_settings_get_paper_size) +STUB(gtk_print_settings_get_printer) +STUB(gtk_print_settings_get_print_pages) +STUB(gtk_print_settings_get_resolution) +STUB(gtk_print_settings_get_reverse) +STUB(gtk_print_settings_get_scale) +STUB(gtk_print_settings_get_use_color) +STUB(gtk_print_settings_has_key) +STUB(gtk_print_settings_new) +STUB(gtk_print_settings_set) +STUB(gtk_print_settings_set_duplex) +STUB(gtk_print_settings_set_n_copies) +STUB(gtk_print_settings_set_orientation) +STUB(gtk_print_settings_set_page_ranges) +STUB(gtk_print_settings_set_paper_size) +STUB(gtk_print_settings_set_printer) +STUB(gtk_print_settings_set_print_pages) +STUB(gtk_print_settings_set_resolution) +STUB(gtk_print_settings_set_reverse) +STUB(gtk_print_settings_set_scale) +STUB(gtk_print_settings_set_use_color) +STUB(gtk_print_unix_dialog_add_custom_tab) +STUB(gtk_print_unix_dialog_get_page_setup) +STUB(gtk_print_unix_dialog_get_selected_printer) +STUB(gtk_print_unix_dialog_get_settings) +STUB(gtk_print_unix_dialog_get_type) +STUB(gtk_print_unix_dialog_new) +STUB(gtk_print_unix_dialog_set_manual_capabilities) +STUB(gtk_print_unix_dialog_set_page_setup) +STUB(gtk_print_unix_dialog_set_settings) +STUB(gtk_progress_bar_new) +STUB(gtk_propagate_event) +STUB(gtk_radio_button_get_type) +STUB(gtk_radio_button_new_with_label) +STUB(gtk_radio_button_new_with_mnemonic) +STUB(gtk_radio_button_new_with_mnemonic_from_widget) +STUB(gtk_range_get_min_slider_size) +STUB(gtk_range_get_type) +STUB(gtk_recent_manager_add_item) +STUB(gtk_recent_manager_get_default) +STUB(gtk_scrollbar_get_type) +STUB(gtk_scrolled_window_new) +STUB(gtk_selection_data_copy) +STUB(gtk_selection_data_free) +STUB(gtk_selection_data_get_data) +STUB(gtk_selection_data_get_length) +STUB(gtk_selection_data_get_selection) +STUB(gtk_selection_data_get_target) +STUB(gtk_selection_data_get_targets) +STUB(gtk_selection_data_set) +STUB(gtk_selection_data_set_pixbuf) +STUB(gtk_selection_data_set_text) +STUB(gtk_selection_data_targets_include_text) +STUB(gtk_separator_get_type) +STUB(gtk_separator_menu_item_new) +STUB(gtk_separator_tool_item_new) +STUB(gtk_settings_get_default) +STUB(gtk_settings_get_for_screen) +STUB(gtk_socket_add_id) +STUB(gtk_socket_get_id) +STUB(gtk_socket_get_type) +STUB(gtk_socket_get_plug_window) +STUB(gtk_socket_new) +STUB(gtk_spin_button_new) +STUB(gtk_statusbar_new) +STUB(gtk_style_lookup_icon_set) +STUB(gtk_table_attach) +STUB(gtk_table_get_type) +STUB(gtk_table_new) +STUB(gtk_target_list_add) +STUB(gtk_target_list_add_image_targets) +STUB(gtk_target_list_new) +STUB(gtk_target_list_unref) +STUB(gtk_targets_include_image) +STUB(gtk_target_table_free) +STUB(gtk_target_table_new_from_list) +STUB(gtk_text_view_new) +STUB(gtk_toggle_button_get_active) +STUB(gtk_toggle_button_get_type) +STUB(gtk_toggle_button_new) +STUB(gtk_toggle_button_set_active) +STUB(gtk_toggle_button_set_inconsistent) +STUB(gtk_toolbar_new) +STUB(gtk_tooltip_get_type) +STUB(gtk_tree_view_append_column) +STUB(gtk_tree_view_column_new) +STUB(gtk_tree_view_column_set_title) +STUB(gtk_tree_view_get_type) +STUB(gtk_tree_view_new) +STUB(gtk_vbox_new) +STUB(gtk_widget_add_events) +STUB(gtk_widget_class_find_style_property) +STUB(gtk_widget_destroy) +STUB(gtk_widget_destroyed) +STUB(gtk_widget_ensure_style) +STUB(gtk_widget_event) +STUB(gtk_widget_get_accessible) +STUB(gtk_widget_get_allocation) +STUB(gtk_widget_get_default_direction) +STUB(gtk_widget_get_display) +STUB(gtk_widget_get_events) +STUB(gtk_widget_get_has_window) +STUB(gtk_widget_get_mapped) +STUB(gtk_widget_get_parent) +STUB(gtk_widget_get_parent_window) +STUB(gtk_widget_get_realized) +STUB(gtk_widget_get_screen) +STUB(gtk_widget_get_settings) +STUB(gtk_widget_get_style) +STUB(gtk_widget_get_toplevel) +STUB(gtk_widget_get_type) +STUB(gtk_widget_get_visible) +STUB(gtk_widget_get_visual) +STUB(gtk_widget_get_window) +STUB(gtk_widget_grab_focus) +STUB(gtk_widget_has_focus) +STUB(gtk_widget_has_grab) +STUB(gtk_widget_hide) +STUB(gtk_widget_is_focus) +STUB(gtk_widget_is_toplevel) +STUB(gtk_widget_map) +STUB(gtk_widget_modify_bg) +STUB(gtk_widget_realize) +STUB(gtk_widget_reparent) +STUB(gtk_widget_set_allocation) +STUB(gtk_widget_set_app_paintable) +STUB(gtk_window_set_auto_startup_notification) +STUB(gtk_window_set_opacity) +STUB(gtk_window_set_screen) +STUB(gtk_widget_set_can_focus) +STUB(gtk_widget_set_direction) +STUB(gtk_widget_set_double_buffered) +STUB(gtk_widget_set_has_window) +STUB(gtk_widget_set_mapped) +STUB(gtk_widget_set_name) +STUB(gtk_widget_set_parent) +STUB(gtk_widget_set_parent_window) +STUB(gtk_widget_set_realized) +STUB(gtk_widget_set_redraw_on_allocate) +STUB(gtk_widget_set_sensitive) +STUB(gtk_widget_set_window) +STUB(gtk_widget_show) +STUB(gtk_widget_show_all) +STUB(gtk_widget_size_allocate) +STUB(gtk_widget_style_get) +STUB(gtk_widget_unparent) +STUB(gtk_window_deiconify) +STUB(gtk_window_fullscreen) +STUB(gtk_window_get_group) +STUB(gtk_window_get_transient_for) +STUB(gtk_window_get_type) +STUB(gtk_window_get_type_hint) +STUB(gtk_window_get_window_type) +STUB(gtk_window_group_add_window) +STUB(gtk_window_group_get_current_grab) +STUB(gtk_window_group_new) +STUB(gtk_window_iconify) +STUB(gtk_window_is_active) +STUB(gtk_window_maximize) +STUB(gtk_window_move) +STUB(gtk_window_new) +STUB(gtk_window_present_with_time) +STUB(gtk_window_resize) +STUB(gtk_window_set_accept_focus) +STUB(gtk_window_set_decorated) +STUB(gtk_window_set_deletable) +STUB(gtk_window_set_destroy_with_parent) +STUB(gtk_window_set_geometry_hints) +STUB(gtk_window_set_icon_name) +STUB(gtk_window_set_modal) +STUB(gtk_window_set_skip_taskbar_hint) +STUB(gtk_window_set_title) +STUB(gtk_window_set_transient_for) +STUB(gtk_window_set_type_hint) +STUB(gtk_window_set_wmclass) +STUB(gtk_window_unfullscreen) +STUB(gtk_window_unmaximize) +#endif + +#ifdef GTK3_SYMBOLS +STUB(gdk_device_get_source) +STUB(gdk_device_manager_get_client_pointer) +STUB(gdk_disable_multidevice) +STUB(gdk_device_manager_list_devices) +STUB(gdk_display_get_device_manager) +STUB(gdk_error_trap_pop_ignored) +STUB(gdk_event_get_source_device) +STUB(gdk_window_get_type) +STUB(gdk_x11_window_get_xid) +STUB(gdk_x11_display_get_type) +STUB(gtk_box_new) +STUB(gtk_cairo_should_draw_window) +STUB(gtk_cairo_transform_to_window) +STUB(gtk_combo_box_text_append) +STUB(gtk_drag_set_icon_surface) +STUB(gtk_get_major_version) +STUB(gtk_get_micro_version) +STUB(gtk_get_minor_version) +STUB(gtk_menu_button_new) +STUB(gtk_offscreen_window_new) +STUB(gtk_paned_new) +STUB(gtk_radio_menu_item_new) +STUB(gtk_render_activity) +STUB(gtk_render_arrow) +STUB(gtk_render_background) +STUB(gtk_render_check) +STUB(gtk_render_expander) +STUB(gtk_render_extension) +STUB(gtk_render_focus) +STUB(gtk_render_frame) +STUB(gtk_render_frame_gap) +STUB(gtk_render_handle) +STUB(gtk_render_line) +STUB(gtk_render_option) +STUB(gtk_render_slider) +STUB(gtk_scale_new) +STUB(gtk_scrollbar_new) +STUB(gtk_style_context_add_class) +STUB(gtk_style_context_add_region) +STUB(gtk_style_context_get) +STUB(gtk_style_context_get_background_color) +STUB(gtk_style_context_get_border) +STUB(gtk_style_context_get_border_color) +STUB(gtk_style_context_get_color) +STUB(gtk_style_context_get_direction) +STUB(gtk_style_context_get_margin) +STUB(gtk_style_context_get_padding) +STUB(gtk_style_context_get_path) +STUB(gtk_style_context_get_property) +STUB(gtk_style_context_get_state) +STUB(gtk_style_context_get_style) +STUB(gtk_style_context_has_class) +STUB(gtk_style_context_invalidate) +STUB(gtk_style_context_list_classes) +STUB(gtk_style_context_new) +STUB(gtk_style_context_remove_class) +STUB(gtk_style_context_remove_region) +STUB(gtk_style_context_restore) +STUB(gtk_style_context_save) +STUB(gtk_style_context_set_direction) +STUB(gtk_style_context_set_path) +STUB(gtk_style_context_set_parent) +STUB(gtk_style_context_set_state) +STUB(gtk_style_properties_lookup_property) +STUB(gtk_tree_view_column_get_button) +STUB(gtk_widget_get_preferred_size) +STUB(gtk_widget_get_state_flags) +STUB(gtk_widget_get_style_context) +STUB(gtk_widget_path_append_for_widget) +STUB(gtk_widget_path_append_type) +STUB(gtk_widget_path_copy) +STUB(gtk_widget_path_free) +STUB(gtk_widget_path_iter_add_class) +STUB(gtk_widget_path_new) +STUB(gtk_widget_path_unref) +STUB(gtk_widget_set_visual) +STUB(gtk_app_chooser_dialog_new_for_content_type) +STUB(gtk_app_chooser_get_type) +STUB(gtk_app_chooser_get_app_info) +STUB(gtk_app_chooser_dialog_get_type) +STUB(gtk_app_chooser_dialog_set_heading) +STUB(gtk_color_chooser_dialog_new) +STUB(gtk_color_chooser_dialog_get_type) +STUB(gtk_color_chooser_get_type) +STUB(gtk_color_chooser_set_rgba) +STUB(gtk_color_chooser_get_rgba) +STUB(gtk_color_chooser_set_use_alpha) +#endif + +#ifdef GTK2_SYMBOLS +STUB(gdk_drawable_get_screen) +STUB(gdk_rgb_get_colormap) +STUB(gdk_rgb_get_visual) +STUB(gdk_window_lookup) +STUB(gdk_window_set_back_pixmap) +STUB(gdk_x11_colormap_foreign_new) +STUB(gdk_x11_colormap_get_xcolormap) +STUB(gdk_x11_drawable_get_xdisplay) +STUB(gdk_x11_drawable_get_xid) +STUB(gdk_x11_window_get_drawable_impl) +STUB(gdkx_visual_get) +STUB(gtk_object_get_type) +#endif + +#ifndef GTK3_SYMBOLS +// Only define the following workaround when using GTK3, which we detect +// by checking if GTK3 stubs are not provided. +#include <X11/Xlib.h> +// Bug 1271100 +// We need to trick system Cairo into not using the XShm extension due to +// a race condition in it that results in frequent BadAccess errors. Cairo +// relies upon XShmQueryExtension to initially detect if XShm is available. +// So we define our own stub that always indicates XShm not being present. +// mozgtk loads before libXext/libcairo and so this stub will take priority. +// Our tree usage goes through xcb and remains unaffected by this. +MOZ_EXPORT Bool +XShmQueryExtension(Display* aDisplay) +{ + return False; +} +#endif + diff --git a/widget/gtk/mozgtk/stub/moz.build b/widget/gtk/mozgtk/stub/moz.build new file mode 100644 index 000000000..1a8e21001 --- /dev/null +++ b/widget/gtk/mozgtk/stub/moz.build @@ -0,0 +1,16 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +SOURCES += [ + '../mozgtk.c', +] + +for var in ('COMMON_SYMBOLS', 'GTK2_SYMBOLS', 'GTK3_SYMBOLS'): + DEFINES[var] = True + +SharedLibrary('mozgtk_stub') + +SONAME = 'mozgtk' diff --git a/widget/gtk/nsAppShell.cpp b/widget/gtk/nsAppShell.cpp new file mode 100644 index 000000000..5473dd883 --- /dev/null +++ b/widget/gtk/nsAppShell.cpp @@ -0,0 +1,271 @@ +/* -*- 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 <sys/types.h> +#include <unistd.h> +#include <fcntl.h> +#include <errno.h> +#include <gdk/gdk.h> +#include "nsAppShell.h" +#include "nsWindow.h" +#include "mozilla/Logging.h" +#include "prenv.h" +#include "mozilla/HangMonitor.h" +#include "mozilla/Unused.h" +#include "GeckoProfiler.h" +#include "nsIPowerManagerService.h" +#ifdef MOZ_ENABLE_DBUS +#include "WakeLockListener.h" +#endif + +using mozilla::Unused; + +#define NOTIFY_TOKEN 0xFA + +PRLogModuleInfo *gWidgetLog = nullptr; +PRLogModuleInfo *gWidgetFocusLog = nullptr; +PRLogModuleInfo *gWidgetDragLog = nullptr; +PRLogModuleInfo *gWidgetDrawLog = nullptr; + +static GPollFunc sPollFunc; + +// Wrapper function to disable hang monitoring while waiting in poll(). +static gint +PollWrapper(GPollFD *ufds, guint nfsd, gint timeout_) +{ + mozilla::HangMonitor::Suspend(); + profiler_sleep_start(); + gint result = (*sPollFunc)(ufds, nfsd, timeout_); + profiler_sleep_end(); + mozilla::HangMonitor::NotifyActivity(); + return result; +} + +#if MOZ_WIDGET_GTK == 3 +// For bug 726483. +static decltype(GtkContainerClass::check_resize) sReal_gtk_window_check_resize; + +static void +wrap_gtk_window_check_resize(GtkContainer *container) +{ + GdkWindow* gdk_window = gtk_widget_get_window(&container->widget); + if (gdk_window) { + g_object_ref(gdk_window); + } + + sReal_gtk_window_check_resize(container); + + if (gdk_window) { + g_object_unref(gdk_window); + } +} + +// Emit resume-events on GdkFrameClock if flush-events has not been +// balanced by resume-events at dispose. +// For https://bugzilla.gnome.org/show_bug.cgi?id=742636 +static decltype(GObjectClass::constructed) sRealGdkFrameClockConstructed; +static decltype(GObjectClass::dispose) sRealGdkFrameClockDispose; +static GQuark sPendingResumeQuark; + +static void +OnFlushEvents(GObject* clock, gpointer) +{ + g_object_set_qdata(clock, sPendingResumeQuark, GUINT_TO_POINTER(1)); +} + +static void +OnResumeEvents(GObject* clock, gpointer) +{ + g_object_set_qdata(clock, sPendingResumeQuark, nullptr); +} + +static void +WrapGdkFrameClockConstructed(GObject* object) +{ + sRealGdkFrameClockConstructed(object); + + g_signal_connect(object, "flush-events", + G_CALLBACK(OnFlushEvents), nullptr); + g_signal_connect(object, "resume-events", + G_CALLBACK(OnResumeEvents), nullptr); +} + +static void +WrapGdkFrameClockDispose(GObject* object) +{ + if (g_object_get_qdata(object, sPendingResumeQuark)) { + g_signal_emit_by_name(object, "resume-events"); + } + + sRealGdkFrameClockDispose(object); +} +#endif + +/*static*/ gboolean +nsAppShell::EventProcessorCallback(GIOChannel *source, + GIOCondition condition, + gpointer data) +{ + nsAppShell *self = static_cast<nsAppShell *>(data); + + unsigned char c; + Unused << read(self->mPipeFDs[0], &c, 1); + NS_ASSERTION(c == (unsigned char) NOTIFY_TOKEN, "wrong token"); + + self->NativeEventCallback(); + return TRUE; +} + +nsAppShell::~nsAppShell() +{ + if (mTag) + g_source_remove(mTag); + if (mPipeFDs[0]) + close(mPipeFDs[0]); + if (mPipeFDs[1]) + close(mPipeFDs[1]); +} + +nsresult +nsAppShell::Init() +{ + // For any versions of Glib before 2.36, g_type_init must be explicitly called + // to safely use the library. Failure to do so may cause various failures/crashes + // in any code that uses Glib, Gdk, or Gtk. In later versions of Glib, this call + // is a no-op. + g_type_init(); + + if (!gWidgetLog) + gWidgetLog = PR_NewLogModule("Widget"); + if (!gWidgetFocusLog) + gWidgetFocusLog = PR_NewLogModule("WidgetFocus"); + if (!gWidgetDragLog) + gWidgetDragLog = PR_NewLogModule("WidgetDrag"); + if (!gWidgetDrawLog) + gWidgetDrawLog = PR_NewLogModule("WidgetDraw"); + +#ifdef MOZ_ENABLE_DBUS + nsCOMPtr<nsIPowerManagerService> powerManagerService = + do_GetService(POWERMANAGERSERVICE_CONTRACTID); + + if (powerManagerService) { + powerManagerService->AddWakeLockListener(WakeLockListener::GetSingleton()); + } else { + NS_WARNING("Failed to retrieve PowerManagerService, wakelocks will be broken!"); + } +#endif + + if (!sPollFunc) { + sPollFunc = g_main_context_get_poll_func(nullptr); + g_main_context_set_poll_func(nullptr, &PollWrapper); + } + +#if MOZ_WIDGET_GTK == 3 + if (!sReal_gtk_window_check_resize && + gtk_check_version(3,8,0) != nullptr) { // GTK 3.0 to GTK 3.6. + // GtkWindow is a static class and so will leak anyway but this ref + // makes sure it isn't recreated. + gpointer gtk_plug_class = g_type_class_ref(GTK_TYPE_WINDOW); + auto check_resize = >K_CONTAINER_CLASS(gtk_plug_class)->check_resize; + sReal_gtk_window_check_resize = *check_resize; + *check_resize = wrap_gtk_window_check_resize; + } + + if (!sPendingResumeQuark && + gtk_check_version(3,14,7) != nullptr) { // GTK 3.0 to GTK 3.14.7. + // GTK 3.8 - 3.14 registered this type when creating the frame clock + // for the root window of the display when the display was opened. + GType gdkFrameClockIdleType = g_type_from_name("GdkFrameClockIdle"); + if (gdkFrameClockIdleType) { // not in versions prior to 3.8 + sPendingResumeQuark = g_quark_from_string("moz-resume-is-pending"); + auto gdk_frame_clock_idle_class = + G_OBJECT_CLASS(g_type_class_peek_static(gdkFrameClockIdleType)); + auto constructed = &gdk_frame_clock_idle_class->constructed; + sRealGdkFrameClockConstructed = *constructed; + *constructed = WrapGdkFrameClockConstructed; + auto dispose = &gdk_frame_clock_idle_class->dispose; + sRealGdkFrameClockDispose = *dispose; + *dispose = WrapGdkFrameClockDispose; + } + } + + // Workaround for bug 1209659 which is fixed by Gtk3.20 + if (gtk_check_version(3, 20, 0) != nullptr) + unsetenv("GTK_CSD"); +#endif + + if (PR_GetEnv("MOZ_DEBUG_PAINTS")) + gdk_window_set_debug_updates(TRUE); + + // Whitelist of only common, stable formats - see bugs 1197059 and 1203078 + GSList* pixbufFormats = gdk_pixbuf_get_formats(); + for (GSList* iter = pixbufFormats; iter; iter = iter->next) { + GdkPixbufFormat* format = static_cast<GdkPixbufFormat*>(iter->data); + gchar* name = gdk_pixbuf_format_get_name(format); + if (strcmp(name, "jpeg") && + strcmp(name, "png") && + strcmp(name, "gif") && + strcmp(name, "bmp") && + strcmp(name, "ico") && + strcmp(name, "xpm") && + strcmp(name, "svg")) { + gdk_pixbuf_format_set_disabled(format, TRUE); + } + g_free(name); + } + g_slist_free(pixbufFormats); + + int err = pipe(mPipeFDs); + if (err) + return NS_ERROR_OUT_OF_MEMORY; + + GIOChannel *ioc; + GSource *source; + + // make the pipe nonblocking + + int flags = fcntl(mPipeFDs[0], F_GETFL, 0); + if (flags == -1) + goto failed; + err = fcntl(mPipeFDs[0], F_SETFL, flags | O_NONBLOCK); + if (err == -1) + goto failed; + flags = fcntl(mPipeFDs[1], F_GETFL, 0); + if (flags == -1) + goto failed; + err = fcntl(mPipeFDs[1], F_SETFL, flags | O_NONBLOCK); + if (err == -1) + goto failed; + + ioc = g_io_channel_unix_new(mPipeFDs[0]); + source = g_io_create_watch(ioc, G_IO_IN); + g_io_channel_unref(ioc); + g_source_set_callback(source, (GSourceFunc)EventProcessorCallback, this, nullptr); + g_source_set_can_recurse(source, TRUE); + mTag = g_source_attach(source, nullptr); + g_source_unref(source); + + return nsBaseAppShell::Init(); +failed: + close(mPipeFDs[0]); + close(mPipeFDs[1]); + mPipeFDs[0] = mPipeFDs[1] = 0; + return NS_ERROR_FAILURE; +} + +void +nsAppShell::ScheduleNativeEventCallback() +{ + unsigned char buf[] = { NOTIFY_TOKEN }; + Unused << write(mPipeFDs[1], buf, 1); +} + +bool +nsAppShell::ProcessNextNativeEvent(bool mayWait) +{ + return g_main_context_iteration(nullptr, mayWait); +} diff --git a/widget/gtk/nsAppShell.h b/widget/gtk/nsAppShell.h new file mode 100644 index 000000000..afdea5074 --- /dev/null +++ b/widget/gtk/nsAppShell.h @@ -0,0 +1,37 @@ +/* -*- 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/. */ + +#ifndef nsAppShell_h__ +#define nsAppShell_h__ + +#include <glib.h> +#include "nsBaseAppShell.h" +#include "nsCOMPtr.h" + +class nsAppShell : public nsBaseAppShell { +public: + nsAppShell() : mTag(0) { + mPipeFDs[0] = mPipeFDs[1] = 0; + } + + // nsBaseAppShell overrides: + nsresult Init(); + virtual void ScheduleNativeEventCallback(); + virtual bool ProcessNextNativeEvent(bool mayWait); + +private: + virtual ~nsAppShell(); + + static gboolean EventProcessorCallback(GIOChannel *source, + GIOCondition condition, + gpointer data); + + int mPipeFDs[2]; + unsigned mTag; +}; + +#endif /* nsAppShell_h__ */ diff --git a/widget/gtk/nsApplicationChooser.cpp b/widget/gtk/nsApplicationChooser.cpp new file mode 100644 index 000000000..76c231cf6 --- /dev/null +++ b/widget/gtk/nsApplicationChooser.cpp @@ -0,0 +1,128 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/Types.h" + +#include <gtk/gtk.h> + +#include "nsApplicationChooser.h" +#include "WidgetUtils.h" +#include "nsIMIMEInfo.h" +#include "nsIWidget.h" +#include "nsCExternalHandlerService.h" +#include "nsComponentManagerUtils.h" +#include "nsGtkUtils.h" +#include "nsPIDOMWindow.h" + +using namespace mozilla; + +NS_IMPL_ISUPPORTS(nsApplicationChooser, nsIApplicationChooser) + +nsApplicationChooser::nsApplicationChooser() +{ +} + +nsApplicationChooser::~nsApplicationChooser() +{ +} + +NS_IMETHODIMP +nsApplicationChooser::Init(mozIDOMWindowProxy* aParent, + const nsACString& aTitle) +{ + NS_ENSURE_TRUE(aParent, NS_ERROR_FAILURE); + auto* parent = nsPIDOMWindowOuter::From(aParent); + mParentWidget = widget::WidgetUtils::DOMWindowToWidget(parent); + mWindowTitle.Assign(aTitle); + return NS_OK; +} + +NS_IMETHODIMP +nsApplicationChooser::Open(const nsACString& aContentType, nsIApplicationChooserFinishedCallback *aCallback) +{ + MOZ_ASSERT(aCallback); + if (mCallback) { + NS_WARNING("Chooser is already in progress."); + return NS_ERROR_ALREADY_INITIALIZED; + } + mCallback = aCallback; + NS_ENSURE_TRUE(mParentWidget, NS_ERROR_FAILURE); + GtkWindow *parent_widget = + GTK_WINDOW(mParentWidget->GetNativeData(NS_NATIVE_SHELLWIDGET)); + + GtkWidget* chooser = + gtk_app_chooser_dialog_new_for_content_type(parent_widget, + (GtkDialogFlags) (GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT), + PromiseFlatCString(aContentType).get()); + gtk_app_chooser_dialog_set_heading(GTK_APP_CHOOSER_DIALOG(chooser), mWindowTitle.BeginReading()); + NS_ADDREF_THIS(); + g_signal_connect(chooser, "response", G_CALLBACK(OnResponse), this); + g_signal_connect(chooser, "destroy", G_CALLBACK(OnDestroy), this); + gtk_widget_show(chooser); + return NS_OK; +} + +/* static */ void +nsApplicationChooser::OnResponse(GtkWidget* chooser, gint response_id, gpointer user_data) +{ + static_cast<nsApplicationChooser*>(user_data)->Done(chooser, response_id); +} + +/* static */ void +nsApplicationChooser::OnDestroy(GtkWidget *chooser, gpointer user_data) +{ + static_cast<nsApplicationChooser*>(user_data)->Done(chooser, GTK_RESPONSE_CANCEL); +} + +void nsApplicationChooser::Done(GtkWidget* chooser, gint response) +{ + nsCOMPtr<nsILocalHandlerApp> localHandler; + nsresult rv; + switch (response) { + case GTK_RESPONSE_OK: + case GTK_RESPONSE_ACCEPT: + { + localHandler = do_CreateInstance(NS_LOCALHANDLERAPP_CONTRACTID, &rv); + if (NS_FAILED(rv)) { + NS_WARNING("Out of memory."); + break; + } + GAppInfo *app_info = gtk_app_chooser_get_app_info(GTK_APP_CHOOSER(chooser)); + + nsCOMPtr<nsIFile> localExecutable; + gchar *fileWithFullPath = g_find_program_in_path(g_app_info_get_executable(app_info)); + rv = NS_NewNativeLocalFile(nsDependentCString(fileWithFullPath), false, getter_AddRefs(localExecutable)); + g_free(fileWithFullPath); + if (NS_FAILED(rv)) { + NS_WARNING("Cannot create local filename."); + localHandler = nullptr; + } else { + localHandler->SetExecutable(localExecutable); + localHandler->SetName(NS_ConvertUTF8toUTF16(g_app_info_get_display_name(app_info))); + } + g_object_unref(app_info); + } + + break; + case GTK_RESPONSE_CANCEL: + case GTK_RESPONSE_CLOSE: + case GTK_RESPONSE_DELETE_EVENT: + break; + default: + NS_WARNING("Unexpected response"); + break; + } + + // A "response" signal won't be sent again but "destroy" will be. + g_signal_handlers_disconnect_by_func(chooser, FuncToGpointer(OnDestroy), this); + gtk_widget_destroy(chooser); + + if (mCallback) { + mCallback->Done(localHandler); + mCallback = nullptr; + } + NS_RELEASE_THIS(); +} + diff --git a/widget/gtk/nsApplicationChooser.h b/widget/gtk/nsApplicationChooser.h new file mode 100644 index 000000000..da16dac71 --- /dev/null +++ b/widget/gtk/nsApplicationChooser.h @@ -0,0 +1,32 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#ifndef nsApplicationChooser_h__ +#define nsApplicationChooser_h__ + +#include <gtk/gtk.h> +#include "nsCOMPtr.h" +#include "nsIApplicationChooser.h" +#include "nsString.h" + +class nsIWidget; + +class nsApplicationChooser final : public nsIApplicationChooser +{ +public: + nsApplicationChooser(); + NS_DECL_ISUPPORTS + NS_DECL_NSIAPPLICATIONCHOOSER + void Done(GtkWidget* chooser, gint response); + +private: + ~nsApplicationChooser(); + nsCOMPtr<nsIWidget> mParentWidget; + nsCString mWindowTitle; + nsCOMPtr<nsIApplicationChooserFinishedCallback> mCallback; + static void OnResponse(GtkWidget* chooser, gint response_id, gpointer user_data); + static void OnDestroy(GtkWidget* chooser, gpointer user_data); +}; +#endif diff --git a/widget/gtk/nsBidiKeyboard.cpp b/widget/gtk/nsBidiKeyboard.cpp new file mode 100644 index 000000000..51102d945 --- /dev/null +++ b/widget/gtk/nsBidiKeyboard.cpp @@ -0,0 +1,55 @@ +/* -*- 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 "prlink.h" + +#include "nsBidiKeyboard.h" +#include <gtk/gtk.h> + +NS_IMPL_ISUPPORTS(nsBidiKeyboard, nsIBidiKeyboard) + +nsBidiKeyboard::nsBidiKeyboard() +{ + Reset(); +} + +NS_IMETHODIMP +nsBidiKeyboard::Reset() +{ + // NB: The default keymap can be null (e.g. in xpcshell). In that case, + // simply assume that we don't have bidi keyboards. + mHaveBidiKeyboards = false; + + GdkDisplay *display = gdk_display_get_default(); + if (!display) + return NS_OK; + + GdkKeymap *keymap = gdk_keymap_get_for_display(display); + mHaveBidiKeyboards = keymap && gdk_keymap_have_bidi_layouts(keymap); + return NS_OK; +} + +nsBidiKeyboard::~nsBidiKeyboard() +{ +} + +NS_IMETHODIMP +nsBidiKeyboard::IsLangRTL(bool *aIsRTL) +{ + if (!mHaveBidiKeyboards) + return NS_ERROR_FAILURE; + + *aIsRTL = (gdk_keymap_get_direction(gdk_keymap_get_default()) == PANGO_DIRECTION_RTL); + + return NS_OK; +} + +NS_IMETHODIMP nsBidiKeyboard::GetHaveBidiKeyboards(bool* aResult) +{ + // not implemented yet + return NS_ERROR_NOT_IMPLEMENTED; +} diff --git a/widget/gtk/nsBidiKeyboard.h b/widget/gtk/nsBidiKeyboard.h new file mode 100644 index 000000000..08c02f354 --- /dev/null +++ b/widget/gtk/nsBidiKeyboard.h @@ -0,0 +1,26 @@ +/* -*- 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/. */ + +#ifndef __nsBidiKeyboard +#define __nsBidiKeyboard +#include "nsIBidiKeyboard.h" + +class nsBidiKeyboard : public nsIBidiKeyboard +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIBIDIKEYBOARD + + nsBidiKeyboard(); + +protected: + virtual ~nsBidiKeyboard(); + + bool mHaveBidiKeyboards; +}; + +#endif // __nsBidiKeyboard diff --git a/widget/gtk/nsCUPSShim.cpp b/widget/gtk/nsCUPSShim.cpp new file mode 100644 index 000000000..d05da307f --- /dev/null +++ b/widget/gtk/nsCUPSShim.cpp @@ -0,0 +1,59 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* ex: set tabstop=8 softtabstop=4 shiftwidth=4 expandtab: */ +/* 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 "nsDebug.h" +#include "nsString.h" +#include "nsCUPSShim.h" +#include "mozilla/ArrayUtils.h" +#include "prlink.h" + + +// List of symbols to find in libcups. Must match symAddr[] defined in Init(). +// Making this an array of arrays instead of pointers allows storing the +// whole thing in read-only memory. +static const char gSymName[][sizeof("cupsPrintFile")] = { + { "cupsAddOption" }, + { "cupsFreeDests" }, + { "cupsGetDest" }, + { "cupsGetDests" }, + { "cupsPrintFile" }, + { "cupsTempFd" }, +}; +static const int gSymNameCt = mozilla::ArrayLength(gSymName); + + +bool +nsCUPSShim::Init() +{ + mCupsLib = PR_LoadLibrary("libcups.so.2"); + if (!mCupsLib) + return false; + + // List of symbol pointers. Must match gSymName[] defined above. + void **symAddr[] = { + (void **)&mCupsAddOption, + (void **)&mCupsFreeDests, + (void **)&mCupsGetDest, + (void **)&mCupsGetDests, + (void **)&mCupsPrintFile, + (void **)&mCupsTempFd, + }; + + for (int i = gSymNameCt; i--; ) { + *(symAddr[i]) = PR_FindSymbol(mCupsLib, gSymName[i]); + if (! *(symAddr[i])) { +#ifdef DEBUG + nsAutoCString msg(gSymName[i]); + msg.AppendLiteral(" not found in CUPS library"); + NS_WARNING(msg.get()); +#endif + PR_UnloadLibrary(mCupsLib); + mCupsLib = nullptr; + return false; + } + } + return true; +} diff --git a/widget/gtk/nsCUPSShim.h b/widget/gtk/nsCUPSShim.h new file mode 100644 index 000000000..3e7d96f3a --- /dev/null +++ b/widget/gtk/nsCUPSShim.h @@ -0,0 +1,86 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* ex: set tabstop=8 softtabstop=4 shiftwidth=4 expandtab: */ +/* 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/. */ + +#ifndef nsCUPSShim_h___ +#define nsCUPSShim_h___ + + +/* Various CUPS data types. We don't #include cups headers to avoid + * requiring CUPS to be installed on the build host (and to avoid having + * to test for CUPS in configure). + */ +typedef struct /**** Printer Options ****/ +{ + char *name; /* Name of option */ + char *value; /* Value of option */ +} cups_option_t; + +typedef struct /**** Destination ****/ +{ + char *name, /* Printer or class name */ + *instance; /* Local instance name or nullptr */ + int is_default; /* Is this printer the default? */ + int num_options; /* Number of options */ + cups_option_t *options; /* Options */ +} cups_dest_t; + +typedef cups_dest_t* (*CupsGetDestType)(const char *printer, + const char *instance, + int num_dests, + cups_dest_t *dests); +typedef int (*CupsGetDestsType)(cups_dest_t **dests); +typedef int (*CupsFreeDestsType)(int num_dests, + cups_dest_t *dests); +typedef int (*CupsPrintFileType)(const char *printer, + const char *filename, + const char *title, + int num_options, + cups_option_t *options); +typedef int (*CupsTempFdType)(char *filename, + int length); +typedef int (*CupsAddOptionType)(const char *name, + const char *value, + int num_options, + cups_option_t **options); + +struct PRLibrary; + +/* Note: this class relies on static initialization. */ +class nsCUPSShim { + public: + /** + * Initialize this object. Attempt to load the CUPS shared + * library and find function pointers for the supported + * functions (see below). + * @return false if the shared library could not be loaded, or if + * any of the functions could not be found. + * true for successful initialization. + */ + bool Init(); + + /** + * @return true if the object was initialized successfully. + * false otherwise. + */ + bool IsInitialized() { return nullptr != mCupsLib; } + + /* Function pointers for supported functions. These are only + * valid after successful initialization. + */ + CupsAddOptionType mCupsAddOption; + CupsFreeDestsType mCupsFreeDests; + CupsGetDestType mCupsGetDest; + CupsGetDestsType mCupsGetDests; + CupsPrintFileType mCupsPrintFile; + CupsTempFdType mCupsTempFd; + + private: + PRLibrary *mCupsLib; +}; + + + +#endif /* nsCUPSShim_h___ */ diff --git a/widget/gtk/nsClipboard.cpp b/widget/gtk/nsClipboard.cpp new file mode 100644 index 000000000..053ae970e --- /dev/null +++ b/widget/gtk/nsClipboard.cpp @@ -0,0 +1,1046 @@ +/* -*- 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/ArrayUtils.h" + +#include "nsArrayUtils.h" +#include "nsClipboard.h" +#include "nsSupportsPrimitives.h" +#include "nsString.h" +#include "nsReadableUtils.h" +#include "nsXPIDLString.h" +#include "nsPrimitiveHelpers.h" +#include "nsIServiceManager.h" +#include "nsImageToPixbuf.h" +#include "nsStringStream.h" +#include "nsIObserverService.h" +#include "mozilla/Services.h" +#include "mozilla/RefPtr.h" +#include "mozilla/TimeStamp.h" + +#include "imgIContainer.h" + +#include <gtk/gtk.h> + +// For manipulation of the X event queue +#include <X11/Xlib.h> +#include <gdk/gdkx.h> +#include <sys/time.h> +#include <sys/types.h> +#include <errno.h> +#include <unistd.h> +#include "X11UndefineNone.h" + +#include "mozilla/dom/EncodingUtils.h" +#include "nsIUnicodeDecoder.h" + +using mozilla::dom::EncodingUtils; +using namespace mozilla; + +// Callback when someone asks us for the data +void +clipboard_get_cb(GtkClipboard *aGtkClipboard, + GtkSelectionData *aSelectionData, + guint info, + gpointer user_data); + +// Callback when someone asks us to clear a clipboard +void +clipboard_clear_cb(GtkClipboard *aGtkClipboard, + gpointer user_data); + +static void +ConvertHTMLtoUCS2 (guchar *data, + int32_t dataLength, + char16_t **unicodeData, + int32_t &outUnicodeLen); + +static void +GetHTMLCharset (guchar * data, int32_t dataLength, nsCString& str); + + +// Our own versions of gtk_clipboard_wait_for_contents and +// gtk_clipboard_wait_for_text, which don't run the event loop while +// waiting for the data. This prevents a lot of problems related to +// dispatching events at unexpected times. + +static GtkSelectionData * +wait_for_contents (GtkClipboard *clipboard, GdkAtom target); + +static gchar * +wait_for_text (GtkClipboard *clipboard); + +static GdkFilterReturn +selection_request_filter (GdkXEvent *gdk_xevent, + GdkEvent *event, + gpointer data); + +nsClipboard::nsClipboard() +{ +} + +nsClipboard::~nsClipboard() +{ + // We have to clear clipboard before gdk_display_close() call. + // See bug 531580 for details. + if (mGlobalTransferable) { + gtk_clipboard_clear(gtk_clipboard_get(GDK_SELECTION_CLIPBOARD)); + } + if (mSelectionTransferable) { + gtk_clipboard_clear(gtk_clipboard_get(GDK_SELECTION_PRIMARY)); + } +} + +NS_IMPL_ISUPPORTS(nsClipboard, nsIClipboard) + +nsresult +nsClipboard::Init(void) +{ + nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService(); + if (!os) + return NS_ERROR_FAILURE; + + os->AddObserver(this, "quit-application", false); + + // A custom event filter to workaround attempting to dereference a null + // selection requestor in GTK3 versions before 3.11.3. See bug 1178799. +#if (MOZ_WIDGET_GTK == 3) && defined(MOZ_X11) + if (gtk_check_version(3, 11, 3)) + gdk_window_add_filter(nullptr, selection_request_filter, nullptr); +#endif + + return NS_OK; +} + +NS_IMETHODIMP +nsClipboard::Observe(nsISupports *aSubject, const char *aTopic, const char16_t *aData) +{ + if (strcmp(aTopic, "quit-application") == 0) { + // application is going to quit, save clipboard content + Store(); + gdk_window_remove_filter(nullptr, selection_request_filter, nullptr); + } + return NS_OK; +} + +nsresult +nsClipboard::Store(void) +{ + // Ask the clipboard manager to store the current clipboard content + if (mGlobalTransferable) { + GtkClipboard *clipboard = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD); + gtk_clipboard_store(clipboard); + } + return NS_OK; +} + +NS_IMETHODIMP +nsClipboard::SetData(nsITransferable *aTransferable, + nsIClipboardOwner *aOwner, int32_t aWhichClipboard) +{ + // See if we can short cut + if ((aWhichClipboard == kGlobalClipboard && + aTransferable == mGlobalTransferable.get() && + aOwner == mGlobalOwner.get()) || + (aWhichClipboard == kSelectionClipboard && + aTransferable == mSelectionTransferable.get() && + aOwner == mSelectionOwner.get())) { + return NS_OK; + } + + // Clear out the clipboard in order to set the new data + EmptyClipboard(aWhichClipboard); + + // List of suported targets + GtkTargetList *list = gtk_target_list_new(nullptr, 0); + + // Get the types of supported flavors + nsCOMPtr<nsIArray> flavors; + + nsresult rv = + aTransferable->FlavorsTransferableCanExport(getter_AddRefs(flavors)); + if (!flavors || NS_FAILED(rv)) + return NS_ERROR_FAILURE; + + // Add all the flavors to this widget's supported type. + bool imagesAdded = false; + uint32_t count; + flavors->GetLength(&count); + for (uint32_t i=0; i < count; i++) { + nsCOMPtr<nsISupportsCString> flavor = do_QueryElementAt(flavors, i); + + if (flavor) { + nsXPIDLCString flavorStr; + flavor->ToString(getter_Copies(flavorStr)); + + // special case text/unicode since we can handle all of + // the string types + if (!strcmp(flavorStr, kUnicodeMime)) { + gtk_target_list_add(list, gdk_atom_intern("UTF8_STRING", FALSE), 0, 0); + gtk_target_list_add(list, gdk_atom_intern("COMPOUND_TEXT", FALSE), 0, 0); + gtk_target_list_add(list, gdk_atom_intern("TEXT", FALSE), 0, 0); + gtk_target_list_add(list, GDK_SELECTION_TYPE_STRING, 0, 0); + continue; + } + + if (flavorStr.EqualsLiteral(kNativeImageMime) || + flavorStr.EqualsLiteral(kPNGImageMime) || + flavorStr.EqualsLiteral(kJPEGImageMime) || + flavorStr.EqualsLiteral(kJPGImageMime) || + flavorStr.EqualsLiteral(kGIFImageMime)) { + // don't bother adding image targets twice + if (!imagesAdded) { + // accept any writable image type + gtk_target_list_add_image_targets(list, 0, TRUE); + imagesAdded = true; + } + continue; + } + + // Add this to our list of valid targets + GdkAtom atom = gdk_atom_intern(flavorStr, FALSE); + gtk_target_list_add(list, atom, 0, 0); + } + } + + // Get GTK clipboard (CLIPBOARD or PRIMARY) + GtkClipboard *gtkClipboard = gtk_clipboard_get(GetSelectionAtom(aWhichClipboard)); + + gint numTargets; + GtkTargetEntry *gtkTargets = gtk_target_table_new_from_list(list, &numTargets); + + // Set getcallback and request to store data after an application exit + if (gtk_clipboard_set_with_data(gtkClipboard, gtkTargets, numTargets, + clipboard_get_cb, clipboard_clear_cb, this)) + { + // We managed to set-up the clipboard so update internal state + // We have to set it now because gtk_clipboard_set_with_data() calls clipboard_clear_cb() + // which reset our internal state + if (aWhichClipboard == kSelectionClipboard) { + mSelectionOwner = aOwner; + mSelectionTransferable = aTransferable; + } + else { + mGlobalOwner = aOwner; + mGlobalTransferable = aTransferable; + gtk_clipboard_set_can_store(gtkClipboard, gtkTargets, numTargets); + } + + rv = NS_OK; + } + else { + rv = NS_ERROR_FAILURE; + } + + gtk_target_table_free(gtkTargets, numTargets); + gtk_target_list_unref(list); + + return rv; +} + +NS_IMETHODIMP +nsClipboard::GetData(nsITransferable *aTransferable, int32_t aWhichClipboard) +{ + if (!aTransferable) + return NS_ERROR_FAILURE; + + GtkClipboard *clipboard; + clipboard = gtk_clipboard_get(GetSelectionAtom(aWhichClipboard)); + + guchar *data = nullptr; + gint length = 0; + bool foundData = false; + nsAutoCString foundFlavor; + + // Get a list of flavors this transferable can import + nsCOMPtr<nsIArray> flavors; + nsresult rv; + rv = aTransferable->FlavorsTransferableCanImport(getter_AddRefs(flavors)); + if (!flavors || NS_FAILED(rv)) + return NS_ERROR_FAILURE; + + uint32_t count; + flavors->GetLength(&count); + for (uint32_t i=0; i < count; i++) { + nsCOMPtr<nsISupportsCString> currentFlavor; + currentFlavor = do_QueryElementAt(flavors, i); + + if (currentFlavor) { + nsXPIDLCString flavorStr; + currentFlavor->ToString(getter_Copies(flavorStr)); + + // Special case text/unicode since we can convert any + // string into text/unicode + if (!strcmp(flavorStr, kUnicodeMime)) { + gchar* new_text = wait_for_text(clipboard); + if (new_text) { + // Convert utf-8 into our unicode format. + NS_ConvertUTF8toUTF16 ucs2string(new_text); + data = (guchar *)ToNewUnicode(ucs2string); + length = ucs2string.Length() * 2; + g_free(new_text); + foundData = true; + foundFlavor = kUnicodeMime; + break; + } + // If the type was text/unicode and we couldn't get + // text off the clipboard, run the next loop + // iteration. + continue; + } + + // For images, we must wrap the data in an nsIInputStream then return instead of break, + // because that code below won't help us. + if (!strcmp(flavorStr, kJPEGImageMime) || + !strcmp(flavorStr, kJPGImageMime) || + !strcmp(flavorStr, kPNGImageMime) || + !strcmp(flavorStr, kGIFImageMime)) { + // Emulate support for image/jpg + if (!strcmp(flavorStr, kJPGImageMime)) { + flavorStr.Assign(kJPEGImageMime); + } + + GdkAtom atom = gdk_atom_intern(flavorStr, FALSE); + + GtkSelectionData *selectionData = wait_for_contents(clipboard, atom); + if (!selectionData) + continue; + + nsCOMPtr<nsIInputStream> byteStream; + NS_NewByteInputStream(getter_AddRefs(byteStream), + (const char*)gtk_selection_data_get_data(selectionData), + gtk_selection_data_get_length(selectionData), + NS_ASSIGNMENT_COPY); + aTransferable->SetTransferData(flavorStr, byteStream, sizeof(nsIInputStream*)); + gtk_selection_data_free(selectionData); + return NS_OK; + } + + // Get the atom for this type and try to request it off + // the clipboard. + GdkAtom atom = gdk_atom_intern(flavorStr, FALSE); + GtkSelectionData *selectionData; + selectionData = wait_for_contents(clipboard, atom); + if (selectionData) { + const guchar *clipboardData = gtk_selection_data_get_data(selectionData); + length = gtk_selection_data_get_length(selectionData); + // Special case text/html since we can convert into UCS2 + if (!strcmp(flavorStr, kHTMLMime)) { + char16_t* htmlBody= nullptr; + int32_t htmlBodyLen = 0; + // Convert text/html into our unicode format + ConvertHTMLtoUCS2(const_cast<guchar*>(clipboardData), length, + &htmlBody, htmlBodyLen); + // Try next data format? + if (!htmlBodyLen) + continue; + data = (guchar *)htmlBody; + length = htmlBodyLen * 2; + } else { + data = (guchar *)moz_xmalloc(length); + if (!data) + break; + memcpy(data, clipboardData, length); + } + gtk_selection_data_free(selectionData); + foundData = true; + foundFlavor = flavorStr; + break; + } + } + } + + if (foundData) { + nsCOMPtr<nsISupports> wrapper; + nsPrimitiveHelpers::CreatePrimitiveForData(foundFlavor.get(), + data, length, + getter_AddRefs(wrapper)); + aTransferable->SetTransferData(foundFlavor.get(), + wrapper, length); + } + + if (data) + free(data); + + return NS_OK; +} + +NS_IMETHODIMP +nsClipboard::EmptyClipboard(int32_t aWhichClipboard) +{ + if (aWhichClipboard == kSelectionClipboard) { + if (mSelectionOwner) { + mSelectionOwner->LosingOwnership(mSelectionTransferable); + mSelectionOwner = nullptr; + } + mSelectionTransferable = nullptr; + } + else { + if (mGlobalOwner) { + mGlobalOwner->LosingOwnership(mGlobalTransferable); + mGlobalOwner = nullptr; + } + mGlobalTransferable = nullptr; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsClipboard::HasDataMatchingFlavors(const char** aFlavorList, uint32_t aLength, + int32_t aWhichClipboard, bool *_retval) +{ + if (!aFlavorList || !_retval) + return NS_ERROR_NULL_POINTER; + + *_retval = false; + + GtkSelectionData *selection_data = + GetTargets(GetSelectionAtom(aWhichClipboard)); + if (!selection_data) + return NS_OK; + + gint n_targets = 0; + GdkAtom *targets = nullptr; + + if (!gtk_selection_data_get_targets(selection_data, + &targets, &n_targets) || + !n_targets) + return NS_OK; + + // Walk through the provided types and try to match it to a + // provided type. + for (uint32_t i = 0; i < aLength && !*_retval; i++) { + // We special case text/unicode here. + if (!strcmp(aFlavorList[i], kUnicodeMime) && + gtk_selection_data_targets_include_text(selection_data)) { + *_retval = true; + break; + } + + for (int32_t j = 0; j < n_targets; j++) { + gchar *atom_name = gdk_atom_name(targets[j]); + if (!atom_name) + continue; + + if (!strcmp(atom_name, aFlavorList[i])) + *_retval = true; + + // X clipboard supports image/jpeg, but we want to emulate support + // for image/jpg as well + if (!strcmp(aFlavorList[i], kJPGImageMime) && !strcmp(atom_name, kJPEGImageMime)) + *_retval = true; + + g_free(atom_name); + + if (*_retval) + break; + } + } + gtk_selection_data_free(selection_data); + g_free(targets); + + return NS_OK; +} + +NS_IMETHODIMP +nsClipboard::SupportsSelectionClipboard(bool *_retval) +{ + *_retval = true; // yeah, unix supports the selection clipboard + return NS_OK; +} + +NS_IMETHODIMP +nsClipboard::SupportsFindClipboard(bool* _retval) +{ + *_retval = false; + return NS_OK; +} + +/* static */ +GdkAtom +nsClipboard::GetSelectionAtom(int32_t aWhichClipboard) +{ + if (aWhichClipboard == kGlobalClipboard) + return GDK_SELECTION_CLIPBOARD; + + return GDK_SELECTION_PRIMARY; +} + +/* static */ +GtkSelectionData * +nsClipboard::GetTargets(GdkAtom aWhichClipboard) +{ + GtkClipboard *clipboard = gtk_clipboard_get(aWhichClipboard); + return wait_for_contents(clipboard, gdk_atom_intern("TARGETS", FALSE)); +} + +nsITransferable * +nsClipboard::GetTransferable(int32_t aWhichClipboard) +{ + nsITransferable *retval; + + if (aWhichClipboard == kSelectionClipboard) + retval = mSelectionTransferable.get(); + else + retval = mGlobalTransferable.get(); + + return retval; +} + +void +nsClipboard::SelectionGetEvent(GtkClipboard *aClipboard, + GtkSelectionData *aSelectionData) +{ + // Someone has asked us to hand them something. The first thing + // that we want to do is see if that something includes text. If + // it does, try to give it text/unicode after converting it to + // utf-8. + + int32_t whichClipboard; + + // which clipboard? + GdkAtom selection = gtk_selection_data_get_selection(aSelectionData); + if (selection == GDK_SELECTION_PRIMARY) + whichClipboard = kSelectionClipboard; + else if (selection == GDK_SELECTION_CLIPBOARD) + whichClipboard = kGlobalClipboard; + else + return; // THAT AIN'T NO CLIPBOARD I EVER HEARD OF + + nsCOMPtr<nsITransferable> trans = GetTransferable(whichClipboard); + if (!trans) { + // We have nothing to serve +#ifdef DEBUG_CLIPBOARD + printf("nsClipboard::SelectionGetEvent() - %s clipboard is empty!\n", + whichClipboard == kSelectionClipboard ? "Selection" : "Global"); +#endif + return; + } + + nsresult rv; + nsCOMPtr<nsISupports> item; + uint32_t len; + + GdkAtom selectionTarget = gtk_selection_data_get_target(aSelectionData); + + // Check to see if the selection data includes any of the string + // types that we support. + if (selectionTarget == gdk_atom_intern ("STRING", FALSE) || + selectionTarget == gdk_atom_intern ("TEXT", FALSE) || + selectionTarget == gdk_atom_intern ("COMPOUND_TEXT", FALSE) || + selectionTarget == gdk_atom_intern ("UTF8_STRING", FALSE)) { + // Try to convert our internal type into a text string. Get + // the transferable for this clipboard and try to get the + // text/unicode type for it. + rv = trans->GetTransferData("text/unicode", getter_AddRefs(item), + &len); + if (!item || NS_FAILED(rv)) + return; + + nsCOMPtr<nsISupportsString> wideString; + wideString = do_QueryInterface(item); + if (!wideString) + return; + + nsAutoString ucs2string; + wideString->GetData(ucs2string); + char *utf8string = ToNewUTF8String(ucs2string); + if (!utf8string) + return; + + gtk_selection_data_set_text (aSelectionData, utf8string, + strlen(utf8string)); + + free(utf8string); + return; + } + + // Check to see if the selection data is an image type + if (gtk_targets_include_image(&selectionTarget, 1, TRUE)) { + // Look through our transfer data for the image + static const char* const imageMimeTypes[] = { + kNativeImageMime, kPNGImageMime, kJPEGImageMime, kJPGImageMime, kGIFImageMime }; + nsCOMPtr<nsISupports> imageItem; + nsCOMPtr<nsISupportsInterfacePointer> ptrPrimitive; + for (uint32_t i = 0; !ptrPrimitive && i < ArrayLength(imageMimeTypes); i++) { + rv = trans->GetTransferData(imageMimeTypes[i], getter_AddRefs(imageItem), &len); + ptrPrimitive = do_QueryInterface(imageItem); + } + if (!ptrPrimitive) + return; + + nsCOMPtr<nsISupports> primitiveData; + ptrPrimitive->GetData(getter_AddRefs(primitiveData)); + nsCOMPtr<imgIContainer> image(do_QueryInterface(primitiveData)); + if (!image) // Not getting an image for an image mime type!? + return; + + GdkPixbuf* pixbuf = nsImageToPixbuf::ImageToPixbuf(image); + if (!pixbuf) + return; + + gtk_selection_data_set_pixbuf(aSelectionData, pixbuf); + g_object_unref(pixbuf); + return; + } + + // Try to match up the selection data target to something our + // transferable provides. + gchar *target_name = gdk_atom_name(selectionTarget); + if (!target_name) + return; + + rv = trans->GetTransferData(target_name, getter_AddRefs(item), &len); + // nothing found? + if (!item || NS_FAILED(rv)) { + g_free(target_name); + return; + } + + void *primitive_data = nullptr; + nsPrimitiveHelpers::CreateDataFromPrimitive(target_name, item, + &primitive_data, len); + + if (primitive_data) { + // Check to see if the selection data is text/html + if (selectionTarget == gdk_atom_intern (kHTMLMime, FALSE)) { + /* + * "text/html" can be encoded UCS2. It is recommended that + * documents transmitted as UCS2 always begin with a ZERO-WIDTH + * NON-BREAKING SPACE character (hexadecimal FEFF, also called + * Byte Order Mark (BOM)). Adding BOM can help other app to + * detect mozilla use UCS2 encoding when copy-paste. + */ + guchar *buffer = (guchar *) + moz_xmalloc((len * sizeof(guchar)) + sizeof(char16_t)); + if (!buffer) + return; + char16_t prefix = 0xFEFF; + memcpy(buffer, &prefix, sizeof(prefix)); + memcpy(buffer + sizeof(prefix), primitive_data, len); + free((guchar *)primitive_data); + primitive_data = (guchar *)buffer; + len += sizeof(prefix); + } + + gtk_selection_data_set(aSelectionData, selectionTarget, + 8, /* 8 bits in a unit */ + (const guchar *)primitive_data, len); + free(primitive_data); + } + + g_free(target_name); + +} + +void +nsClipboard::SelectionClearEvent(GtkClipboard *aGtkClipboard) +{ + int32_t whichClipboard; + + // which clipboard? + if (aGtkClipboard == gtk_clipboard_get(GDK_SELECTION_PRIMARY)) + whichClipboard = kSelectionClipboard; + else if (aGtkClipboard == gtk_clipboard_get(GDK_SELECTION_CLIPBOARD)) + whichClipboard = kGlobalClipboard; + else + return; // THAT AIN'T NO CLIPBOARD I EVER HEARD OF + + EmptyClipboard(whichClipboard); +} + +void +clipboard_get_cb(GtkClipboard *aGtkClipboard, + GtkSelectionData *aSelectionData, + guint info, + gpointer user_data) +{ + nsClipboard *aClipboard = static_cast<nsClipboard *>(user_data); + aClipboard->SelectionGetEvent(aGtkClipboard, aSelectionData); +} + +void +clipboard_clear_cb(GtkClipboard *aGtkClipboard, + gpointer user_data) +{ + nsClipboard *aClipboard = static_cast<nsClipboard *>(user_data); + aClipboard->SelectionClearEvent(aGtkClipboard); +} + +/* + * when copy-paste, mozilla wants data encoded using UCS2, + * other app such as StarOffice use "text/html"(RFC2854). + * This function convert data(got from GTK clipboard) + * to data mozilla wanted. + * + * data from GTK clipboard can be 3 forms: + * 1. From current mozilla + * "text/html", charset = utf-16 + * 2. From old version mozilla or mozilla-based app + * content("body" only), charset = utf-16 + * 3. From other app who use "text/html" when copy-paste + * "text/html", has "charset" info + * + * data : got from GTK clipboard + * dataLength: got from GTK clipboard + * body : pass to Mozilla + * bodyLength: pass to Mozilla + */ +void ConvertHTMLtoUCS2(guchar * data, int32_t dataLength, + char16_t** unicodeData, int32_t& outUnicodeLen) +{ + nsAutoCString charset; + GetHTMLCharset(data, dataLength, charset);// get charset of HTML + if (charset.EqualsLiteral("UTF-16")) {//current mozilla + outUnicodeLen = (dataLength / 2) - 1; + *unicodeData = reinterpret_cast<char16_t*> + (moz_xmalloc((outUnicodeLen + sizeof('\0')) * + sizeof(char16_t))); + if (*unicodeData) { + memcpy(*unicodeData, data + sizeof(char16_t), + outUnicodeLen * sizeof(char16_t)); + (*unicodeData)[outUnicodeLen] = '\0'; + } + } else if (charset.EqualsLiteral("UNKNOWN")) { + outUnicodeLen = 0; + return; + } else { + // app which use "text/html" to copy&paste + nsCOMPtr<nsIUnicodeDecoder> decoder; + // get the decoder + nsAutoCString encoding; + if (!EncodingUtils::FindEncodingForLabelNoReplacement(charset, + encoding)) { +#ifdef DEBUG_CLIPBOARD + g_print(" get unicode decoder error\n"); +#endif + outUnicodeLen = 0; + return; + } + decoder = EncodingUtils::DecoderForEncoding(encoding); + // converting + nsresult rv = decoder->GetMaxLength((const char *)data, dataLength, + &outUnicodeLen); + if (NS_WARN_IF(NS_FAILED(rv))) { + outUnicodeLen = 0; + return; + } + + // |outUnicodeLen| is number of chars + if (outUnicodeLen) { + *unicodeData = reinterpret_cast<char16_t*> + (moz_xmalloc((outUnicodeLen + sizeof('\0')) * + sizeof(char16_t))); + if (*unicodeData) { + int32_t numberTmp = dataLength; + decoder->Convert((const char *)data, &numberTmp, + *unicodeData, &outUnicodeLen); +#ifdef DEBUG_CLIPBOARD + if (numberTmp != dataLength) + printf("didn't consume all the bytes\n"); +#endif + // null terminate. Convert() doesn't do it for us + (*unicodeData)[outUnicodeLen] = '\0'; + } + } // if valid length + } +} + +/* + * get "charset" information from clipboard data + * return value can be: + * 1. "UTF-16": mozilla or "text/html" with "charset=utf-16" + * 2. "UNKNOWN": mozilla can't detect what encode it use + * 3. other: "text/html" with other charset than utf-16 + */ +void GetHTMLCharset(guchar * data, int32_t dataLength, nsCString& str) +{ + // if detect "FFFE" or "FEFF", assume UTF-16 + char16_t* beginChar = (char16_t*)data; + if ((beginChar[0] == 0xFFFE) || (beginChar[0] == 0xFEFF)) { + str.AssignLiteral("UTF-16"); + return; + } + // no "FFFE" and "FEFF", assume ASCII first to find "charset" info + const nsDependentCString htmlStr((const char *)data, dataLength); + nsACString::const_iterator start, end; + htmlStr.BeginReading(start); + htmlStr.EndReading(end); + nsACString::const_iterator valueStart(start), valueEnd(start); + + if (CaseInsensitiveFindInReadable( + NS_LITERAL_CSTRING("CONTENT=\"text/html;"), + start, end)) { + start = end; + htmlStr.EndReading(end); + + if (CaseInsensitiveFindInReadable( + NS_LITERAL_CSTRING("charset="), + start, end)) { + valueStart = end; + start = end; + htmlStr.EndReading(end); + + if (FindCharInReadable('"', start, end)) + valueEnd = start; + } + } + // find "charset" in HTML + if (valueStart != valueEnd) { + str = Substring(valueStart, valueEnd); + ToUpperCase(str); +#ifdef DEBUG_CLIPBOARD + printf("Charset of HTML = %s\n", charsetUpperStr.get()); +#endif + return; + } + str.AssignLiteral("UNKNOWN"); +} + +static void +DispatchSelectionNotifyEvent(GtkWidget *widget, XEvent *xevent) +{ + GdkEvent event; + event.selection.type = GDK_SELECTION_NOTIFY; + event.selection.window = gtk_widget_get_window(widget); + event.selection.selection = gdk_x11_xatom_to_atom(xevent->xselection.selection); + event.selection.target = gdk_x11_xatom_to_atom(xevent->xselection.target); + event.selection.property = gdk_x11_xatom_to_atom(xevent->xselection.property); + event.selection.time = xevent->xselection.time; + + gtk_widget_event(widget, &event); +} + +static void +DispatchPropertyNotifyEvent(GtkWidget *widget, XEvent *xevent) +{ + GdkWindow *window = gtk_widget_get_window(widget); + if ((gdk_window_get_events(window)) & GDK_PROPERTY_CHANGE_MASK) { + GdkEvent event; + event.property.type = GDK_PROPERTY_NOTIFY; + event.property.window = window; + event.property.atom = gdk_x11_xatom_to_atom(xevent->xproperty.atom); + event.property.time = xevent->xproperty.time; + event.property.state = xevent->xproperty.state; + + gtk_widget_event(widget, &event); + } +} + +struct checkEventContext +{ + GtkWidget *cbWidget; + Atom selAtom; +}; + +static Bool +checkEventProc(Display *display, XEvent *event, XPointer arg) +{ + checkEventContext *context = (checkEventContext *) arg; + + if (event->xany.type == SelectionNotify || + (event->xany.type == PropertyNotify && + event->xproperty.atom == context->selAtom)) { + + GdkWindow *cbWindow = + gdk_x11_window_lookup_for_display(gdk_x11_lookup_xdisplay(display), + event->xany.window); + if (cbWindow) { + GtkWidget *cbWidget = nullptr; + gdk_window_get_user_data(cbWindow, (gpointer *)&cbWidget); + if (cbWidget && GTK_IS_WIDGET(cbWidget)) { + context->cbWidget = cbWidget; + return True; + } + } + } + + return False; +} + +// Idle timeout for receiving selection and property notify events (microsec) +static const int kClipboardTimeout = 500000; + +static gchar* CopyRetrievedData(const gchar *aData) +{ + return g_strdup(aData); +} + +static GtkSelectionData* CopyRetrievedData(GtkSelectionData *aData) +{ + // A negative length indicates that retrieving the data failed. + return gtk_selection_data_get_length(aData) >= 0 ? + gtk_selection_data_copy(aData) : nullptr; +} + +class RetrievalContext { + ~RetrievalContext() + { + MOZ_ASSERT(!mData, "Wait() wasn't called"); + } + +public: + NS_INLINE_DECL_REFCOUNTING(RetrievalContext) + enum State { INITIAL, COMPLETED, TIMED_OUT }; + + RetrievalContext() : mState(INITIAL), mData(nullptr) {} + + /** + * Call this when data has been retrieved. + */ + template <class T> void Complete(T *aData) + { + if (mState == INITIAL) { + mState = COMPLETED; + mData = CopyRetrievedData(aData); + } else { + // Already timed out + MOZ_ASSERT(mState == TIMED_OUT); + } + } + + /** + * Spins X event loop until timing out or being completed. Returns + * null if we time out, otherwise returns the completed data (passing + * ownership to caller). + */ + void *Wait(); + +protected: + State mState; + void* mData; +}; + +void * +RetrievalContext::Wait() +{ + if (mState == COMPLETED) { // the request completed synchronously + void *data = mData; + mData = nullptr; + return data; + } + + GdkDisplay *gdkDisplay = gdk_display_get_default(); + if (GDK_IS_X11_DISPLAY(gdkDisplay)) { + Display *xDisplay = GDK_DISPLAY_XDISPLAY(gdkDisplay); + checkEventContext context; + context.cbWidget = nullptr; + context.selAtom = gdk_x11_atom_to_xatom(gdk_atom_intern("GDK_SELECTION", + FALSE)); + + // Send X events which are relevant to the ongoing selection retrieval + // to the clipboard widget. Wait until either the operation completes, or + // we hit our timeout. All other X events remain queued. + + int select_result; + + int cnumber = ConnectionNumber(xDisplay); + fd_set select_set; + FD_ZERO(&select_set); + FD_SET(cnumber, &select_set); + ++cnumber; + TimeStamp start = TimeStamp::Now(); + + do { + XEvent xevent; + + while (XCheckIfEvent(xDisplay, &xevent, checkEventProc, + (XPointer) &context)) { + + if (xevent.xany.type == SelectionNotify) + DispatchSelectionNotifyEvent(context.cbWidget, &xevent); + else + DispatchPropertyNotifyEvent(context.cbWidget, &xevent); + + if (mState == COMPLETED) { + void *data = mData; + mData = nullptr; + return data; + } + } + + TimeStamp now = TimeStamp::Now(); + struct timeval tv; + tv.tv_sec = 0; + tv.tv_usec = std::max<int32_t>(0, + kClipboardTimeout - (now - start).ToMicroseconds()); + select_result = select(cnumber, &select_set, nullptr, nullptr, &tv); + } while (select_result == 1 || + (select_result == -1 && errno == EINTR)); + } +#ifdef DEBUG_CLIPBOARD + printf("exceeded clipboard timeout\n"); +#endif + mState = TIMED_OUT; + return nullptr; +} + +static void +clipboard_contents_received(GtkClipboard *clipboard, + GtkSelectionData *selection_data, + gpointer data) +{ + RetrievalContext *context = static_cast<RetrievalContext*>(data); + context->Complete(selection_data); + context->Release(); +} + +static GtkSelectionData * +wait_for_contents(GtkClipboard *clipboard, GdkAtom target) +{ + RefPtr<RetrievalContext> context = new RetrievalContext(); + // Balanced by Release in clipboard_contents_received + context.get()->AddRef(); + gtk_clipboard_request_contents(clipboard, target, + clipboard_contents_received, + context.get()); + return static_cast<GtkSelectionData*>(context->Wait()); +} + +static void +clipboard_text_received(GtkClipboard *clipboard, + const gchar *text, + gpointer data) +{ + RetrievalContext *context = static_cast<RetrievalContext*>(data); + context->Complete(text); + context->Release(); +} + +static gchar * +wait_for_text(GtkClipboard *clipboard) +{ + RefPtr<RetrievalContext> context = new RetrievalContext(); + // Balanced by Release in clipboard_text_received + context.get()->AddRef(); + gtk_clipboard_request_text(clipboard, clipboard_text_received, context.get()); + return static_cast<gchar*>(context->Wait()); +} + +static GdkFilterReturn +selection_request_filter(GdkXEvent *gdk_xevent, GdkEvent *event, gpointer data) +{ + XEvent *xevent = static_cast<XEvent*>(gdk_xevent); + if (xevent->xany.type == SelectionRequest) { + if (xevent->xselectionrequest.requestor == X11None) + return GDK_FILTER_REMOVE; + + GdkDisplay *display = gdk_x11_lookup_xdisplay( + xevent->xselectionrequest.display); + if (!display) + return GDK_FILTER_REMOVE; + + GdkWindow *window = gdk_x11_window_foreign_new_for_display(display, + xevent->xselectionrequest.requestor); + if (!window) + return GDK_FILTER_REMOVE; + + g_object_unref(window); + } + return GDK_FILTER_CONTINUE; +} diff --git a/widget/gtk/nsClipboard.h b/widget/gtk/nsClipboard.h new file mode 100644 index 000000000..70c866a01 --- /dev/null +++ b/widget/gtk/nsClipboard.h @@ -0,0 +1,58 @@ +/* -*- 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/. */ + +#ifndef __nsClipboard_h_ +#define __nsClipboard_h_ + +#include "nsIClipboard.h" +#include "nsIObserver.h" +#include <gtk/gtk.h> + +class nsClipboard : public nsIClipboard, + public nsIObserver +{ +public: + nsClipboard(); + + NS_DECL_ISUPPORTS + + NS_DECL_NSICLIPBOARD + NS_DECL_NSIOBSERVER + + // Make sure we are initialized, called from the factory + // constructor + nsresult Init (void); + + // Someone requested the selection + void SelectionGetEvent (GtkClipboard *aGtkClipboard, + GtkSelectionData *aSelectionData); + void SelectionClearEvent (GtkClipboard *aGtkClipboard); + +private: + virtual ~nsClipboard(); + + // Utility methods + static GdkAtom GetSelectionAtom (int32_t aWhichClipboard); + static GtkSelectionData *GetTargets (GdkAtom aWhichClipboard); + + // Save global clipboard content to gtk + nsresult Store (void); + + // Get our hands on the correct transferable, given a specific + // clipboard + nsITransferable *GetTransferable (int32_t aWhichClipboard); + + // Hang on to our owners and transferables so we can transfer data + // when asked. + nsCOMPtr<nsIClipboardOwner> mSelectionOwner; + nsCOMPtr<nsIClipboardOwner> mGlobalOwner; + nsCOMPtr<nsITransferable> mSelectionTransferable; + nsCOMPtr<nsITransferable> mGlobalTransferable; + +}; + +#endif /* __nsClipboard_h_ */ diff --git a/widget/gtk/nsColorPicker.cpp b/widget/gtk/nsColorPicker.cpp new file mode 100644 index 000000000..93ab8bb9a --- /dev/null +++ b/widget/gtk/nsColorPicker.cpp @@ -0,0 +1,252 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 <gtk/gtk.h> + +#include "nsColor.h" +#include "nsColorPicker.h" +#include "nsGtkUtils.h" +#include "nsIWidget.h" +#include "WidgetUtils.h" +#include "nsPIDOMWindow.h" + +NS_IMPL_ISUPPORTS(nsColorPicker, nsIColorPicker) + +#if defined(ACTIVATE_GTK3_COLOR_PICKER) && GTK_CHECK_VERSION(3,4,0) +int nsColorPicker::convertGdkRgbaComponent(gdouble color_component) { + // GdkRGBA value is in range [0.0..1.0]. We need something in range [0..255] + return color_component * 255 + 0.5; +} + +gdouble nsColorPicker::convertToGdkRgbaComponent(int color_component) { + return color_component / 255.0; +} + +GdkRGBA nsColorPicker::convertToRgbaColor(nscolor color) { + GdkRGBA result = { convertToGdkRgbaComponent(NS_GET_R(color)), + convertToGdkRgbaComponent(NS_GET_G(color)), + convertToGdkRgbaComponent(NS_GET_B(color)), + convertToGdkRgbaComponent(NS_GET_A(color)) }; + + return result; +} +#else +int nsColorPicker::convertGdkColorComponent(guint16 color_component) { + // GdkColor value is in range [0..65535]. We need something in range [0..255] + return (color_component * 255 + 127) / 65535; +} + +guint16 nsColorPicker::convertToGdkColorComponent(int color_component) { + return color_component * 65535 / 255; +} + +GdkColor nsColorPicker::convertToGdkColor(nscolor color) { + GdkColor result = { 0 /* obsolete, unused 'pixel' value */, + convertToGdkColorComponent(NS_GET_R(color)), + convertToGdkColorComponent(NS_GET_G(color)), + convertToGdkColorComponent(NS_GET_B(color)) }; + + return result; +} + +GtkColorSelection* nsColorPicker::WidgetGetColorSelection(GtkWidget* widget) +{ + return GTK_COLOR_SELECTION(gtk_color_selection_dialog_get_color_selection( + GTK_COLOR_SELECTION_DIALOG(widget))); +} +#endif + +NS_IMETHODIMP nsColorPicker::Init(mozIDOMWindowProxy *aParent, + const nsAString& title, + const nsAString& initialColor) +{ + auto* parent = nsPIDOMWindowOuter::From(aParent); + mParentWidget = mozilla::widget::WidgetUtils::DOMWindowToWidget(parent); + mTitle = title; + mInitialColor = initialColor; + + return NS_OK; +} + +NS_IMETHODIMP nsColorPicker::Open(nsIColorPickerShownCallback *aColorPickerShownCallback) +{ + + // Input color string should be 7 length (i.e. a string representing a valid + // simple color) + if (mInitialColor.Length() != 7) { + return NS_ERROR_FAILURE; + } + + const nsAString& withoutHash = StringTail(mInitialColor, 6); + nscolor color; + if (!NS_HexToRGBA(withoutHash, nsHexColorType::NoAlpha, &color)) { + return NS_ERROR_FAILURE; + } + + if (mCallback) { + // It means Open has already been called: this is not allowed + NS_WARNING("mCallback is already set. Open called twice?"); + return NS_ERROR_FAILURE; + } + mCallback = aColorPickerShownCallback; + + nsXPIDLCString title; + title.Adopt(ToNewUTF8String(mTitle)); + GtkWindow *parent_window = GTK_WINDOW(mParentWidget->GetNativeData(NS_NATIVE_SHELLWIDGET)); + +#if defined(ACTIVATE_GTK3_COLOR_PICKER) && GTK_CHECK_VERSION(3,4,0) + GtkWidget* color_chooser = gtk_color_chooser_dialog_new(title, parent_window); + + if (parent_window) { + gtk_window_set_destroy_with_parent(GTK_WINDOW(color_chooser), TRUE); + } + + gtk_color_chooser_set_use_alpha(GTK_COLOR_CHOOSER(color_chooser), FALSE); + GdkRGBA color_rgba = convertToRgbaColor(color); + gtk_color_chooser_set_rgba(GTK_COLOR_CHOOSER(color_chooser), + &color_rgba); + + g_signal_connect(GTK_COLOR_CHOOSER(color_chooser), "color-activated", + G_CALLBACK(OnColorChanged), this); +#else + GtkWidget *color_chooser = gtk_color_selection_dialog_new(title); + + if (parent_window) { + GtkWindow *window = GTK_WINDOW(color_chooser); + gtk_window_set_transient_for(window, parent_window); + gtk_window_set_destroy_with_parent(window, TRUE); + } + + GdkColor color_gdk = convertToGdkColor(color); + gtk_color_selection_set_current_color(WidgetGetColorSelection(color_chooser), + &color_gdk); + + g_signal_connect(WidgetGetColorSelection(color_chooser), "color-changed", + G_CALLBACK(OnColorChanged), this); +#endif + + NS_ADDREF_THIS(); + + g_signal_connect(color_chooser, "response", G_CALLBACK(OnResponse), this); + g_signal_connect(color_chooser, "destroy", G_CALLBACK(OnDestroy), this); + gtk_widget_show(color_chooser); + + return NS_OK; +} + +#if defined(ACTIVATE_GTK3_COLOR_PICKER) && GTK_CHECK_VERSION(3,4,0) +/* static */ void +nsColorPicker::OnColorChanged(GtkColorChooser* color_chooser, GdkRGBA* color, + gpointer user_data) +{ + static_cast<nsColorPicker*>(user_data)->Update(color); +} + +void +nsColorPicker::Update(GdkRGBA* color) +{ + SetColor(color); + if (mCallback) { + mCallback->Update(mColor); + } +} + +void nsColorPicker::SetColor(const GdkRGBA* color) +{ + mColor.Assign('#'); + mColor += ToHexString(convertGdkRgbaComponent(color->red)); + mColor += ToHexString(convertGdkRgbaComponent(color->green)); + mColor += ToHexString(convertGdkRgbaComponent(color->blue)); +} +#else +/* static */ void +nsColorPicker::OnColorChanged(GtkColorSelection* colorselection, + gpointer user_data) +{ + static_cast<nsColorPicker*>(user_data)->Update(colorselection); +} + +void +nsColorPicker::Update(GtkColorSelection* colorselection) +{ + ReadValueFromColorSelection(colorselection); + if (mCallback) { + mCallback->Update(mColor); + } +} + +void nsColorPicker::ReadValueFromColorSelection(GtkColorSelection* colorselection) +{ + GdkColor rgba; + gtk_color_selection_get_current_color(colorselection, &rgba); + + mColor.Assign('#'); + mColor += ToHexString(convertGdkColorComponent(rgba.red)); + mColor += ToHexString(convertGdkColorComponent(rgba.green)); + mColor += ToHexString(convertGdkColorComponent(rgba.blue)); +} +#endif + +/* static */ void +nsColorPicker::OnResponse(GtkWidget* color_chooser, gint response_id, + gpointer user_data) +{ + static_cast<nsColorPicker*>(user_data)-> + Done(color_chooser, response_id); +} + +/* static */ void +nsColorPicker::OnDestroy(GtkWidget* color_chooser, gpointer user_data) +{ + static_cast<nsColorPicker*>(user_data)-> + Done(color_chooser, GTK_RESPONSE_CANCEL); +} + +void +nsColorPicker::Done(GtkWidget* color_chooser, gint response) +{ + switch (response) { + case GTK_RESPONSE_OK: + case GTK_RESPONSE_ACCEPT: +#if defined(ACTIVATE_GTK3_COLOR_PICKER) && GTK_CHECK_VERSION(3,4,0) + GdkRGBA color; + gtk_color_chooser_get_rgba(GTK_COLOR_CHOOSER(color_chooser), &color); + SetColor(&color); +#else + ReadValueFromColorSelection(WidgetGetColorSelection(color_chooser)); +#endif + break; + case GTK_RESPONSE_CANCEL: + case GTK_RESPONSE_CLOSE: + case GTK_RESPONSE_DELETE_EVENT: + mColor = mInitialColor; + break; + default: + NS_WARNING("Unexpected response"); + break; + } + + // A "response" signal won't be sent again but "destroy" will be. + g_signal_handlers_disconnect_by_func(color_chooser, + FuncToGpointer(OnDestroy), this); + + gtk_widget_destroy(color_chooser); + if (mCallback) { + mCallback->Done(mColor); + mCallback = nullptr; + } + + NS_RELEASE_THIS(); +} + +nsString nsColorPicker::ToHexString(int n) +{ + nsString result; + if (n <= 0x0F) { + result.Append('0'); + } + result.AppendInt(n, 16); + return result; +} diff --git a/widget/gtk/nsColorPicker.h b/widget/gtk/nsColorPicker.h new file mode 100644 index 000000000..107e6f058 --- /dev/null +++ b/widget/gtk/nsColorPicker.h @@ -0,0 +1,73 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#ifndef nsColorPicker_h__ +#define nsColorPicker_h__ + +#include <gtk/gtk.h> + +#include "nsCOMPtr.h" +#include "nsIColorPicker.h" +#include "nsString.h" + +// Don't activate the GTK3 color picker for now, because it is missing a few +// things, mainly the ability to let the user select a color on the screen. +// See bug 1198256. +#undef ACTIVATE_GTK3_COLOR_PICKER + +class nsIWidget; + +class nsColorPicker final : public nsIColorPicker +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSICOLORPICKER + + nsColorPicker() {}; + +private: + ~nsColorPicker() {}; + + static nsString ToHexString(int n); + + static void OnResponse(GtkWidget* dialog, gint response_id, + gpointer user_data); + static void OnDestroy(GtkWidget* dialog, gpointer user_data); + +#if defined(ACTIVATE_GTK3_COLOR_PICKER) && GTK_CHECK_VERSION(3,4,0) + static void OnColorChanged(GtkColorChooser* color_chooser, GdkRGBA* color, + gpointer user_data); + + static int convertGdkRgbaComponent(gdouble color_component); + static gdouble convertToGdkRgbaComponent(int color_component); + static GdkRGBA convertToRgbaColor(nscolor color); + + void Update(GdkRGBA* color); + void SetColor(const GdkRGBA* color); +#else + static void OnColorChanged(GtkColorSelection* colorselection, + gpointer user_data); + + // Conversion functions for color + static int convertGdkColorComponent(guint16 color_component); + static guint16 convertToGdkColorComponent(int color_component); + static GdkColor convertToGdkColor(nscolor color); + + static GtkColorSelection* WidgetGetColorSelection(GtkWidget* widget); + + void Update(GtkColorSelection* colorselection); + void ReadValueFromColorSelection(GtkColorSelection* colorselection); +#endif + + void Done(GtkWidget* dialog, gint response_id); + + nsCOMPtr<nsIWidget> mParentWidget; + nsCOMPtr<nsIColorPickerShownCallback> mCallback; + nsString mTitle; + nsString mColor; + nsString mInitialColor; +}; + +#endif // nsColorPicker_h__ diff --git a/widget/gtk/nsDeviceContextSpecG.cpp b/widget/gtk/nsDeviceContextSpecG.cpp new file mode 100644 index 000000000..0bd87bd85 --- /dev/null +++ b/widget/gtk/nsDeviceContextSpecG.cpp @@ -0,0 +1,498 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 "nsDeviceContextSpecG.h" + +#include "mozilla/gfx/PrintTargetPDF.h" +#include "mozilla/gfx/PrintTargetPS.h" +#include "mozilla/Logging.h" + +#include "plstr.h" +#include "prenv.h" /* for PR_GetEnv */ + +#include "nsPrintfCString.h" +#include "nsReadableUtils.h" +#include "nsStringEnumerator.h" +#include "nsIServiceManager.h" +#include "nsThreadUtils.h" + +#include "nsPSPrinters.h" +#include "nsPaperPS.h" /* Paper size list */ + +#include "nsPrintSettingsGTK.h" + +#include "nsIFileStreams.h" +#include "nsIFile.h" +#include "nsTArray.h" +#include "nsThreadUtils.h" + +#include "mozilla/Preferences.h" + +#include <unistd.h> +#include <sys/types.h> +#include <sys/stat.h> + +using namespace mozilla; + +using mozilla::gfx::IntSize; +using mozilla::gfx::PrintTarget; +using mozilla::gfx::PrintTargetPDF; +using mozilla::gfx::PrintTargetPS; + +static PRLogModuleInfo * +GetDeviceContextSpecGTKLog() +{ + static PRLogModuleInfo *sLog; + if (!sLog) + sLog = PR_NewLogModule("DeviceContextSpecGTK"); + return sLog; +} +/* Macro to make lines shorter */ +#define DO_PR_DEBUG_LOG(x) MOZ_LOG(GetDeviceContextSpecGTKLog(), mozilla::LogLevel::Debug, x) + +//---------------------------------------------------------------------------------- +// The printer data is shared between the PrinterEnumerator and the nsDeviceContextSpecGTK +// The PrinterEnumerator creates the printer info +// but the nsDeviceContextSpecGTK cleans it up +// If it gets created (via the Page Setup Dialog) but the user never prints anything +// then it will never be delete, so this class takes care of that. +class GlobalPrinters { +public: + static GlobalPrinters* GetInstance() { return &mGlobalPrinters; } + ~GlobalPrinters() { FreeGlobalPrinters(); } + + void FreeGlobalPrinters(); + nsresult InitializeGlobalPrinters(); + + bool PrintersAreAllocated() { return mGlobalPrinterList != nullptr; } + uint32_t GetNumPrinters() + { return mGlobalPrinterList ? mGlobalPrinterList->Length() : 0; } + nsString* GetStringAt(int32_t aInx) { return &mGlobalPrinterList->ElementAt(aInx); } + void GetDefaultPrinterName(char16_t **aDefaultPrinterName); + +protected: + GlobalPrinters() {} + + static GlobalPrinters mGlobalPrinters; + static nsTArray<nsString>* mGlobalPrinterList; +}; + +//--------------- +// static members +GlobalPrinters GlobalPrinters::mGlobalPrinters; +nsTArray<nsString>* GlobalPrinters::mGlobalPrinterList = nullptr; +//--------------- + +nsDeviceContextSpecGTK::nsDeviceContextSpecGTK() + : mGtkPrintSettings(nullptr) + , mGtkPageSetup(nullptr) +{ + DO_PR_DEBUG_LOG(("nsDeviceContextSpecGTK::nsDeviceContextSpecGTK()\n")); +} + +nsDeviceContextSpecGTK::~nsDeviceContextSpecGTK() +{ + DO_PR_DEBUG_LOG(("nsDeviceContextSpecGTK::~nsDeviceContextSpecGTK()\n")); + + if (mGtkPageSetup) { + g_object_unref(mGtkPageSetup); + } + + if (mGtkPrintSettings) { + g_object_unref(mGtkPrintSettings); + } +} + +NS_IMPL_ISUPPORTS(nsDeviceContextSpecGTK, + nsIDeviceContextSpec) + +already_AddRefed<PrintTarget> nsDeviceContextSpecGTK::MakePrintTarget() +{ + double width, height; + mPrintSettings->GetEffectivePageSize(&width, &height); + + // convert twips to points + width /= TWIPS_PER_POINT_FLOAT; + height /= TWIPS_PER_POINT_FLOAT; + + DO_PR_DEBUG_LOG(("\"%s\", %f, %f\n", mPath, width, height)); + nsresult rv; + + // We shouldn't be attempting to get a surface if we've already got a spool + // file. + MOZ_ASSERT(!mSpoolFile); + + // Spool file. Use Glib's temporary file function since we're + // already dependent on the gtk software stack. + gchar *buf; + gint fd = g_file_open_tmp("XXXXXX.tmp", &buf, nullptr); + if (-1 == fd) + return nullptr; + close(fd); + + rv = NS_NewNativeLocalFile(nsDependentCString(buf), false, + getter_AddRefs(mSpoolFile)); + if (NS_FAILED(rv)) { + unlink(buf); + return nullptr; + } + + mSpoolName = buf; + g_free(buf); + + mSpoolFile->SetPermissions(0600); + + nsCOMPtr<nsIFileOutputStream> stream = do_CreateInstance("@mozilla.org/network/file-output-stream;1"); + rv = stream->Init(mSpoolFile, -1, -1, 0); + if (NS_FAILED(rv)) + return nullptr; + + int16_t format; + mPrintSettings->GetOutputFormat(&format); + + // Determine the real format with some GTK magic + if (format == nsIPrintSettings::kOutputFormatNative) { + if (mIsPPreview) { + // There is nothing to detect on Print Preview, use PS. + format = nsIPrintSettings::kOutputFormatPS; + } else { + return nullptr; + } + } + + IntSize size = IntSize::Truncate(width, height); + + if (format == nsIPrintSettings::kOutputFormatPDF) { + return PrintTargetPDF::CreateOrNull(stream, size); + } + + int32_t orientation; + mPrintSettings->GetOrientation(&orientation); + return PrintTargetPS::CreateOrNull(stream, + size, + orientation == nsIPrintSettings::kPortraitOrientation + ? PrintTargetPS::PORTRAIT + : PrintTargetPS::LANDSCAPE); +} + +/** ------------------------------------------------------- + * Initialize the nsDeviceContextSpecGTK + * @update dc 2/15/98 + * @update syd 3/2/99 + */ +NS_IMETHODIMP nsDeviceContextSpecGTK::Init(nsIWidget *aWidget, + nsIPrintSettings* aPS, + bool aIsPrintPreview) +{ + DO_PR_DEBUG_LOG(("nsDeviceContextSpecGTK::Init(aPS=%p)\n", aPS)); + + if (gtk_major_version < 2 || + (gtk_major_version == 2 && gtk_minor_version < 10)) + return NS_ERROR_NOT_AVAILABLE; // I'm so sorry bz + + mPrintSettings = do_QueryInterface(aPS); + if (!mPrintSettings) + return NS_ERROR_NO_INTERFACE; + + mIsPPreview = aIsPrintPreview; + + // This is only set by embedders + bool toFile; + aPS->GetPrintToFile(&toFile); + + mToPrinter = !toFile && !aIsPrintPreview; + + mGtkPrintSettings = mPrintSettings->GetGtkPrintSettings(); + mGtkPageSetup = mPrintSettings->GetGtkPageSetup(); + + // This is a horrible workaround for some printer driver bugs that treat custom page sizes different + // to standard ones. If our paper object matches one of a standard one, use a standard paper size + // object instead. See bug 414314 for more info. + GtkPaperSize* geckosHackishPaperSize = gtk_page_setup_get_paper_size(mGtkPageSetup); + GtkPaperSize* standardGtkPaperSize = gtk_paper_size_new(gtk_paper_size_get_name(geckosHackishPaperSize)); + + mGtkPageSetup = gtk_page_setup_copy(mGtkPageSetup); + mGtkPrintSettings = gtk_print_settings_copy(mGtkPrintSettings); + + GtkPaperSize* properPaperSize; + if (gtk_paper_size_is_equal(geckosHackishPaperSize, standardGtkPaperSize)) { + properPaperSize = standardGtkPaperSize; + } else { + properPaperSize = geckosHackishPaperSize; + } + gtk_print_settings_set_paper_size(mGtkPrintSettings, properPaperSize); + gtk_page_setup_set_paper_size_and_default_margins(mGtkPageSetup, properPaperSize); + gtk_paper_size_free(standardGtkPaperSize); + + return NS_OK; +} + +static void +#if (MOZ_WIDGET_GTK == 3) +print_callback(GtkPrintJob *aJob, gpointer aData, const GError *aError) { +#else +print_callback(GtkPrintJob *aJob, gpointer aData, GError *aError) { +#endif + g_object_unref(aJob); + ((nsIFile*) aData)->Remove(false); +} + +static void +ns_release_macro(gpointer aData) { + nsIFile* spoolFile = (nsIFile*) aData; + NS_RELEASE(spoolFile); +} + +/* static */ +gboolean nsDeviceContextSpecGTK::PrinterEnumerator(GtkPrinter *aPrinter, + gpointer aData) { + nsDeviceContextSpecGTK *spec = (nsDeviceContextSpecGTK*)aData; + + // Find the printer whose name matches the one inside the settings. + nsXPIDLString printerName; + nsresult rv = + spec->mPrintSettings->GetPrinterName(getter_Copies(printerName)); + if (NS_SUCCEEDED(rv) && printerName) { + NS_ConvertUTF16toUTF8 requestedName(printerName); + const char* currentName = gtk_printer_get_name(aPrinter); + if (requestedName.Equals(currentName)) { + spec->mPrintSettings->SetGtkPrinter(aPrinter); + + // Bug 1145916 - attempting to kick off a print job for this printer + // during this tick of the event loop will result in the printer backend + // misunderstanding what the capabilities of the printer are due to a + // GTK bug (https://bugzilla.gnome.org/show_bug.cgi?id=753041). We + // sidestep this by deferring the print to the next tick. + NS_DispatchToCurrentThread(NewRunnableMethod(spec, &nsDeviceContextSpecGTK::StartPrintJob)); + return TRUE; + } + } + + // We haven't found it yet - keep searching... + return FALSE; +} + +void nsDeviceContextSpecGTK::StartPrintJob() { + GtkPrintJob* job = gtk_print_job_new(mTitle.get(), + mPrintSettings->GetGtkPrinter(), + mGtkPrintSettings, + mGtkPageSetup); + + if (!gtk_print_job_set_source_file(job, mSpoolName.get(), nullptr)) + return; + + NS_ADDREF(mSpoolFile.get()); + gtk_print_job_send(job, print_callback, mSpoolFile, ns_release_macro); +} + +void +nsDeviceContextSpecGTK::EnumeratePrinters() +{ + gtk_enumerate_printers(&nsDeviceContextSpecGTK::PrinterEnumerator, this, + nullptr, TRUE); +} + +NS_IMETHODIMP +nsDeviceContextSpecGTK::BeginDocument(const nsAString& aTitle, + const nsAString& aPrintToFileName, + int32_t aStartPage, int32_t aEndPage) +{ + mTitle.Truncate(); + AppendUTF16toUTF8(aTitle, mTitle); + return NS_OK; +} + +NS_IMETHODIMP nsDeviceContextSpecGTK::EndDocument() +{ + if (mToPrinter) { + // At this point, we might have a GtkPrinter set up in nsPrintSettingsGTK, + // or we might not. In the single-process case, we probably will, as this + // is populated by the print settings dialog, or set to the default + // printer. + // In the multi-process case, we proxy the print settings dialog over to + // the parent process, and only get the name of the printer back on the + // content process side. In that case, we need to enumerate the printers + // on the content side, and find a printer with a matching name. + + GtkPrinter* printer = mPrintSettings->GetGtkPrinter(); + if (printer) { + // We have a printer, so we can print right away. + StartPrintJob(); + } else { + // We don't have a printer. We have to enumerate the printers and find + // one with a matching name. + NS_DispatchToCurrentThread(NewRunnableMethod(this, &nsDeviceContextSpecGTK::EnumeratePrinters)); + } + } else { + // Handle print-to-file ourselves for the benefit of embedders + nsXPIDLString targetPath; + nsCOMPtr<nsIFile> destFile; + mPrintSettings->GetToFileName(getter_Copies(targetPath)); + + nsresult rv = NS_NewNativeLocalFile(NS_ConvertUTF16toUTF8(targetPath), + false, getter_AddRefs(destFile)); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoString destLeafName; + rv = destFile->GetLeafName(destLeafName); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIFile> destDir; + rv = destFile->GetParent(getter_AddRefs(destDir)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = mSpoolFile->MoveTo(destDir, destLeafName); + NS_ENSURE_SUCCESS(rv, rv); + + // This is the standard way to get the UNIX umask. Ugh. + mode_t mask = umask(0); + umask(mask); + // If you're not familiar with umasks, they contain the bits of what NOT to set in the permissions + // (thats because files and directories have different numbers of bits for their permissions) + destFile->SetPermissions(0666 & ~(mask)); + } + return NS_OK; +} + +// Printer Enumerator +nsPrinterEnumeratorGTK::nsPrinterEnumeratorGTK() +{ +} + +NS_IMPL_ISUPPORTS(nsPrinterEnumeratorGTK, nsIPrinterEnumerator) + +NS_IMETHODIMP nsPrinterEnumeratorGTK::GetPrinterNameList(nsIStringEnumerator **aPrinterNameList) +{ + NS_ENSURE_ARG_POINTER(aPrinterNameList); + *aPrinterNameList = nullptr; + + nsresult rv = GlobalPrinters::GetInstance()->InitializeGlobalPrinters(); + if (NS_FAILED(rv)) { + return rv; + } + + uint32_t numPrinters = GlobalPrinters::GetInstance()->GetNumPrinters(); + nsTArray<nsString> *printers = new nsTArray<nsString>(numPrinters); + if (!printers) { + GlobalPrinters::GetInstance()->FreeGlobalPrinters(); + return NS_ERROR_OUT_OF_MEMORY; + } + + uint32_t count = 0; + while( count < numPrinters ) + { + printers->AppendElement(*GlobalPrinters::GetInstance()->GetStringAt(count++)); + } + GlobalPrinters::GetInstance()->FreeGlobalPrinters(); + + return NS_NewAdoptingStringEnumerator(aPrinterNameList, printers); +} + +NS_IMETHODIMP nsPrinterEnumeratorGTK::GetDefaultPrinterName(char16_t **aDefaultPrinterName) +{ + DO_PR_DEBUG_LOG(("nsPrinterEnumeratorGTK::GetDefaultPrinterName()\n")); + NS_ENSURE_ARG_POINTER(aDefaultPrinterName); + + GlobalPrinters::GetInstance()->GetDefaultPrinterName(aDefaultPrinterName); + + DO_PR_DEBUG_LOG(("GetDefaultPrinterName(): default printer='%s'.\n", NS_ConvertUTF16toUTF8(*aDefaultPrinterName).get())); + return NS_OK; +} + +NS_IMETHODIMP nsPrinterEnumeratorGTK::InitPrintSettingsFromPrinter(const char16_t *aPrinterName, nsIPrintSettings *aPrintSettings) +{ + DO_PR_DEBUG_LOG(("nsPrinterEnumeratorGTK::InitPrintSettingsFromPrinter()")); + + NS_ENSURE_ARG_POINTER(aPrintSettings); + + /* Set filename */ + nsAutoCString filename; + const char *path; + + if (!(path = PR_GetEnv("PWD"))) + path = PR_GetEnv("HOME"); + + if (path) + filename = nsPrintfCString("%s/mozilla.pdf", path); + else + filename.AssignLiteral("mozilla.pdf"); + + DO_PR_DEBUG_LOG(("Setting default filename to '%s'\n", filename.get())); + aPrintSettings->SetToFileName(NS_ConvertUTF8toUTF16(filename).get()); + + aPrintSettings->SetIsInitializedFromPrinter(true); + + return NS_OK; +} + +//---------------------------------------------------------------------- +nsresult GlobalPrinters::InitializeGlobalPrinters () +{ + if (PrintersAreAllocated()) { + return NS_OK; + } + + mGlobalPrinterList = new nsTArray<nsString>(); + + nsPSPrinterList psMgr; + if (psMgr.Enabled()) { + /* Get the list of PostScript-module printers */ + // XXX: this function is the only user of GetPrinterList + // So it may be interesting to convert the nsCStrings + // in this function, we would save one loop here + nsTArray<nsCString> printerList; + psMgr.GetPrinterList(printerList); + for (uint32_t i = 0; i < printerList.Length(); i++) + { + mGlobalPrinterList->AppendElement(NS_ConvertUTF8toUTF16(printerList[i])); + } + } + + /* If there are no printers available after all checks, return an error */ + if (!mGlobalPrinterList->Length()) + { + /* Make sure we do not cache an empty printer list */ + FreeGlobalPrinters(); + + return NS_ERROR_GFX_PRINTER_NO_PRINTER_AVAILABLE; + } + + return NS_OK; +} + +//---------------------------------------------------------------------- +void GlobalPrinters::FreeGlobalPrinters() +{ + if (mGlobalPrinterList) { + delete mGlobalPrinterList; + mGlobalPrinterList = nullptr; + } +} + +void +GlobalPrinters::GetDefaultPrinterName(char16_t **aDefaultPrinterName) +{ + *aDefaultPrinterName = nullptr; + + bool allocate = !GlobalPrinters::GetInstance()->PrintersAreAllocated(); + + if (allocate) { + nsresult rv = GlobalPrinters::GetInstance()->InitializeGlobalPrinters(); + if (NS_FAILED(rv)) { + return; + } + } + NS_ASSERTION(GlobalPrinters::GetInstance()->PrintersAreAllocated(), "no GlobalPrinters"); + + if (GlobalPrinters::GetInstance()->GetNumPrinters() == 0) + return; + + *aDefaultPrinterName = ToNewUnicode(*GlobalPrinters::GetInstance()->GetStringAt(0)); + + if (allocate) { + GlobalPrinters::GetInstance()->FreeGlobalPrinters(); + } +} + diff --git a/widget/gtk/nsDeviceContextSpecG.h b/widget/gtk/nsDeviceContextSpecG.h new file mode 100644 index 000000000..921c6275c --- /dev/null +++ b/widget/gtk/nsDeviceContextSpecG.h @@ -0,0 +1,76 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#ifndef nsDeviceContextSpecGTK_h___ +#define nsDeviceContextSpecGTK_h___ + +#include "nsIDeviceContextSpec.h" +#include "nsIPrintSettings.h" +#include "nsIPrinterEnumerator.h" +#include "nsCOMPtr.h" +#include "nsString.h" +#include "mozilla/Attributes.h" + +#include "nsCRT.h" /* should be <limits.h>? */ + +#include <gtk/gtk.h> +#include <gtk/gtkunixprint.h> + +#define NS_PORTRAIT 0 +#define NS_LANDSCAPE 1 + +class nsPrintSettingsGTK; + +class nsDeviceContextSpecGTK : public nsIDeviceContextSpec +{ +public: + nsDeviceContextSpecGTK(); + + NS_DECL_ISUPPORTS + + virtual already_AddRefed<PrintTarget> MakePrintTarget() final; + + NS_IMETHOD Init(nsIWidget *aWidget, nsIPrintSettings* aPS, + bool aIsPrintPreview) override; + NS_IMETHOD BeginDocument(const nsAString& aTitle, + const nsAString& aPrintToFileName, + int32_t aStartPage, int32_t aEndPage) override; + NS_IMETHOD EndDocument() override; + NS_IMETHOD BeginPage() override { return NS_OK; } + NS_IMETHOD EndPage() override { return NS_OK; } + +protected: + virtual ~nsDeviceContextSpecGTK(); + nsCOMPtr<nsPrintSettingsGTK> mPrintSettings; + bool mToPrinter : 1; /* If true, print to printer */ + bool mIsPPreview : 1; /* If true, is print preview */ + char mPath[PATH_MAX]; /* If toPrinter = false, dest file */ + char mPrinter[256]; /* Printer name */ + GtkPrintSettings* mGtkPrintSettings; + GtkPageSetup* mGtkPageSetup; + + nsCString mSpoolName; + nsCOMPtr<nsIFile> mSpoolFile; + nsCString mTitle; + +private: + void EnumeratePrinters(); + void StartPrintJob(); + static gboolean PrinterEnumerator(GtkPrinter *aPrinter, gpointer aData); +}; + +//------------------------------------------------------------------------- +// Printer Enumerator +//------------------------------------------------------------------------- +class nsPrinterEnumeratorGTK final : public nsIPrinterEnumerator +{ + ~nsPrinterEnumeratorGTK() {} +public: + nsPrinterEnumeratorGTK(); + NS_DECL_ISUPPORTS + NS_DECL_NSIPRINTERENUMERATOR +}; + +#endif /* !nsDeviceContextSpecGTK_h___ */ diff --git a/widget/gtk/nsDragService.cpp b/widget/gtk/nsDragService.cpp new file mode 100644 index 000000000..15b4eeffa --- /dev/null +++ b/widget/gtk/nsDragService.cpp @@ -0,0 +1,2109 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim: set ts=4 et sw=4 tw=80: */ +/* 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 "nsDragService.h" +#include "nsArrayUtils.h" +#include "nsIObserverService.h" +#include "nsWidgetsCID.h" +#include "nsWindow.h" +#include "nsIServiceManager.h" +#include "nsXPCOM.h" +#include "nsISupportsPrimitives.h" +#include "nsIIOService.h" +#include "nsIFileURL.h" +#include "nsNetUtil.h" +#include "mozilla/Logging.h" +#include "nsTArray.h" +#include "nsPrimitiveHelpers.h" +#include "prtime.h" +#include "prthread.h" +#include <dlfcn.h> +#include <gtk/gtk.h> +#include <gdk/gdkx.h> +#include "nsCRT.h" +#include "mozilla/BasicEvents.h" +#include "mozilla/Services.h" + +#include "gfxXlibSurface.h" +#include "gfxContext.h" +#include "nsImageToPixbuf.h" +#include "nsPresContext.h" +#include "nsIContent.h" +#include "nsIDocument.h" +#include "nsISelection.h" +#include "nsViewManager.h" +#include "nsIFrame.h" +#include "nsGtkUtils.h" +#include "mozilla/gfx/2D.h" +#include "gfxPlatform.h" +#include "nsScreenGtk.h" +#include "nsArrayUtils.h" + +using namespace mozilla; +using namespace mozilla::gfx; + +// This sets how opaque the drag image is +#define DRAG_IMAGE_ALPHA_LEVEL 0.5 + +// These values are copied from GtkDragResult (rather than using GtkDragResult +// directly) so that this code can be compiled against versions of GTK+ that +// do not have GtkDragResult. +// GtkDragResult is available from GTK+ version 2.12. +enum { + MOZ_GTK_DRAG_RESULT_SUCCESS, + MOZ_GTK_DRAG_RESULT_NO_TARGET +}; + +static PRLogModuleInfo *sDragLm = nullptr; + +// data used for synthetic periodic motion events sent to the source widget +// grabbing real events for the drag. +static guint sMotionEventTimerID; +static GdkEvent *sMotionEvent; +static GtkWidget *sGrabWidget; + +static const char gMimeListType[] = "application/x-moz-internal-item-list"; +static const char gMozUrlType[] = "_NETSCAPE_URL"; +static const char gTextUriListType[] = "text/uri-list"; +static const char gTextPlainUTF8Type[] = "text/plain;charset=utf-8"; + +static void +invisibleSourceDragBegin(GtkWidget *aWidget, + GdkDragContext *aContext, + gpointer aData); + +static void +invisibleSourceDragEnd(GtkWidget *aWidget, + GdkDragContext *aContext, + gpointer aData); + +static gboolean +invisibleSourceDragFailed(GtkWidget *aWidget, + GdkDragContext *aContext, + gint aResult, + gpointer aData); + +static void +invisibleSourceDragDataGet(GtkWidget *aWidget, + GdkDragContext *aContext, + GtkSelectionData *aSelectionData, + guint aInfo, + guint32 aTime, + gpointer aData); + +nsDragService::nsDragService() + : mScheduledTask(eDragTaskNone) + , mTaskSource(0) +{ + // We have to destroy the hidden widget before the event loop stops + // running. + nsCOMPtr<nsIObserverService> obsServ = + mozilla::services::GetObserverService(); + obsServ->AddObserver(this, "quit-application", false); + + // our hidden source widget +#if (MOZ_WIDGET_GTK == 2) + mHiddenWidget = gtk_window_new(GTK_WINDOW_POPUP); +#else + // Using an offscreen window works around bug 983843. + mHiddenWidget = gtk_offscreen_window_new(); +#endif + // make sure that the widget is realized so that + // we can use it as a drag source. + gtk_widget_realize(mHiddenWidget); + // hook up our internal signals so that we can get some feedback + // from our drag source + g_signal_connect(mHiddenWidget, "drag_begin", + G_CALLBACK(invisibleSourceDragBegin), this); + g_signal_connect(mHiddenWidget, "drag_data_get", + G_CALLBACK(invisibleSourceDragDataGet), this); + g_signal_connect(mHiddenWidget, "drag_end", + G_CALLBACK(invisibleSourceDragEnd), this); + // drag-failed is available from GTK+ version 2.12 + guint dragFailedID = g_signal_lookup("drag-failed", + G_TYPE_FROM_INSTANCE(mHiddenWidget)); + if (dragFailedID) { + g_signal_connect_closure_by_id(mHiddenWidget, dragFailedID, 0, + g_cclosure_new(G_CALLBACK(invisibleSourceDragFailed), + this, nullptr), + FALSE); + } + + // set up our logging module + if (!sDragLm) + sDragLm = PR_NewLogModule("nsDragService"); + MOZ_LOG(sDragLm, LogLevel::Debug, ("nsDragService::nsDragService")); + mCanDrop = false; + mTargetDragDataReceived = false; + mTargetDragData = 0; + mTargetDragDataLen = 0; +} + +nsDragService::~nsDragService() +{ + MOZ_LOG(sDragLm, LogLevel::Debug, ("nsDragService::~nsDragService")); + if (mTaskSource) + g_source_remove(mTaskSource); + +} + +NS_IMPL_ISUPPORTS_INHERITED(nsDragService, nsBaseDragService, nsIObserver) + +/* static */ nsDragService* +nsDragService::GetInstance() +{ + static const nsIID iid = NS_DRAGSERVICE_CID; + nsCOMPtr<nsIDragService> dragService = do_GetService(iid); + return static_cast<nsDragService*>(dragService.get()); + // We rely on XPCOM keeping a reference to the service. +} + +// nsIObserver + +NS_IMETHODIMP +nsDragService::Observe(nsISupports *aSubject, const char *aTopic, + const char16_t *aData) +{ + if (!nsCRT::strcmp(aTopic, "quit-application")) { + MOZ_LOG(sDragLm, LogLevel::Debug, + ("nsDragService::Observe(\"quit-application\")")); + if (mHiddenWidget) { + gtk_widget_destroy(mHiddenWidget); + mHiddenWidget = 0; + } + TargetResetData(); + } else { + NS_NOTREACHED("unexpected topic"); + return NS_ERROR_UNEXPECTED; + } + + return NS_OK; +} + +// Support for periodic drag events + +// http://www.whatwg.org/specs/web-apps/current-work/multipage/dnd.html#drag-and-drop-processing-model +// and the Xdnd protocol both recommend that drag events are sent periodically, +// but GTK does not normally provide this. +// +// Here GTK is periodically stimulated by copies of the most recent mouse +// motion events so as to send drag position messages to the destination when +// appropriate (after it has received a status event from the previous +// message). +// +// (If events were sent only on the destination side then the destination +// would have no message to which it could reply with a drag status. Without +// sending a drag status to the source, the destination would not be able to +// change its feedback re whether it could accept the drop, and so the +// source's behavior on drop will not be consistent.) + +static gboolean +DispatchMotionEventCopy(gpointer aData) +{ + // Clear the timer id before OnSourceGrabEventAfter is called during event + // dispatch. + sMotionEventTimerID = 0; + + GdkEvent *event = sMotionEvent; + sMotionEvent = nullptr; + // If there is no longer a grab on the widget, then the drag is over and + // there is no need to continue drag motion. + if (gtk_widget_has_grab(sGrabWidget)) { + gtk_propagate_event(sGrabWidget, event); + } + gdk_event_free(event); + + // Cancel this timer; + // We've already started another if the motion event was dispatched. + return FALSE; +} + +static void +OnSourceGrabEventAfter(GtkWidget *widget, GdkEvent *event, gpointer user_data) +{ + // If there is no longer a grab on the widget, then the drag motion is + // over (though the data may not be fetched yet). + if (!gtk_widget_has_grab(sGrabWidget)) + return; + + if (event->type == GDK_MOTION_NOTIFY) { + if (sMotionEvent) { + gdk_event_free(sMotionEvent); + } + sMotionEvent = gdk_event_copy(event); + + // Update the cursor position. The last of these recorded gets used for + // the eDragEnd event. + nsDragService *dragService = static_cast<nsDragService*>(user_data); + gint scale = nsScreenGtk::GetGtkMonitorScaleFactor(); + auto p = LayoutDeviceIntPoint::Round(event->motion.x_root * scale, + event->motion.y_root * scale); + dragService->SetDragEndPoint(p); + } else if (sMotionEvent && (event->type == GDK_KEY_PRESS || + event->type == GDK_KEY_RELEASE)) { + // Update modifier state from key events. + sMotionEvent->motion.state = event->key.state; + } else { + return; + } + + if (sMotionEventTimerID) { + g_source_remove(sMotionEventTimerID); + } + + // G_PRIORITY_DEFAULT_IDLE is lower priority than GDK's redraw idle source + // and lower than GTK's idle source that sends drag position messages after + // motion-notify signals. + // + // http://www.whatwg.org/specs/web-apps/current-work/multipage/dnd.html#drag-and-drop-processing-model + // recommends an interval of 350ms +/- 200ms. + sMotionEventTimerID = + g_timeout_add_full(G_PRIORITY_DEFAULT_IDLE, 350, + DispatchMotionEventCopy, nullptr, nullptr); +} + +static GtkWindow* +GetGtkWindow(nsIDOMDocument *aDocument) +{ + nsCOMPtr<nsIDocument> doc = do_QueryInterface(aDocument); + if (!doc) + return nullptr; + + nsCOMPtr<nsIPresShell> presShell = doc->GetShell(); + if (!presShell) + return nullptr; + + RefPtr<nsViewManager> vm = presShell->GetViewManager(); + if (!vm) + return nullptr; + + nsCOMPtr<nsIWidget> widget; + vm->GetRootWidget(getter_AddRefs(widget)); + if (!widget) + return nullptr; + + GtkWidget *gtkWidget = + static_cast<nsWindow*>(widget.get())->GetMozContainerWidget(); + if (!gtkWidget) + return nullptr; + + GtkWidget *toplevel = nullptr; + toplevel = gtk_widget_get_toplevel(gtkWidget); + if (!GTK_IS_WINDOW(toplevel)) + return nullptr; + + return GTK_WINDOW(toplevel); +} + +// nsIDragService + +NS_IMETHODIMP +nsDragService::InvokeDragSession(nsIDOMNode *aDOMNode, + nsIArray * aArrayTransferables, + nsIScriptableRegion * aRegion, + uint32_t aActionType, + nsContentPolicyType aContentPolicyType = + nsIContentPolicy::TYPE_OTHER) +{ + MOZ_LOG(sDragLm, LogLevel::Debug, ("nsDragService::InvokeDragSession")); + + // If the previous source drag has not yet completed, signal handlers need + // to be removed from sGrabWidget and dragend needs to be dispatched to + // the source node, but we can't call EndDragSession yet because we don't + // know whether or not the drag succeeded. + if (mSourceNode) + return NS_ERROR_NOT_AVAILABLE; + + return nsBaseDragService::InvokeDragSession(aDOMNode, aArrayTransferables, + aRegion, aActionType, + aContentPolicyType); +} + +// nsBaseDragService +nsresult +nsDragService::InvokeDragSessionImpl(nsIArray* aArrayTransferables, + nsIScriptableRegion* aRegion, + uint32_t aActionType) +{ + // make sure that we have an array of transferables to use + if (!aArrayTransferables) + return NS_ERROR_INVALID_ARG; + // set our reference to the transferables. this will also addref + // the transferables since we're going to hang onto this beyond the + // length of this call + mSourceDataItems = aArrayTransferables; + // get the list of items we offer for drags + GtkTargetList *sourceList = GetSourceList(); + + if (!sourceList) + return NS_OK; + + // stored temporarily until the drag-begin signal has been received + mSourceRegion = aRegion; + + // save our action type + GdkDragAction action = GDK_ACTION_DEFAULT; + + if (aActionType & DRAGDROP_ACTION_COPY) + action = (GdkDragAction)(action | GDK_ACTION_COPY); + if (aActionType & DRAGDROP_ACTION_MOVE) + action = (GdkDragAction)(action | GDK_ACTION_MOVE); + if (aActionType & DRAGDROP_ACTION_LINK) + action = (GdkDragAction)(action | GDK_ACTION_LINK); + + // Create a fake event for the drag so we can pass the time (so to speak). + // If we don't do this, then, when the timestamp for the pending button + // release event is used for the ungrab, the ungrab can fail due to the + // timestamp being _earlier_ than CurrentTime. + GdkEvent event; + memset(&event, 0, sizeof(GdkEvent)); + event.type = GDK_BUTTON_PRESS; + event.button.window = gtk_widget_get_window(mHiddenWidget); + event.button.time = nsWindow::GetLastUserInputTime(); + + // Put the drag widget in the window group of the source node so that the + // gtk_grab_add during gtk_drag_begin is effective. + // gtk_window_get_group(nullptr) returns the default window group. + GtkWindowGroup *window_group = + gtk_window_get_group(GetGtkWindow(mSourceDocument)); + gtk_window_group_add_window(window_group, + GTK_WINDOW(mHiddenWidget)); + +#if (MOZ_WIDGET_GTK == 3) + // Get device for event source + GdkDisplay *display = gdk_display_get_default(); + GdkDeviceManager *device_manager = gdk_display_get_device_manager(display); + event.button.device = gdk_device_manager_get_client_pointer(device_manager); +#endif + + // start our drag. + GdkDragContext *context = gtk_drag_begin(mHiddenWidget, + sourceList, + action, + 1, + &event); + + mSourceRegion = nullptr; + + nsresult rv; + if (context) { + StartDragSession(); + + // GTK uses another hidden window for receiving mouse events. + sGrabWidget = gtk_window_group_get_current_grab(window_group); + if (sGrabWidget) { + g_object_ref(sGrabWidget); + // Only motion and key events are required but connect to + // "event-after" as this is never blocked by other handlers. + g_signal_connect(sGrabWidget, "event-after", + G_CALLBACK(OnSourceGrabEventAfter), this); + } + // We don't have a drag end point yet. + mEndDragPoint = LayoutDeviceIntPoint(-1, -1); + rv = NS_OK; + } + else { + rv = NS_ERROR_FAILURE; + } + + gtk_target_list_unref(sourceList); + + return rv; +} + +bool +nsDragService::SetAlphaPixmap(SourceSurface *aSurface, + GdkDragContext *aContext, + int32_t aXOffset, + int32_t aYOffset, + const LayoutDeviceIntRect& dragRect) +{ + GdkScreen* screen = gtk_widget_get_screen(mHiddenWidget); + + // Transparent drag icons need, like a lot of transparency-related things, + // a compositing X window manager + if (!gdk_screen_is_composited(screen)) + return false; + +#if (MOZ_WIDGET_GTK == 2) + GdkColormap* alphaColormap = gdk_screen_get_rgba_colormap(screen); + if (!alphaColormap) + return false; + + GdkPixmap* pixmap = gdk_pixmap_new(nullptr, dragRect.width, dragRect.height, + gdk_colormap_get_visual(alphaColormap)->depth); + if (!pixmap) + return false; + + gdk_drawable_set_colormap(GDK_DRAWABLE(pixmap), alphaColormap); + + // Make a DrawTarget wrapped around the pixmap to render on + RefPtr<DrawTarget> dt = + nsWindow::GetDrawTargetForGdkDrawable(GDK_DRAWABLE(pixmap), + IntSize(dragRect.width, + dragRect.height)); + if (!dt) + return false; + + // Clear it... + dt->ClearRect(Rect(0, 0, dragRect.width, dragRect.height)); + + // ...and paint the drag image with translucency + dt->DrawSurface(aSurface, + Rect(0, 0, dragRect.width, dragRect.height), + Rect(0, 0, dragRect.width, dragRect.height), + DrawSurfaceOptions(), + DrawOptions(DRAG_IMAGE_ALPHA_LEVEL, CompositionOp::OP_SOURCE)); + + // The drag transaction addrefs the pixmap, so we can just unref it from us here + gtk_drag_set_icon_pixmap(aContext, alphaColormap, pixmap, nullptr, + aXOffset, aYOffset); + g_object_unref(pixmap); + return true; +#else +#ifdef cairo_image_surface_create +#error "Looks like we're including Mozilla's cairo instead of system cairo" +#endif + // Prior to GTK 3.9.12, cairo surfaces passed into gtk_drag_set_icon_surface + // had their shape information derived from the alpha channel and used with + // the X SHAPE extension instead of being displayed as an ARGB window. + // See bug 1249604. + if (gtk_check_version(3, 9, 12)) + return false; + + // TODO: grab X11 pixmap or image data instead of expensive readback. + cairo_surface_t *surf = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, + dragRect.width, + dragRect.height); + if (!surf) + return false; + + RefPtr<DrawTarget> dt = gfxPlatform::CreateDrawTargetForData( + cairo_image_surface_get_data(surf), + nsIntSize(dragRect.width, dragRect.height), + cairo_image_surface_get_stride(surf), + SurfaceFormat::B8G8R8A8); + if (!dt) + return false; + + dt->ClearRect(Rect(0, 0, dragRect.width, dragRect.height)); + dt->DrawSurface(aSurface, + Rect(0, 0, dragRect.width, dragRect.height), + Rect(0, 0, dragRect.width, dragRect.height), + DrawSurfaceOptions(), + DrawOptions(DRAG_IMAGE_ALPHA_LEVEL, CompositionOp::OP_SOURCE)); + + cairo_surface_mark_dirty(surf); + cairo_surface_set_device_offset(surf, -aXOffset, -aYOffset); + + // Ensure that the surface is drawn at the correct scale on HiDPI displays. + static auto sCairoSurfaceSetDeviceScalePtr = + (void (*)(cairo_surface_t*,double,double)) + dlsym(RTLD_DEFAULT, "cairo_surface_set_device_scale"); + if (sCairoSurfaceSetDeviceScalePtr) { + gint scale = nsScreenGtk::GetGtkMonitorScaleFactor(); + sCairoSurfaceSetDeviceScalePtr(surf, scale, scale); + } + + gtk_drag_set_icon_surface(aContext, surf); + cairo_surface_destroy(surf); + return true; +#endif +} + +NS_IMETHODIMP +nsDragService::StartDragSession() +{ + MOZ_LOG(sDragLm, LogLevel::Debug, ("nsDragService::StartDragSession")); + return nsBaseDragService::StartDragSession(); +} + +NS_IMETHODIMP +nsDragService::EndDragSession(bool aDoneDrag) +{ + MOZ_LOG(sDragLm, LogLevel::Debug, ("nsDragService::EndDragSession %d", + aDoneDrag)); + + if (sGrabWidget) { + g_signal_handlers_disconnect_by_func(sGrabWidget, + FuncToGpointer(OnSourceGrabEventAfter), this); + g_object_unref(sGrabWidget); + sGrabWidget = nullptr; + + if (sMotionEventTimerID) { + g_source_remove(sMotionEventTimerID); + sMotionEventTimerID = 0; + } + if (sMotionEvent) { + gdk_event_free(sMotionEvent); + sMotionEvent = nullptr; + } + } + + // unset our drag action + SetDragAction(DRAGDROP_ACTION_NONE); + + // We're done with the drag context. + mTargetDragContextForRemote = nullptr; + + return nsBaseDragService::EndDragSession(aDoneDrag); +} + +// nsIDragSession +NS_IMETHODIMP +nsDragService::SetCanDrop(bool aCanDrop) +{ + MOZ_LOG(sDragLm, LogLevel::Debug, ("nsDragService::SetCanDrop %d", + aCanDrop)); + mCanDrop = aCanDrop; + return NS_OK; +} + +NS_IMETHODIMP +nsDragService::GetCanDrop(bool *aCanDrop) +{ + MOZ_LOG(sDragLm, LogLevel::Debug, ("nsDragService::GetCanDrop")); + *aCanDrop = mCanDrop; + return NS_OK; +} + +static void +UTF16ToNewUTF8(const char16_t* aUTF16, + uint32_t aUTF16Len, + char** aUTF8, + uint32_t* aUTF8Len) +{ + nsDependentSubstring utf16(aUTF16, aUTF16Len); + *aUTF8 = ToNewUTF8String(utf16, aUTF8Len); +} + +static void +UTF8ToNewUTF16(const char* aUTF8, + uint32_t aUTF8Len, + char16_t** aUTF16, + uint32_t* aUTF16Len) +{ + nsDependentCSubstring utf8(aUTF8, aUTF8Len); + *aUTF16 = UTF8ToNewUnicode(utf8, aUTF16Len); +} + +// count the number of URIs in some text/uri-list format data. +static uint32_t +CountTextUriListItems(const char *data, + uint32_t datalen) +{ + const char *p = data; + const char *endPtr = p + datalen; + uint32_t count = 0; + + while (p < endPtr) { + // skip whitespace (if any) + while (p < endPtr && *p != '\0' && isspace(*p)) + p++; + // if we aren't at the end of the line ... + if (p != endPtr && *p != '\0' && *p != '\n' && *p != '\r') + count++; + // skip to the end of the line + while (p < endPtr && *p != '\0' && *p != '\n') + p++; + p++; // skip the actual newline as well. + } + return count; +} + +// extract an item from text/uri-list formatted data and convert it to +// unicode. +static void +GetTextUriListItem(const char *data, + uint32_t datalen, + uint32_t aItemIndex, + char16_t **convertedText, + uint32_t *convertedTextLen) +{ + const char *p = data; + const char *endPtr = p + datalen; + unsigned int count = 0; + + *convertedText = nullptr; + while (p < endPtr) { + // skip whitespace (if any) + while (p < endPtr && *p != '\0' && isspace(*p)) + p++; + // if we aren't at the end of the line, we have a url + if (p != endPtr && *p != '\0' && *p != '\n' && *p != '\r') + count++; + // this is the item we are after ... + if (aItemIndex + 1 == count) { + const char *q = p; + while (q < endPtr && *q != '\0' && *q != '\n' && *q != '\r') + q++; + UTF8ToNewUTF16(p, q - p, convertedText, convertedTextLen); + break; + } + // skip to the end of the line + while (p < endPtr && *p != '\0' && *p != '\n') + p++; + p++; // skip the actual newline as well. + } + + // didn't find the desired item, so just pass the whole lot + if (!*convertedText) { + UTF8ToNewUTF16(data, datalen, convertedText, convertedTextLen); + } +} + +NS_IMETHODIMP +nsDragService::GetNumDropItems(uint32_t * aNumItems) +{ + MOZ_LOG(sDragLm, LogLevel::Debug, ("nsDragService::GetNumDropItems")); + + if (!mTargetWidget) { + MOZ_LOG(sDragLm, LogLevel::Debug, + ("*** warning: GetNumDropItems \ + called without a valid target widget!\n")); + *aNumItems = 0; + return NS_OK; + } + + bool isList = IsTargetContextList(); + if (isList) + mSourceDataItems->GetLength(aNumItems); + else { + GdkAtom gdkFlavor = gdk_atom_intern(gTextUriListType, FALSE); + GetTargetDragData(gdkFlavor); + if (mTargetDragData) { + const char *data = reinterpret_cast<char*>(mTargetDragData); + *aNumItems = CountTextUriListItems(data, mTargetDragDataLen); + } else + *aNumItems = 1; + } + MOZ_LOG(sDragLm, LogLevel::Debug, ("%d items", *aNumItems)); + return NS_OK; +} + + +NS_IMETHODIMP +nsDragService::GetData(nsITransferable * aTransferable, + uint32_t aItemIndex) +{ + MOZ_LOG(sDragLm, LogLevel::Debug, ("nsDragService::GetData %d", aItemIndex)); + + // make sure that we have a transferable + if (!aTransferable) + return NS_ERROR_INVALID_ARG; + + if (!mTargetWidget) { + MOZ_LOG(sDragLm, LogLevel::Debug, + ("*** warning: GetData \ + called without a valid target widget!\n")); + return NS_ERROR_FAILURE; + } + + // get flavor list that includes all acceptable flavors (including + // ones obtained through conversion). Flavors are nsISupportsStrings + // so that they can be seen from JS. + nsCOMPtr<nsIArray> flavorList; + nsresult rv = aTransferable->FlavorsTransferableCanImport( + getter_AddRefs(flavorList)); + if (NS_FAILED(rv)) + return rv; + + // count the number of flavors + uint32_t cnt; + flavorList->GetLength(&cnt); + unsigned int i; + + // check to see if this is an internal list + bool isList = IsTargetContextList(); + + if (isList) { + MOZ_LOG(sDragLm, LogLevel::Debug, ("it's a list...")); + // find a matching flavor + for (i = 0; i < cnt; ++i) { + nsCOMPtr<nsISupportsCString> currentFlavor; + currentFlavor = do_QueryElementAt(flavorList, i); + if (!currentFlavor) + continue; + + nsXPIDLCString flavorStr; + currentFlavor->ToString(getter_Copies(flavorStr)); + MOZ_LOG(sDragLm, + LogLevel::Debug, + ("flavor is %s\n", (const char *)flavorStr)); + // get the item with the right index + nsCOMPtr<nsITransferable> item = + do_QueryElementAt(mSourceDataItems, aItemIndex); + if (!item) + continue; + + nsCOMPtr<nsISupports> data; + uint32_t tmpDataLen = 0; + MOZ_LOG(sDragLm, LogLevel::Debug, + ("trying to get transfer data for %s\n", + (const char *)flavorStr)); + rv = item->GetTransferData(flavorStr, + getter_AddRefs(data), + &tmpDataLen); + if (NS_FAILED(rv)) { + MOZ_LOG(sDragLm, LogLevel::Debug, ("failed.\n")); + continue; + } + MOZ_LOG(sDragLm, LogLevel::Debug, ("succeeded.\n")); + rv = aTransferable->SetTransferData(flavorStr,data,tmpDataLen); + if (NS_FAILED(rv)) { + MOZ_LOG(sDragLm, + LogLevel::Debug, + ("fail to set transfer data into transferable!\n")); + continue; + } + // ok, we got the data + return NS_OK; + } + // if we got this far, we failed + return NS_ERROR_FAILURE; + } + + // Now walk down the list of flavors. When we find one that is + // actually present, copy out the data into the transferable in that + // format. SetTransferData() implicitly handles conversions. + for ( i = 0; i < cnt; ++i ) { + nsCOMPtr<nsISupportsCString> currentFlavor; + currentFlavor = do_QueryElementAt(flavorList, i); + if (currentFlavor) { + // find our gtk flavor + nsXPIDLCString flavorStr; + currentFlavor->ToString(getter_Copies(flavorStr)); + GdkAtom gdkFlavor = gdk_atom_intern(flavorStr, FALSE); + MOZ_LOG(sDragLm, LogLevel::Debug, + ("looking for data in type %s, gdk flavor %ld\n", + static_cast<const char*>(flavorStr), gdkFlavor)); + bool dataFound = false; + if (gdkFlavor) { + GetTargetDragData(gdkFlavor); + } + if (mTargetDragData) { + MOZ_LOG(sDragLm, LogLevel::Debug, ("dataFound = true\n")); + dataFound = true; + } + else { + MOZ_LOG(sDragLm, LogLevel::Debug, ("dataFound = false\n")); + + // Dragging and dropping from the file manager would cause us + // to parse the source text as a nsIFile URL. + if ( strcmp(flavorStr, kFileMime) == 0 ) { + gdkFlavor = gdk_atom_intern(kTextMime, FALSE); + GetTargetDragData(gdkFlavor); + if (!mTargetDragData) { + gdkFlavor = gdk_atom_intern(gTextUriListType, FALSE); + GetTargetDragData(gdkFlavor); + } + if (mTargetDragData) { + const char* text = static_cast<char*>(mTargetDragData); + char16_t* convertedText = nullptr; + uint32_t convertedTextLen = 0; + + GetTextUriListItem(text, mTargetDragDataLen, aItemIndex, + &convertedText, &convertedTextLen); + + if (convertedText) { + nsCOMPtr<nsIIOService> ioService = do_GetIOService(&rv); + nsCOMPtr<nsIURI> fileURI; + rv = ioService->NewURI(NS_ConvertUTF16toUTF8(convertedText), + nullptr, nullptr, getter_AddRefs(fileURI)); + if (NS_SUCCEEDED(rv)) { + nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(fileURI, &rv); + if (NS_SUCCEEDED(rv)) { + nsCOMPtr<nsIFile> file; + rv = fileURL->GetFile(getter_AddRefs(file)); + if (NS_SUCCEEDED(rv)) { + // The common wrapping code at the end of + // this function assumes the data is text + // and calls text-specific operations. + // Make a secret hideout here for nsIFile + // objects and return early. + aTransferable->SetTransferData(flavorStr, file, + convertedTextLen); + g_free(convertedText); + return NS_OK; + } + } + } + g_free(convertedText); + } + continue; + } + } + + // if we are looking for text/unicode and we fail to find it + // on the clipboard first, try again with text/plain. If that + // is present, convert it to unicode. + if ( strcmp(flavorStr, kUnicodeMime) == 0 ) { + MOZ_LOG(sDragLm, LogLevel::Debug, + ("we were looking for text/unicode... \ + trying with text/plain;charset=utf-8\n")); + gdkFlavor = gdk_atom_intern(gTextPlainUTF8Type, FALSE); + GetTargetDragData(gdkFlavor); + if (mTargetDragData) { + MOZ_LOG(sDragLm, LogLevel::Debug, ("Got textplain data\n")); + const char* castedText = + reinterpret_cast<char*>(mTargetDragData); + char16_t* convertedText = nullptr; + NS_ConvertUTF8toUTF16 ucs2string(castedText, + mTargetDragDataLen); + convertedText = ToNewUnicode(ucs2string); + if ( convertedText ) { + MOZ_LOG(sDragLm, LogLevel::Debug, + ("successfully converted plain text \ + to unicode.\n")); + // out with the old, in with the new + g_free(mTargetDragData); + mTargetDragData = convertedText; + mTargetDragDataLen = ucs2string.Length() * 2; + dataFound = true; + } // if plain text data on clipboard + } else { + MOZ_LOG(sDragLm, LogLevel::Debug, + ("we were looking for text/unicode... \ + trying again with text/plain\n")); + gdkFlavor = gdk_atom_intern(kTextMime, FALSE); + GetTargetDragData(gdkFlavor); + if (mTargetDragData) { + MOZ_LOG(sDragLm, LogLevel::Debug, ("Got textplain data\n")); + const char* castedText = + reinterpret_cast<char*>(mTargetDragData); + char16_t* convertedText = nullptr; + uint32_t convertedTextLen = 0; + UTF8ToNewUTF16(castedText, mTargetDragDataLen, + &convertedText, &convertedTextLen); + if ( convertedText ) { + MOZ_LOG(sDragLm, LogLevel::Debug, + ("successfully converted plain text \ + to unicode.\n")); + // out with the old, in with the new + g_free(mTargetDragData); + mTargetDragData = convertedText; + mTargetDragDataLen = convertedTextLen * 2; + dataFound = true; + } // if plain text data on clipboard + } // if plain text flavor present + } // if plain text charset=utf-8 flavor present + } // if looking for text/unicode + + // if we are looking for text/x-moz-url and we failed to find + // it on the clipboard, try again with text/uri-list, and then + // _NETSCAPE_URL + if (strcmp(flavorStr, kURLMime) == 0) { + MOZ_LOG(sDragLm, LogLevel::Debug, + ("we were looking for text/x-moz-url...\ + trying again with text/uri-list\n")); + gdkFlavor = gdk_atom_intern(gTextUriListType, FALSE); + GetTargetDragData(gdkFlavor); + if (mTargetDragData) { + MOZ_LOG(sDragLm, LogLevel::Debug, + ("Got text/uri-list data\n")); + const char *data = + reinterpret_cast<char*>(mTargetDragData); + char16_t* convertedText = nullptr; + uint32_t convertedTextLen = 0; + + GetTextUriListItem(data, mTargetDragDataLen, aItemIndex, + &convertedText, &convertedTextLen); + + if ( convertedText ) { + MOZ_LOG(sDragLm, LogLevel::Debug, + ("successfully converted \ + _NETSCAPE_URL to unicode.\n")); + // out with the old, in with the new + g_free(mTargetDragData); + mTargetDragData = convertedText; + mTargetDragDataLen = convertedTextLen * 2; + dataFound = true; + } + } + else { + MOZ_LOG(sDragLm, LogLevel::Debug, + ("failed to get text/uri-list data\n")); + } + if (!dataFound) { + MOZ_LOG(sDragLm, LogLevel::Debug, + ("we were looking for text/x-moz-url...\ + trying again with _NETSCAP_URL\n")); + gdkFlavor = gdk_atom_intern(gMozUrlType, FALSE); + GetTargetDragData(gdkFlavor); + if (mTargetDragData) { + MOZ_LOG(sDragLm, LogLevel::Debug, + ("Got _NETSCAPE_URL data\n")); + const char* castedText = + reinterpret_cast<char*>(mTargetDragData); + char16_t* convertedText = nullptr; + uint32_t convertedTextLen = 0; + UTF8ToNewUTF16(castedText, mTargetDragDataLen, &convertedText, &convertedTextLen); + if ( convertedText ) { + MOZ_LOG(sDragLm, + LogLevel::Debug, + ("successfully converted _NETSCAPE_URL \ + to unicode.\n")); + // out with the old, in with the new + g_free(mTargetDragData); + mTargetDragData = convertedText; + mTargetDragDataLen = convertedTextLen * 2; + dataFound = true; + } + } + else { + MOZ_LOG(sDragLm, LogLevel::Debug, + ("failed to get _NETSCAPE_URL data\n")); + } + } + } + + } // else we try one last ditch effort to find our data + + if (dataFound) { + if (strcmp(flavorStr, kCustomTypesMime) != 0) { + // the DOM only wants LF, so convert from MacOS line endings + // to DOM line endings. + nsLinebreakHelpers::ConvertPlatformToDOMLinebreaks( + flavorStr, + &mTargetDragData, + reinterpret_cast<int*>(&mTargetDragDataLen)); + } + + // put it into the transferable. + nsCOMPtr<nsISupports> genericDataWrapper; + nsPrimitiveHelpers::CreatePrimitiveForData(flavorStr, + mTargetDragData, mTargetDragDataLen, + getter_AddRefs(genericDataWrapper)); + aTransferable->SetTransferData(flavorStr, + genericDataWrapper, + mTargetDragDataLen); + // we found one, get out of this loop! + MOZ_LOG(sDragLm, LogLevel::Debug, ("dataFound and converted!\n")); + break; + } + } // if (currentFlavor) + } // foreach flavor + + return NS_OK; + +} + +NS_IMETHODIMP +nsDragService::IsDataFlavorSupported(const char *aDataFlavor, + bool *_retval) +{ + MOZ_LOG(sDragLm, LogLevel::Debug, ("nsDragService::IsDataFlavorSupported %s", + aDataFlavor)); + if (!_retval) + return NS_ERROR_INVALID_ARG; + + // set this to no by default + *_retval = false; + + // check to make sure that we have a drag object set, here + if (!mTargetWidget) { + MOZ_LOG(sDragLm, LogLevel::Debug, + ("*** warning: IsDataFlavorSupported \ + called without a valid target widget!\n")); + return NS_OK; + } + + // check to see if the target context is a list. + bool isList = IsTargetContextList(); + // if it is, just look in the internal data since we are the source + // for it. + if (isList) { + MOZ_LOG(sDragLm, LogLevel::Debug, ("It's a list..")); + uint32_t numDragItems = 0; + // if we don't have mDataItems we didn't start this drag so it's + // an external client trying to fool us. + if (!mSourceDataItems) + return NS_OK; + mSourceDataItems->GetLength(&numDragItems); + for (uint32_t itemIndex = 0; itemIndex < numDragItems; ++itemIndex) { + nsCOMPtr<nsITransferable> currItem = + do_QueryElementAt(mSourceDataItems, itemIndex); + if (currItem) { + nsCOMPtr <nsIArray> flavorList; + currItem->FlavorsTransferableCanExport( + getter_AddRefs(flavorList)); + if (flavorList) { + uint32_t numFlavors; + flavorList->GetLength( &numFlavors ); + for ( uint32_t flavorIndex = 0; + flavorIndex < numFlavors ; + ++flavorIndex ) { + nsCOMPtr<nsISupportsCString> currentFlavor; + currentFlavor = do_QueryElementAt(flavorList, flavorIndex); + if (currentFlavor) { + nsXPIDLCString flavorStr; + currentFlavor->ToString(getter_Copies(flavorStr)); + MOZ_LOG(sDragLm, LogLevel::Debug, + ("checking %s against %s\n", + (const char *)flavorStr, aDataFlavor)); + if (strcmp(flavorStr, aDataFlavor) == 0) { + MOZ_LOG(sDragLm, LogLevel::Debug, + ("boioioioiooioioioing!\n")); + *_retval = true; + } + } + } + } + } + } + return NS_OK; + } + + // check the target context vs. this flavor, one at a time + GList *tmp; + for (tmp = gdk_drag_context_list_targets(mTargetDragContext); + tmp; tmp = tmp->next) { + /* Bug 331198 */ + GdkAtom atom = GDK_POINTER_TO_ATOM(tmp->data); + gchar *name = nullptr; + name = gdk_atom_name(atom); + MOZ_LOG(sDragLm, LogLevel::Debug, + ("checking %s against %s\n", name, aDataFlavor)); + if (name && (strcmp(name, aDataFlavor) == 0)) { + MOZ_LOG(sDragLm, LogLevel::Debug, ("good!\n")); + *_retval = true; + } + // check for automatic text/uri-list -> text/x-moz-url mapping + if (!*_retval && + name && + (strcmp(name, gTextUriListType) == 0) && + (strcmp(aDataFlavor, kURLMime) == 0 || + strcmp(aDataFlavor, kFileMime) == 0)) { + MOZ_LOG(sDragLm, LogLevel::Debug, + ("good! ( it's text/uri-list and \ + we're checking against text/x-moz-url )\n")); + *_retval = true; + } + // check for automatic _NETSCAPE_URL -> text/x-moz-url mapping + if (!*_retval && + name && + (strcmp(name, gMozUrlType) == 0) && + (strcmp(aDataFlavor, kURLMime) == 0)) { + MOZ_LOG(sDragLm, LogLevel::Debug, + ("good! ( it's _NETSCAPE_URL and \ + we're checking against text/x-moz-url )\n")); + *_retval = true; + } + // check for auto text/plain -> text/unicode mapping + if (!*_retval && + name && + (strcmp(name, kTextMime) == 0) && + ((strcmp(aDataFlavor, kUnicodeMime) == 0) || + (strcmp(aDataFlavor, kFileMime) == 0))) { + MOZ_LOG(sDragLm, LogLevel::Debug, + ("good! ( it's text plain and we're checking \ + against text/unicode or application/x-moz-file)\n")); + *_retval = true; + } + g_free(name); + } + return NS_OK; +} + +void +nsDragService::ReplyToDragMotion(GdkDragContext* aDragContext) +{ + MOZ_LOG(sDragLm, LogLevel::Debug, + ("nsDragService::ReplyToDragMotion %d", mCanDrop)); + + GdkDragAction action = (GdkDragAction)0; + if (mCanDrop) { + // notify the dragger if we can drop + switch (mDragAction) { + case DRAGDROP_ACTION_COPY: + action = GDK_ACTION_COPY; + break; + case DRAGDROP_ACTION_LINK: + action = GDK_ACTION_LINK; + break; + case DRAGDROP_ACTION_NONE: + action = (GdkDragAction)0; + break; + default: + action = GDK_ACTION_MOVE; + break; + } + } + + gdk_drag_status(aDragContext, action, mTargetTime); +} + +void +nsDragService::TargetDataReceived(GtkWidget *aWidget, + GdkDragContext *aContext, + gint aX, + gint aY, + GtkSelectionData *aSelectionData, + guint aInfo, + guint32 aTime) +{ + MOZ_LOG(sDragLm, LogLevel::Debug, ("nsDragService::TargetDataReceived")); + TargetResetData(); + mTargetDragDataReceived = true; + gint len = gtk_selection_data_get_length(aSelectionData); + const guchar* data = gtk_selection_data_get_data(aSelectionData); + if (len > 0 && data) { + mTargetDragDataLen = len; + mTargetDragData = g_malloc(mTargetDragDataLen); + memcpy(mTargetDragData, data, mTargetDragDataLen); + } + else { + MOZ_LOG(sDragLm, LogLevel::Debug, + ("Failed to get data. selection data len was %d\n", + mTargetDragDataLen)); + } +} + +bool +nsDragService::IsTargetContextList(void) +{ + bool retval = false; + + // gMimeListType drags only work for drags within a single process. The + // gtk_drag_get_source_widget() function will return nullptr if the source + // of the drag is another app, so we use it to check if a gMimeListType + // drop will work or not. + if (gtk_drag_get_source_widget(mTargetDragContext) == nullptr) + return retval; + + GList *tmp; + + // walk the list of context targets and see if one of them is a list + // of items. + for (tmp = gdk_drag_context_list_targets(mTargetDragContext); + tmp; tmp = tmp->next) { + /* Bug 331198 */ + GdkAtom atom = GDK_POINTER_TO_ATOM(tmp->data); + gchar *name = nullptr; + name = gdk_atom_name(atom); + if (name && strcmp(name, gMimeListType) == 0) + retval = true; + g_free(name); + if (retval) + break; + } + return retval; +} + +// Maximum time to wait for a "drag_received" arrived, in microseconds +#define NS_DND_TIMEOUT 500000 + +void +nsDragService::GetTargetDragData(GdkAtom aFlavor) +{ + MOZ_LOG(sDragLm, LogLevel::Debug, ("getting data flavor %d\n", aFlavor)); + MOZ_LOG(sDragLm, LogLevel::Debug, ("mLastWidget is %p and mLastContext is %p\n", + mTargetWidget.get(), + mTargetDragContext.get())); + // reset our target data areas + TargetResetData(); + gtk_drag_get_data(mTargetWidget, mTargetDragContext, aFlavor, mTargetTime); + + MOZ_LOG(sDragLm, LogLevel::Debug, ("about to start inner iteration.")); + PRTime entryTime = PR_Now(); + while (!mTargetDragDataReceived && mDoingDrag) { + // check the number of iterations + MOZ_LOG(sDragLm, LogLevel::Debug, ("doing iteration...\n")); + PR_Sleep(20*PR_TicksPerSecond()/1000); /* sleep for 20 ms/iteration */ + if (PR_Now()-entryTime > NS_DND_TIMEOUT) break; + gtk_main_iteration(); + } + MOZ_LOG(sDragLm, LogLevel::Debug, ("finished inner iteration\n")); +} + +void +nsDragService::TargetResetData(void) +{ + mTargetDragDataReceived = false; + // make sure to free old data if we have to + g_free(mTargetDragData); + mTargetDragData = 0; + mTargetDragDataLen = 0; +} + +GtkTargetList * +nsDragService::GetSourceList(void) +{ + if (!mSourceDataItems) + return nullptr; + nsTArray<GtkTargetEntry*> targetArray; + GtkTargetEntry *targets; + GtkTargetList *targetList = 0; + uint32_t targetCount = 0; + unsigned int numDragItems = 0; + + mSourceDataItems->GetLength(&numDragItems); + + // Check to see if we're dragging > 1 item. + if (numDragItems > 1) { + // as the Xdnd protocol only supports a single item (or is it just + // gtk's implementation?), we don't advertise all flavours listed + // in the nsITransferable. + + // the application/x-moz-internal-item-list format, which preserves + // all information for drags within the same mozilla instance. + GtkTargetEntry *listTarget = + (GtkTargetEntry *)g_malloc(sizeof(GtkTargetEntry)); + listTarget->target = g_strdup(gMimeListType); + listTarget->flags = 0; + MOZ_LOG(sDragLm, LogLevel::Debug, + ("automatically adding target %s\n", listTarget->target)); + targetArray.AppendElement(listTarget); + + // check what flavours are supported so we can decide what other + // targets to advertise. + nsCOMPtr<nsITransferable> currItem = + do_QueryElementAt(mSourceDataItems, 0); + + if (currItem) { + nsCOMPtr <nsIArray> flavorList; + currItem->FlavorsTransferableCanExport(getter_AddRefs(flavorList)); + if (flavorList) { + uint32_t numFlavors; + flavorList->GetLength( &numFlavors ); + for (uint32_t flavorIndex = 0; + flavorIndex < numFlavors ; + ++flavorIndex ) { + nsCOMPtr<nsISupportsCString> currentFlavor; + currentFlavor = do_QueryElementAt(flavorList, flavorIndex); + if (currentFlavor) { + nsXPIDLCString flavorStr; + currentFlavor->ToString(getter_Copies(flavorStr)); + + // check if text/x-moz-url is supported. + // If so, advertise + // text/uri-list. + if (strcmp(flavorStr, kURLMime) == 0) { + listTarget = + (GtkTargetEntry *)g_malloc(sizeof(GtkTargetEntry)); + listTarget->target = g_strdup(gTextUriListType); + listTarget->flags = 0; + MOZ_LOG(sDragLm, LogLevel::Debug, + ("automatically adding target %s\n", + listTarget->target)); + targetArray.AppendElement(listTarget); + } + } + } // foreach flavor in item + } // if valid flavor list + } // if item is a transferable + } else if (numDragItems == 1) { + nsCOMPtr<nsITransferable> currItem = + do_QueryElementAt(mSourceDataItems, 0); + if (currItem) { + nsCOMPtr <nsIArray> flavorList; + currItem->FlavorsTransferableCanExport(getter_AddRefs(flavorList)); + if (flavorList) { + uint32_t numFlavors; + flavorList->GetLength( &numFlavors ); + for (uint32_t flavorIndex = 0; + flavorIndex < numFlavors ; + ++flavorIndex ) { + nsCOMPtr<nsISupportsCString> currentFlavor; + currentFlavor = do_QueryElementAt(flavorList, flavorIndex); + if (currentFlavor) { + nsXPIDLCString flavorStr; + currentFlavor->ToString(getter_Copies(flavorStr)); + GtkTargetEntry *target = + (GtkTargetEntry *)g_malloc(sizeof(GtkTargetEntry)); + target->target = g_strdup(flavorStr); + target->flags = 0; + MOZ_LOG(sDragLm, LogLevel::Debug, + ("adding target %s\n", target->target)); + targetArray.AppendElement(target); + + // If there is a file, add the text/uri-list type. + if (strcmp(flavorStr, kFileMime) == 0) { + GtkTargetEntry *urilistTarget = + (GtkTargetEntry *)g_malloc(sizeof(GtkTargetEntry)); + urilistTarget->target = g_strdup(gTextUriListType); + urilistTarget->flags = 0; + MOZ_LOG(sDragLm, LogLevel::Debug, + ("automatically adding target %s\n", + urilistTarget->target)); + targetArray.AppendElement(urilistTarget); + } + // Check to see if this is text/unicode. + // If it is, add text/plain + // since we automatically support text/plain + // if we support text/unicode. + else if (strcmp(flavorStr, kUnicodeMime) == 0) { + GtkTargetEntry *plainUTF8Target = + (GtkTargetEntry *)g_malloc(sizeof(GtkTargetEntry)); + plainUTF8Target->target = g_strdup(gTextPlainUTF8Type); + plainUTF8Target->flags = 0; + MOZ_LOG(sDragLm, LogLevel::Debug, + ("automatically adding target %s\n", + plainUTF8Target->target)); + targetArray.AppendElement(plainUTF8Target); + + GtkTargetEntry *plainTarget = + (GtkTargetEntry *)g_malloc(sizeof(GtkTargetEntry)); + plainTarget->target = g_strdup(kTextMime); + plainTarget->flags = 0; + MOZ_LOG(sDragLm, LogLevel::Debug, + ("automatically adding target %s\n", + plainTarget->target)); + targetArray.AppendElement(plainTarget); + } + // Check to see if this is the x-moz-url type. + // If it is, add _NETSCAPE_URL + // this is a type used by everybody. + else if (strcmp(flavorStr, kURLMime) == 0) { + GtkTargetEntry *urlTarget = + (GtkTargetEntry *)g_malloc(sizeof(GtkTargetEntry)); + urlTarget->target = g_strdup(gMozUrlType); + urlTarget->flags = 0; + MOZ_LOG(sDragLm, LogLevel::Debug, + ("automatically adding target %s\n", + urlTarget->target)); + targetArray.AppendElement(urlTarget); + } + } + } // foreach flavor in item + } // if valid flavor list + } // if item is a transferable + } // if it is a single item drag + + // get all the elements that we created. + targetCount = targetArray.Length(); + if (targetCount) { + // allocate space to create the list of valid targets + targets = + (GtkTargetEntry *)g_malloc(sizeof(GtkTargetEntry) * targetCount); + uint32_t targetIndex; + for ( targetIndex = 0; targetIndex < targetCount; ++targetIndex) { + GtkTargetEntry *disEntry = targetArray.ElementAt(targetIndex); + // this is a string reference but it will be freed later. + targets[targetIndex].target = disEntry->target; + targets[targetIndex].flags = disEntry->flags; + targets[targetIndex].info = 0; + } + targetList = gtk_target_list_new(targets, targetCount); + // clean up the target list + for (uint32_t cleanIndex = 0; cleanIndex < targetCount; ++cleanIndex) { + GtkTargetEntry *thisTarget = targetArray.ElementAt(cleanIndex); + g_free(thisTarget->target); + g_free(thisTarget); + } + g_free(targets); + } + return targetList; +} + +void +nsDragService::SourceEndDragSession(GdkDragContext *aContext, + gint aResult) +{ + // this just releases the list of data items that we provide + mSourceDataItems = nullptr; + + if (!mDoingDrag || mScheduledTask == eDragTaskSourceEnd) + // EndDragSession() was already called on drop + // or SourceEndDragSession on drag-failed + return; + + if (mEndDragPoint.x < 0) { + // We don't have a drag end point, so guess + gint x, y; + GdkDisplay* display = gdk_display_get_default(); + if (display) { + gint scale = nsScreenGtk::GetGtkMonitorScaleFactor(); + gdk_display_get_pointer(display, nullptr, &x, &y, nullptr); + SetDragEndPoint(LayoutDeviceIntPoint(x * scale, y * scale)); + } + } + + // Either the drag was aborted or the drop occurred outside the app. + // The dropEffect of mDataTransfer is not updated for motion outside the + // app, but is needed for the dragend event, so set it now. + + uint32_t dropEffect; + + if (aResult == MOZ_GTK_DRAG_RESULT_SUCCESS) { + + // With GTK+ versions 2.10.x and prior the drag may have been + // cancelled (but no drag-failed signal would have been sent). + // aContext->dest_window will be non-nullptr only if the drop was + // sent. + GdkDragAction action = + gdk_drag_context_get_dest_window(aContext) ? + gdk_drag_context_get_actions(aContext) : (GdkDragAction)0; + + // Only one bit of action should be set, but, just in case someone + // does something funny, erring away from MOVE, and not recording + // unusual action combinations as NONE. + if (!action) + dropEffect = DRAGDROP_ACTION_NONE; + else if (action & GDK_ACTION_COPY) + dropEffect = DRAGDROP_ACTION_COPY; + else if (action & GDK_ACTION_LINK) + dropEffect = DRAGDROP_ACTION_LINK; + else if (action & GDK_ACTION_MOVE) + dropEffect = DRAGDROP_ACTION_MOVE; + else + dropEffect = DRAGDROP_ACTION_COPY; + + } else { + + dropEffect = DRAGDROP_ACTION_NONE; + + if (aResult != MOZ_GTK_DRAG_RESULT_NO_TARGET) { + mUserCancelled = true; + } + } + + if (mDataTransfer) { + mDataTransfer->SetDropEffectInt(dropEffect); + } + + // Schedule the appropriate drag end dom events. + Schedule(eDragTaskSourceEnd, nullptr, nullptr, LayoutDeviceIntPoint(), 0); +} + +static void +CreateUriList(nsIArray *items, gchar **text, gint *length) +{ + uint32_t i, count; + GString *uriList = g_string_new(nullptr); + + items->GetLength(&count); + for (i = 0; i < count; i++) { + nsCOMPtr<nsITransferable> item; + item = do_QueryElementAt(items, i); + + if (item) { + uint32_t tmpDataLen = 0; + void *tmpData = nullptr; + nsresult rv = NS_OK; + nsCOMPtr<nsISupports> data; + rv = item->GetTransferData(kURLMime, + getter_AddRefs(data), + &tmpDataLen); + + if (NS_SUCCEEDED(rv)) { + nsPrimitiveHelpers::CreateDataFromPrimitive(kURLMime, + data, + &tmpData, + tmpDataLen); + char* plainTextData = nullptr; + char16_t* castedUnicode = reinterpret_cast<char16_t*> + (tmpData); + uint32_t plainTextLen = 0; + UTF16ToNewUTF8(castedUnicode, + tmpDataLen / 2, + &plainTextData, + &plainTextLen); + if (plainTextData) { + uint32_t j; + + // text/x-moz-url is of form url + "\n" + title. + // We just want the url. + for (j = 0; j < plainTextLen; j++) + if (plainTextData[j] == '\n' || + plainTextData[j] == '\r') { + plainTextData[j] = '\0'; + break; + } + g_string_append(uriList, plainTextData); + g_string_append(uriList, "\r\n"); + // this wasn't allocated with glib + free(plainTextData); + } + if (tmpData) { + // this wasn't allocated with glib + free(tmpData); + } + } else { + // There is no uri available. If there is a file available, + // create a uri from the file. + nsCOMPtr<nsISupports> data; + rv = item->GetTransferData(kFileMime, + getter_AddRefs(data), + &tmpDataLen); + if (NS_SUCCEEDED(rv)) { + nsCOMPtr<nsIFile> file = do_QueryInterface(data); + if (!file) { + // Sometimes the file is wrapped in a + // nsISupportsInterfacePointer. See bug 1310193 for + // removing this distinction. + nsCOMPtr<nsISupportsInterfacePointer> ptr = + do_QueryInterface(data); + if (ptr) { + ptr->GetData(getter_AddRefs(data)); + file = do_QueryInterface(data); + } + } + + if (file) { + nsCOMPtr<nsIURI> fileURI; + NS_NewFileURI(getter_AddRefs(fileURI), file); + if (fileURI) { + nsAutoCString uristring; + fileURI->GetSpec(uristring); + g_string_append(uriList, uristring.get()); + g_string_append(uriList, "\r\n"); + } + } + } + } + } + } + *text = uriList->str; + *length = uriList->len + 1; + g_string_free(uriList, FALSE); // don't free the data +} + + +void +nsDragService::SourceDataGet(GtkWidget *aWidget, + GdkDragContext *aContext, + GtkSelectionData *aSelectionData, + guint32 aTime) +{ + MOZ_LOG(sDragLm, LogLevel::Debug, ("nsDragService::SourceDataGet")); + GdkAtom target = gtk_selection_data_get_target(aSelectionData); + nsXPIDLCString mimeFlavor; + gchar *typeName = 0; + typeName = gdk_atom_name(target); + if (!typeName) { + MOZ_LOG(sDragLm, LogLevel::Debug, ("failed to get atom name.\n")); + return; + } + + MOZ_LOG(sDragLm, LogLevel::Debug, ("Type is %s\n", typeName)); + // make a copy since |nsXPIDLCString| won't use |g_free|... + mimeFlavor.Adopt(strdup(typeName)); + g_free(typeName); + // check to make sure that we have data items to return. + if (!mSourceDataItems) { + MOZ_LOG(sDragLm, LogLevel::Debug, ("Failed to get our data items\n")); + return; + } + + nsCOMPtr<nsITransferable> item; + item = do_QueryElementAt(mSourceDataItems, 0); + if (item) { + // if someone was asking for text/plain, lookup unicode instead so + // we can convert it. + bool needToDoConversionToPlainText = false; + const char* actualFlavor = mimeFlavor; + if (strcmp(mimeFlavor, kTextMime) == 0 || + strcmp(mimeFlavor, gTextPlainUTF8Type) == 0) { + actualFlavor = kUnicodeMime; + needToDoConversionToPlainText = true; + } + // if someone was asking for _NETSCAPE_URL we need to convert to + // plain text but we also need to look for x-moz-url + else if (strcmp(mimeFlavor, gMozUrlType) == 0) { + actualFlavor = kURLMime; + needToDoConversionToPlainText = true; + } + // if someone was asking for text/uri-list we need to convert to + // plain text. + else if (strcmp(mimeFlavor, gTextUriListType) == 0) { + actualFlavor = gTextUriListType; + needToDoConversionToPlainText = true; + } + else + actualFlavor = mimeFlavor; + + uint32_t tmpDataLen = 0; + void *tmpData = nullptr; + nsresult rv; + nsCOMPtr<nsISupports> data; + rv = item->GetTransferData(actualFlavor, + getter_AddRefs(data), + &tmpDataLen); + if (NS_SUCCEEDED(rv)) { + nsPrimitiveHelpers::CreateDataFromPrimitive (actualFlavor, data, + &tmpData, tmpDataLen); + // if required, do the extra work to convert unicode to plain + // text and replace the output values with the plain text. + if (needToDoConversionToPlainText) { + char* plainTextData = nullptr; + char16_t* castedUnicode = reinterpret_cast<char16_t*> + (tmpData); + uint32_t plainTextLen = 0; + UTF16ToNewUTF8(castedUnicode, + tmpDataLen / 2, + &plainTextData, + &plainTextLen); + if (tmpData) { + // this was not allocated using glib + free(tmpData); + tmpData = plainTextData; + tmpDataLen = plainTextLen; + } + } + if (tmpData) { + // this copies the data + gtk_selection_data_set(aSelectionData, target, + 8, + (guchar *)tmpData, tmpDataLen); + // this wasn't allocated with glib + free(tmpData); + } + } else { + if (strcmp(mimeFlavor, gTextUriListType) == 0) { + // fall back for text/uri-list + gchar *uriList; + gint length; + CreateUriList(mSourceDataItems, &uriList, &length); + gtk_selection_data_set(aSelectionData, target, + 8, (guchar *)uriList, length); + g_free(uriList); + return; + } + } + } +} + +void nsDragService::SetDragIcon(GdkDragContext* aContext) +{ + if (!mHasImage && !mSelection) + return; + + LayoutDeviceIntRect dragRect; + nsPresContext* pc; + RefPtr<SourceSurface> surface; + DrawDrag(mSourceNode, mSourceRegion, mScreenPosition, + &dragRect, &surface, &pc); + if (!pc) + return; + + LayoutDeviceIntPoint screenPoint = + ConvertToUnscaledDevPixels(pc, mScreenPosition); + int32_t offsetX = screenPoint.x - dragRect.x; + int32_t offsetY = screenPoint.y - dragRect.y; + + // If a popup is set as the drag image, use its widget. Otherwise, use + // the surface that DrawDrag created. + // + // XXX: Disable drag popups on GTK 3.19.4 and above: see bug 1264454. + // Fix this once a new GTK version ships that does not destroy our + // widget in gtk_drag_set_icon_widget. + if (mDragPopup && gtk_check_version(3, 19, 4)) { + GtkWidget* gtkWidget = nullptr; + nsIFrame* frame = mDragPopup->GetPrimaryFrame(); + if (frame) { + // DrawDrag ensured that this is a popup frame. + nsCOMPtr<nsIWidget> widget = frame->GetNearestWidget(); + if (widget) { + gtkWidget = (GtkWidget *)widget->GetNativeData(NS_NATIVE_SHELLWIDGET); + if (gtkWidget) { + OpenDragPopup(); + gtk_drag_set_icon_widget(aContext, gtkWidget, offsetX, offsetY); + } + } + } + } + else if (surface) { + if (!SetAlphaPixmap(surface, aContext, offsetX, offsetY, dragRect)) { + GdkPixbuf* dragPixbuf = + nsImageToPixbuf::SourceSurfaceToPixbuf(surface, dragRect.width, dragRect.height); + if (dragPixbuf) { + gtk_drag_set_icon_pixbuf(aContext, dragPixbuf, offsetX, offsetY); + g_object_unref(dragPixbuf); + } + } + } +} + +static void +invisibleSourceDragBegin(GtkWidget *aWidget, + GdkDragContext *aContext, + gpointer aData) +{ + MOZ_LOG(sDragLm, LogLevel::Debug, ("invisibleSourceDragBegin")); + nsDragService *dragService = (nsDragService *)aData; + + dragService->SetDragIcon(aContext); +} + +static void +invisibleSourceDragDataGet(GtkWidget *aWidget, + GdkDragContext *aContext, + GtkSelectionData *aSelectionData, + guint aInfo, + guint32 aTime, + gpointer aData) +{ + MOZ_LOG(sDragLm, LogLevel::Debug, ("invisibleSourceDragDataGet")); + nsDragService *dragService = (nsDragService *)aData; + dragService->SourceDataGet(aWidget, aContext, + aSelectionData, aTime); +} + +static gboolean +invisibleSourceDragFailed(GtkWidget *aWidget, + GdkDragContext *aContext, + gint aResult, + gpointer aData) +{ + MOZ_LOG(sDragLm, LogLevel::Debug, ("invisibleSourceDragFailed %i", aResult)); + nsDragService *dragService = (nsDragService *)aData; + // End the drag session now (rather than waiting for the drag-end signal) + // so that operations performed on dropEffect == none can start immediately + // rather than waiting for the drag-failed animation to finish. + dragService->SourceEndDragSession(aContext, aResult); + + // We should return TRUE to disable the drag-failed animation iff the + // source performed an operation when dropEffect was none, but the handler + // of the dragend DOM event doesn't provide this information. + return FALSE; +} + +static void +invisibleSourceDragEnd(GtkWidget *aWidget, + GdkDragContext *aContext, + gpointer aData) +{ + MOZ_LOG(sDragLm, LogLevel::Debug, ("invisibleSourceDragEnd")); + nsDragService *dragService = (nsDragService *)aData; + + // The drag has ended. Release the hostages! + dragService->SourceEndDragSession(aContext, MOZ_GTK_DRAG_RESULT_SUCCESS); +} + +// The following methods handle responding to GTK drag signals and +// tracking state between these signals. +// +// In general, GTK does not expect us to run the event loop while handling its +// drag signals, however our drag event handlers may run the +// event loop, most often to fetch information about the drag data. +// +// GTK, for example, uses the return value from drag-motion signals to +// determine whether drag-leave signals should be sent. If an event loop is +// run during drag-motion the XdndLeave message can get processed but when GTK +// receives the message it does not yet know that it needs to send the +// drag-leave signal to our widget. +// +// After a drag-drop signal, we need to reply with gtk_drag_finish(). +// However, gtk_drag_finish should happen after the drag-drop signal handler +// returns so that when the Motif drag protocol is used, the +// XmTRANSFER_SUCCESS during gtk_drag_finish is sent after the XmDROP_START +// reply sent on return from the drag-drop signal handler. +// +// Similarly drag-end for a successful drag and drag-failed are not good +// times to run a nested event loop as gtk_drag_drop_finished() and +// gtk_drag_source_info_destroy() don't gtk_drag_clear_source_info() or remove +// drop_timeout until after at least the first of these signals is sent. +// Processing other events (e.g. a slow GDK_DROP_FINISHED reply, or the drop +// timeout) could cause gtk_drag_drop_finished to be called again with the +// same GtkDragSourceInfo, which won't like being destroyed twice. +// +// Therefore we reply to the signals immediately and schedule a task to +// dispatch the Gecko events, which may run the event loop. +// +// Action in response to drag-leave signals is also delayed until the event +// loop runs again so that we find out whether a drag-drop signal follows. +// +// A single task is scheduled to manage responses to all three GTK signals. +// If further signals are received while the task is scheduled, the scheduled +// response is updated, sometimes effectively compressing successive signals. +// +// No Gecko drag events are dispatched (during nested event loops) while other +// Gecko drag events are in flight. This helps event handlers that may not +// expect nested events, while accessing an event's dataTransfer for example. + +gboolean +nsDragService::ScheduleMotionEvent(nsWindow *aWindow, + GdkDragContext *aDragContext, + LayoutDeviceIntPoint aWindowPoint, guint aTime) +{ + if (mScheduledTask == eDragTaskMotion) { + // The drag source has sent another motion message before we've + // replied to the previous. That shouldn't happen with Xdnd. The + // spec for Motif drags is less clear, but we'll just update the + // scheduled task with the new position reply only to the most + // recent message. + NS_WARNING("Drag Motion message received before previous reply was sent"); + } + + // Returning TRUE means we'll reply with a status message, unless we first + // get a leave. + return Schedule(eDragTaskMotion, aWindow, aDragContext, + aWindowPoint, aTime); +} + +void +nsDragService::ScheduleLeaveEvent() +{ + // We don't know at this stage whether a drop signal will immediately + // follow. If the drop signal gets sent it will happen before we return + // to the main loop and the scheduled leave task will be replaced. + if (!Schedule(eDragTaskLeave, nullptr, nullptr, LayoutDeviceIntPoint(), 0)) { + NS_WARNING("Drag leave after drop"); + } +} + +gboolean +nsDragService::ScheduleDropEvent(nsWindow *aWindow, + GdkDragContext *aDragContext, + LayoutDeviceIntPoint aWindowPoint, guint aTime) +{ + if (!Schedule(eDragTaskDrop, aWindow, + aDragContext, aWindowPoint, aTime)) { + NS_WARNING("Additional drag drop ignored"); + return FALSE; + } + + SetDragEndPoint(aWindowPoint + aWindow->WidgetToScreenOffset()); + + // We'll reply with gtk_drag_finish(). + return TRUE; +} + +gboolean +nsDragService::Schedule(DragTask aTask, nsWindow *aWindow, + GdkDragContext *aDragContext, + LayoutDeviceIntPoint aWindowPoint, guint aTime) +{ + // If there is an existing leave or motion task scheduled, then that + // will be replaced. When the new task is run, it will dispatch + // any necessary leave or motion events. + + // If aTask is eDragTaskSourceEnd, then it will replace even a scheduled + // drop event (which could happen if the drop event has not been processed + // within the allowed time). Otherwise, if we haven't yet run a scheduled + // drop or end task, just say that we are not ready to receive another + // drop. + if (mScheduledTask == eDragTaskSourceEnd || + (mScheduledTask == eDragTaskDrop && aTask != eDragTaskSourceEnd)) + return FALSE; + + mScheduledTask = aTask; + mPendingWindow = aWindow; + mPendingDragContext = aDragContext; + mPendingWindowPoint = aWindowPoint; + mPendingTime = aTime; + + if (!mTaskSource) { + // High priority is used here because the native events involved have + // already waited at default priority. Perhaps a lower than default + // priority could be used for motion tasks because there is a chance + // that a leave or drop is waiting, but managing different priorities + // may not be worth the effort. Motion tasks shouldn't queue up as + // they should be throttled based on replies. + mTaskSource = g_idle_add_full(G_PRIORITY_HIGH, TaskDispatchCallback, + this, nullptr); + } + return TRUE; +} + +gboolean +nsDragService::TaskDispatchCallback(gpointer data) +{ + RefPtr<nsDragService> dragService = static_cast<nsDragService*>(data); + return dragService->RunScheduledTask(); +} + +gboolean +nsDragService::RunScheduledTask() +{ + if (mTargetWindow && mTargetWindow != mPendingWindow) { + MOZ_LOG(sDragLm, LogLevel::Debug, + ("nsDragService: dispatch drag leave (%p)\n", + mTargetWindow.get())); + mTargetWindow->DispatchDragEvent(eDragExit, mTargetWindowPoint, 0); + + if (!mSourceNode) { + // The drag that was initiated in a different app. End the drag + // session, since we're done with it for now (until the user drags + // back into this app). + EndDragSession(false); + } + } + + // It is possible that the pending state has been updated during dispatch + // of the leave event. That's fine. + + // Now we collect the pending state because, from this point on, we want + // to use the same state for all events dispatched. All state is updated + // so that when other tasks are scheduled during dispatch here, this + // task is considered to have already been run. + bool positionHasChanged = + mPendingWindow != mTargetWindow || + mPendingWindowPoint != mTargetWindowPoint; + DragTask task = mScheduledTask; + mScheduledTask = eDragTaskNone; + mTargetWindow = mPendingWindow.forget(); + mTargetWindowPoint = mPendingWindowPoint; + + if (task == eDragTaskLeave || task == eDragTaskSourceEnd) { + if (task == eDragTaskSourceEnd) { + // Dispatch drag end events. + EndDragSession(true); + } + + // Nothing more to do + // Returning false removes the task source from the event loop. + mTaskSource = 0; + return FALSE; + } + + // This may be the start of a destination drag session. + StartDragSession(); + + // mTargetWidget may be nullptr if the window has been destroyed. + // (The leave event is not scheduled if a drop task is still scheduled.) + // We still reply appropriately to indicate that the drop will or didn't + // succeeed. + mTargetWidget = mTargetWindow->GetMozContainerWidget(); + mTargetDragContext.steal(mPendingDragContext); + mTargetTime = mPendingTime; + + // http://www.whatwg.org/specs/web-apps/current-work/multipage/dnd.html#drag-and-drop-processing-model + // (as at 27 December 2010) indicates that a "drop" event should only be + // fired (at the current target element) if the current drag operation is + // not none. The current drag operation will only be set to a non-none + // value during a "dragover" event. + // + // If the user has ended the drag before any dragover events have been + // sent, then the spec recommends skipping the drop (because the current + // drag operation is none). However, here we assume that, by releasing + // the mouse button, the user has indicated that they want to drop, so we + // proceed with the drop where possible. + // + // In order to make the events appear to content in the same way as if the + // spec is being followed we make sure to dispatch a "dragover" event with + // appropriate coordinates and check canDrop before the "drop" event. + // + // When the Xdnd protocol is used for source/destination communication (as + // should be the case with GTK source applications) a dragover event + // should have already been sent during the drag-motion signal, which + // would have already been received because XdndDrop messages do not + // contain a position. However, we can't assume the same when the Motif + // protocol is used. + if (task == eDragTaskMotion || positionHasChanged) { + UpdateDragAction(); + TakeDragEventDispatchedToChildProcess(); // Clear the old value. + DispatchMotionEvents(); + if (task == eDragTaskMotion) { + if (TakeDragEventDispatchedToChildProcess()) { + mTargetDragContextForRemote = mTargetDragContext; + } else { + // Reply to tell the source whether we can drop and what + // action would be taken. + ReplyToDragMotion(mTargetDragContext); + } + } + } + + if (task == eDragTaskDrop) { + gboolean success = DispatchDropEvent(); + + // Perhaps we should set the del parameter to TRUE when the drag + // action is move, but we don't know whether the data was successfully + // transferred. + gtk_drag_finish(mTargetDragContext, success, + /* del = */ FALSE, mTargetTime); + + // This drag is over, so clear out our reference to the previous + // window. + mTargetWindow = nullptr; + // Make sure to end the drag session. If this drag started in a + // different app, we won't get a drag_end signal to end it from. + EndDragSession(true); + } + + // We're done with the drag context. + mTargetWidget = nullptr; + mTargetDragContext = nullptr; + + // If we got another drag signal while running the sheduled task, that + // must have happened while running a nested event loop. Leave the task + // source on the event loop. + if (mScheduledTask != eDragTaskNone) + return TRUE; + + // We have no task scheduled. + // Returning false removes the task source from the event loop. + mTaskSource = 0; + return FALSE; +} + +// This will update the drag action based on the information in the +// drag context. Gtk gets this from a combination of the key settings +// and what the source is offering. + +void +nsDragService::UpdateDragAction() +{ + // This doesn't look right. dragSession.dragAction is used by + // nsContentUtils::SetDataTransferInEvent() to set the initial + // dataTransfer.dropEffect, so GdkDragContext::suggested_action would be + // more appropriate. GdkDragContext::actions should be used to set + // dataTransfer.effectAllowed, which doesn't currently happen with + // external sources. + + // default is to do nothing + int action = nsIDragService::DRAGDROP_ACTION_NONE; + GdkDragAction gdkAction = gdk_drag_context_get_actions(mTargetDragContext); + + // set the default just in case nothing matches below + if (gdkAction & GDK_ACTION_DEFAULT) + action = nsIDragService::DRAGDROP_ACTION_MOVE; + + // first check to see if move is set + if (gdkAction & GDK_ACTION_MOVE) + action = nsIDragService::DRAGDROP_ACTION_MOVE; + + // then fall to the others + else if (gdkAction & GDK_ACTION_LINK) + action = nsIDragService::DRAGDROP_ACTION_LINK; + + // copy is ctrl + else if (gdkAction & GDK_ACTION_COPY) + action = nsIDragService::DRAGDROP_ACTION_COPY; + + // update the drag information + SetDragAction(action); +} + +NS_IMETHODIMP +nsDragService::UpdateDragEffect() +{ + if (mTargetDragContextForRemote) { + ReplyToDragMotion(mTargetDragContextForRemote); + mTargetDragContextForRemote = nullptr; + } + return NS_OK; +} + +void +nsDragService::DispatchMotionEvents() +{ + mCanDrop = false; + + FireDragEventAtSource(eDrag); + + mTargetWindow->DispatchDragEvent(eDragOver, mTargetWindowPoint, + mTargetTime); +} + +// Returns true if the drop was successful +gboolean +nsDragService::DispatchDropEvent() +{ + // We need to check IsDestroyed here because the nsRefPtr + // only protects this from being deleted, it does NOT protect + // against nsView::~nsView() calling Destroy() on it, bug 378273. + if (mTargetWindow->IsDestroyed()) + return FALSE; + + EventMessage msg = mCanDrop ? eDrop : eDragExit; + + mTargetWindow->DispatchDragEvent(msg, mTargetWindowPoint, mTargetTime); + + return mCanDrop; +} diff --git a/widget/gtk/nsDragService.h b/widget/gtk/nsDragService.h new file mode 100644 index 000000000..90c113106 --- /dev/null +++ b/widget/gtk/nsDragService.h @@ -0,0 +1,226 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim: set ts=4 et sw=4 tw=80: */ +/* 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/. */ + +#ifndef nsDragService_h__ +#define nsDragService_h__ + +#include "mozilla/RefPtr.h" +#include "nsBaseDragService.h" +#include "nsIObserver.h" +#include "nsAutoRef.h" +#include <gtk/gtk.h> + +class nsWindow; + +namespace mozilla { +namespace gfx { +class SourceSurface; +} +} + +#ifndef HAVE_NSGOBJECTREFTRAITS +#define HAVE_NSGOBJECTREFTRAITS +template <class T> +class nsGObjectRefTraits : public nsPointerRefTraits<T> { +public: + static void Release(T *aPtr) { g_object_unref(aPtr); } + static void AddRef(T *aPtr) { g_object_ref(aPtr); } +}; +#endif + +#ifndef HAVE_NSAUTOREFTRAITS_GTKWIDGET +#define HAVE_NSAUTOREFTRAITS_GTKWIDGET +template <> +class nsAutoRefTraits<GtkWidget> : public nsGObjectRefTraits<GtkWidget> { }; +#endif + +#ifndef HAVE_NSAUTOREFTRAITS_GDKDRAGCONTEXT +#define HAVE_NSAUTOREFTRAITS_GDKDRAGCONTEXT +template <> +class nsAutoRefTraits<GdkDragContext> : + public nsGObjectRefTraits<GdkDragContext> { }; +#endif + +/** + * Native GTK DragService wrapper + */ + +class nsDragService final : public nsBaseDragService, + public nsIObserver +{ +public: + nsDragService(); + + NS_DECL_ISUPPORTS_INHERITED + + NS_DECL_NSIOBSERVER + + // nsBaseDragService + virtual nsresult InvokeDragSessionImpl(nsIArray* anArrayTransferables, + nsIScriptableRegion* aRegion, + uint32_t aActionType) override; + // nsIDragService + NS_IMETHOD InvokeDragSession (nsIDOMNode *aDOMNode, + nsIArray * anArrayTransferables, + nsIScriptableRegion * aRegion, + uint32_t aActionType, + nsContentPolicyType aContentPolicyType) override; + NS_IMETHOD StartDragSession() override; + NS_IMETHOD EndDragSession(bool aDoneDrag) override; + + // nsIDragSession + NS_IMETHOD SetCanDrop (bool aCanDrop) override; + NS_IMETHOD GetCanDrop (bool *aCanDrop) override; + NS_IMETHOD GetNumDropItems (uint32_t * aNumItems) override; + NS_IMETHOD GetData (nsITransferable * aTransferable, + uint32_t aItemIndex) override; + NS_IMETHOD IsDataFlavorSupported (const char *aDataFlavor, + bool *_retval) override; + + NS_IMETHOD UpdateDragEffect() override; + + // Methods called from nsWindow to handle responding to GTK drag + // destination signals + + static nsDragService* GetInstance(); + + void TargetDataReceived (GtkWidget *aWidget, + GdkDragContext *aContext, + gint aX, + gint aY, + GtkSelectionData *aSelection_data, + guint aInfo, + guint32 aTime); + + gboolean ScheduleMotionEvent(nsWindow *aWindow, + GdkDragContext *aDragContext, + mozilla::LayoutDeviceIntPoint aWindowPoint, + guint aTime); + void ScheduleLeaveEvent(); + gboolean ScheduleDropEvent(nsWindow *aWindow, + GdkDragContext *aDragContext, + mozilla::LayoutDeviceIntPoint aWindowPoint, + guint aTime); + + nsWindow* GetMostRecentDestWindow() + { + return mScheduledTask == eDragTaskNone ? mTargetWindow + : mPendingWindow; + } + + // END PUBLIC API + + // These methods are public only so that they can be called from functions + // with C calling conventions. They are called for drags started with the + // invisible widget. + void SourceEndDragSession(GdkDragContext *aContext, + gint aResult); + void SourceDataGet(GtkWidget *widget, + GdkDragContext *context, + GtkSelectionData *selection_data, + guint32 aTime); + + // set the drag icon during drag-begin + void SetDragIcon(GdkDragContext* aContext); + +protected: + virtual ~nsDragService(); + +private: + + // mScheduledTask indicates what signal has been received from GTK and + // so what needs to be dispatched when the scheduled task is run. It is + // eDragTaskNone when there is no task scheduled (but the + // previous task may still not have finished running). + enum DragTask { + eDragTaskNone, + eDragTaskMotion, + eDragTaskLeave, + eDragTaskDrop, + eDragTaskSourceEnd + }; + DragTask mScheduledTask; + // mTaskSource is the GSource id for the task that is either scheduled + // or currently running. It is 0 if no task is scheduled or running. + guint mTaskSource; + + // target/destination side vars + // These variables keep track of the state of the current drag. + + // mPendingWindow, mPendingWindowPoint, mPendingDragContext, and + // mPendingTime, carry information from the GTK signal that will be used + // when the scheduled task is run. mPendingWindow and mPendingDragContext + // will be nullptr if the scheduled task is eDragTaskLeave. + RefPtr<nsWindow> mPendingWindow; + mozilla::LayoutDeviceIntPoint mPendingWindowPoint; + nsCountedRef<GdkDragContext> mPendingDragContext; + guint mPendingTime; + + // mTargetWindow and mTargetWindowPoint record the position of the last + // eDragTaskMotion or eDragTaskDrop task that was run or is still running. + // mTargetWindow is cleared once the drag has completed or left. + RefPtr<nsWindow> mTargetWindow; + mozilla::LayoutDeviceIntPoint mTargetWindowPoint; + // mTargetWidget and mTargetDragContext are set only while dispatching + // motion or drop events. mTime records the corresponding timestamp. + nsCountedRef<GtkWidget> mTargetWidget; + nsCountedRef<GdkDragContext> mTargetDragContext; + // mTargetDragContextForRemote is set while waiting for a reply from + // a child process. + nsCountedRef<GdkDragContext> mTargetDragContextForRemote; + guint mTargetTime; + + // is it OK to drop on us? + bool mCanDrop; + + // have we received our drag data? + bool mTargetDragDataReceived; + // last data received and its length + void *mTargetDragData; + uint32_t mTargetDragDataLen; + // is the current target drag context contain a list? + bool IsTargetContextList(void); + // this will get the native data from the last target given a + // specific flavor + void GetTargetDragData(GdkAtom aFlavor); + // this will reset all of the target vars + void TargetResetData(void); + + // source side vars + + // the source of our drags + GtkWidget *mHiddenWidget; + // our source data items + nsCOMPtr<nsIArray> mSourceDataItems; + + nsCOMPtr<nsIScriptableRegion> mSourceRegion; + + // get a list of the sources in gtk's format + GtkTargetList *GetSourceList(void); + + // attempts to create a semi-transparent drag image. Returns TRUE if + // successful, FALSE if not + bool SetAlphaPixmap(SourceSurface *aPixbuf, + GdkDragContext *aContext, + int32_t aXOffset, + int32_t aYOffset, + const mozilla::LayoutDeviceIntRect &dragRect); + + gboolean Schedule(DragTask aTask, nsWindow *aWindow, + GdkDragContext *aDragContext, + mozilla::LayoutDeviceIntPoint aWindowPoint, guint aTime); + + // Callback for g_idle_add_full() to run mScheduledTask. + static gboolean TaskDispatchCallback(gpointer data); + gboolean RunScheduledTask(); + void UpdateDragAction(); + void DispatchMotionEvents(); + void ReplyToDragMotion(GdkDragContext* aDragContext); + gboolean DispatchDropEvent(); +}; + +#endif // nsDragService_h__ + diff --git a/widget/gtk/nsFilePicker.cpp b/widget/gtk/nsFilePicker.cpp new file mode 100644 index 000000000..172cb4444 --- /dev/null +++ b/widget/gtk/nsFilePicker.cpp @@ -0,0 +1,610 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/Types.h" +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> + +#include <gtk/gtk.h> + +#include "nsGtkUtils.h" +#include "nsIFileURL.h" +#include "nsIURI.h" +#include "nsIWidget.h" +#include "nsIFile.h" +#include "nsIStringBundle.h" + +#include "nsArrayEnumerator.h" +#include "nsMemory.h" +#include "nsEnumeratorUtils.h" +#include "nsNetUtil.h" +#include "nsReadableUtils.h" +#include "mozcontainer.h" + +#include "nsFilePicker.h" + +using namespace mozilla; + +#define MAX_PREVIEW_SIZE 180 +// bug 1184009 +#define MAX_PREVIEW_SOURCE_SIZE 4096 + +nsIFile *nsFilePicker::mPrevDisplayDirectory = nullptr; + +void +nsFilePicker::Shutdown() +{ + NS_IF_RELEASE(mPrevDisplayDirectory); +} + +static GtkFileChooserAction +GetGtkFileChooserAction(int16_t aMode) +{ + GtkFileChooserAction action; + + switch (aMode) { + case nsIFilePicker::modeSave: + action = GTK_FILE_CHOOSER_ACTION_SAVE; + break; + + case nsIFilePicker::modeGetFolder: + action = GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER; + break; + + case nsIFilePicker::modeOpen: + case nsIFilePicker::modeOpenMultiple: + action = GTK_FILE_CHOOSER_ACTION_OPEN; + break; + + default: + NS_WARNING("Unknown nsIFilePicker mode"); + action = GTK_FILE_CHOOSER_ACTION_OPEN; + break; + } + + return action; +} + + +static void +UpdateFilePreviewWidget(GtkFileChooser *file_chooser, + gpointer preview_widget_voidptr) +{ + GtkImage *preview_widget = GTK_IMAGE(preview_widget_voidptr); + char *image_filename = gtk_file_chooser_get_preview_filename(file_chooser); + struct stat st_buf; + + if (!image_filename) { + gtk_file_chooser_set_preview_widget_active(file_chooser, FALSE); + return; + } + + gint preview_width = 0; + gint preview_height = 0; + /* check type of file + * if file is named pipe, Open is blocking which may lead to UI + * nonresponsiveness; if file is directory/socket, it also isn't + * likely to get preview */ + if (stat(image_filename, &st_buf) || (!S_ISREG(st_buf.st_mode))) { + g_free(image_filename); + gtk_file_chooser_set_preview_widget_active(file_chooser, FALSE); + return; /* stat failed or file is not regular */ + } + + GdkPixbufFormat *preview_format = gdk_pixbuf_get_file_info(image_filename, + &preview_width, + &preview_height); + if (!preview_format || + preview_width <= 0 || preview_height <= 0 || + preview_width > MAX_PREVIEW_SOURCE_SIZE || + preview_height > MAX_PREVIEW_SOURCE_SIZE) { + g_free(image_filename); + gtk_file_chooser_set_preview_widget_active(file_chooser, FALSE); + return; + } + + GdkPixbuf *preview_pixbuf = nullptr; + // Only scale down images that are too big + if (preview_width > MAX_PREVIEW_SIZE || preview_height > MAX_PREVIEW_SIZE) { + preview_pixbuf = gdk_pixbuf_new_from_file_at_size(image_filename, + MAX_PREVIEW_SIZE, + MAX_PREVIEW_SIZE, + nullptr); + } + else { + preview_pixbuf = gdk_pixbuf_new_from_file(image_filename, nullptr); + } + + g_free(image_filename); + + if (!preview_pixbuf) { + gtk_file_chooser_set_preview_widget_active(file_chooser, FALSE); + return; + } + + GdkPixbuf *preview_pixbuf_temp = preview_pixbuf; + preview_pixbuf = gdk_pixbuf_apply_embedded_orientation(preview_pixbuf_temp); + g_object_unref(preview_pixbuf_temp); + + // This is the easiest way to do center alignment without worrying about containers + // Minimum 3px padding each side (hence the 6) just to make things nice + gint x_padding = (MAX_PREVIEW_SIZE + 6 - gdk_pixbuf_get_width(preview_pixbuf)) / 2; + gtk_misc_set_padding(GTK_MISC(preview_widget), x_padding, 0); + + gtk_image_set_from_pixbuf(preview_widget, preview_pixbuf); + g_object_unref(preview_pixbuf); + gtk_file_chooser_set_preview_widget_active(file_chooser, TRUE); +} + +static nsAutoCString +MakeCaseInsensitiveShellGlob(const char* aPattern) { + // aPattern is UTF8 + nsAutoCString result; + unsigned int len = strlen(aPattern); + + for (unsigned int i = 0; i < len; i++) { + if (!g_ascii_isalpha(aPattern[i])) { + // non-ASCII characters will also trigger this path, so unicode + // is safely handled albeit case-sensitively + result.Append(aPattern[i]); + continue; + } + + // add the lowercase and uppercase version of a character to a bracket + // match, so it matches either the lowercase or uppercase char. + result.Append('['); + result.Append(g_ascii_tolower(aPattern[i])); + result.Append(g_ascii_toupper(aPattern[i])); + result.Append(']'); + + } + + return result; +} + +NS_IMPL_ISUPPORTS(nsFilePicker, nsIFilePicker) + +nsFilePicker::nsFilePicker() + : mSelectedType(0) + , mRunning(false) + , mAllowURLs(false) +#if (MOZ_WIDGET_GTK == 3) + , mFileChooserDelegate(nullptr) +#endif +{ +} + +nsFilePicker::~nsFilePicker() +{ +} + +void +ReadMultipleFiles(gpointer filename, gpointer array) +{ + nsCOMPtr<nsIFile> localfile; + nsresult rv = NS_NewNativeLocalFile(nsDependentCString(static_cast<char*>(filename)), + false, + getter_AddRefs(localfile)); + if (NS_SUCCEEDED(rv)) { + nsCOMArray<nsIFile>& files = *static_cast<nsCOMArray<nsIFile>*>(array); + files.AppendObject(localfile); + } + + g_free(filename); +} + +void +nsFilePicker::ReadValuesFromFileChooser(GtkWidget *file_chooser) +{ + mFiles.Clear(); + + if (mMode == nsIFilePicker::modeOpenMultiple) { + mFileURL.Truncate(); + + GSList *list = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(file_chooser)); + g_slist_foreach(list, ReadMultipleFiles, static_cast<gpointer>(&mFiles)); + g_slist_free(list); + } else { + gchar *filename = gtk_file_chooser_get_uri(GTK_FILE_CHOOSER(file_chooser)); + mFileURL.Assign(filename); + g_free(filename); + } + + GtkFileFilter *filter = gtk_file_chooser_get_filter(GTK_FILE_CHOOSER(file_chooser)); + GSList *filter_list = gtk_file_chooser_list_filters(GTK_FILE_CHOOSER(file_chooser)); + + mSelectedType = static_cast<int16_t>(g_slist_index(filter_list, filter)); + g_slist_free(filter_list); + + // Remember last used directory. + nsCOMPtr<nsIFile> file; + GetFile(getter_AddRefs(file)); + if (file) { + nsCOMPtr<nsIFile> dir; + file->GetParent(getter_AddRefs(dir)); + if (dir) { + dir.swap(mPrevDisplayDirectory); + } + } +} + +void +nsFilePicker::InitNative(nsIWidget *aParent, + const nsAString& aTitle) +{ + mParentWidget = aParent; + mTitle.Assign(aTitle); +} + +NS_IMETHODIMP +nsFilePicker::AppendFilters(int32_t aFilterMask) +{ + mAllowURLs = !!(aFilterMask & filterAllowURLs); + return nsBaseFilePicker::AppendFilters(aFilterMask); +} + +NS_IMETHODIMP +nsFilePicker::AppendFilter(const nsAString& aTitle, const nsAString& aFilter) +{ + if (aFilter.EqualsLiteral("..apps")) { + // No platform specific thing we can do here, really.... + return NS_OK; + } + + nsAutoCString filter, name; + CopyUTF16toUTF8(aFilter, filter); + CopyUTF16toUTF8(aTitle, name); + + mFilters.AppendElement(filter); + mFilterNames.AppendElement(name); + + return NS_OK; +} + +NS_IMETHODIMP +nsFilePicker::SetDefaultString(const nsAString& aString) +{ + mDefault = aString; + + return NS_OK; +} + +NS_IMETHODIMP +nsFilePicker::GetDefaultString(nsAString& aString) +{ + // Per API... + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsFilePicker::SetDefaultExtension(const nsAString& aExtension) +{ + mDefaultExtension = aExtension; + + return NS_OK; +} + +NS_IMETHODIMP +nsFilePicker::GetDefaultExtension(nsAString& aExtension) +{ + aExtension = mDefaultExtension; + + return NS_OK; +} + +NS_IMETHODIMP +nsFilePicker::GetFilterIndex(int32_t *aFilterIndex) +{ + *aFilterIndex = mSelectedType; + + return NS_OK; +} + +NS_IMETHODIMP +nsFilePicker::SetFilterIndex(int32_t aFilterIndex) +{ + mSelectedType = aFilterIndex; + + return NS_OK; +} + +NS_IMETHODIMP +nsFilePicker::GetFile(nsIFile **aFile) +{ + NS_ENSURE_ARG_POINTER(aFile); + + *aFile = nullptr; + nsCOMPtr<nsIURI> uri; + nsresult rv = GetFileURL(getter_AddRefs(uri)); + if (!uri) + return rv; + + nsCOMPtr<nsIFileURL> fileURL(do_QueryInterface(uri, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIFile> file; + rv = fileURL->GetFile(getter_AddRefs(file)); + NS_ENSURE_SUCCESS(rv, rv); + + file.forget(aFile); + return NS_OK; +} + +NS_IMETHODIMP +nsFilePicker::GetFileURL(nsIURI **aFileURL) +{ + *aFileURL = nullptr; + return NS_NewURI(aFileURL, mFileURL); +} + +NS_IMETHODIMP +nsFilePicker::GetFiles(nsISimpleEnumerator **aFiles) +{ + NS_ENSURE_ARG_POINTER(aFiles); + + if (mMode == nsIFilePicker::modeOpenMultiple) { + return NS_NewArrayEnumerator(aFiles, mFiles); + } + + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsFilePicker::Show(int16_t *aReturn) +{ + NS_ENSURE_ARG_POINTER(aReturn); + + nsresult rv = Open(nullptr); + if (NS_FAILED(rv)) + return rv; + + while (mRunning) { + g_main_context_iteration(nullptr, TRUE); + } + + *aReturn = mResult; + return NS_OK; +} + +NS_IMETHODIMP +nsFilePicker::Open(nsIFilePickerShownCallback *aCallback) +{ + // Can't show two dialogs concurrently with the same filepicker + if (mRunning) + return NS_ERROR_NOT_AVAILABLE; + + nsXPIDLCString title; + title.Adopt(ToNewUTF8String(mTitle)); + + GtkWindow *parent_widget = + GTK_WINDOW(mParentWidget->GetNativeData(NS_NATIVE_SHELLWIDGET)); + + GtkFileChooserAction action = GetGtkFileChooserAction(mMode); + + const gchar* accept_button; + NS_ConvertUTF16toUTF8 buttonLabel(mOkButtonLabel); + if (!mOkButtonLabel.IsEmpty()) { + accept_button = buttonLabel.get(); + } else { + accept_button = (action == GTK_FILE_CHOOSER_ACTION_SAVE) ? + GTK_STOCK_SAVE : GTK_STOCK_OPEN; + } + + GtkWidget *file_chooser = + gtk_file_chooser_dialog_new(title, parent_widget, action, + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + accept_button, GTK_RESPONSE_ACCEPT, + nullptr); + gtk_dialog_set_alternative_button_order(GTK_DIALOG(file_chooser), + GTK_RESPONSE_ACCEPT, + GTK_RESPONSE_CANCEL, + -1); + if (mAllowURLs) { + gtk_file_chooser_set_local_only(GTK_FILE_CHOOSER(file_chooser), FALSE); + } + + if (action == GTK_FILE_CHOOSER_ACTION_OPEN || action == GTK_FILE_CHOOSER_ACTION_SAVE) { + GtkWidget *img_preview = gtk_image_new(); + gtk_file_chooser_set_preview_widget(GTK_FILE_CHOOSER(file_chooser), img_preview); + g_signal_connect(file_chooser, "update-preview", G_CALLBACK(UpdateFilePreviewWidget), img_preview); + } + + GtkWindow *window = GTK_WINDOW(file_chooser); + gtk_window_set_modal(window, TRUE); + if (parent_widget) { + gtk_window_set_destroy_with_parent(window, TRUE); + } + + NS_ConvertUTF16toUTF8 defaultName(mDefault); + switch (mMode) { + case nsIFilePicker::modeOpenMultiple: + gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(file_chooser), TRUE); + break; + case nsIFilePicker::modeSave: + gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(file_chooser), + defaultName.get()); + break; + } + + nsCOMPtr<nsIFile> defaultPath; + if (mDisplayDirectory) { + mDisplayDirectory->Clone(getter_AddRefs(defaultPath)); + } else if (mPrevDisplayDirectory) { + mPrevDisplayDirectory->Clone(getter_AddRefs(defaultPath)); + } + + if (defaultPath) { + if (!defaultName.IsEmpty() && mMode != nsIFilePicker::modeSave) { + // Try to select the intended file. Even if it doesn't exist, GTK still switches + // directories. + defaultPath->AppendNative(defaultName); + nsAutoCString path; + defaultPath->GetNativePath(path); + gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(file_chooser), path.get()); + } else { + nsAutoCString directory; + defaultPath->GetNativePath(directory); + +#if (MOZ_WIDGET_GTK == 3) + // Workaround for problematic refcounting in GTK3 before 3.16. + // We need to keep a reference to the dialog's internal delegate. + // Otherwise, if our dialog gets destroyed, we'll lose the dialog's + // delegate by the time this gets processed in the event loop. + // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1166741 + GtkDialog *dialog = GTK_DIALOG(file_chooser); + GtkContainer *area = GTK_CONTAINER(gtk_dialog_get_content_area(dialog)); + gtk_container_forall(area, [](GtkWidget *widget, + gpointer data) { + if (GTK_IS_FILE_CHOOSER_WIDGET(widget)) { + auto result = static_cast<GtkFileChooserWidget**>(data); + *result = GTK_FILE_CHOOSER_WIDGET(widget); + } + }, &mFileChooserDelegate); + + if (mFileChooserDelegate) + g_object_ref(mFileChooserDelegate); +#endif + + gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(file_chooser), + directory.get()); + } + } + + gtk_dialog_set_default_response(GTK_DIALOG(file_chooser), GTK_RESPONSE_ACCEPT); + + int32_t count = mFilters.Length(); + for (int32_t i = 0; i < count; ++i) { + // This is fun... the GTK file picker does not accept a list of filters + // so we need to split out each string, and add it manually. + + char **patterns = g_strsplit(mFilters[i].get(), ";", -1); + if (!patterns) { + return NS_ERROR_OUT_OF_MEMORY; + } + + GtkFileFilter *filter = gtk_file_filter_new(); + for (int j = 0; patterns[j] != nullptr; ++j) { + nsAutoCString caseInsensitiveFilter = MakeCaseInsensitiveShellGlob(g_strstrip(patterns[j])); + gtk_file_filter_add_pattern(filter, caseInsensitiveFilter.get()); + } + + g_strfreev(patterns); + + if (!mFilterNames[i].IsEmpty()) { + // If we have a name for our filter, let's use that. + const char *filter_name = mFilterNames[i].get(); + gtk_file_filter_set_name(filter, filter_name); + } else { + // If we don't have a name, let's just use the filter pattern. + const char *filter_pattern = mFilters[i].get(); + gtk_file_filter_set_name(filter, filter_pattern); + } + + gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(file_chooser), filter); + + // Set the initially selected filter + if (mSelectedType == i) { + gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(file_chooser), filter); + } + } + + gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(file_chooser), TRUE); + + mRunning = true; + mCallback = aCallback; + NS_ADDREF_THIS(); + g_signal_connect(file_chooser, "response", G_CALLBACK(OnResponse), this); + g_signal_connect(file_chooser, "destroy", G_CALLBACK(OnDestroy), this); + gtk_widget_show(file_chooser); + + return NS_OK; +} + +/* static */ void +nsFilePicker::OnResponse(GtkWidget* file_chooser, gint response_id, + gpointer user_data) +{ + static_cast<nsFilePicker*>(user_data)-> + Done(file_chooser, response_id); +} + +/* static */ void +nsFilePicker::OnDestroy(GtkWidget* file_chooser, gpointer user_data) +{ + static_cast<nsFilePicker*>(user_data)-> + Done(file_chooser, GTK_RESPONSE_CANCEL); +} + +void +nsFilePicker::Done(GtkWidget* file_chooser, gint response) +{ + mRunning = false; + + int16_t result; + switch (response) { + case GTK_RESPONSE_OK: + case GTK_RESPONSE_ACCEPT: + ReadValuesFromFileChooser(file_chooser); + result = nsIFilePicker::returnOK; + if (mMode == nsIFilePicker::modeSave) { + nsCOMPtr<nsIFile> file; + GetFile(getter_AddRefs(file)); + if (file) { + bool exists = false; + file->Exists(&exists); + if (exists) + result = nsIFilePicker::returnReplace; + } + } + break; + + case GTK_RESPONSE_CANCEL: + case GTK_RESPONSE_CLOSE: + case GTK_RESPONSE_DELETE_EVENT: + result = nsIFilePicker::returnCancel; + break; + + default: + NS_WARNING("Unexpected response"); + result = nsIFilePicker::returnCancel; + break; + } + + // A "response" signal won't be sent again but "destroy" will be. + g_signal_handlers_disconnect_by_func(file_chooser, + FuncToGpointer(OnDestroy), this); + + // When response_id is GTK_RESPONSE_DELETE_EVENT or when called from + // OnDestroy, the widget would be destroyed anyway but it is fine if + // gtk_widget_destroy is called more than once. gtk_widget_destroy has + // requests that any remaining references be released, but the reference + // count will not be decremented again if GtkWindow's reference has already + // been released. + gtk_widget_destroy(file_chooser); + +#if (MOZ_WIDGET_GTK == 3) + if (mFileChooserDelegate) { + // Properly deref our acquired reference. We call this after + // gtk_widget_destroy() to try and ensure that pending file info + // queries caused by updating the current folder have been cancelled. + // However, we do not know for certain when the callback will run after + // cancelled. + g_idle_add([](gpointer data) -> gboolean { + g_object_unref(data); + return G_SOURCE_REMOVE; + }, mFileChooserDelegate); + mFileChooserDelegate = nullptr; + } +#endif + + if (mCallback) { + mCallback->Done(result); + mCallback = nullptr; + } else { + mResult = result; + } + NS_RELEASE_THIS(); +} diff --git a/widget/gtk/nsFilePicker.h b/widget/gtk/nsFilePicker.h new file mode 100644 index 000000000..2b5042098 --- /dev/null +++ b/widget/gtk/nsFilePicker.h @@ -0,0 +1,82 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#ifndef nsFilePicker_h__ +#define nsFilePicker_h__ + +#include <gtk/gtk.h> + +#include "nsBaseFilePicker.h" +#include "nsString.h" +#include "nsTArray.h" +#include "nsCOMArray.h" + +class nsIWidget; +class nsIFile; + +class nsFilePicker : public nsBaseFilePicker +{ +public: + nsFilePicker(); + + NS_DECL_ISUPPORTS + + // nsIFilePicker (less what's in nsBaseFilePicker) + NS_IMETHOD Open(nsIFilePickerShownCallback *aCallback) override; + NS_IMETHOD AppendFilters(int32_t aFilterMask) override; + NS_IMETHOD AppendFilter(const nsAString& aTitle, + const nsAString& aFilter) override; + NS_IMETHOD SetDefaultString(const nsAString& aString) override; + NS_IMETHOD GetDefaultString(nsAString& aString) override; + NS_IMETHOD SetDefaultExtension(const nsAString& aExtension) override; + NS_IMETHOD GetDefaultExtension(nsAString& aExtension) override; + NS_IMETHOD GetFilterIndex(int32_t *aFilterIndex) override; + NS_IMETHOD SetFilterIndex(int32_t aFilterIndex) override; + NS_IMETHOD GetFile(nsIFile **aFile) override; + NS_IMETHOD GetFileURL(nsIURI **aFileURL) override; + NS_IMETHOD GetFiles(nsISimpleEnumerator **aFiles) override; + NS_IMETHOD Show(int16_t *aReturn) override; + + // nsBaseFilePicker + virtual void InitNative(nsIWidget *aParent, + const nsAString& aTitle) override; + + static void Shutdown(); + +protected: + virtual ~nsFilePicker(); + + void ReadValuesFromFileChooser(GtkWidget *file_chooser); + + static void OnResponse(GtkWidget* dialog, gint response_id, + gpointer user_data); + static void OnDestroy(GtkWidget* dialog, gpointer user_data); + void Done(GtkWidget* dialog, gint response_id); + + nsCOMPtr<nsIWidget> mParentWidget; + nsCOMPtr<nsIFilePickerShownCallback> mCallback; + nsCOMArray<nsIFile> mFiles; + + int16_t mSelectedType; + int16_t mResult; + bool mRunning; + bool mAllowURLs; + nsCString mFileURL; + nsString mTitle; + nsString mDefault; + nsString mDefaultExtension; + + nsTArray<nsCString> mFilters; + nsTArray<nsCString> mFilterNames; + +private: + static nsIFile *mPrevDisplayDirectory; + +#if (MOZ_WIDGET_GTK == 3) + GtkFileChooserWidget *mFileChooserDelegate; +#endif +}; + +#endif diff --git a/widget/gtk/nsGTKToolkit.h b/widget/gtk/nsGTKToolkit.h new file mode 100644 index 000000000..ae0d55b63 --- /dev/null +++ b/widget/gtk/nsGTKToolkit.h @@ -0,0 +1,54 @@ +/* -*- 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/. */ + +#ifndef GTKTOOLKIT_H +#define GTKTOOLKIT_H + +#include "nsString.h" +#include <gtk/gtk.h> + +/** + * Wrapper around the thread running the message pump. + * The toolkit abstraction is necessary because the message pump must + * execute within the same thread that created the widget under Win32. + */ + +class nsGTKToolkit +{ +public: + nsGTKToolkit(); + + static nsGTKToolkit* GetToolkit(); + + static void Shutdown() { + delete gToolkit; + gToolkit = nullptr; + } + + /** + * Get/set our value of DESKTOP_STARTUP_ID. When non-empty, this is applied + * to the next toplevel window to be shown or focused (and then immediately + * cleared). + */ + void SetDesktopStartupID(const nsACString& aID) { mDesktopStartupID = aID; } + void GetDesktopStartupID(nsACString* aID) { *aID = mDesktopStartupID; } + + /** + * Get/set the timestamp value to be used, if non-zero, to focus the + * next top-level window to be shown or focused (upon which it is cleared). + */ + void SetFocusTimestamp(uint32_t aTimestamp) { mFocusTimestamp = aTimestamp; } + uint32_t GetFocusTimestamp() { return mFocusTimestamp; } + +private: + static nsGTKToolkit* gToolkit; + + nsCString mDesktopStartupID; + uint32_t mFocusTimestamp; +}; + +#endif // GTKTOOLKIT_H diff --git a/widget/gtk/nsGtkCursors.h b/widget/gtk/nsGtkCursors.h new file mode 100644 index 000000000..b7df7f65e --- /dev/null +++ b/widget/gtk/nsGtkCursors.h @@ -0,0 +1,405 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:expandtab:shiftwidth=2:tabstop=2: + */ +/* 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/. */ + +#ifndef nsGtkCursors_h__ +#define nsGtkCursors_h__ + +typedef struct { + const unsigned char *bits; + const unsigned char *mask_bits; + int hot_x; + int hot_y; + const char *hash; +} nsGtkCursor; + +/* MOZ_CURSOR_HAND_GRAB */ +static const unsigned char moz_hand_grab_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, + 0x60, 0x39, 0x00, 0x00, 0x90, 0x49, 0x00, 0x00, 0x90, 0x49, 0x01, 0x00, + 0x20, 0xc9, 0x02, 0x00, 0x20, 0x49, 0x02, 0x00, 0x58, 0x40, 0x02, 0x00, + 0x64, 0x00, 0x02, 0x00, 0x44, 0x00, 0x01, 0x00, 0x08, 0x00, 0x01, 0x00, + 0x10, 0x00, 0x01, 0x00, 0x10, 0x80, 0x00, 0x00, 0x20, 0x80, 0x00, 0x00, + 0x40, 0x40, 0x00, 0x00, 0x80, 0x40, 0x00, 0x00, 0x80, 0x40, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + +static const unsigned char moz_hand_grab_mask_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x60, 0x3f, 0x00, 0x00, + 0xf0, 0x7f, 0x00, 0x00, 0xf8, 0xff, 0x01, 0x00, 0xf8, 0xff, 0x03, 0x00, + 0xf0, 0xff, 0x07, 0x00, 0xf8, 0xff, 0x07, 0x00, 0xfc, 0xff, 0x07, 0x00, + 0xfe, 0xff, 0x07, 0x00, 0xfe, 0xff, 0x03, 0x00, 0xfc, 0xff, 0x03, 0x00, + 0xf8, 0xff, 0x03, 0x00, 0xf8, 0xff, 0x01, 0x00, 0xf0, 0xff, 0x01, 0x00, + 0xe0, 0xff, 0x00, 0x00, 0xc0, 0xff, 0x00, 0x00, 0xc0, 0xff, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + +/* MOZ_CURSOR_HAND_GRABBING */ +static const unsigned char moz_hand_grabbing_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xc0, 0x36, 0x00, 0x00, 0x20, 0xc9, 0x00, 0x00, 0x20, 0x40, 0x01, 0x00, + 0x40, 0x00, 0x01, 0x00, 0x60, 0x00, 0x01, 0x00, 0x10, 0x00, 0x01, 0x00, + 0x10, 0x00, 0x01, 0x00, 0x10, 0x80, 0x00, 0x00, 0x20, 0x80, 0x00, 0x00, + 0x40, 0x40, 0x00, 0x00, 0x80, 0x40, 0x00, 0x00, 0x80, 0x40, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + +static const unsigned char moz_hand_grabbing_mask_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x36, 0x00, 0x00, + 0xe0, 0xff, 0x00, 0x00, 0xf0, 0xff, 0x01, 0x00, 0xf0, 0xff, 0x03, 0x00, + 0xe0, 0xff, 0x03, 0x00, 0xf0, 0xff, 0x03, 0x00, 0xf8, 0xff, 0x03, 0x00, + 0xf8, 0xff, 0x03, 0x00, 0xf8, 0xff, 0x01, 0x00, 0xf0, 0xff, 0x01, 0x00, + 0xe0, 0xff, 0x00, 0x00, 0xc0, 0xff, 0x00, 0x00, 0xc0, 0x7f, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + +/* MOZ_CURSOR_COPY */ +static const unsigned char moz_copy_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x0c, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00, + 0x7c, 0x00, 0x00, 0x00, 0xfc, 0x00, 0x00, 0x00, 0xfc, 0x01, 0x00, 0x00, + 0xfc, 0x03, 0x00, 0x00, 0x7c, 0x30, 0x00, 0x00, 0x6c, 0x30, 0x00, 0x00, + 0xc4, 0xfc, 0x00, 0x00, 0xc0, 0xfc, 0x00, 0x00, 0x80, 0x31, 0x00, 0x00, + 0x80, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + +static const unsigned char moz_copy_mask_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, + 0x1e, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00, 0x7e, 0x00, 0x00, 0x00, + 0xfe, 0x00, 0x00, 0x00, 0xfe, 0x01, 0x00, 0x00, 0xfe, 0x03, 0x00, 0x00, + 0xfe, 0x37, 0x00, 0x00, 0xfe, 0x7b, 0x00, 0x00, 0xfe, 0xfc, 0x00, 0x00, + 0xee, 0xff, 0x01, 0x00, 0xe4, 0xff, 0x01, 0x00, 0xc0, 0xff, 0x00, 0x00, + 0xc0, 0x7b, 0x00, 0x00, 0x80, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + +/* MOZ_CURSOR_ALIAS */ +static const unsigned char moz_alias_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x0c, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00, + 0x7c, 0x00, 0x00, 0x00, 0xfc, 0x00, 0x00, 0x00, 0xfc, 0x01, 0x00, 0x00, + 0xfc, 0x03, 0x00, 0x00, 0x7c, 0xf0, 0x00, 0x00, 0x6c, 0xe0, 0x00, 0x00, + 0xc4, 0xf0, 0x00, 0x00, 0xc0, 0xb0, 0x00, 0x00, 0x80, 0x19, 0x00, 0x00, + 0x80, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + +static const unsigned char moz_alias_mask_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, + 0x1e, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00, 0x7e, 0x00, 0x00, 0x00, + 0xfe, 0x00, 0x00, 0x00, 0xfe, 0x01, 0x00, 0x00, 0xfe, 0x03, 0x00, 0x00, + 0xfe, 0xf7, 0x00, 0x00, 0xfe, 0xfb, 0x01, 0x00, 0xfe, 0xf0, 0x01, 0x00, + 0xee, 0xf9, 0x01, 0x00, 0xe4, 0xf9, 0x01, 0x00, 0xc0, 0xbf, 0x00, 0x00, + 0xc0, 0x3f, 0x00, 0x00, 0x80, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + +/* MOZ_CURSOR_CONTEXT_MENU */ +static const unsigned char moz_menu_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x0c, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00, + 0x7c, 0x00, 0x00, 0x00, 0xfc, 0x00, 0x00, 0x00, 0xfc, 0xfd, 0x00, 0x00, + 0xfc, 0xff, 0x00, 0x00, 0x7c, 0x84, 0x00, 0x00, 0x6c, 0xfc, 0x00, 0x00, + 0xc4, 0x84, 0x00, 0x00, 0xc0, 0xfc, 0x00, 0x00, 0x80, 0x85, 0x00, 0x00, + 0x80, 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + +static const unsigned char moz_menu_mask_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, + 0x1e, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00, 0x7e, 0x00, 0x00, 0x00, + 0xfe, 0x00, 0x00, 0x00, 0xfe, 0xfd, 0x00, 0x00, 0xfe, 0xff, 0x01, 0x00, + 0xfe, 0xff, 0x01, 0x00, 0xfe, 0xff, 0x01, 0x00, 0xfe, 0xfe, 0x01, 0x00, + 0xee, 0xff, 0x01, 0x00, 0xe4, 0xff, 0x01, 0x00, 0xc0, 0xff, 0x01, 0x00, + 0xc0, 0xff, 0x01, 0x00, 0x80, 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + +/* MOZ_CURSOR_SPINNING */ +static const unsigned char moz_spinning_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x0c, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00, + 0x7c, 0x00, 0x00, 0x00, 0xfc, 0x00, 0x00, 0x00, 0xfc, 0x01, 0x00, 0x00, + 0xfc, 0x3b, 0x00, 0x00, 0x7c, 0x38, 0x00, 0x00, 0x6c, 0x54, 0x00, 0x00, + 0xc4, 0xdc, 0x00, 0x00, 0xc0, 0x44, 0x00, 0x00, 0x80, 0x39, 0x00, 0x00, + 0x80, 0x39, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + +static const unsigned char moz_spinning_mask_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, + 0x1e, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00, 0x7e, 0x00, 0x00, 0x00, + 0xfe, 0x00, 0x00, 0x00, 0xfe, 0x01, 0x00, 0x00, 0xfe, 0x3b, 0x00, 0x00, + 0xfe, 0x7f, 0x00, 0x00, 0xfe, 0x7f, 0x00, 0x00, 0xfe, 0xfe, 0x00, 0x00, + 0xee, 0xff, 0x01, 0x00, 0xe4, 0xff, 0x00, 0x00, 0xc0, 0x7f, 0x00, 0x00, + 0xc0, 0x7f, 0x00, 0x00, 0x80, 0x39, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + +/* MOZ_CURSOR_ZOOM_IN */ +static const unsigned char moz_zoom_in_bits[] = { + 0xf0, 0x00, 0x00, 0x00, 0x0c, 0x03, 0x00, 0x00, 0x02, 0x04, 0x00, 0x00, + 0x62, 0x04, 0x00, 0x00, 0x61, 0x08, 0x00, 0x00, 0xf9, 0x09, 0x00, 0x00, + 0xf9, 0x09, 0x00, 0x00, 0x61, 0x08, 0x00, 0x00, 0x62, 0x04, 0x00, 0x00, + 0x02, 0x04, 0x00, 0x00, 0x0c, 0x0f, 0x00, 0x00, 0xf0, 0x1c, 0x00, 0x00, + 0x00, 0x38, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, 0xe0, 0x00, 0x00, + 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + +static const unsigned char moz_zoom_in_mask_bits[] = { + 0xf0, 0x00, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0xfe, 0x07, 0x00, 0x00, + 0xfe, 0x07, 0x00, 0x00, 0xff, 0x0f, 0x00, 0x00, 0xff, 0x0f, 0x00, 0x00, + 0xff, 0x0f, 0x00, 0x00, 0xff, 0x0f, 0x00, 0x00, 0xfe, 0x07, 0x00, 0x00, + 0xfe, 0x07, 0x00, 0x00, 0xfc, 0x0f, 0x00, 0x00, 0xf0, 0x1c, 0x00, 0x00, + 0x00, 0x38, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, 0xe0, 0x00, 0x00, + 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + +/* MOZ_CURSOR_ZOOM_OUT */ +static const unsigned char moz_zoom_out_bits[] = { + 0xf0, 0x00, 0x00, 0x00, 0x0c, 0x03, 0x00, 0x00, 0x02, 0x04, 0x00, 0x00, + 0x02, 0x04, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0xf9, 0x09, 0x00, 0x00, + 0xf9, 0x09, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x02, 0x04, 0x00, 0x00, + 0x02, 0x04, 0x00, 0x00, 0x0c, 0x0f, 0x00, 0x00, 0xf0, 0x1c, 0x00, 0x00, + 0x00, 0x38, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, 0xe0, 0x00, 0x00, + 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + +static const unsigned char moz_zoom_out_mask_bits[] = { + 0xf0, 0x00, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0xfe, 0x07, 0x00, 0x00, + 0xfe, 0x07, 0x00, 0x00, 0xff, 0x0f, 0x00, 0x00, 0xff, 0x0f, 0x00, 0x00, + 0xff, 0x0f, 0x00, 0x00, 0xff, 0x0f, 0x00, 0x00, 0xfe, 0x07, 0x00, 0x00, + 0xfe, 0x07, 0x00, 0x00, 0xfc, 0x0f, 0x00, 0x00, 0xf0, 0x1c, 0x00, 0x00, + 0x00, 0x38, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, 0xe0, 0x00, 0x00, + 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + +/* MOZ_CURSOR_NOT_ALLOWED */ +static const unsigned char moz_not_allowed_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x80, 0x1f, 0x00, 0x00, 0xe0, 0x7f, 0x00, 0x00, + 0xf0, 0xf0, 0x00, 0x00, 0x38, 0xc0, 0x01, 0x00, 0x7c, 0x80, 0x03, 0x00, + 0xec, 0x00, 0x03, 0x00, 0xce, 0x01, 0x07, 0x00, 0x86, 0x03, 0x06, 0x00, + 0x06, 0x07, 0x06, 0x00, 0x06, 0x0e, 0x06, 0x00, 0x06, 0x1c, 0x06, 0x00, + 0x0e, 0x38, 0x07, 0x00, 0x0c, 0x70, 0x03, 0x00, 0x1c, 0xe0, 0x03, 0x00, + 0x38, 0xc0, 0x01, 0x00, 0xf0, 0xf0, 0x00, 0x00, 0xe0, 0x7f, 0x00, 0x00, + 0x80, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + +static const unsigned char moz_not_allowed_mask_bits[] = { + 0x80, 0x1f, 0x00, 0x00, 0xe0, 0x7f, 0x00, 0x00, 0xf0, 0xff, 0x00, 0x00, + 0xf8, 0xff, 0x01, 0x00, 0xfc, 0xf0, 0x03, 0x00, 0xfe, 0xc0, 0x07, 0x00, + 0xfe, 0x81, 0x07, 0x00, 0xff, 0x83, 0x0f, 0x00, 0xcf, 0x07, 0x0f, 0x00, + 0x8f, 0x0f, 0x0f, 0x00, 0x0f, 0x1f, 0x0f, 0x00, 0x0f, 0x3e, 0x0f, 0x00, + 0x1f, 0xfc, 0x0f, 0x00, 0x1e, 0xf8, 0x07, 0x00, 0x3e, 0xf0, 0x07, 0x00, + 0xfc, 0xf0, 0x03, 0x00, 0xf8, 0xff, 0x01, 0x00, 0xf0, 0xff, 0x00, 0x00, + 0xe0, 0x7f, 0x00, 0x00, 0x80, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + +/* MOZ_CURSOR_VERTICAL_TEXT */ +static const unsigned char moz_vertical_text_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x02, 0x40, 0x00, 0x00, 0x02, 0x40, 0x00, 0x00, + 0x06, 0x60, 0x00, 0x00, 0xfc, 0x3f, 0x00, 0x00, 0x06, 0x60, 0x00, 0x00, + 0x02, 0x40, 0x00, 0x00, 0x02, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + +static const unsigned char moz_vertical_text_mask_bits[] = { + 0x07, 0xe0, 0x00, 0x00, 0x07, 0xe0, 0x00, 0x00, 0x0f, 0xf0, 0x00, 0x00, + 0xff, 0xff, 0x00, 0x00, 0xfe, 0x7f, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, + 0x0f, 0xf0, 0x00, 0x00, 0x07, 0xe0, 0x00, 0x00, 0x07, 0xe0, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + +/* MOZ_CURSOR_NESW_RESIZE */ +static const unsigned char moz_nesw_resize_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0xc0, 0xff, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, + 0x00, 0xbe, 0x00, 0x00, 0x00, 0xbc, 0x00, 0x00, 0x00, 0xb8, 0x00, 0x00, + 0x02, 0xb4, 0x00, 0x00, 0x02, 0xa2, 0x00, 0x00, 0x02, 0x81, 0x00, 0x00, + 0x8a, 0x80, 0x00, 0x00, 0x5a, 0x80, 0x00, 0x00, 0x3a, 0x00, 0x00, 0x00, + 0x7a, 0x00, 0x00, 0x00, 0xfa, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0xfe, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + +static const unsigned char moz_nesw_resize_mask_bits[] = { + 0xc0, 0xff, 0x01, 0x00, 0xc0, 0xff, 0x01, 0x00, 0xc0, 0xff, 0x01, 0x00, + 0x00, 0xff, 0x01, 0x00, 0x00, 0xfe, 0x01, 0x00, 0x00, 0xfc, 0x01, 0x00, + 0x07, 0xfe, 0x01, 0x00, 0x07, 0xf7, 0x01, 0x00, 0x8f, 0xe3, 0x01, 0x00, + 0xdf, 0xc1, 0x01, 0x00, 0xff, 0xc0, 0x01, 0x00, 0x7f, 0x00, 0x00, 0x00, + 0xff, 0x00, 0x00, 0x00, 0xff, 0x01, 0x00, 0x00, 0xff, 0x07, 0x00, 0x00, + 0xff, 0x07, 0x00, 0x00, 0xff, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + +/* MOZ_CURSOR_NWSE_RESIZE */ +static const unsigned char moz_nwse_resize_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0xfe, 0x07, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0xfa, 0x00, 0x00, 0x00, 0x7a, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x00, 0x00, + 0x5a, 0x80, 0x00, 0x00, 0x8a, 0x80, 0x00, 0x00, 0x02, 0x81, 0x00, 0x00, + 0x02, 0xa2, 0x00, 0x00, 0x02, 0xb4, 0x00, 0x00, 0x00, 0xb8, 0x00, 0x00, + 0x00, 0xbc, 0x00, 0x00, 0x00, 0xbe, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, + 0xc0, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + +static const unsigned char moz_nwse_resize_mask_bits[] = { + 0xff, 0x07, 0x00, 0x00, 0xff, 0x07, 0x00, 0x00, 0xff, 0x07, 0x00, 0x00, + 0xff, 0x01, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x00, 0x00, + 0xff, 0xc0, 0x01, 0x00, 0xdf, 0xc1, 0x01, 0x00, 0x8f, 0xe3, 0x01, 0x00, + 0x07, 0xf7, 0x01, 0x00, 0x07, 0xfe, 0x01, 0x00, 0x00, 0xfc, 0x01, 0x00, + 0x00, 0xfe, 0x01, 0x00, 0x00, 0xff, 0x01, 0x00, 0xc0, 0xff, 0x01, 0x00, + 0xc0, 0xff, 0x01, 0x00, 0xc0, 0xff, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + +/* MOZ_CURSOR_NONE */ +static const unsigned char moz_none_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + +static const unsigned char moz_none_mask_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + +enum { + MOZ_CURSOR_HAND_GRAB, + MOZ_CURSOR_HAND_GRABBING, + MOZ_CURSOR_COPY, + MOZ_CURSOR_ALIAS, + MOZ_CURSOR_CONTEXT_MENU, + MOZ_CURSOR_SPINNING, + MOZ_CURSOR_ZOOM_IN, + MOZ_CURSOR_ZOOM_OUT, + MOZ_CURSOR_NOT_ALLOWED, + MOZ_CURSOR_VERTICAL_TEXT, + MOZ_CURSOR_NESW_RESIZE, + MOZ_CURSOR_NWSE_RESIZE, + MOZ_CURSOR_NONE +}; + +// create custom pixmap cursor. The hash values must stay in sync with the +// bitmap data above. To see the hash function, have a look at XcursorImageHash +// in libXcursor +static const nsGtkCursor GtkCursors[] = { + { moz_hand_grab_bits, moz_hand_grab_mask_bits, 10, 10, "5aca4d189052212118709018842178c0" }, + { moz_hand_grabbing_bits, moz_hand_grabbing_mask_bits, 10, 10, "208530c400c041818281048008011002" }, + { moz_copy_bits, moz_copy_mask_bits, 2, 2, "08ffe1cb5fe6fc01f906f1c063814ccf" }, + { moz_alias_bits, moz_alias_mask_bits, 2, 2, "0876e1c15ff2fc01f906f1c363074c0f" }, + { moz_menu_bits, moz_menu_mask_bits, 2, 2, "08ffe1e65f80fcfdf9fff11263e74c48" }, + { moz_spinning_bits, moz_spinning_mask_bits, 2, 2, "08e8e1c95fe2fc01f976f1e063a24ccd" }, + { moz_zoom_in_bits, moz_zoom_in_mask_bits, 6, 6, "f41c0e382c94c0958e07017e42b00462" }, + { moz_zoom_out_bits, moz_zoom_out_mask_bits, 6, 6, "f41c0e382c97c0938e07017e42800402" }, + { moz_not_allowed_bits, moz_not_allowed_mask_bits, 9, 9, "03b6e0fcb3499374a867d041f52298f0" }, + { moz_vertical_text_bits, moz_vertical_text_mask_bits, 8, 4, "048008013003cff3c00c801001200000" }, + { moz_nesw_resize_bits, moz_nesw_resize_mask_bits, 8, 8, "50585d75b494802d0151028115016902" }, + { moz_nwse_resize_bits, moz_nwse_resize_mask_bits, 8, 8, "38c5dff7c7b8962045400281044508d2" }, + { moz_none_bits, moz_none_mask_bits, 0, 0, nullptr } +}; + +#endif /* nsGtkCursors_h__ */ diff --git a/widget/gtk/nsGtkKeyUtils.cpp b/widget/gtk/nsGtkKeyUtils.cpp new file mode 100644 index 000000000..ce55cf18e --- /dev/null +++ b/widget/gtk/nsGtkKeyUtils.cpp @@ -0,0 +1,1483 @@ +/* -*- 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/Logging.h" + +#include "nsGtkKeyUtils.h" + +#include <gdk/gdkkeysyms.h> +#include <algorithm> +#include <gdk/gdk.h> +#include <gdk/gdkx.h> +#if (MOZ_WIDGET_GTK == 3) +#include <gdk/gdkkeysyms-compat.h> +#endif +#include <X11/XKBlib.h> +#include "WidgetUtils.h" +#include "keysym2ucs.h" +#include "nsContentUtils.h" +#include "nsGtkUtils.h" +#include "nsIBidiKeyboard.h" +#include "nsServiceManagerUtils.h" + +#include "mozilla/ArrayUtils.h" +#include "mozilla/MouseEvents.h" +#include "mozilla/TextEvents.h" + +namespace mozilla { +namespace widget { + +LazyLogModule gKeymapWrapperLog("KeymapWrapperWidgets"); + +#define IS_ASCII_ALPHABETICAL(key) \ + ((('a' <= key) && (key <= 'z')) || (('A' <= key) && (key <= 'Z'))) + +#define MOZ_MODIFIER_KEYS "MozKeymapWrapper" + +KeymapWrapper* KeymapWrapper::sInstance = nullptr; +guint KeymapWrapper::sLastRepeatableHardwareKeyCode = 0; +KeymapWrapper::RepeatState KeymapWrapper::sRepeatState = + KeymapWrapper::NOT_PRESSED; + +static const char* GetBoolName(bool aBool) +{ + return aBool ? "TRUE" : "FALSE"; +} + +/* static */ const char* +KeymapWrapper::GetModifierName(Modifier aModifier) +{ + switch (aModifier) { + case CAPS_LOCK: return "CapsLock"; + case NUM_LOCK: return "NumLock"; + case SCROLL_LOCK: return "ScrollLock"; + case SHIFT: return "Shift"; + case CTRL: return "Ctrl"; + case ALT: return "Alt"; + case SUPER: return "Super"; + case HYPER: return "Hyper"; + case META: return "Meta"; + case LEVEL3: return "Level3"; + case LEVEL5: return "Level5"; + case NOT_MODIFIER: return "NotModifier"; + default: return "InvalidValue"; + } +} + +/* static */ KeymapWrapper::Modifier +KeymapWrapper::GetModifierForGDKKeyval(guint aGdkKeyval) +{ + switch (aGdkKeyval) { + case GDK_Caps_Lock: return CAPS_LOCK; + case GDK_Num_Lock: return NUM_LOCK; + case GDK_Scroll_Lock: return SCROLL_LOCK; + case GDK_Shift_Lock: + case GDK_Shift_L: + case GDK_Shift_R: return SHIFT; + case GDK_Control_L: + case GDK_Control_R: return CTRL; + case GDK_Alt_L: + case GDK_Alt_R: return ALT; + case GDK_Super_L: + case GDK_Super_R: return SUPER; + case GDK_Hyper_L: + case GDK_Hyper_R: return HYPER; + case GDK_Meta_L: + case GDK_Meta_R: return META; + case GDK_ISO_Level3_Shift: + case GDK_Mode_switch: return LEVEL3; + case GDK_ISO_Level5_Shift: return LEVEL5; + default: return NOT_MODIFIER; + } +} + +guint +KeymapWrapper::GetModifierMask(Modifier aModifier) const +{ + switch (aModifier) { + case CAPS_LOCK: + return GDK_LOCK_MASK; + case NUM_LOCK: + return mModifierMasks[INDEX_NUM_LOCK]; + case SCROLL_LOCK: + return mModifierMasks[INDEX_SCROLL_LOCK]; + case SHIFT: + return GDK_SHIFT_MASK; + case CTRL: + return GDK_CONTROL_MASK; + case ALT: + return mModifierMasks[INDEX_ALT]; + case SUPER: + return mModifierMasks[INDEX_SUPER]; + case HYPER: + return mModifierMasks[INDEX_HYPER]; + case META: + return mModifierMasks[INDEX_META]; + case LEVEL3: + return mModifierMasks[INDEX_LEVEL3]; + case LEVEL5: + return mModifierMasks[INDEX_LEVEL5]; + default: + return 0; + } +} + +KeymapWrapper::ModifierKey* +KeymapWrapper::GetModifierKey(guint aHardwareKeycode) +{ + for (uint32_t i = 0; i < mModifierKeys.Length(); i++) { + ModifierKey& key = mModifierKeys[i]; + if (key.mHardwareKeycode == aHardwareKeycode) { + return &key; + } + } + return nullptr; +} + +/* static */ KeymapWrapper* +KeymapWrapper::GetInstance() +{ + if (sInstance) { + sInstance->Init(); + return sInstance; + } + + sInstance = new KeymapWrapper(); + return sInstance; +} + +/* static */ void +KeymapWrapper::Shutdown() +{ + if (sInstance) { + delete sInstance; + sInstance = nullptr; + } +} + +KeymapWrapper::KeymapWrapper() : + mInitialized(false), mGdkKeymap(gdk_keymap_get_default()), + mXKBBaseEventCode(0) +{ + MOZ_LOG(gKeymapWrapperLog, LogLevel::Info, + ("%p Constructor, mGdkKeymap=%p", + this, mGdkKeymap)); + + g_object_ref(mGdkKeymap); + g_signal_connect(mGdkKeymap, "keys-changed", + (GCallback)OnKeysChanged, this); + g_signal_connect(mGdkKeymap, "direction-changed", + (GCallback)OnDirectionChanged, this); + + if (GDK_IS_X11_DISPLAY(gdk_display_get_default())) + InitXKBExtension(); + + Init(); +} + +void +KeymapWrapper::Init() +{ + if (mInitialized) { + return; + } + mInitialized = true; + + MOZ_LOG(gKeymapWrapperLog, LogLevel::Info, + ("%p Init, mGdkKeymap=%p", + this, mGdkKeymap)); + + mModifierKeys.Clear(); + memset(mModifierMasks, 0, sizeof(mModifierMasks)); + + if (GDK_IS_X11_DISPLAY(gdk_display_get_default())) + InitBySystemSettings(); + + gdk_window_add_filter(nullptr, FilterEvents, this); + + MOZ_LOG(gKeymapWrapperLog, LogLevel::Info, + ("%p Init, CapsLock=0x%X, NumLock=0x%X, " + "ScrollLock=0x%X, Level3=0x%X, Level5=0x%X, " + "Shift=0x%X, Ctrl=0x%X, Alt=0x%X, Meta=0x%X, Super=0x%X, Hyper=0x%X", + this, + GetModifierMask(CAPS_LOCK), GetModifierMask(NUM_LOCK), + GetModifierMask(SCROLL_LOCK), GetModifierMask(LEVEL3), + GetModifierMask(LEVEL5), + GetModifierMask(SHIFT), GetModifierMask(CTRL), + GetModifierMask(ALT), GetModifierMask(META), + GetModifierMask(SUPER), GetModifierMask(HYPER))); +} + +void +KeymapWrapper::InitXKBExtension() +{ + PodZero(&mKeyboardState); + + int xkbMajorVer = XkbMajorVersion; + int xkbMinorVer = XkbMinorVersion; + if (!XkbLibraryVersion(&xkbMajorVer, &xkbMinorVer)) { + MOZ_LOG(gKeymapWrapperLog, LogLevel::Info, + ("%p InitXKBExtension failed due to failure of " + "XkbLibraryVersion()", this)); + return; + } + + Display* display = + gdk_x11_display_get_xdisplay(gdk_display_get_default()); + + // XkbLibraryVersion() set xkbMajorVer and xkbMinorVer to that of the + // library, which may be newer than what is required of the server in + // XkbQueryExtension(), so these variables should be reset to + // XkbMajorVersion and XkbMinorVersion before the XkbQueryExtension call. + xkbMajorVer = XkbMajorVersion; + xkbMinorVer = XkbMinorVersion; + int opcode, baseErrorCode; + if (!XkbQueryExtension(display, &opcode, &mXKBBaseEventCode, &baseErrorCode, + &xkbMajorVer, &xkbMinorVer)) { + MOZ_LOG(gKeymapWrapperLog, LogLevel::Info, + ("%p InitXKBExtension failed due to failure of " + "XkbQueryExtension(), display=0x%p", this, display)); + return; + } + + if (!XkbSelectEventDetails(display, XkbUseCoreKbd, XkbStateNotify, + XkbModifierStateMask, XkbModifierStateMask)) { + MOZ_LOG(gKeymapWrapperLog, LogLevel::Info, + ("%p InitXKBExtension failed due to failure of " + "XkbSelectEventDetails() for XModifierStateMask, display=0x%p", + this, display)); + return; + } + + if (!XkbSelectEventDetails(display, XkbUseCoreKbd, XkbControlsNotify, + XkbPerKeyRepeatMask, XkbPerKeyRepeatMask)) { + MOZ_LOG(gKeymapWrapperLog, LogLevel::Info, + ("%p InitXKBExtension failed due to failure of " + "XkbSelectEventDetails() for XkbControlsNotify, display=0x%p", + this, display)); + return; + } + + if (!XGetKeyboardControl(display, &mKeyboardState)) { + MOZ_LOG(gKeymapWrapperLog, LogLevel::Info, + ("%p InitXKBExtension failed due to failure of " + "XGetKeyboardControl(), display=0x%p", + this, display)); + return; + } + + MOZ_LOG(gKeymapWrapperLog, LogLevel::Info, + ("%p InitXKBExtension, Succeeded", this)); +} + +void +KeymapWrapper::InitBySystemSettings() +{ + MOZ_LOG(gKeymapWrapperLog, LogLevel::Info, + ("%p InitBySystemSettings, mGdkKeymap=%p", + this, mGdkKeymap)); + + Display* display = + gdk_x11_display_get_xdisplay(gdk_display_get_default()); + + int min_keycode = 0; + int max_keycode = 0; + XDisplayKeycodes(display, &min_keycode, &max_keycode); + + int keysyms_per_keycode = 0; + KeySym* xkeymap = XGetKeyboardMapping(display, min_keycode, + max_keycode - min_keycode + 1, + &keysyms_per_keycode); + if (!xkeymap) { + MOZ_LOG(gKeymapWrapperLog, LogLevel::Info, + ("%p InitBySystemSettings, " + "Failed due to null xkeymap", this)); + return; + } + + XModifierKeymap* xmodmap = XGetModifierMapping(display); + if (!xmodmap) { + MOZ_LOG(gKeymapWrapperLog, LogLevel::Info, + ("%p InitBySystemSettings, " + "Failed due to null xmodmap", this)); + XFree(xkeymap); + return; + } + MOZ_LOG(gKeymapWrapperLog, LogLevel::Info, + ("%p InitBySystemSettings, min_keycode=%d, " + "max_keycode=%d, keysyms_per_keycode=%d, max_keypermod=%d", + this, min_keycode, max_keycode, keysyms_per_keycode, + xmodmap->max_keypermod)); + + // The modifiermap member of the XModifierKeymap structure contains 8 sets + // of max_keypermod KeyCodes, one for each modifier in the order Shift, + // Lock, Control, Mod1, Mod2, Mod3, Mod4, and Mod5. + // Only nonzero KeyCodes have meaning in each set, and zero KeyCodes are + // ignored. + + // Note that two or more modifiers may use one modifier flag. E.g., + // on Ubuntu 10.10, Alt and Meta share the Mod1 in default settings. + // And also Super and Hyper share the Mod4. In such cases, we need to + // decide which modifier flag means one of DOM modifiers. + + // mod[0] is Modifier introduced by Mod1. + Modifier mod[5]; + int32_t foundLevel[5]; + for (uint32_t i = 0; i < ArrayLength(mod); i++) { + mod[i] = NOT_MODIFIER; + foundLevel[i] = INT32_MAX; + } + const uint32_t map_size = 8 * xmodmap->max_keypermod; + for (uint32_t i = 0; i < map_size; i++) { + KeyCode keycode = xmodmap->modifiermap[i]; + MOZ_LOG(gKeymapWrapperLog, LogLevel::Info, + ("%p InitBySystemSettings, " + " i=%d, keycode=0x%08X", + this, i, keycode)); + if (!keycode || keycode < min_keycode || keycode > max_keycode) { + continue; + } + + ModifierKey* modifierKey = GetModifierKey(keycode); + if (!modifierKey) { + modifierKey = mModifierKeys.AppendElement(ModifierKey(keycode)); + } + + const KeySym* syms = + xkeymap + (keycode - min_keycode) * keysyms_per_keycode; + const uint32_t bit = i / xmodmap->max_keypermod; + modifierKey->mMask |= 1 << bit; + + // We need to know the meaning of Mod1, Mod2, Mod3, Mod4 and Mod5. + // Let's skip if current map is for others. + if (bit < 3) { + continue; + } + + const int32_t modIndex = bit - 3; + for (int32_t j = 0; j < keysyms_per_keycode; j++) { + Modifier modifier = GetModifierForGDKKeyval(syms[j]); + MOZ_LOG(gKeymapWrapperLog, LogLevel::Info, + ("%p InitBySystemSettings, " + " Mod%d, j=%d, syms[j]=%s(0x%X), modifier=%s", + this, modIndex + 1, j, gdk_keyval_name(syms[j]), syms[j], + GetModifierName(modifier))); + + switch (modifier) { + case NOT_MODIFIER: + // Don't overwrite the stored information with + // NOT_MODIFIER. + break; + case CAPS_LOCK: + case SHIFT: + case CTRL: + // Ignore the modifiers defined in GDK spec. They shouldn't + // be mapped to Mod1-5 because they must not work on native + // GTK applications. + break; + default: + // If new modifier is found in higher level than stored + // value, we don't need to overwrite it. + if (j > foundLevel[modIndex]) { + break; + } + // If new modifier is more important than stored value, + // we should overwrite it with new modifier. + if (j == foundLevel[modIndex]) { + mod[modIndex] = std::min(modifier, mod[modIndex]); + break; + } + foundLevel[modIndex] = j; + mod[modIndex] = modifier; + break; + } + } + } + + for (uint32_t i = 0; i < COUNT_OF_MODIFIER_INDEX; i++) { + Modifier modifier; + switch (i) { + case INDEX_NUM_LOCK: + modifier = NUM_LOCK; + break; + case INDEX_SCROLL_LOCK: + modifier = SCROLL_LOCK; + break; + case INDEX_ALT: + modifier = ALT; + break; + case INDEX_META: + modifier = META; + break; + case INDEX_SUPER: + modifier = SUPER; + break; + case INDEX_HYPER: + modifier = HYPER; + break; + case INDEX_LEVEL3: + modifier = LEVEL3; + break; + case INDEX_LEVEL5: + modifier = LEVEL5; + break; + default: + MOZ_CRASH("All indexes must be handled here"); + } + for (uint32_t j = 0; j < ArrayLength(mod); j++) { + if (modifier == mod[j]) { + mModifierMasks[i] |= 1 << (j + 3); + } + } + } + + XFreeModifiermap(xmodmap); + XFree(xkeymap); +} + +KeymapWrapper::~KeymapWrapper() +{ + gdk_window_remove_filter(nullptr, FilterEvents, this); + g_signal_handlers_disconnect_by_func(mGdkKeymap, + FuncToGpointer(OnKeysChanged), this); + g_signal_handlers_disconnect_by_func(mGdkKeymap, + FuncToGpointer(OnDirectionChanged), this); + g_object_unref(mGdkKeymap); + MOZ_LOG(gKeymapWrapperLog, LogLevel::Info, + ("%p Destructor", this)); +} + +/* static */ GdkFilterReturn +KeymapWrapper::FilterEvents(GdkXEvent* aXEvent, + GdkEvent* aGdkEvent, + gpointer aData) +{ + XEvent* xEvent = static_cast<XEvent*>(aXEvent); + switch (xEvent->type) { + case KeyPress: { + // If the key doesn't support auto repeat, ignore the event because + // even if such key (e.g., Shift) is pressed during auto repeat of + // anoter key, it doesn't stop the auto repeat. + KeymapWrapper* self = static_cast<KeymapWrapper*>(aData); + if (!self->IsAutoRepeatableKey(xEvent->xkey.keycode)) { + break; + } + if (sRepeatState == NOT_PRESSED) { + sRepeatState = FIRST_PRESS; + } else if (sLastRepeatableHardwareKeyCode == xEvent->xkey.keycode) { + sRepeatState = REPEATING; + } else { + // If a different key is pressed while another key is pressed, + // auto repeat system repeats only the last pressed key. + // So, setting new keycode and setting repeat state as first key + // press should work fine. + sRepeatState = FIRST_PRESS; + } + sLastRepeatableHardwareKeyCode = xEvent->xkey.keycode; + break; + } + case KeyRelease: { + if (sLastRepeatableHardwareKeyCode != xEvent->xkey.keycode) { + // This case means the key release event is caused by + // a non-repeatable key such as Shift or a repeatable key that + // was pressed before sLastRepeatableHardwareKeyCode was + // pressed. + break; + } + sRepeatState = NOT_PRESSED; + break; + } + case FocusOut: { + // At moving focus, we should reset keyboard repeat state. + // Strictly, this causes incorrect behavior. However, this + // correctness must be enough for web applications. + sRepeatState = NOT_PRESSED; + break; + } + default: { + KeymapWrapper* self = static_cast<KeymapWrapper*>(aData); + if (xEvent->type != self->mXKBBaseEventCode) { + break; + } + XkbEvent* xkbEvent = (XkbEvent*)xEvent; + if (xkbEvent->any.xkb_type != XkbControlsNotify || + !(xkbEvent->ctrls.changed_ctrls & XkbPerKeyRepeatMask)) { + break; + } + if (!XGetKeyboardControl(xkbEvent->any.display, + &self->mKeyboardState)) { + MOZ_LOG(gKeymapWrapperLog, LogLevel::Info, + ("%p FilterEvents failed due to failure " + "of XGetKeyboardControl(), display=0x%p", + self, xkbEvent->any.display)); + } + break; + } + } + + return GDK_FILTER_CONTINUE; +} + +static void +ResetBidiKeyboard() +{ + // Reset the bidi keyboard settings for the new GdkKeymap + nsCOMPtr<nsIBidiKeyboard> bidiKeyboard = nsContentUtils::GetBidiKeyboard(); + if (bidiKeyboard) { + bidiKeyboard->Reset(); + } + WidgetUtils::SendBidiKeyboardInfoToContent(); +} + +/* static */ void +KeymapWrapper::OnKeysChanged(GdkKeymap *aGdkKeymap, + KeymapWrapper* aKeymapWrapper) +{ + MOZ_LOG(gKeymapWrapperLog, LogLevel::Info, + ("OnKeysChanged, aGdkKeymap=%p, aKeymapWrapper=%p", + aGdkKeymap, aKeymapWrapper)); + + MOZ_ASSERT(sInstance == aKeymapWrapper, + "This instance must be the singleton instance"); + + // We cannot reintialize here becasue we don't have GdkWindow which is using + // the GdkKeymap. We'll reinitialize it when next GetInstance() is called. + sInstance->mInitialized = false; + ResetBidiKeyboard(); +} + +// static +void +KeymapWrapper::OnDirectionChanged(GdkKeymap *aGdkKeymap, + KeymapWrapper* aKeymapWrapper) +{ + // XXX + // A lot of diretion-changed signal might be fired on switching bidi + // keyboard when using both ibus (with arabic layout) and fcitx (with IME). + // See https://github.com/fcitx/fcitx/issues/257 + // + // Also, when using ibus, switching to IM might not cause this signal. + // See https://github.com/ibus/ibus/issues/1848 + + MOZ_LOG(gKeymapWrapperLog, LogLevel::Info, + ("OnDirectionChanged, aGdkKeymap=%p, aKeymapWrapper=%p", + aGdkKeymap, aKeymapWrapper)); + + ResetBidiKeyboard(); +} + +/* static */ guint +KeymapWrapper::GetCurrentModifierState() +{ + GdkModifierType modifiers; + gdk_display_get_pointer(gdk_display_get_default(), + nullptr, nullptr, nullptr, &modifiers); + return static_cast<guint>(modifiers); +} + +/* static */ bool +KeymapWrapper::AreModifiersCurrentlyActive(Modifiers aModifiers) +{ + guint modifierState = GetCurrentModifierState(); + return AreModifiersActive(aModifiers, modifierState); +} + +/* static */ bool +KeymapWrapper::AreModifiersActive(Modifiers aModifiers, + guint aModifierState) +{ + NS_ENSURE_TRUE(aModifiers, false); + + KeymapWrapper* keymapWrapper = GetInstance(); + for (uint32_t i = 0; i < sizeof(Modifier) * 8 && aModifiers; i++) { + Modifier modifier = static_cast<Modifier>(1 << i); + if (!(aModifiers & modifier)) { + continue; + } + if (!(aModifierState & keymapWrapper->GetModifierMask(modifier))) { + return false; + } + aModifiers &= ~modifier; + } + return true; +} + +/* static */ void +KeymapWrapper::InitInputEvent(WidgetInputEvent& aInputEvent, + guint aModifierState) +{ + KeymapWrapper* keymapWrapper = GetInstance(); + + aInputEvent.mModifiers = 0; + // DOM Meta key should be TRUE only on Mac. We need to discuss this + // issue later. + if (keymapWrapper->AreModifiersActive(SHIFT, aModifierState)) { + aInputEvent.mModifiers |= MODIFIER_SHIFT; + } + if (keymapWrapper->AreModifiersActive(CTRL, aModifierState)) { + aInputEvent.mModifiers |= MODIFIER_CONTROL; + } + if (keymapWrapper->AreModifiersActive(ALT, aModifierState)) { + aInputEvent.mModifiers |= MODIFIER_ALT; + } + if (keymapWrapper->AreModifiersActive(META, aModifierState)) { + aInputEvent.mModifiers |= MODIFIER_META; + } + if (keymapWrapper->AreModifiersActive(SUPER, aModifierState) || + keymapWrapper->AreModifiersActive(HYPER, aModifierState)) { + aInputEvent.mModifiers |= MODIFIER_OS; + } + if (keymapWrapper->AreModifiersActive(LEVEL3, aModifierState) || + keymapWrapper->AreModifiersActive(LEVEL5, aModifierState)) { + aInputEvent.mModifiers |= MODIFIER_ALTGRAPH; + } + if (keymapWrapper->AreModifiersActive(CAPS_LOCK, aModifierState)) { + aInputEvent.mModifiers |= MODIFIER_CAPSLOCK; + } + if (keymapWrapper->AreModifiersActive(NUM_LOCK, aModifierState)) { + aInputEvent.mModifiers |= MODIFIER_NUMLOCK; + } + if (keymapWrapper->AreModifiersActive(SCROLL_LOCK, aModifierState)) { + aInputEvent.mModifiers |= MODIFIER_SCROLLLOCK; + } + + MOZ_LOG(gKeymapWrapperLog, LogLevel::Debug, + ("%p InitInputEvent, aModifierState=0x%08X, " + "aInputEvent.mModifiers=0x%04X (Shift: %s, Control: %s, Alt: %s, " + "Meta: %s, OS: %s, AltGr: %s, " + "CapsLock: %s, NumLock: %s, ScrollLock: %s)", + keymapWrapper, aModifierState, aInputEvent.mModifiers, + GetBoolName(aInputEvent.mModifiers & MODIFIER_SHIFT), + GetBoolName(aInputEvent.mModifiers & MODIFIER_CONTROL), + GetBoolName(aInputEvent.mModifiers & MODIFIER_ALT), + GetBoolName(aInputEvent.mModifiers & MODIFIER_META), + GetBoolName(aInputEvent.mModifiers & MODIFIER_OS), + GetBoolName(aInputEvent.mModifiers & MODIFIER_ALTGRAPH), + GetBoolName(aInputEvent.mModifiers & MODIFIER_CAPSLOCK), + GetBoolName(aInputEvent.mModifiers & MODIFIER_NUMLOCK), + GetBoolName(aInputEvent.mModifiers & MODIFIER_SCROLLLOCK))); + + switch(aInputEvent.mClass) { + case eMouseEventClass: + case eMouseScrollEventClass: + case eWheelEventClass: + case eDragEventClass: + case eSimpleGestureEventClass: + break; + default: + return; + } + + WidgetMouseEventBase& mouseEvent = *aInputEvent.AsMouseEventBase(); + mouseEvent.buttons = 0; + if (aModifierState & GDK_BUTTON1_MASK) { + mouseEvent.buttons |= WidgetMouseEvent::eLeftButtonFlag; + } + if (aModifierState & GDK_BUTTON3_MASK) { + mouseEvent.buttons |= WidgetMouseEvent::eRightButtonFlag; + } + if (aModifierState & GDK_BUTTON2_MASK) { + mouseEvent.buttons |= WidgetMouseEvent::eMiddleButtonFlag; + } + + MOZ_LOG(gKeymapWrapperLog, LogLevel::Debug, + ("%p InitInputEvent, aInputEvent has buttons, " + "aInputEvent.buttons=0x%04X (Left: %s, Right: %s, Middle: %s, " + "4th (BACK): %s, 5th (FORWARD): %s)", + keymapWrapper, mouseEvent.buttons, + GetBoolName(mouseEvent.buttons & WidgetMouseEvent::eLeftButtonFlag), + GetBoolName(mouseEvent.buttons & WidgetMouseEvent::eRightButtonFlag), + GetBoolName(mouseEvent.buttons & WidgetMouseEvent::eMiddleButtonFlag), + GetBoolName(mouseEvent.buttons & WidgetMouseEvent::e4thButtonFlag), + GetBoolName(mouseEvent.buttons & WidgetMouseEvent::e5thButtonFlag))); +} + +/* static */ uint32_t +KeymapWrapper::ComputeDOMKeyCode(const GdkEventKey* aGdkKeyEvent) +{ + // If the keyval indicates it's a modifier key, we should use unshifted + // key's modifier keyval. + guint keyval = aGdkKeyEvent->keyval; + if (GetModifierForGDKKeyval(keyval)) { + // But if the keyval without modifiers isn't a modifier key, we + // shouldn't use it. E.g., Japanese keyboard layout's + // Shift + Eisu-Toggle key is CapsLock. This is an actual rare case, + // Windows uses different keycode for a physical key for different + // shift key state. + guint keyvalWithoutModifier = GetGDKKeyvalWithoutModifier(aGdkKeyEvent); + if (GetModifierForGDKKeyval(keyvalWithoutModifier)) { + keyval = keyvalWithoutModifier; + } + // Note that the modifier keycode and activating or deactivating + // modifier flag may be mismatched, but it's okay. If a DOM key + // event handler is testing a keydown event, it's more likely being + // used to test which key is being pressed than to test which + // modifier will become active. So, if we computed DOM keycode + // from modifier flag which were changing by the physical key, then + // there would be no other way for the user to generate the original + // keycode. + uint32_t DOMKeyCode = GetDOMKeyCodeFromKeyPairs(keyval); + NS_ASSERTION(DOMKeyCode, "All modifier keys must have a DOM keycode"); + return DOMKeyCode; + } + + // If the key isn't printable, let's look at the key pairs. + uint32_t charCode = GetCharCodeFor(aGdkKeyEvent); + if (!charCode) { + // Always use unshifted keycode for the non-printable key. + // XXX It might be better to decide DOM keycode from all keyvals of + // the hardware keycode. However, I think that it's too excessive. + guint keyvalWithoutModifier = GetGDKKeyvalWithoutModifier(aGdkKeyEvent); + uint32_t DOMKeyCode = GetDOMKeyCodeFromKeyPairs(keyvalWithoutModifier); + if (!DOMKeyCode) { + // If the unshifted keyval couldn't be mapped to a DOM keycode, + // we should fallback to legacy logic, so, we should recompute with + // the keyval with aGdkKeyEvent. + DOMKeyCode = GetDOMKeyCodeFromKeyPairs(keyval); + } + return DOMKeyCode; + } + + // printable numpad keys should be resolved here. + switch (keyval) { + case GDK_KP_Multiply: return NS_VK_MULTIPLY; + case GDK_KP_Add: return NS_VK_ADD; + case GDK_KP_Separator: return NS_VK_SEPARATOR; + case GDK_KP_Subtract: return NS_VK_SUBTRACT; + case GDK_KP_Decimal: return NS_VK_DECIMAL; + case GDK_KP_Divide: return NS_VK_DIVIDE; + case GDK_KP_0: return NS_VK_NUMPAD0; + case GDK_KP_1: return NS_VK_NUMPAD1; + case GDK_KP_2: return NS_VK_NUMPAD2; + case GDK_KP_3: return NS_VK_NUMPAD3; + case GDK_KP_4: return NS_VK_NUMPAD4; + case GDK_KP_5: return NS_VK_NUMPAD5; + case GDK_KP_6: return NS_VK_NUMPAD6; + case GDK_KP_7: return NS_VK_NUMPAD7; + case GDK_KP_8: return NS_VK_NUMPAD8; + case GDK_KP_9: return NS_VK_NUMPAD9; + } + + KeymapWrapper* keymapWrapper = GetInstance(); + + // Ignore all modifier state except NumLock. + guint baseState = + (aGdkKeyEvent->state & keymapWrapper->GetModifierMask(NUM_LOCK)); + + // Basically, we should use unmodified character for deciding our keyCode. + uint32_t unmodifiedChar = + keymapWrapper->GetCharCodeFor(aGdkKeyEvent, baseState, + aGdkKeyEvent->group); + if (IsBasicLatinLetterOrNumeral(unmodifiedChar)) { + // If the unmodified character is an ASCII alphabet or an ASCII + // numeric, it's the best hint for deciding our keyCode. + return WidgetUtils::ComputeKeyCodeFromChar(unmodifiedChar); + } + + // If the unmodified character is not an ASCII character, that means we + // couldn't find the hint. We should reset it. + if (unmodifiedChar > 0x7F) { + unmodifiedChar = 0; + } + + // Retry with shifted keycode. + guint shiftState = (baseState | keymapWrapper->GetModifierMask(SHIFT)); + uint32_t shiftedChar = + keymapWrapper->GetCharCodeFor(aGdkKeyEvent, shiftState, + aGdkKeyEvent->group); + if (IsBasicLatinLetterOrNumeral(shiftedChar)) { + // A shifted character can be an ASCII alphabet on Hebrew keyboard + // layout. And also shifted character can be an ASCII numeric on + // AZERTY keyboad layout. Then, it's a good hint for deciding our + // keyCode. + return WidgetUtils::ComputeKeyCodeFromChar(shiftedChar); + } + + // If the shifted unmodified character isn't an ASCII character, we should + // discard it too. + if (shiftedChar > 0x7F) { + shiftedChar = 0; + } + + // If current keyboard layout isn't ASCII alphabet inputtable layout, + // look for ASCII alphabet inputtable keyboard layout. If the key + // inputs an ASCII alphabet or an ASCII numeric, we should use it + // for deciding our keyCode. + // Note that it's important not to use alternative keyboard layout for ASCII + // alphabet inputabble keyboard layout because the keycode for the key with + // alternative keyboard layout may conflict with another key on current + // keyboard layout. + if (!keymapWrapper->IsLatinGroup(aGdkKeyEvent->group)) { + gint minGroup = keymapWrapper->GetFirstLatinGroup(); + if (minGroup >= 0) { + uint32_t unmodCharLatin = + keymapWrapper->GetCharCodeFor(aGdkKeyEvent, baseState, + minGroup); + if (IsBasicLatinLetterOrNumeral(unmodCharLatin)) { + // If the unmodified character is an ASCII alphabet or + // an ASCII numeric, we should use it for the keyCode. + return WidgetUtils::ComputeKeyCodeFromChar(unmodCharLatin); + } + uint32_t shiftedCharLatin = + keymapWrapper->GetCharCodeFor(aGdkKeyEvent, shiftState, + minGroup); + if (IsBasicLatinLetterOrNumeral(shiftedCharLatin)) { + // If the shifted character is an ASCII alphabet or an ASCII + // numeric, we should use it for the keyCode. + return WidgetUtils::ComputeKeyCodeFromChar(shiftedCharLatin); + } + } + } + + // If unmodified character is in ASCII range, use it. Otherwise, use + // shifted character. + if (!unmodifiedChar && !shiftedChar) { + return 0; + } + return WidgetUtils::ComputeKeyCodeFromChar( + unmodifiedChar ? unmodifiedChar : shiftedChar); +} + +KeyNameIndex +KeymapWrapper::ComputeDOMKeyNameIndex(const GdkEventKey* aGdkKeyEvent) +{ + switch (aGdkKeyEvent->keyval) { + +#define NS_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX(aNativeKey, aKeyNameIndex) \ + case aNativeKey: return aKeyNameIndex; + +#include "NativeKeyToDOMKeyName.h" + +#undef NS_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX + + default: + break; + } + + return KEY_NAME_INDEX_Unidentified; +} + +/* static */ CodeNameIndex +KeymapWrapper::ComputeDOMCodeNameIndex(const GdkEventKey* aGdkKeyEvent) +{ + switch (aGdkKeyEvent->hardware_keycode) { + +#define NS_NATIVE_KEY_TO_DOM_CODE_NAME_INDEX(aNativeKey, aCodeNameIndex) \ + case aNativeKey: return aCodeNameIndex; + +#include "NativeKeyToDOMCodeName.h" + +#undef NS_NATIVE_KEY_TO_DOM_CODE_NAME_INDEX + + default: + break; + } + + return CODE_NAME_INDEX_UNKNOWN; +} + +/* static */ void +KeymapWrapper::InitKeyEvent(WidgetKeyboardEvent& aKeyEvent, + GdkEventKey* aGdkKeyEvent) +{ + KeymapWrapper* keymapWrapper = GetInstance(); + + aKeyEvent.mCodeNameIndex = ComputeDOMCodeNameIndex(aGdkKeyEvent); + MOZ_ASSERT(aKeyEvent.mCodeNameIndex != CODE_NAME_INDEX_USE_STRING); + aKeyEvent.mKeyNameIndex = + keymapWrapper->ComputeDOMKeyNameIndex(aGdkKeyEvent); + if (aKeyEvent.mKeyNameIndex == KEY_NAME_INDEX_Unidentified) { + uint32_t charCode = GetCharCodeFor(aGdkKeyEvent); + if (!charCode) { + charCode = keymapWrapper->GetUnmodifiedCharCodeFor(aGdkKeyEvent); + } + if (charCode) { + aKeyEvent.mKeyNameIndex = KEY_NAME_INDEX_USE_STRING; + MOZ_ASSERT(aKeyEvent.mKeyValue.IsEmpty(), + "Uninitialized mKeyValue must be empty"); + AppendUCS4ToUTF16(charCode, aKeyEvent.mKeyValue); + } + } + aKeyEvent.mKeyCode = ComputeDOMKeyCode(aGdkKeyEvent); + + if (aKeyEvent.mKeyNameIndex != KEY_NAME_INDEX_USE_STRING || + aKeyEvent.mMessage != eKeyPress) { + aKeyEvent.mKeyCode = ComputeDOMKeyCode(aGdkKeyEvent); + } else { + aKeyEvent.mKeyCode = 0; + } + + // NOTE: The state of given key event indicates adjacent state of + // modifier keys. E.g., even if the event is Shift key press event, + // the bit for Shift is still false. By the same token, even if the + // event is Shift key release event, the bit for Shift is still true. + // Unfortunately, gdk_keyboard_get_modifiers() returns current modifier + // state. It means if there're some pending modifier key press or + // key release events, the result isn't what we want. + guint modifierState = aGdkKeyEvent->state; + GdkDisplay* gdkDisplay = gdk_display_get_default(); + if (aGdkKeyEvent->is_modifier && GDK_IS_X11_DISPLAY(gdkDisplay)) { + Display* display = + gdk_x11_display_get_xdisplay(gdkDisplay); + if (XEventsQueued(display, QueuedAfterReading)) { + XEvent nextEvent; + XPeekEvent(display, &nextEvent); + if (nextEvent.type == keymapWrapper->mXKBBaseEventCode) { + XkbEvent* XKBEvent = (XkbEvent*)&nextEvent; + if (XKBEvent->any.xkb_type == XkbStateNotify) { + XkbStateNotifyEvent* stateNotifyEvent = + (XkbStateNotifyEvent*)XKBEvent; + modifierState &= ~0xFF; + modifierState |= stateNotifyEvent->lookup_mods; + } + } + } + } + InitInputEvent(aKeyEvent, modifierState); + + switch (aGdkKeyEvent->keyval) { + case GDK_Shift_L: + case GDK_Control_L: + case GDK_Alt_L: + case GDK_Super_L: + case GDK_Hyper_L: + case GDK_Meta_L: + aKeyEvent.mLocation = nsIDOMKeyEvent::DOM_KEY_LOCATION_LEFT; + break; + + case GDK_Shift_R: + case GDK_Control_R: + case GDK_Alt_R: + case GDK_Super_R: + case GDK_Hyper_R: + case GDK_Meta_R: + aKeyEvent.mLocation = nsIDOMKeyEvent::DOM_KEY_LOCATION_RIGHT; + break; + + case GDK_KP_0: + case GDK_KP_1: + case GDK_KP_2: + case GDK_KP_3: + case GDK_KP_4: + case GDK_KP_5: + case GDK_KP_6: + case GDK_KP_7: + case GDK_KP_8: + case GDK_KP_9: + case GDK_KP_Space: + case GDK_KP_Tab: + case GDK_KP_Enter: + case GDK_KP_F1: + case GDK_KP_F2: + case GDK_KP_F3: + case GDK_KP_F4: + case GDK_KP_Home: + case GDK_KP_Left: + case GDK_KP_Up: + case GDK_KP_Right: + case GDK_KP_Down: + case GDK_KP_Prior: // same as GDK_KP_Page_Up + case GDK_KP_Next: // same as GDK_KP_Page_Down + case GDK_KP_End: + case GDK_KP_Begin: + case GDK_KP_Insert: + case GDK_KP_Delete: + case GDK_KP_Equal: + case GDK_KP_Multiply: + case GDK_KP_Add: + case GDK_KP_Separator: + case GDK_KP_Subtract: + case GDK_KP_Decimal: + case GDK_KP_Divide: + aKeyEvent.mLocation = nsIDOMKeyEvent::DOM_KEY_LOCATION_NUMPAD; + break; + + default: + aKeyEvent.mLocation = nsIDOMKeyEvent::DOM_KEY_LOCATION_STANDARD; + break; + } + + MOZ_LOG(gKeymapWrapperLog, LogLevel::Info, + ("%p InitKeyEvent, modifierState=0x%08X " + "aGdkKeyEvent={ type=%s, keyval=%s(0x%X), state=0x%08X, " + "hardware_keycode=0x%08X, is_modifier=%s } " + "aKeyEvent={ message=%s, isShift=%s, isControl=%s, " + "isAlt=%s, isMeta=%s }", + keymapWrapper, modifierState, + ((aGdkKeyEvent->type == GDK_KEY_PRESS) ? + "GDK_KEY_PRESS" : "GDK_KEY_RELEASE"), + gdk_keyval_name(aGdkKeyEvent->keyval), + aGdkKeyEvent->keyval, aGdkKeyEvent->state, + aGdkKeyEvent->hardware_keycode, + GetBoolName(aGdkKeyEvent->is_modifier), + ((aKeyEvent.mMessage == eKeyDown) ? "eKeyDown" : + (aKeyEvent.mMessage == eKeyPress) ? "eKeyPress" : "eKeyUp"), + GetBoolName(aKeyEvent.IsShift()), GetBoolName(aKeyEvent.IsControl()), + GetBoolName(aKeyEvent.IsAlt()), GetBoolName(aKeyEvent.IsMeta()))); + + // The transformations above and in gdk for the keyval are not invertible + // so link to the GdkEvent (which will vanish soon after return from the + // event callback) to give plugins access to hardware_keycode and state. + // (An XEvent would be nice but the GdkEvent is good enough.) + aKeyEvent.mPluginEvent.Copy(*aGdkKeyEvent); + aKeyEvent.mTime = aGdkKeyEvent->time; + aKeyEvent.mNativeKeyEvent = static_cast<void*>(aGdkKeyEvent); + aKeyEvent.mIsRepeat = sRepeatState == REPEATING && + aGdkKeyEvent->hardware_keycode == sLastRepeatableHardwareKeyCode; +} + +/* static */ uint32_t +KeymapWrapper::GetCharCodeFor(const GdkEventKey *aGdkKeyEvent) +{ + // Anything above 0xf000 is considered a non-printable + // Exception: directly encoded UCS characters + if (aGdkKeyEvent->keyval > 0xf000 && + (aGdkKeyEvent->keyval & 0xff000000) != 0x01000000) { + // Keypad keys are an exception: they return a value different + // from their non-keypad equivalents, but mozilla doesn't distinguish. + switch (aGdkKeyEvent->keyval) { + case GDK_KP_Space: return ' '; + case GDK_KP_Equal: return '='; + case GDK_KP_Multiply: return '*'; + case GDK_KP_Add: return '+'; + case GDK_KP_Separator: return ','; + case GDK_KP_Subtract: return '-'; + case GDK_KP_Decimal: return '.'; + case GDK_KP_Divide: return '/'; + case GDK_KP_0: return '0'; + case GDK_KP_1: return '1'; + case GDK_KP_2: return '2'; + case GDK_KP_3: return '3'; + case GDK_KP_4: return '4'; + case GDK_KP_5: return '5'; + case GDK_KP_6: return '6'; + case GDK_KP_7: return '7'; + case GDK_KP_8: return '8'; + case GDK_KP_9: return '9'; + default: return 0; // non-printables + } + } + + static const long MAX_UNICODE = 0x10FFFF; + + // we're supposedly printable, let's try to convert + long ucs = keysym2ucs(aGdkKeyEvent->keyval); + if ((ucs != -1) && (ucs < MAX_UNICODE)) { + return ucs; + } + + // I guess we couldn't convert + return 0; +} + +uint32_t +KeymapWrapper::GetCharCodeFor(const GdkEventKey *aGdkKeyEvent, + guint aModifierState, + gint aGroup) +{ + guint keyval; + if (!gdk_keymap_translate_keyboard_state(mGdkKeymap, + aGdkKeyEvent->hardware_keycode, + GdkModifierType(aModifierState), + aGroup, &keyval, nullptr, nullptr, nullptr)) { + return 0; + } + GdkEventKey tmpEvent = *aGdkKeyEvent; + tmpEvent.state = aModifierState; + tmpEvent.keyval = keyval; + tmpEvent.group = aGroup; + return GetCharCodeFor(&tmpEvent); +} + +uint32_t +KeymapWrapper::GetUnmodifiedCharCodeFor(const GdkEventKey* aGdkKeyEvent) +{ + guint state = aGdkKeyEvent->state & + (GetModifierMask(SHIFT) | GetModifierMask(CAPS_LOCK) | + GetModifierMask(NUM_LOCK) | GetModifierMask(SCROLL_LOCK) | + GetModifierMask(LEVEL3) | GetModifierMask(LEVEL5)); + uint32_t charCode = GetCharCodeFor(aGdkKeyEvent, GdkModifierType(state), + aGdkKeyEvent->group); + if (charCode) { + return charCode; + } + // If no character is mapped to the key when Level3 Shift or Level5 Shift + // is active, let's return a character which is inputted by the key without + // Level3 nor Level5 Shift. + guint stateWithoutAltGraph = + state & ~(GetModifierMask(LEVEL3) | GetModifierMask(LEVEL5)); + if (state == stateWithoutAltGraph) { + return 0; + } + return GetCharCodeFor(aGdkKeyEvent, GdkModifierType(stateWithoutAltGraph), + aGdkKeyEvent->group); +} + +gint +KeymapWrapper::GetKeyLevel(GdkEventKey *aGdkKeyEvent) +{ + gint level; + if (!gdk_keymap_translate_keyboard_state(mGdkKeymap, + aGdkKeyEvent->hardware_keycode, + GdkModifierType(aGdkKeyEvent->state), + aGdkKeyEvent->group, nullptr, nullptr, &level, nullptr)) { + return -1; + } + return level; +} + +gint +KeymapWrapper::GetFirstLatinGroup() +{ + GdkKeymapKey *keys; + gint count; + gint minGroup = -1; + if (gdk_keymap_get_entries_for_keyval(mGdkKeymap, GDK_a, &keys, &count)) { + // find the minimum number group for latin inputtable layout + for (gint i = 0; i < count && minGroup != 0; ++i) { + if (keys[i].level != 0 && keys[i].level != 1) { + continue; + } + if (minGroup >= 0 && keys[i].group > minGroup) { + continue; + } + minGroup = keys[i].group; + } + g_free(keys); + } + return minGroup; +} + +bool +KeymapWrapper::IsLatinGroup(guint8 aGroup) +{ + GdkKeymapKey *keys; + gint count; + bool result = false; + if (gdk_keymap_get_entries_for_keyval(mGdkKeymap, GDK_a, &keys, &count)) { + for (gint i = 0; i < count; ++i) { + if (keys[i].level != 0 && keys[i].level != 1) { + continue; + } + if (keys[i].group == aGroup) { + result = true; + break; + } + } + g_free(keys); + } + return result; +} + +bool +KeymapWrapper::IsAutoRepeatableKey(guint aHardwareKeyCode) +{ + uint8_t indexOfArray = aHardwareKeyCode / 8; + MOZ_ASSERT(indexOfArray < ArrayLength(mKeyboardState.auto_repeats), + "invalid index"); + char bitMask = 1 << (aHardwareKeyCode % 8); + return (mKeyboardState.auto_repeats[indexOfArray] & bitMask) != 0; +} + +/* static */ bool +KeymapWrapper::IsBasicLatinLetterOrNumeral(uint32_t aCharCode) +{ + return (aCharCode >= 'a' && aCharCode <= 'z') || + (aCharCode >= 'A' && aCharCode <= 'Z') || + (aCharCode >= '0' && aCharCode <= '9'); +} + +/* static */ guint +KeymapWrapper::GetGDKKeyvalWithoutModifier(const GdkEventKey *aGdkKeyEvent) +{ + KeymapWrapper* keymapWrapper = GetInstance(); + guint state = + (aGdkKeyEvent->state & keymapWrapper->GetModifierMask(NUM_LOCK)); + guint keyval; + if (!gdk_keymap_translate_keyboard_state(keymapWrapper->mGdkKeymap, + aGdkKeyEvent->hardware_keycode, GdkModifierType(state), + aGdkKeyEvent->group, &keyval, nullptr, nullptr, nullptr)) { + return 0; + } + return keyval; +} + +/* static */ uint32_t +KeymapWrapper::GetDOMKeyCodeFromKeyPairs(guint aGdkKeyval) +{ + switch (aGdkKeyval) { + case GDK_Cancel: return NS_VK_CANCEL; + case GDK_BackSpace: return NS_VK_BACK; + case GDK_Tab: + case GDK_ISO_Left_Tab: return NS_VK_TAB; + case GDK_Clear: return NS_VK_CLEAR; + case GDK_Return: return NS_VK_RETURN; + case GDK_Shift_L: + case GDK_Shift_R: + case GDK_Shift_Lock: return NS_VK_SHIFT; + case GDK_Control_L: + case GDK_Control_R: return NS_VK_CONTROL; + case GDK_Alt_L: + case GDK_Alt_R: return NS_VK_ALT; + case GDK_Meta_L: + case GDK_Meta_R: return NS_VK_META; + + // Assume that Super or Hyper is always mapped to physical Win key. + case GDK_Super_L: + case GDK_Super_R: + case GDK_Hyper_L: + case GDK_Hyper_R: return NS_VK_WIN; + + // GTK's AltGraph key is similar to Mac's Option (Alt) key. However, + // unfortunately, browsers on Mac are using NS_VK_ALT for it even though + // it's really different from Alt key on Windows. + // On the other hand, GTK's AltGrapsh keys are really different from + // Alt key. However, there is no AltGrapsh key on Windows. On Windows, + // both Ctrl and Alt keys are pressed internally when AltGr key is + // pressed. For some languages' users, AltGraph key is important, so, + // web applications on such locale may want to know AltGraph key press. + // Therefore, we should map AltGr keycode for them only on GTK. + case GDK_ISO_Level3_Shift: + case GDK_ISO_Level5_Shift: + // We assume that Mode_switch is always used for level3 shift. + case GDK_Mode_switch: return NS_VK_ALTGR; + + case GDK_Pause: return NS_VK_PAUSE; + case GDK_Caps_Lock: return NS_VK_CAPS_LOCK; + case GDK_Kana_Lock: + case GDK_Kana_Shift: return NS_VK_KANA; + case GDK_Hangul: return NS_VK_HANGUL; + // case GDK_XXX: return NS_VK_JUNJA; + // case GDK_XXX: return NS_VK_FINAL; + case GDK_Hangul_Hanja: return NS_VK_HANJA; + case GDK_Kanji: return NS_VK_KANJI; + case GDK_Escape: return NS_VK_ESCAPE; + case GDK_Henkan: return NS_VK_CONVERT; + case GDK_Muhenkan: return NS_VK_NONCONVERT; + // case GDK_XXX: return NS_VK_ACCEPT; + // case GDK_XXX: return NS_VK_MODECHANGE; + case GDK_Page_Up: return NS_VK_PAGE_UP; + case GDK_Page_Down: return NS_VK_PAGE_DOWN; + case GDK_End: return NS_VK_END; + case GDK_Home: return NS_VK_HOME; + case GDK_Left: return NS_VK_LEFT; + case GDK_Up: return NS_VK_UP; + case GDK_Right: return NS_VK_RIGHT; + case GDK_Down: return NS_VK_DOWN; + case GDK_Select: return NS_VK_SELECT; + case GDK_Print: return NS_VK_PRINT; + case GDK_Execute: return NS_VK_EXECUTE; + case GDK_Insert: return NS_VK_INSERT; + case GDK_Delete: return NS_VK_DELETE; + case GDK_Help: return NS_VK_HELP; + + // keypad keys + case GDK_KP_Left: return NS_VK_LEFT; + case GDK_KP_Right: return NS_VK_RIGHT; + case GDK_KP_Up: return NS_VK_UP; + case GDK_KP_Down: return NS_VK_DOWN; + case GDK_KP_Page_Up: return NS_VK_PAGE_UP; + // Not sure what these are + // case GDK_KP_Prior: return NS_VK_; + // case GDK_KP_Next: return NS_VK_; + case GDK_KP_Begin: return NS_VK_CLEAR; // Num-unlocked 5 + case GDK_KP_Page_Down: return NS_VK_PAGE_DOWN; + case GDK_KP_Home: return NS_VK_HOME; + case GDK_KP_End: return NS_VK_END; + case GDK_KP_Insert: return NS_VK_INSERT; + case GDK_KP_Delete: return NS_VK_DELETE; + case GDK_KP_Enter: return NS_VK_RETURN; + + case GDK_Num_Lock: return NS_VK_NUM_LOCK; + case GDK_Scroll_Lock: return NS_VK_SCROLL_LOCK; + + // Function keys + case GDK_F1: return NS_VK_F1; + case GDK_F2: return NS_VK_F2; + case GDK_F3: return NS_VK_F3; + case GDK_F4: return NS_VK_F4; + case GDK_F5: return NS_VK_F5; + case GDK_F6: return NS_VK_F6; + case GDK_F7: return NS_VK_F7; + case GDK_F8: return NS_VK_F8; + case GDK_F9: return NS_VK_F9; + case GDK_F10: return NS_VK_F10; + case GDK_F11: return NS_VK_F11; + case GDK_F12: return NS_VK_F12; + case GDK_F13: return NS_VK_F13; + case GDK_F14: return NS_VK_F14; + case GDK_F15: return NS_VK_F15; + case GDK_F16: return NS_VK_F16; + case GDK_F17: return NS_VK_F17; + case GDK_F18: return NS_VK_F18; + case GDK_F19: return NS_VK_F19; + case GDK_F20: return NS_VK_F20; + case GDK_F21: return NS_VK_F21; + case GDK_F22: return NS_VK_F22; + case GDK_F23: return NS_VK_F23; + case GDK_F24: return NS_VK_F24; + + // context menu key, keysym 0xff67, typically keycode 117 on 105-key + // (Microsoft) x86 keyboards, located between right 'Windows' key and + // right Ctrl key + case GDK_Menu: return NS_VK_CONTEXT_MENU; + case GDK_Sleep: return NS_VK_SLEEP; + + case GDK_3270_Attn: return NS_VK_ATTN; + case GDK_3270_CursorSelect: return NS_VK_CRSEL; + case GDK_3270_ExSelect: return NS_VK_EXSEL; + case GDK_3270_EraseEOF: return NS_VK_EREOF; + case GDK_3270_Play: return NS_VK_PLAY; + // case GDK_XXX: return NS_VK_ZOOM; + case GDK_3270_PA1: return NS_VK_PA1; + + // map Sun Keyboard special keysyms on to NS_VK keys + + // Sun F11 key generates SunF36(0x1005ff10) keysym + case 0x1005ff10: return NS_VK_F11; + // Sun F12 key generates SunF37(0x1005ff11) keysym + case 0x1005ff11: return NS_VK_F12; + default: return 0; + } +} + +void +KeymapWrapper::WillDispatchKeyboardEvent(WidgetKeyboardEvent& aKeyEvent, + GdkEventKey* aGdkKeyEvent) +{ + GetInstance()->WillDispatchKeyboardEventInternal(aKeyEvent, aGdkKeyEvent); +} + +void +KeymapWrapper::WillDispatchKeyboardEventInternal(WidgetKeyboardEvent& aKeyEvent, + GdkEventKey* aGdkKeyEvent) +{ + uint32_t charCode = GetCharCodeFor(aGdkKeyEvent); + if (!charCode) { + MOZ_LOG(gKeymapWrapperLog, LogLevel::Info, + ("%p WillDispatchKeyboardEventInternal, " + "mKeyCode=0x%02X, charCode=0x%08X", + this, aKeyEvent.mKeyCode, aKeyEvent.mCharCode)); + return; + } + + // The mCharCode was set from mKeyValue. However, for example, when Ctrl key + // is pressed, its value should indicate an ASCII character for backward + // compatibility rather than inputting character without the modifiers. + // Therefore, we need to modify mCharCode value here. + aKeyEvent.SetCharCode(charCode); + + gint level = GetKeyLevel(aGdkKeyEvent); + if (level != 0 && level != 1) { + MOZ_LOG(gKeymapWrapperLog, LogLevel::Info, + ("%p WillDispatchKeyboardEventInternal, " + "mKeyCode=0x%02X, mCharCode=0x%08X, level=%d", + this, aKeyEvent.mKeyCode, aKeyEvent.mCharCode, level)); + return; + } + + guint baseState = aGdkKeyEvent->state & + ~(GetModifierMask(SHIFT) | GetModifierMask(CTRL) | + GetModifierMask(ALT) | GetModifierMask(META) | + GetModifierMask(SUPER) | GetModifierMask(HYPER)); + + // We shold send both shifted char and unshifted char, all keyboard layout + // users can use all keys. Don't change event.mCharCode. On some keyboard + // layouts, Ctrl/Alt/Meta keys are used for inputting some characters. + AlternativeCharCode altCharCodes(0, 0); + // unshifted charcode of current keyboard layout. + altCharCodes.mUnshiftedCharCode = + GetCharCodeFor(aGdkKeyEvent, baseState, aGdkKeyEvent->group); + bool isLatin = (altCharCodes.mUnshiftedCharCode <= 0xFF); + // shifted charcode of current keyboard layout. + altCharCodes.mShiftedCharCode = + GetCharCodeFor(aGdkKeyEvent, + baseState | GetModifierMask(SHIFT), + aGdkKeyEvent->group); + isLatin = isLatin && (altCharCodes.mShiftedCharCode <= 0xFF); + if (altCharCodes.mUnshiftedCharCode || altCharCodes.mShiftedCharCode) { + aKeyEvent.mAlternativeCharCodes.AppendElement(altCharCodes); + } + + bool needLatinKeyCodes = !isLatin; + if (!needLatinKeyCodes) { + needLatinKeyCodes = + (IS_ASCII_ALPHABETICAL(altCharCodes.mUnshiftedCharCode) != + IS_ASCII_ALPHABETICAL(altCharCodes.mShiftedCharCode)); + } + + // If current keyboard layout can input Latin characters, we don't need + // more information. + if (!needLatinKeyCodes) { + MOZ_LOG(gKeymapWrapperLog, LogLevel::Info, + ("%p WillDispatchKeyboardEventInternal, " + "mKeyCode=0x%02X, mCharCode=0x%08X, level=%d, altCharCodes={ " + "mUnshiftedCharCode=0x%08X, mShiftedCharCode=0x%08X }", + this, aKeyEvent.mKeyCode, aKeyEvent.mCharCode, level, + altCharCodes.mUnshiftedCharCode, altCharCodes.mShiftedCharCode)); + return; + } + + // Next, find Latin inputtable keyboard layout. + gint minGroup = GetFirstLatinGroup(); + if (minGroup < 0) { + MOZ_LOG(gKeymapWrapperLog, LogLevel::Info, + ("%p WillDispatchKeyboardEventInternal, " + "Latin keyboard layout isn't found: " + "mKeyCode=0x%02X, mCharCode=0x%08X, level=%d, " + "altCharCodes={ mUnshiftedCharCode=0x%08X, " + "mShiftedCharCode=0x%08X }", + this, aKeyEvent.mKeyCode, aKeyEvent.mCharCode, level, + altCharCodes.mUnshiftedCharCode, altCharCodes.mShiftedCharCode)); + return; + } + + AlternativeCharCode altLatinCharCodes(0, 0); + uint32_t unmodifiedCh = + aKeyEvent.IsShift() ? altCharCodes.mShiftedCharCode : + altCharCodes.mUnshiftedCharCode; + + // unshifted charcode of found keyboard layout. + uint32_t ch = GetCharCodeFor(aGdkKeyEvent, baseState, minGroup); + altLatinCharCodes.mUnshiftedCharCode = + IsBasicLatinLetterOrNumeral(ch) ? ch : 0; + // shifted charcode of found keyboard layout. + ch = GetCharCodeFor(aGdkKeyEvent, + baseState | GetModifierMask(SHIFT), + minGroup); + altLatinCharCodes.mShiftedCharCode = + IsBasicLatinLetterOrNumeral(ch) ? ch : 0; + if (altLatinCharCodes.mUnshiftedCharCode || + altLatinCharCodes.mShiftedCharCode) { + aKeyEvent.mAlternativeCharCodes.AppendElement(altLatinCharCodes); + } + // If the mCharCode is not Latin, and the level is 0 or 1, we should + // replace the mCharCode to Latin char if Alt and Meta keys are not + // pressed. (Alt should be sent the localized char for accesskey + // like handling of Web Applications.) + ch = aKeyEvent.IsShift() ? altLatinCharCodes.mShiftedCharCode : + altLatinCharCodes.mUnshiftedCharCode; + if (ch && !(aKeyEvent.IsAlt() || aKeyEvent.IsMeta()) && + charCode == unmodifiedCh) { + aKeyEvent.SetCharCode(ch); + } + + MOZ_LOG(gKeymapWrapperLog, LogLevel::Info, + ("%p WillDispatchKeyboardEventInternal, " + "mKeyCode=0x%02X, mCharCode=0x%08X, level=%d, minGroup=%d, " + "altCharCodes={ mUnshiftedCharCode=0x%08X, " + "mShiftedCharCode=0x%08X } " + "altLatinCharCodes={ mUnshiftedCharCode=0x%08X, " + "mShiftedCharCode=0x%08X }", + this, aKeyEvent.mKeyCode, aKeyEvent.mCharCode, level, minGroup, + altCharCodes.mUnshiftedCharCode, altCharCodes.mShiftedCharCode, + altLatinCharCodes.mUnshiftedCharCode, + altLatinCharCodes.mShiftedCharCode)); +} + +} // namespace widget +} // namespace mozilla diff --git a/widget/gtk/nsGtkKeyUtils.h b/widget/gtk/nsGtkKeyUtils.h new file mode 100644 index 000000000..67528d817 --- /dev/null +++ b/widget/gtk/nsGtkKeyUtils.h @@ -0,0 +1,360 @@ +/* -*- 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/. */ + +#ifndef __nsGdkKeyUtils_h__ +#define __nsGdkKeyUtils_h__ + +#include "nsTArray.h" +#include "mozilla/EventForwards.h" + +#include <gdk/gdk.h> +#include <X11/XKBlib.h> + +namespace mozilla { +namespace widget { + +/** + * KeymapWrapper is a wrapper class of GdkKeymap. GdkKeymap doesn't support + * all our needs, therefore, we need to access lower level APIs. + * But such code is usually complex and might be slow. Against such issues, + * we should cache some information. + * + * This class provides only static methods. The methods is using internal + * singleton instance which is initialized by default GdkKeymap. When the + * GdkKeymap is destroyed, the singleton instance will be destroyed. + */ + +class KeymapWrapper +{ +public: + /** + * Compute an our DOM keycode from a GDK keyval. + */ + static uint32_t ComputeDOMKeyCode(const GdkEventKey* aGdkKeyEvent); + + /** + * Compute a DOM key name index from aGdkKeyEvent. + */ + KeyNameIndex ComputeDOMKeyNameIndex(const GdkEventKey* aGdkKeyEvent); + + /** + * Compute a DOM code name index from aGdkKeyEvent. + */ + static CodeNameIndex ComputeDOMCodeNameIndex( + const GdkEventKey* aGdkKeyEvent); + + /** + * Modifier is list of modifiers which we support in widget level. + */ + enum Modifier { + NOT_MODIFIER = 0x0000, + CAPS_LOCK = 0x0001, + NUM_LOCK = 0x0002, + SCROLL_LOCK = 0x0004, + SHIFT = 0x0008, + CTRL = 0x0010, + ALT = 0x0020, + META = 0x0040, + SUPER = 0x0080, + HYPER = 0x0100, + LEVEL3 = 0x0200, + LEVEL5 = 0x0400 + }; + + /** + * Modifiers is used for combination of Modifier. + * E.g., |Modifiers modifiers = (SHIFT | CTRL);| means Shift and Ctrl. + */ + typedef uint32_t Modifiers; + + /** + * GetCurrentModifierState() returns current modifier key state. + * The "current" means actual state of hardware keyboard when this is + * called. I.e., if some key events are not still dispatched by GDK, + * the state may mismatch with GdkEventKey::state. + * + * @return Current modifier key state. + */ + static guint GetCurrentModifierState(); + + /** + * AreModifiersCurrentlyActive() checks the "current" modifier state + * on aGdkWindow with the keymap of the singleton instance. + * + * @param aModifiers One or more of Modifier values except + * NOT_MODIFIER. + * @return TRUE if all of modifieres in aModifiers are + * active. Otherwise, FALSE. + */ + static bool AreModifiersCurrentlyActive(Modifiers aModifiers); + + /** + * AreModifiersActive() just checks whether aModifierState indicates + * all modifiers in aModifiers are active or not. + * + * @param aModifiers One or more of Modifier values except + * NOT_MODIFIER. + * @param aModifierState GDK's modifier states. + * @return TRUE if aGdkModifierType indecates all of + * modifiers in aModifier are active. + * Otherwise, FALSE. + */ + static bool AreModifiersActive(Modifiers aModifiers, + guint aModifierState); + + /** + * InitInputEvent() initializes the aInputEvent with aModifierState. + */ + static void InitInputEvent(WidgetInputEvent& aInputEvent, + guint aModifierState); + + /** + * InitKeyEvent() intializes aKeyEvent's modifier key related members + * and keycode related values. + * + * @param aKeyEvent It's an WidgetKeyboardEvent which needs to be + * initialized. + * @param aGdkKeyEvent A native GDK key event. + */ + static void InitKeyEvent(WidgetKeyboardEvent& aKeyEvent, + GdkEventKey* aGdkKeyEvent); + + /** + * WillDispatchKeyboardEvent() is called via + * TextEventDispatcherListener::WillDispatchKeyboardEvent(). + * + * @param aKeyEvent An instance of KeyboardEvent which will be + * dispatched. This method should set charCode + * and alternative char codes if it's necessary. + * @param aGdkKeyEvent A GdkEventKey instance which caused the + * aKeyEvent. + */ + static void WillDispatchKeyboardEvent(WidgetKeyboardEvent& aKeyEvent, + GdkEventKey* aGdkKeyEvent); + + /** + * Destroys the singleton KeymapWrapper instance, if it exists. + */ + static void Shutdown(); + +protected: + + /** + * GetInstance() returns a KeymapWrapper instance. + * + * @return A singleton instance of KeymapWrapper. + */ + static KeymapWrapper* GetInstance(); + + KeymapWrapper(); + ~KeymapWrapper(); + + bool mInitialized; + + /** + * Initializing methods. + */ + void Init(); + void InitXKBExtension(); + void InitBySystemSettings(); + + /** + * mModifierKeys stores each hardware key information. + */ + struct ModifierKey { + guint mHardwareKeycode; + guint mMask; + + explicit ModifierKey(guint aHardwareKeycode) : + mHardwareKeycode(aHardwareKeycode), mMask(0) + { + } + }; + nsTArray<ModifierKey> mModifierKeys; + + /** + * GetModifierKey() returns modifier key information of the hardware + * keycode. If the key isn't a modifier key, returns nullptr. + */ + ModifierKey* GetModifierKey(guint aHardwareKeycode); + + /** + * mModifierMasks is bit masks for each modifier. The index should be one + * of ModifierIndex values. + */ + enum ModifierIndex { + INDEX_NUM_LOCK, + INDEX_SCROLL_LOCK, + INDEX_ALT, + INDEX_META, + INDEX_SUPER, + INDEX_HYPER, + INDEX_LEVEL3, + INDEX_LEVEL5, + COUNT_OF_MODIFIER_INDEX + }; + guint mModifierMasks[COUNT_OF_MODIFIER_INDEX]; + + guint GetModifierMask(Modifier aModifier) const; + + /** + * @param aGdkKeyval A GDK defined modifier key value such as + * GDK_Shift_L. + * @return Returns Modifier values for aGdkKeyval. + * If the given key code isn't a modifier key, + * returns NOT_MODIFIER. + */ + static Modifier GetModifierForGDKKeyval(guint aGdkKeyval); + + static const char* GetModifierName(Modifier aModifier); + + /** + * mGdkKeymap is a wrapped instance by this class. + */ + GdkKeymap* mGdkKeymap; + + /** + * The base event code of XKB extension. + */ + int mXKBBaseEventCode; + + /** + * Only auto_repeats[] stores valid value. If you need to use other + * members, you need to listen notification events for them. + * See a call of XkbSelectEventDetails() with XkbControlsNotify in + * InitXKBExtension(). + */ + XKeyboardState mKeyboardState; + + /** + * Pointer of the singleton instance. + */ + static KeymapWrapper* sInstance; + + /** + * Auto key repeat management. + */ + static guint sLastRepeatableHardwareKeyCode; + enum RepeatState + { + NOT_PRESSED, + FIRST_PRESS, + REPEATING + }; + static RepeatState sRepeatState; + + /** + * IsAutoRepeatableKey() returns true if the key supports auto repeat. + * Otherwise, false. + */ + bool IsAutoRepeatableKey(guint aHardwareKeyCode); + + /** + * Signal handlers. + */ + static void OnKeysChanged(GdkKeymap* aKeymap, KeymapWrapper* aKeymapWrapper); + static void OnDirectionChanged(GdkKeymap *aGdkKeymap, + KeymapWrapper* aKeymapWrapper); + + /** + * GetCharCodeFor() Computes what character is inputted by the key event + * with aModifierState and aGroup. + * + * @param aGdkKeyEvent Native key event, must not be nullptr. + * @param aModifierState Combination of GdkModifierType which you + * want to test with aGdkKeyEvent. + * @param aGroup Set group in the mGdkKeymap. + * @return charCode which is inputted by aGdkKeyEvent. + * If failed, this returns 0. + */ + static uint32_t GetCharCodeFor(const GdkEventKey *aGdkKeyEvent); + uint32_t GetCharCodeFor(const GdkEventKey *aGdkKeyEvent, + guint aModifierState, + gint aGroup); + + /** + * GetUnmodifiedCharCodeFor() computes what character is inputted by the + * key event without Ctrl/Alt/Meta/Super/Hyper modifiers. + * If Level3 or Level5 Shift causes no character input, this also ignores + * them. + * + * @param aGdkKeyEvent Native key event, must not be nullptr. + * @return charCode which is computed without modifiers + * which prevent text input. + */ + uint32_t GetUnmodifiedCharCodeFor(const GdkEventKey* aGdkKeyEvent); + + /** + * GetKeyLevel() returns level of the aGdkKeyEvent in mGdkKeymap. + * + * @param aGdkKeyEvent Native key event, must not be nullptr. + * @return Using level. Typically, this is 0 or 1. + * If failed, this returns -1. + */ + gint GetKeyLevel(GdkEventKey *aGdkKeyEvent); + + /** + * GetFirstLatinGroup() returns group of mGdkKeymap which can input an + * ASCII character by GDK_A. + * + * @return group value of GdkEventKey. + */ + gint GetFirstLatinGroup(); + + /** + * IsLatinGroup() checkes whether the keyboard layout of aGroup is + * ASCII alphabet inputtable or not. + * + * @param aGroup The group value of GdkEventKey. + * @return TRUE if the keyboard layout can input + * ASCII alphabet. Otherwise, FALSE. + */ + bool IsLatinGroup(guint8 aGroup); + + /** + * IsBasicLatinLetterOrNumeral() Checks whether the aCharCode is an + * alphabet or a numeric character in ASCII. + * + * @param aCharCode Charcode which you want to test. + * @return TRUE if aCharCode is an alphabet or a numeric + * in ASCII range. Otherwise, FALSE. + */ + static bool IsBasicLatinLetterOrNumeral(uint32_t aCharCode); + + /** + * GetGDKKeyvalWithoutModifier() returns the keyval for aGdkKeyEvent when + * ignoring the modifier state except NumLock. (NumLock is a key to change + * some key's meaning.) + */ + static guint GetGDKKeyvalWithoutModifier(const GdkEventKey *aGdkKeyEvent); + + /** + * GetDOMKeyCodeFromKeyPairs() returns DOM keycode for aGdkKeyval if + * it's in KeyPair table. + */ + static uint32_t GetDOMKeyCodeFromKeyPairs(guint aGdkKeyval); + + /** + * FilterEvents() listens all events on all our windows. + * Be careful, this may make damage to performance if you add expensive + * code in this method. + */ + static GdkFilterReturn FilterEvents(GdkXEvent* aXEvent, + GdkEvent* aGdkEvent, + gpointer aData); + + /** + * See the document of WillDispatchKeyboardEvent(). + */ + void WillDispatchKeyboardEventInternal(WidgetKeyboardEvent& aKeyEvent, + GdkEventKey* aGdkKeyEvent); +}; + +} // namespace widget +} // namespace mozilla + +#endif /* __nsGdkKeyUtils_h__ */ diff --git a/widget/gtk/nsGtkUtils.h b/widget/gtk/nsGtkUtils.h new file mode 100644 index 000000000..cb41ddaf7 --- /dev/null +++ b/widget/gtk/nsGtkUtils.h @@ -0,0 +1,24 @@ +/* -*- 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/. */ + +#ifndef nsGtkUtils_h__ +#define nsGtkUtils_h__ + +#include <glib.h> + +// 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))); +} + +#endif // nsGtkUtils_h__ diff --git a/widget/gtk/nsIImageToPixbuf.h b/widget/gtk/nsIImageToPixbuf.h new file mode 100644 index 000000000..0faa1c6e8 --- /dev/null +++ b/widget/gtk/nsIImageToPixbuf.h @@ -0,0 +1,34 @@ +/* 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/. */ + +#ifndef NSIIMAGETOPIXBUF_H_ +#define NSIIMAGETOPIXBUF_H_ + +#include "nsISupports.h" + +// dfa4ac93-83f2-4ab8-9b2a-0ff7022aebe2 +#define NSIIMAGETOPIXBUF_IID \ +{ 0xdfa4ac93, 0x83f2, 0x4ab8, \ + { 0x9b, 0x2a, 0x0f, 0xf7, 0x02, 0x2a, 0xeb, 0xe2 } } + +class imgIContainer; +typedef struct _GdkPixbuf GdkPixbuf; + +/** + * An interface that allows converting the current frame of an imgIContainer to a GdkPixbuf*. + */ +class nsIImageToPixbuf : public nsISupports { + public: + NS_DECLARE_STATIC_IID_ACCESSOR(NSIIMAGETOPIXBUF_IID) + + /** + * The return value, if not null, should be released as needed + * by the caller using g_object_unref. + */ + NS_IMETHOD_(GdkPixbuf*) ConvertImageToPixbuf(imgIContainer* aImage) = 0; +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(nsIImageToPixbuf, NSIIMAGETOPIXBUF_IID) + +#endif diff --git a/widget/gtk/nsIdleServiceGTK.cpp b/widget/gtk/nsIdleServiceGTK.cpp new file mode 100644 index 000000000..6b6832c82 --- /dev/null +++ b/widget/gtk/nsIdleServiceGTK.cpp @@ -0,0 +1,132 @@ +/* -*- 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 <gtk/gtk.h> + +#include "nsIdleServiceGTK.h" +#include "nsIServiceManager.h" +#include "nsDebug.h" +#include "prlink.h" +#include "mozilla/Logging.h" + +using mozilla::LogLevel; + +static PRLogModuleInfo* sIdleLog = nullptr; + +typedef bool (*_XScreenSaverQueryExtension_fn)(Display* dpy, int* event_base, + int* error_base); + +typedef XScreenSaverInfo* (*_XScreenSaverAllocInfo_fn)(void); + +typedef void (*_XScreenSaverQueryInfo_fn)(Display* dpy, Drawable drw, + XScreenSaverInfo *info); + +static bool sInitialized = false; +static _XScreenSaverQueryExtension_fn _XSSQueryExtension = nullptr; +static _XScreenSaverAllocInfo_fn _XSSAllocInfo = nullptr; +static _XScreenSaverQueryInfo_fn _XSSQueryInfo = nullptr; + +NS_IMPL_ISUPPORTS_INHERITED0(nsIdleServiceGTK, nsIdleService) + +static void Initialize() +{ + if (!GDK_IS_X11_DISPLAY(gdk_display_get_default())) + return; + + // This will leak - See comments in ~nsIdleServiceGTK(). + PRLibrary* xsslib = PR_LoadLibrary("libXss.so.1"); + if (!xsslib) // ouch. + { + MOZ_LOG(sIdleLog, LogLevel::Warning, ("Failed to find libXss.so!\n")); + return; + } + + _XSSQueryExtension = (_XScreenSaverQueryExtension_fn) + PR_FindFunctionSymbol(xsslib, "XScreenSaverQueryExtension"); + _XSSAllocInfo = (_XScreenSaverAllocInfo_fn) + PR_FindFunctionSymbol(xsslib, "XScreenSaverAllocInfo"); + _XSSQueryInfo = (_XScreenSaverQueryInfo_fn) + PR_FindFunctionSymbol(xsslib, "XScreenSaverQueryInfo"); + + if (!_XSSQueryExtension) + MOZ_LOG(sIdleLog, LogLevel::Warning, ("Failed to get XSSQueryExtension!\n")); + if (!_XSSAllocInfo) + MOZ_LOG(sIdleLog, LogLevel::Warning, ("Failed to get XSSAllocInfo!\n")); + if (!_XSSQueryInfo) + MOZ_LOG(sIdleLog, LogLevel::Warning, ("Failed to get XSSQueryInfo!\n")); + + sInitialized = true; +} + +nsIdleServiceGTK::nsIdleServiceGTK() + : mXssInfo(nullptr) +{ + if (!sIdleLog) + sIdleLog = PR_NewLogModule("nsIIdleService"); + + Initialize(); +} + +nsIdleServiceGTK::~nsIdleServiceGTK() +{ + if (mXssInfo) + XFree(mXssInfo); + +// It is not safe to unload libXScrnSaver until each display is closed because +// the library registers callbacks through XESetCloseDisplay (Bug 397607). +// (Also the library and its functions are scoped for the file not the object.) +#if 0 + if (xsslib) { + PR_UnloadLibrary(xsslib); + xsslib = nullptr; + } +#endif +} + +bool +nsIdleServiceGTK::PollIdleTime(uint32_t *aIdleTime) +{ + if (!sInitialized) { + // For some reason, we could not find xscreensaver. + return false; + } + + // Ask xscreensaver about idle time: + *aIdleTime = 0; + + // We might not have a display (cf. in xpcshell) + Display *dplay = GDK_DISPLAY_XDISPLAY(gdk_display_get_default()); + if (!dplay) { + MOZ_LOG(sIdleLog, LogLevel::Warning, ("No display found!\n")); + return false; + } + + if (!_XSSQueryExtension || !_XSSAllocInfo || !_XSSQueryInfo) { + return false; + } + + int event_base, error_base; + if (_XSSQueryExtension(dplay, &event_base, &error_base)) + { + if (!mXssInfo) + mXssInfo = _XSSAllocInfo(); + if (!mXssInfo) + return false; + _XSSQueryInfo(dplay, GDK_ROOT_WINDOW(), mXssInfo); + *aIdleTime = mXssInfo->idle; + return true; + } + // If we get here, we couldn't get to XScreenSaver: + MOZ_LOG(sIdleLog, LogLevel::Warning, ("XSSQueryExtension returned false!\n")); + return false; +} + +bool +nsIdleServiceGTK::UsePollMode() +{ + return sInitialized; +} diff --git a/widget/gtk/nsIdleServiceGTK.h b/widget/gtk/nsIdleServiceGTK.h new file mode 100644 index 000000000..01ae9268e --- /dev/null +++ b/widget/gtk/nsIdleServiceGTK.h @@ -0,0 +1,52 @@ +/* -*- 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/. */ + +#ifndef nsIdleServiceGTK_h__ +#define nsIdleServiceGTK_h__ + +#include "nsIdleService.h" +#include <X11/Xlib.h> +#include <X11/Xutil.h> +#include <gdk/gdkx.h> + +typedef struct { + Window window; // Screen saver window + int state; // ScreenSaver(Off,On,Disabled) + int kind; // ScreenSaver(Blanked,Internal,External) + unsigned long til_or_since; // milliseconds since/til screensaver kicks in + unsigned long idle; // milliseconds idle + unsigned long event_mask; // event stuff +} XScreenSaverInfo; + +class nsIdleServiceGTK : public nsIdleService +{ +public: + NS_DECL_ISUPPORTS_INHERITED + + virtual bool PollIdleTime(uint32_t* aIdleTime) override; + + static already_AddRefed<nsIdleServiceGTK> GetInstance() + { + RefPtr<nsIdleServiceGTK> idleService = + nsIdleService::GetInstance().downcast<nsIdleServiceGTK>(); + if (!idleService) { + idleService = new nsIdleServiceGTK(); + } + + return idleService.forget(); + } + +private: + ~nsIdleServiceGTK(); + XScreenSaverInfo* mXssInfo; + +protected: + nsIdleServiceGTK(); + virtual bool UsePollMode() override; +}; + +#endif // nsIdleServiceGTK_h__ diff --git a/widget/gtk/nsImageToPixbuf.cpp b/widget/gtk/nsImageToPixbuf.cpp new file mode 100644 index 000000000..e06605b2b --- /dev/null +++ b/widget/gtk/nsImageToPixbuf.cpp @@ -0,0 +1,120 @@ +/* vim:set sw=4 sts=4 et cin: */ +/* 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 <gdk-pixbuf/gdk-pixbuf.h> + +#include "nsImageToPixbuf.h" + +#include "imgIContainer.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/RefPtr.h" + +using mozilla::gfx::DataSourceSurface; +using mozilla::gfx::SurfaceFormat; + +NS_IMPL_ISUPPORTS(nsImageToPixbuf, nsIImageToPixbuf) + +inline unsigned char +unpremultiply (unsigned char color, + unsigned char alpha) +{ + if (alpha == 0) + return 0; + // plus alpha/2 to round instead of truncate + return (color * 255 + alpha / 2) / alpha; +} + +NS_IMETHODIMP_(GdkPixbuf*) +nsImageToPixbuf::ConvertImageToPixbuf(imgIContainer* aImage) +{ + return ImageToPixbuf(aImage); +} + +GdkPixbuf* +nsImageToPixbuf::ImageToPixbuf(imgIContainer* aImage) +{ + RefPtr<SourceSurface> surface = + aImage->GetFrame(imgIContainer::FRAME_CURRENT, + imgIContainer::FLAG_SYNC_DECODE); + + // If the last call failed, it was probably because our call stack originates + // in an imgINotificationObserver event, meaning that we're not allowed request + // a sync decode. Presumably the originating event is something sensible like + // OnStopFrame(), so we can just retry the call without a sync decode. + if (!surface) + surface = aImage->GetFrame(imgIContainer::FRAME_CURRENT, + imgIContainer::FLAG_NONE); + + NS_ENSURE_TRUE(surface, nullptr); + + return SourceSurfaceToPixbuf(surface, + surface->GetSize().width, + surface->GetSize().height); +} + +GdkPixbuf* +nsImageToPixbuf::SourceSurfaceToPixbuf(SourceSurface* aSurface, + int32_t aWidth, + int32_t aHeight) +{ + MOZ_ASSERT(aWidth <= aSurface->GetSize().width && + aHeight <= aSurface->GetSize().height, + "Requested rect is bigger than the supplied surface"); + + GdkPixbuf* pixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, + aWidth, aHeight); + if (!pixbuf) + return nullptr; + + uint32_t destStride = gdk_pixbuf_get_rowstride (pixbuf); + guchar* destPixels = gdk_pixbuf_get_pixels (pixbuf); + + RefPtr<DataSourceSurface> dataSurface = aSurface->GetDataSurface(); + DataSourceSurface::MappedSurface map; + if (!dataSurface->Map(DataSourceSurface::MapType::READ, &map)) + return nullptr; + + uint8_t* srcData = map.mData; + int32_t srcStride = map.mStride; + + SurfaceFormat format = dataSurface->GetFormat(); + + for (int32_t row = 0; row < aHeight; ++row) { + for (int32_t col = 0; col < aWidth; ++col) { + guchar* destPixel = destPixels + row * destStride + 4 * col; + + uint32_t* srcPixel = + reinterpret_cast<uint32_t*>((srcData + row * srcStride + 4 * col)); + + if (format == SurfaceFormat::B8G8R8A8) { + const uint8_t a = (*srcPixel >> 24) & 0xFF; + const uint8_t r = unpremultiply((*srcPixel >> 16) & 0xFF, a); + const uint8_t g = unpremultiply((*srcPixel >> 8) & 0xFF, a); + const uint8_t b = unpremultiply((*srcPixel >> 0) & 0xFF, a); + + *destPixel++ = r; + *destPixel++ = g; + *destPixel++ = b; + *destPixel++ = a; + } else { + MOZ_ASSERT(format == SurfaceFormat::B8G8R8X8); + + const uint8_t r = (*srcPixel >> 16) & 0xFF; + const uint8_t g = (*srcPixel >> 8) & 0xFF; + const uint8_t b = (*srcPixel >> 0) & 0xFF; + + *destPixel++ = r; + *destPixel++ = g; + *destPixel++ = b; + *destPixel++ = 0xFF; // A + } + } + } + + dataSurface->Unmap(); + + return pixbuf; +} + diff --git a/widget/gtk/nsImageToPixbuf.h b/widget/gtk/nsImageToPixbuf.h new file mode 100644 index 000000000..9c026048a --- /dev/null +++ b/widget/gtk/nsImageToPixbuf.h @@ -0,0 +1,46 @@ +/* vim:set sw=4 sts=4 et cin: */ +/* 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/. */ + +#ifndef NSIMAGETOPIXBUF_H_ +#define NSIMAGETOPIXBUF_H_ + +#include "nsIImageToPixbuf.h" +#include "mozilla/Attributes.h" + +namespace mozilla { +namespace gfx { +class SourceSurface; +} +} + +class nsImageToPixbuf final : public nsIImageToPixbuf { + typedef mozilla::gfx::SourceSurface SourceSurface; + + public: + NS_DECL_ISUPPORTS + NS_IMETHOD_(GdkPixbuf*) ConvertImageToPixbuf(imgIContainer* aImage) override; + + // Friendlier version of ConvertImageToPixbuf for callers inside of + // widget + /** + * The return value of all these, if not null, should be + * released as needed by the caller using g_object_unref. + */ + static GdkPixbuf* ImageToPixbuf(imgIContainer * aImage); + static GdkPixbuf* SourceSurfaceToPixbuf(SourceSurface* aSurface, + int32_t aWidth, + int32_t aHeight); + + private: + ~nsImageToPixbuf() {} +}; + + +// fc2389b8-c650-4093-9e42-b05e5f0685b7 +#define NS_IMAGE_TO_PIXBUF_CID \ +{ 0xfc2389b8, 0xc650, 0x4093, \ + { 0x9e, 0x42, 0xb0, 0x5e, 0x5f, 0x06, 0x85, 0xb7 } } + +#endif diff --git a/widget/gtk/nsLookAndFeel.cpp b/widget/gtk/nsLookAndFeel.cpp new file mode 100644 index 000000000..53430dfbb --- /dev/null +++ b/widget/gtk/nsLookAndFeel.cpp @@ -0,0 +1,1465 @@ +/* -*- 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/. */ + +// for strtod() +#include <stdlib.h> + +#include "nsLookAndFeel.h" + +#include <gtk/gtk.h> +#include <gdk/gdk.h> + +#include <pango/pango.h> +#include <pango/pango-fontmap.h> + +#include <fontconfig/fontconfig.h> +#include "gfxPlatformGtk.h" +#include "nsScreenGtk.h" + +#include "gtkdrawing.h" +#include "nsStyleConsts.h" +#include "gfxFontConstants.h" +#include "WidgetUtils.h" + +#include <dlfcn.h> + +#include "mozilla/gfx/2D.h" + +#if MOZ_WIDGET_GTK != 2 +#include <cairo-gobject.h> +#include "WidgetStyleCache.h" +#include "prenv.h" +#endif + +using mozilla::LookAndFeel; + +#define GDK_COLOR_TO_NS_RGB(c) \ + ((nscolor) NS_RGB(c.red>>8, c.green>>8, c.blue>>8)) +#define GDK_RGBA_TO_NS_RGBA(c) \ + ((nscolor) NS_RGBA((int)((c).red*255), (int)((c).green*255), \ + (int)((c).blue*255), (int)((c).alpha*255))) + +nsLookAndFeel::nsLookAndFeel() + : nsXPLookAndFeel(), +#if (MOZ_WIDGET_GTK == 2) + mStyle(nullptr), +#else + mBackgroundStyle(nullptr), + mButtonStyle(nullptr), +#endif + mDefaultFontCached(false), mButtonFontCached(false), + mFieldFontCached(false), mMenuFontCached(false) +{ + Init(); +} + +nsLookAndFeel::~nsLookAndFeel() +{ +#if (MOZ_WIDGET_GTK == 2) + g_object_unref(mStyle); +#else + g_object_unref(mBackgroundStyle); + g_object_unref(mButtonStyle); +#endif +} + +#if MOZ_WIDGET_GTK != 2 +static void +GetLightAndDarkness(const GdkRGBA& aColor, + double* aLightness, double* aDarkness) +{ + double sum = aColor.red + aColor.green + aColor.blue; + *aLightness = sum * aColor.alpha; + *aDarkness = (3.0 - sum) * aColor.alpha; +} + +static bool +GetGradientColors(const GValue* aValue, + GdkRGBA* aLightColor, GdkRGBA* aDarkColor) +{ + if (!G_TYPE_CHECK_VALUE_TYPE(aValue, CAIRO_GOBJECT_TYPE_PATTERN)) + return false; + + auto pattern = static_cast<cairo_pattern_t*>(g_value_get_boxed(aValue)); + if (!pattern) + return false; + + // Just picking the lightest and darkest colors as simple samples rather + // than trying to blend, which could get messy if there are many stops. + if (CAIRO_STATUS_SUCCESS != + cairo_pattern_get_color_stop_rgba(pattern, 0, nullptr, &aDarkColor->red, + &aDarkColor->green, &aDarkColor->blue, + &aDarkColor->alpha)) + return false; + + double maxLightness, maxDarkness; + GetLightAndDarkness(*aDarkColor, &maxLightness, &maxDarkness); + *aLightColor = *aDarkColor; + + GdkRGBA stop; + for (int index = 1; + CAIRO_STATUS_SUCCESS == + cairo_pattern_get_color_stop_rgba(pattern, index, nullptr, + &stop.red, &stop.green, + &stop.blue, &stop.alpha); + ++index) { + double lightness, darkness; + GetLightAndDarkness(stop, &lightness, &darkness); + if (lightness > maxLightness) { + maxLightness = lightness; + *aLightColor = stop; + } + if (darkness > maxDarkness) { + maxDarkness = darkness; + *aDarkColor = stop; + } + } + + return true; +} + +static bool +GetUnicoBorderGradientColors(GtkStyleContext* aContext, + GdkRGBA* aLightColor, GdkRGBA* aDarkColor) +{ + // Ubuntu 12.04 has GTK engine Unico-1.0.2, which overrides render_frame, + // providing its own border code. Ubuntu 14.04 has + // Unico-1.0.3+14.04.20140109, which does not override render_frame, and + // so does not need special attention. The earlier Unico can be detected + // by the -unico-border-gradient style property it registers. + // gtk_style_properties_lookup_property() is checked first to avoid the + // warning from gtk_style_context_get_property() when the property does + // not exist. (gtk_render_frame() of GTK+ 3.16 no longer uses the + // engine.) + const char* propertyName = "-unico-border-gradient"; + if (!gtk_style_properties_lookup_property(propertyName, nullptr, nullptr)) + return false; + + // -unico-border-gradient is used only when the CSS node's engine is Unico. + GtkThemingEngine* engine; + GtkStateFlags state = gtk_style_context_get_state(aContext); + gtk_style_context_get(aContext, state, "engine", &engine, nullptr); + if (strcmp(g_type_name(G_TYPE_FROM_INSTANCE(engine)), "UnicoEngine") != 0) + return false; + + // draw_border() of Unico engine uses -unico-border-gradient + // in preference to border-color. + GValue value = G_VALUE_INIT; + gtk_style_context_get_property(aContext, propertyName, state, &value); + + bool result = GetGradientColors(&value, aLightColor, aDarkColor); + + g_value_unset(&value); + return result; +} + +// Sets |aLightColor| and |aDarkColor| to colors from |aContext|. Returns +// true if |aContext| uses these colors to render a visible border. +// If returning false, then the colors returned are a fallback from the +// border-color value even though |aContext| does not use these colors to +// render a border. +static bool +GetBorderColors(GtkStyleContext* aContext, + GdkRGBA* aLightColor, GdkRGBA* aDarkColor) +{ + // Determine whether the border on this style context is visible. + GtkStateFlags state = gtk_style_context_get_state(aContext); + GtkBorderStyle borderStyle; + gtk_style_context_get(aContext, state, GTK_STYLE_PROPERTY_BORDER_STYLE, + &borderStyle, nullptr); + bool visible = borderStyle != GTK_BORDER_STYLE_NONE && + borderStyle != GTK_BORDER_STYLE_HIDDEN; + if (visible) { + // GTK has an initial value of zero for border-widths, and so themes + // need to explicitly set border-widths to make borders visible. + GtkBorder border; + gtk_style_context_get_border(aContext, GTK_STATE_FLAG_NORMAL, &border); + visible = border.top != 0 || border.right != 0 || + border.bottom != 0 || border.left != 0; + } + + if (visible && + GetUnicoBorderGradientColors(aContext, aLightColor, aDarkColor)) + return true; + + // The initial value for the border-color is the foreground color, and so + // this will usually return a color distinct from the background even if + // there is no visible border detected. + gtk_style_context_get_border_color(aContext, state, aDarkColor); + // TODO GTK3 - update aLightColor + // for GTK_BORDER_STYLE_INSET/OUTSET/GROVE/RIDGE border styles. + // https://bugzilla.mozilla.org/show_bug.cgi?id=978172#c25 + *aLightColor = *aDarkColor; + return visible; +} + +static bool +GetBorderColors(GtkStyleContext* aContext, + nscolor* aLightColor, nscolor* aDarkColor) +{ + GdkRGBA lightColor, darkColor; + bool ret = GetBorderColors(aContext, &lightColor, &darkColor); + *aLightColor = GDK_RGBA_TO_NS_RGBA(lightColor); + *aDarkColor = GDK_RGBA_TO_NS_RGBA(darkColor); + return ret; +} +#endif + +nsresult +nsLookAndFeel::NativeGetColor(ColorID aID, nscolor& aColor) +{ +#if (MOZ_WIDGET_GTK == 3) + GdkRGBA gdk_color; +#endif + nsresult res = NS_OK; + + switch (aID) { + // These colors don't seem to be used for anything anymore in Mozilla + // (except here at least TextSelectBackground and TextSelectForeground) + // The CSS2 colors below are used. +#if (MOZ_WIDGET_GTK == 2) + case eColorID_WindowBackground: + aColor = GDK_COLOR_TO_NS_RGB(mStyle->base[GTK_STATE_NORMAL]); + break; + case eColorID_WindowForeground: + aColor = GDK_COLOR_TO_NS_RGB(mStyle->text[GTK_STATE_NORMAL]); + break; + case eColorID_WidgetBackground: + aColor = GDK_COLOR_TO_NS_RGB(mStyle->bg[GTK_STATE_NORMAL]); + break; + case eColorID_WidgetForeground: + aColor = GDK_COLOR_TO_NS_RGB(mStyle->fg[GTK_STATE_NORMAL]); + break; + case eColorID_WidgetSelectBackground: + aColor = GDK_COLOR_TO_NS_RGB(mStyle->bg[GTK_STATE_SELECTED]); + break; + case eColorID_WidgetSelectForeground: + aColor = GDK_COLOR_TO_NS_RGB(mStyle->fg[GTK_STATE_SELECTED]); + break; +#else + case eColorID_WindowBackground: + case eColorID_WidgetBackground: + case eColorID_TextBackground: + case eColorID_activecaption: // active window caption background + case eColorID_appworkspace: // MDI background color + case eColorID_background: // desktop background + case eColorID_window: + case eColorID_windowframe: + case eColorID__moz_dialog: + case eColorID__moz_combobox: + aColor = sMozWindowBackground; + break; + case eColorID_WindowForeground: + case eColorID_WidgetForeground: + case eColorID_TextForeground: + case eColorID_captiontext: // text in active window caption, size box, and scrollbar arrow box (!) + case eColorID_windowtext: + case eColorID__moz_dialogtext: + aColor = sMozWindowText; + break; + case eColorID_WidgetSelectBackground: + case eColorID_TextSelectBackground: + case eColorID_IMESelectedRawTextBackground: + case eColorID_IMESelectedConvertedTextBackground: + case eColorID__moz_dragtargetzone: + case eColorID__moz_cellhighlight: + case eColorID__moz_html_cellhighlight: + case eColorID_highlight: // preference selected item, + aColor = sTextSelectedBackground; + break; + case eColorID_WidgetSelectForeground: + case eColorID_TextSelectForeground: + case eColorID_IMESelectedRawTextForeground: + case eColorID_IMESelectedConvertedTextForeground: + case eColorID_highlighttext: + case eColorID__moz_cellhighlighttext: + case eColorID__moz_html_cellhighlighttext: + aColor = sTextSelectedText; + break; +#endif + case eColorID_Widget3DHighlight: + aColor = NS_RGB(0xa0,0xa0,0xa0); + break; + case eColorID_Widget3DShadow: + aColor = NS_RGB(0x40,0x40,0x40); + break; +#if (MOZ_WIDGET_GTK == 2) + case eColorID_TextBackground: + // not used? + aColor = GDK_COLOR_TO_NS_RGB(mStyle->base[GTK_STATE_NORMAL]); + break; + case eColorID_TextForeground: + // not used? + aColor = GDK_COLOR_TO_NS_RGB(mStyle->text[GTK_STATE_NORMAL]); + break; + case eColorID_TextSelectBackground: + case eColorID_IMESelectedRawTextBackground: + case eColorID_IMESelectedConvertedTextBackground: + // still used + aColor = GDK_COLOR_TO_NS_RGB(mStyle->base[GTK_STATE_SELECTED]); + break; + case eColorID_TextSelectForeground: + case eColorID_IMESelectedRawTextForeground: + case eColorID_IMESelectedConvertedTextForeground: + // still used + aColor = GDK_COLOR_TO_NS_RGB(mStyle->text[GTK_STATE_SELECTED]); + break; +#endif + case eColorID_IMERawInputBackground: + case eColorID_IMEConvertedTextBackground: + aColor = NS_TRANSPARENT; + break; + case eColorID_IMERawInputForeground: + case eColorID_IMEConvertedTextForeground: + aColor = NS_SAME_AS_FOREGROUND_COLOR; + break; + case eColorID_IMERawInputUnderline: + case eColorID_IMEConvertedTextUnderline: + aColor = NS_SAME_AS_FOREGROUND_COLOR; + break; + case eColorID_IMESelectedRawTextUnderline: + case eColorID_IMESelectedConvertedTextUnderline: + aColor = NS_TRANSPARENT; + break; + case eColorID_SpellCheckerUnderline: + aColor = NS_RGB(0xff, 0, 0); + break; + +#if (MOZ_WIDGET_GTK == 2) + // css2 http://www.w3.org/TR/REC-CSS2/ui.html#system-colors + case eColorID_activeborder: + // active window border + aColor = GDK_COLOR_TO_NS_RGB(mStyle->bg[GTK_STATE_NORMAL]); + break; + case eColorID_activecaption: + // active window caption background + aColor = GDK_COLOR_TO_NS_RGB(mStyle->bg[GTK_STATE_NORMAL]); + break; + case eColorID_appworkspace: + // MDI background color + aColor = GDK_COLOR_TO_NS_RGB(mStyle->bg[GTK_STATE_NORMAL]); + break; + case eColorID_background: + // desktop background + aColor = GDK_COLOR_TO_NS_RGB(mStyle->bg[GTK_STATE_NORMAL]); + break; + case eColorID_captiontext: + // text in active window caption, size box, and scrollbar arrow box (!) + aColor = GDK_COLOR_TO_NS_RGB(mStyle->fg[GTK_STATE_NORMAL]); + break; + case eColorID_graytext: + // disabled text in windows, menus, etc. + aColor = GDK_COLOR_TO_NS_RGB(mStyle->fg[GTK_STATE_INSENSITIVE]); + break; + case eColorID_highlight: + // background of selected item + aColor = GDK_COLOR_TO_NS_RGB(mStyle->base[GTK_STATE_SELECTED]); + break; + case eColorID_highlighttext: + // text of selected item + aColor = GDK_COLOR_TO_NS_RGB(mStyle->text[GTK_STATE_SELECTED]); + break; + case eColorID_inactiveborder: + // inactive window border + aColor = GDK_COLOR_TO_NS_RGB(mStyle->bg[GTK_STATE_NORMAL]); + break; + case eColorID_inactivecaption: + // inactive window caption + aColor = GDK_COLOR_TO_NS_RGB(mStyle->bg[GTK_STATE_INSENSITIVE]); + break; + case eColorID_inactivecaptiontext: + // text in inactive window caption + aColor = GDK_COLOR_TO_NS_RGB(mStyle->fg[GTK_STATE_INSENSITIVE]); + break; +#else + // css2 http://www.w3.org/TR/REC-CSS2/ui.html#system-colors + case eColorID_activeborder: + // active window border + gtk_style_context_get_border_color(mBackgroundStyle, + GTK_STATE_FLAG_NORMAL, &gdk_color); + aColor = GDK_RGBA_TO_NS_RGBA(gdk_color); + break; + case eColorID_inactiveborder: + // inactive window border + gtk_style_context_get_border_color(mBackgroundStyle, + GTK_STATE_FLAG_INSENSITIVE, + &gdk_color); + aColor = GDK_RGBA_TO_NS_RGBA(gdk_color); + break; + case eColorID_graytext: // disabled text in windows, menus, etc. + case eColorID_inactivecaptiontext: // text in inactive window caption + aColor = sMenuTextInactive; + break; + case eColorID_inactivecaption: + // inactive window caption + gtk_style_context_get_background_color(mBackgroundStyle, + GTK_STATE_FLAG_INSENSITIVE, + &gdk_color); + aColor = GDK_RGBA_TO_NS_RGBA(gdk_color); + break; +#endif + case eColorID_infobackground: + // tooltip background color + aColor = sInfoBackground; + break; + case eColorID_infotext: + // tooltip text color + aColor = sInfoText; + break; + case eColorID_menu: + // menu background + aColor = sMenuBackground; + break; + case eColorID_menutext: + // menu text + aColor = sMenuText; + break; + case eColorID_scrollbar: + // scrollbar gray area +#if (MOZ_WIDGET_GTK == 2) + aColor = GDK_COLOR_TO_NS_RGB(mStyle->bg[GTK_STATE_ACTIVE]); +#else + aColor = sMozScrollbar; +#endif + break; + + case eColorID_threedlightshadow: + // 3-D highlighted inner edge color + // always same as background in GTK code + case eColorID_threedface: + case eColorID_buttonface: + // 3-D face color +#if (MOZ_WIDGET_GTK == 3) + aColor = sMozWindowBackground; +#else + aColor = sButtonBackground; +#endif + break; + + case eColorID_buttontext: + // text on push buttons + aColor = sButtonText; + break; + + case eColorID_buttonhighlight: + // 3-D highlighted edge color + case eColorID_threedhighlight: + // 3-D highlighted outer edge color + aColor = sFrameOuterLightBorder; + break; + + case eColorID_buttonshadow: + // 3-D shadow edge color + case eColorID_threedshadow: + // 3-D shadow inner edge color + aColor = sFrameInnerDarkBorder; + break; + +#if (MOZ_WIDGET_GTK == 2) + case eColorID_threeddarkshadow: + // 3-D shadow outer edge color + aColor = GDK_COLOR_TO_NS_RGB(mStyle->black); + break; + + case eColorID_window: + case eColorID_windowframe: + aColor = GDK_COLOR_TO_NS_RGB(mStyle->bg[GTK_STATE_NORMAL]); + break; + + case eColorID_windowtext: + aColor = GDK_COLOR_TO_NS_RGB(mStyle->fg[GTK_STATE_NORMAL]); + break; + + case eColorID__moz_eventreerow: + case eColorID__moz_field: + aColor = GDK_COLOR_TO_NS_RGB(mStyle->base[GTK_STATE_NORMAL]); + break; + case eColorID__moz_fieldtext: + aColor = GDK_COLOR_TO_NS_RGB(mStyle->text[GTK_STATE_NORMAL]); + break; + case eColorID__moz_dialog: + aColor = GDK_COLOR_TO_NS_RGB(mStyle->bg[GTK_STATE_NORMAL]); + break; + case eColorID__moz_dialogtext: + aColor = GDK_COLOR_TO_NS_RGB(mStyle->fg[GTK_STATE_NORMAL]); + break; + case eColorID__moz_dragtargetzone: + aColor = GDK_COLOR_TO_NS_RGB(mStyle->bg[GTK_STATE_SELECTED]); + break; + case eColorID__moz_buttondefault: + // default button border color + aColor = GDK_COLOR_TO_NS_RGB(mStyle->black); + break; + case eColorID__moz_buttonhoverface: + aColor = GDK_COLOR_TO_NS_RGB(mStyle->bg[GTK_STATE_PRELIGHT]); + break; + case eColorID__moz_buttonhovertext: + aColor = GDK_COLOR_TO_NS_RGB(mStyle->fg[GTK_STATE_PRELIGHT]); + break; + case eColorID__moz_cellhighlight: + case eColorID__moz_html_cellhighlight: + aColor = GDK_COLOR_TO_NS_RGB(mStyle->base[GTK_STATE_ACTIVE]); + break; + case eColorID__moz_cellhighlighttext: + case eColorID__moz_html_cellhighlighttext: + aColor = GDK_COLOR_TO_NS_RGB(mStyle->text[GTK_STATE_ACTIVE]); + break; +#else + case eColorID_threeddarkshadow: + // Hardcode to black + aColor = NS_RGB(0x00,0x00,0x00); + break; + + case eColorID__moz_eventreerow: + case eColorID__moz_field: + aColor = sMozFieldBackground; + break; + case eColorID__moz_fieldtext: + aColor = sMozFieldText; + break; + case eColorID__moz_buttondefault: + // default button border color + gtk_style_context_get_border_color(mButtonStyle, + GTK_STATE_FLAG_NORMAL, &gdk_color); + aColor = GDK_RGBA_TO_NS_RGBA(gdk_color); + break; + case eColorID__moz_buttonhoverface: + gtk_style_context_get_background_color(mButtonStyle, + GTK_STATE_FLAG_PRELIGHT, + &gdk_color); + aColor = GDK_RGBA_TO_NS_RGBA(gdk_color); + break; + case eColorID__moz_buttonhovertext: + aColor = sButtonHoverText; + break; +#endif + case eColorID__moz_menuhover: + aColor = sMenuHover; + break; + case eColorID__moz_menuhovertext: + aColor = sMenuHoverText; + break; + case eColorID__moz_oddtreerow: + aColor = sOddCellBackground; + break; + case eColorID__moz_nativehyperlinktext: + aColor = sNativeHyperLinkText; + break; + case eColorID__moz_comboboxtext: + aColor = sComboBoxText; + break; +#if (MOZ_WIDGET_GTK == 2) + case eColorID__moz_combobox: + aColor = sComboBoxBackground; + break; +#endif + case eColorID__moz_menubartext: + aColor = sMenuBarText; + break; + case eColorID__moz_menubarhovertext: + aColor = sMenuBarHoverText; + break; + case eColorID__moz_gtk_info_bar_text: +#if (MOZ_WIDGET_GTK == 3) + aColor = sInfoBarText; +#else + aColor = sInfoText; +#endif + break; + default: + /* default color is BLACK */ + aColor = 0; + res = NS_ERROR_FAILURE; + break; + } + + return res; +} + +#if (MOZ_WIDGET_GTK == 2) +static void darken_gdk_color(GdkColor *src, GdkColor *dest) +{ + gdouble red; + gdouble green; + gdouble blue; + + red = (gdouble) src->red / 65535.0; + green = (gdouble) src->green / 65535.0; + blue = (gdouble) src->blue / 65535.0; + + red *= 0.93; + green *= 0.93; + blue *= 0.93; + + dest->red = red * 65535.0; + dest->green = green * 65535.0; + dest->blue = blue * 65535.0; +} +#endif + +static int32_t CheckWidgetStyle(GtkWidget* aWidget, const char* aStyle, int32_t aResult) { + gboolean value = FALSE; + gtk_widget_style_get(aWidget, aStyle, &value, nullptr); + return value ? aResult : 0; +} + +static int32_t ConvertGTKStepperStyleToMozillaScrollArrowStyle(GtkWidget* aWidget) +{ + if (!aWidget) + return mozilla::LookAndFeel::eScrollArrowStyle_Single; + + return + CheckWidgetStyle(aWidget, "has-backward-stepper", + mozilla::LookAndFeel::eScrollArrow_StartBackward) | + CheckWidgetStyle(aWidget, "has-forward-stepper", + mozilla::LookAndFeel::eScrollArrow_EndForward) | + CheckWidgetStyle(aWidget, "has-secondary-backward-stepper", + mozilla::LookAndFeel::eScrollArrow_EndBackward) | + CheckWidgetStyle(aWidget, "has-secondary-forward-stepper", + mozilla::LookAndFeel::eScrollArrow_StartForward); +} + +nsresult +nsLookAndFeel::GetIntImpl(IntID aID, int32_t &aResult) +{ + nsresult res = NS_OK; + + // Set these before they can get overrided in the nsXPLookAndFeel. + switch (aID) { + case eIntID_ScrollButtonLeftMouseButtonAction: + aResult = 0; + return NS_OK; + case eIntID_ScrollButtonMiddleMouseButtonAction: + aResult = 1; + return NS_OK; + case eIntID_ScrollButtonRightMouseButtonAction: + aResult = 2; + return NS_OK; + default: + break; + } + + res = nsXPLookAndFeel::GetIntImpl(aID, aResult); + if (NS_SUCCEEDED(res)) + return res; + res = NS_OK; + + switch (aID) { + case eIntID_CaretBlinkTime: + { + GtkSettings *settings; + gint blink_time; + gboolean blink; + + settings = gtk_settings_get_default (); + g_object_get (settings, + "gtk-cursor-blink-time", &blink_time, + "gtk-cursor-blink", &blink, + nullptr); + + if (blink) + aResult = (int32_t) blink_time; + else + aResult = 0; + break; + } + case eIntID_CaretWidth: + aResult = 1; + break; + case eIntID_ShowCaretDuringSelection: + aResult = 0; + break; + case eIntID_SelectTextfieldsOnKeyFocus: + { + GtkWidget *entry; + GtkSettings *settings; + gboolean select_on_focus; + + entry = gtk_entry_new(); + g_object_ref_sink(entry); + settings = gtk_widget_get_settings(entry); + g_object_get(settings, + "gtk-entry-select-on-focus", + &select_on_focus, + nullptr); + + if(select_on_focus) + aResult = 1; + else + aResult = 0; + + gtk_widget_destroy(entry); + g_object_unref(entry); + } + break; + case eIntID_ScrollToClick: + { + GtkSettings *settings; + gboolean warps_slider = FALSE; + + settings = gtk_settings_get_default (); + if (g_object_class_find_property (G_OBJECT_GET_CLASS(settings), + "gtk-primary-button-warps-slider")) { + g_object_get (settings, + "gtk-primary-button-warps-slider", + &warps_slider, + nullptr); + } + + if (warps_slider) + aResult = 1; + else + aResult = 0; + } + break; + case eIntID_SubmenuDelay: + { + GtkSettings *settings; + gint delay; + + settings = gtk_settings_get_default (); + g_object_get (settings, "gtk-menu-popup-delay", &delay, nullptr); + aResult = (int32_t) delay; + break; + } + case eIntID_TooltipDelay: + { + aResult = 500; + break; + } + case eIntID_MenusCanOverlapOSBar: + // we want XUL popups to be able to overlap the task bar. + aResult = 1; + break; + case eIntID_SkipNavigatingDisabledMenuItem: + aResult = 1; + break; + case eIntID_DragThresholdX: + case eIntID_DragThresholdY: + { + GtkWidget* box = gtk_hbox_new(FALSE, 5); + gint threshold = 0; + g_object_get(gtk_widget_get_settings(box), + "gtk-dnd-drag-threshold", &threshold, + nullptr); + g_object_ref_sink(box); + + aResult = threshold; + } + break; + case eIntID_ScrollArrowStyle: + moz_gtk_init(); + aResult = + ConvertGTKStepperStyleToMozillaScrollArrowStyle(moz_gtk_get_scrollbar_widget()); + break; + case eIntID_ScrollSliderStyle: + aResult = eScrollThumbStyle_Proportional; + break; + case eIntID_TreeOpenDelay: + aResult = 1000; + break; + case eIntID_TreeCloseDelay: + aResult = 1000; + break; + case eIntID_TreeLazyScrollDelay: + aResult = 150; + break; + case eIntID_TreeScrollDelay: + aResult = 100; + break; + case eIntID_TreeScrollLinesMax: + aResult = 3; + break; + case eIntID_DWMCompositor: + case eIntID_WindowsClassic: + case eIntID_WindowsDefaultTheme: + case eIntID_WindowsThemeIdentifier: + case eIntID_OperatingSystemVersionIdentifier: + aResult = 0; + res = NS_ERROR_NOT_IMPLEMENTED; + break; + case eIntID_TouchEnabled: +#if MOZ_WIDGET_GTK == 3 + aResult = mozilla::widget::WidgetUtils::IsTouchDeviceSupportPresent(); + break; +#else + aResult = 0; + res = NS_ERROR_NOT_IMPLEMENTED; +#endif + break; + case eIntID_MacGraphiteTheme: + aResult = 0; + res = NS_ERROR_NOT_IMPLEMENTED; + break; + case eIntID_AlertNotificationOrigin: + aResult = NS_ALERT_TOP; + break; + case eIntID_IMERawInputUnderlineStyle: + case eIntID_IMEConvertedTextUnderlineStyle: + aResult = NS_STYLE_TEXT_DECORATION_STYLE_SOLID; + break; + case eIntID_IMESelectedRawTextUnderlineStyle: + case eIntID_IMESelectedConvertedTextUnderline: + aResult = NS_STYLE_TEXT_DECORATION_STYLE_NONE; + break; + case eIntID_SpellCheckerUnderlineStyle: + aResult = NS_STYLE_TEXT_DECORATION_STYLE_WAVY; + break; + case eIntID_MenuBarDrag: + aResult = sMenuSupportsDrag; + break; + case eIntID_ScrollbarButtonAutoRepeatBehavior: + aResult = 1; + break; + case eIntID_SwipeAnimationEnabled: + aResult = 0; + break; + case eIntID_ColorPickerAvailable: + aResult = 1; + break; + case eIntID_ContextMenuOffsetVertical: + case eIntID_ContextMenuOffsetHorizontal: + aResult = 2; + break; + default: + aResult = 0; + res = NS_ERROR_FAILURE; + } + + return res; +} + +nsresult +nsLookAndFeel::GetFloatImpl(FloatID aID, float &aResult) +{ + nsresult res = NS_OK; + res = nsXPLookAndFeel::GetFloatImpl(aID, aResult); + if (NS_SUCCEEDED(res)) + return res; + res = NS_OK; + + switch (aID) { + case eFloatID_IMEUnderlineRelativeSize: + aResult = 1.0f; + break; + case eFloatID_SpellCheckerUnderlineRelativeSize: + aResult = 1.0f; + break; + case eFloatID_CaretAspectRatio: + aResult = sCaretRatio; + break; + default: + aResult = -1.0; + res = NS_ERROR_FAILURE; + } + return res; +} + +static void +GetSystemFontInfo(GtkWidget *aWidget, + nsString *aFontName, + gfxFontStyle *aFontStyle) +{ + GtkSettings *settings = gtk_widget_get_settings(aWidget); + + aFontStyle->style = NS_FONT_STYLE_NORMAL; + + gchar *fontname; + g_object_get(settings, "gtk-font-name", &fontname, nullptr); + + PangoFontDescription *desc; + desc = pango_font_description_from_string(fontname); + + aFontStyle->systemFont = true; + + g_free(fontname); + + NS_NAMED_LITERAL_STRING(quote, "\""); + NS_ConvertUTF8toUTF16 family(pango_font_description_get_family(desc)); + *aFontName = quote + family + quote; + + aFontStyle->weight = pango_font_description_get_weight(desc); + + // FIXME: Set aFontStyle->stretch correctly! + aFontStyle->stretch = NS_FONT_STRETCH_NORMAL; + + float size = float(pango_font_description_get_size(desc)) / PANGO_SCALE; + + // |size| is now either pixels or pango-points (not Mozilla-points!) + + if (!pango_font_description_get_size_is_absolute(desc)) { + // |size| is in pango-points, so convert to pixels. + size *= float(gfxPlatformGtk::GetDPI()) / POINTS_PER_INCH_FLOAT; + } + + // Scale fonts up on HiDPI displays. + // This would be done automatically with cairo, but we manually manage + // the display scale for platform consistency. + size *= nsScreenGtk::GetGtkMonitorScaleFactor(); + + // |size| is now pixels + + aFontStyle->size = size; + + pango_font_description_free(desc); +} + +static void +GetSystemFontInfo(LookAndFeel::FontID aID, + nsString *aFontName, + gfxFontStyle *aFontStyle) +{ + if (aID == LookAndFeel::eFont_Widget) { + GtkWidget *label = gtk_label_new("M"); + GtkWidget *parent = gtk_fixed_new(); + GtkWidget *window = gtk_window_new(GTK_WINDOW_POPUP); + + gtk_container_add(GTK_CONTAINER(parent), label); + gtk_container_add(GTK_CONTAINER(window), parent); + + gtk_widget_ensure_style(label); + GetSystemFontInfo(label, aFontName, aFontStyle); + gtk_widget_destroy(window); // no unref, windows are different + + } else if (aID == LookAndFeel::eFont_Button) { + GtkWidget *label = gtk_label_new("M"); + GtkWidget *parent = gtk_fixed_new(); + GtkWidget *button = gtk_button_new(); + GtkWidget *window = gtk_window_new(GTK_WINDOW_POPUP); + + gtk_container_add(GTK_CONTAINER(button), label); + gtk_container_add(GTK_CONTAINER(parent), button); + gtk_container_add(GTK_CONTAINER(window), parent); + + gtk_widget_ensure_style(label); + GetSystemFontInfo(label, aFontName, aFontStyle); + gtk_widget_destroy(window); // no unref, windows are different + + } else if (aID == LookAndFeel::eFont_Field) { + GtkWidget *entry = gtk_entry_new(); + GtkWidget *parent = gtk_fixed_new(); + GtkWidget *window = gtk_window_new(GTK_WINDOW_POPUP); + + gtk_container_add(GTK_CONTAINER(parent), entry); + gtk_container_add(GTK_CONTAINER(window), parent); + + gtk_widget_ensure_style(entry); + GetSystemFontInfo(entry, aFontName, aFontStyle); + gtk_widget_destroy(window); // no unref, windows are different + + } else { + MOZ_ASSERT(aID == LookAndFeel::eFont_Menu, "unexpected font ID"); + GtkWidget *accel_label = gtk_accel_label_new("M"); + GtkWidget *menuitem = gtk_menu_item_new(); + GtkWidget *menu = gtk_menu_new(); + g_object_ref_sink(menu); + + gtk_container_add(GTK_CONTAINER(menuitem), accel_label); + gtk_menu_shell_append((GtkMenuShell *)GTK_MENU(menu), menuitem); + + gtk_widget_ensure_style(accel_label); + GetSystemFontInfo(accel_label, aFontName, aFontStyle); + g_object_unref(menu); + } +} + +bool +nsLookAndFeel::GetFontImpl(FontID aID, nsString& aFontName, + gfxFontStyle& aFontStyle, + float aDevPixPerCSSPixel) +{ + nsString *cachedFontName = nullptr; + gfxFontStyle *cachedFontStyle = nullptr; + bool *isCached = nullptr; + + switch (aID) { + case eFont_Menu: // css2 + case eFont_PullDownMenu: // css3 + cachedFontName = &mMenuFontName; + cachedFontStyle = &mMenuFontStyle; + isCached = &mMenuFontCached; + aID = eFont_Menu; + break; + + case eFont_Field: // css3 + case eFont_List: // css3 + cachedFontName = &mFieldFontName; + cachedFontStyle = &mFieldFontStyle; + isCached = &mFieldFontCached; + aID = eFont_Field; + break; + + case eFont_Button: // css3 + cachedFontName = &mButtonFontName; + cachedFontStyle = &mButtonFontStyle; + isCached = &mButtonFontCached; + break; + + case eFont_Caption: // css2 + case eFont_Icon: // css2 + case eFont_MessageBox: // css2 + case eFont_SmallCaption: // css2 + case eFont_StatusBar: // css2 + case eFont_Window: // css3 + case eFont_Document: // css3 + case eFont_Workspace: // css3 + case eFont_Desktop: // css3 + case eFont_Info: // css3 + case eFont_Dialog: // css3 + case eFont_Tooltips: // moz + case eFont_Widget: // moz + cachedFontName = &mDefaultFontName; + cachedFontStyle = &mDefaultFontStyle; + isCached = &mDefaultFontCached; + aID = eFont_Widget; + break; + } + + if (!*isCached) { + GetSystemFontInfo(aID, cachedFontName, cachedFontStyle); + *isCached = true; + } + + aFontName = *cachedFontName; + aFontStyle = *cachedFontStyle; + return true; +} + +#if (MOZ_WIDGET_GTK == 3) +static GtkStyleContext* +create_context(GtkWidgetPath *path) +{ + GtkStyleContext *style = gtk_style_context_new(); + gtk_style_context_set_path(style, path); + return(style); +} +#endif + +void +nsLookAndFeel::Init() +{ + GdkColor colorValue; + GdkColor *colorValuePtr; + +#if (MOZ_WIDGET_GTK == 2) + NS_ASSERTION(!mStyle, "already initialized"); + // GtkInvisibles come with a refcount that is not floating + // (since their initialization code calls g_object_ref_sink) and + // their destroy code releases that reference (which means they + // have to be explicitly destroyed, since calling unref enough + // to cause destruction would lead to *another* unref). + // However, this combination means that it's actually still ok + // to use the normal pattern, which is to g_object_ref_sink + // after construction, and then destroy *and* unref when we're + // done. (Though we could skip the g_object_ref_sink and the + // corresponding g_object_unref, but that's particular to + // GtkInvisibles and GtkWindows.) + GtkWidget *widget = gtk_invisible_new(); + g_object_ref_sink(widget); // effectively g_object_ref (see above) + + gtk_widget_ensure_style(widget); + mStyle = gtk_style_copy(gtk_widget_get_style(widget)); + + gtk_widget_destroy(widget); + g_object_unref(widget); + + // tooltip foreground and background + GtkStyle *style = gtk_rc_get_style_by_paths(gtk_settings_get_default(), + "gtk-tooltips", "GtkWindow", + GTK_TYPE_WINDOW); + if (style) { + sInfoBackground = GDK_COLOR_TO_NS_RGB(style->bg[GTK_STATE_NORMAL]); + sInfoText = GDK_COLOR_TO_NS_RGB(style->fg[GTK_STATE_NORMAL]); + } + + // menu foreground & menu background + GtkWidget *accel_label = gtk_accel_label_new("M"); + GtkWidget *menuitem = gtk_menu_item_new(); + GtkWidget *menu = gtk_menu_new(); + + g_object_ref_sink(menu); + + gtk_container_add(GTK_CONTAINER(menuitem), accel_label); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem); + + gtk_widget_set_style(accel_label, nullptr); + gtk_widget_set_style(menu, nullptr); + gtk_widget_realize(menu); + gtk_widget_realize(accel_label); + + style = gtk_widget_get_style(accel_label); + if (style) { + sMenuText = GDK_COLOR_TO_NS_RGB(style->fg[GTK_STATE_NORMAL]); + } + + style = gtk_widget_get_style(menu); + if (style) { + sMenuBackground = GDK_COLOR_TO_NS_RGB(style->bg[GTK_STATE_NORMAL]); + } + + style = gtk_widget_get_style(menuitem); + if (style) { + sMenuHover = GDK_COLOR_TO_NS_RGB(style->bg[GTK_STATE_PRELIGHT]); + sMenuHoverText = GDK_COLOR_TO_NS_RGB(style->fg[GTK_STATE_PRELIGHT]); + } + + g_object_unref(menu); +#else + GdkRGBA color; + GtkStyleContext *style; + + // Gtk manages a screen's CSS in the settings object so we + // ask Gtk to create it explicitly. Otherwise we may end up + // with wrong color theme, see Bug 972382 + GtkSettings *settings = gtk_settings_get_for_screen(gdk_screen_get_default()); + + // Disable dark theme because it interacts poorly with widget styling in + // web content (see bug 1216658). + // To avoid triggering reload of theme settings unnecessarily, only set the + // setting when necessary. + const gchar* dark_setting = "gtk-application-prefer-dark-theme"; + gboolean dark; + g_object_get(settings, dark_setting, &dark, nullptr); + + if (dark && !PR_GetEnv("MOZ_ALLOW_GTK_DARK_THEME")) { + g_object_set(settings, dark_setting, FALSE, nullptr); + } + + GtkWidgetPath *path = gtk_widget_path_new(); + gtk_widget_path_append_type(path, GTK_TYPE_WINDOW); + + mBackgroundStyle = create_context(path); + gtk_style_context_add_class(mBackgroundStyle, GTK_STYLE_CLASS_BACKGROUND); + + mButtonStyle = create_context(path); + gtk_style_context_add_class(mButtonStyle, GTK_STYLE_CLASS_BUTTON); + + // Scrollbar colors + style = create_context(path); + gtk_style_context_add_class(style, GTK_STYLE_CLASS_SCROLLBAR); + gtk_style_context_add_class(style, GTK_STYLE_CLASS_TROUGH); + gtk_style_context_get_background_color(style, GTK_STATE_FLAG_NORMAL, &color); + sMozScrollbar = GDK_RGBA_TO_NS_RGBA(color); + g_object_unref(style); + + // Window colors + style = create_context(path); + gtk_style_context_save(style); + gtk_style_context_add_class(style, GTK_STYLE_CLASS_BACKGROUND); + gtk_style_context_get_background_color(style, GTK_STATE_FLAG_NORMAL, &color); + sMozWindowBackground = GDK_RGBA_TO_NS_RGBA(color); + gtk_style_context_get_color(style, GTK_STATE_FLAG_NORMAL, &color); + sMozWindowText = GDK_RGBA_TO_NS_RGBA(color); + gtk_style_context_restore(style); + g_object_unref(style); + + // tooltip foreground and background + style = ClaimStyleContext(MOZ_GTK_TOOLTIP); + gtk_style_context_get_background_color(style, GTK_STATE_FLAG_NORMAL, &color); + sInfoBackground = GDK_RGBA_TO_NS_RGBA(color); + { + GtkStyleContext* boxStyle = + CreateStyleForWidget(gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0), + style); + GtkStyleContext* labelStyle = + CreateStyleForWidget(gtk_label_new(nullptr), boxStyle); + gtk_style_context_get_color(labelStyle, GTK_STATE_FLAG_NORMAL, &color); + g_object_unref(labelStyle); + g_object_unref(boxStyle); + } + sInfoText = GDK_RGBA_TO_NS_RGBA(color); + ReleaseStyleContext(style); + + // menu foreground & menu background + GtkWidget *accel_label = gtk_accel_label_new("M"); + GtkWidget *menuitem = gtk_menu_item_new(); + GtkWidget *menu = gtk_menu_new(); + + g_object_ref_sink(menu); + + gtk_container_add(GTK_CONTAINER(menuitem), accel_label); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem); + + style = gtk_widget_get_style_context(accel_label); + gtk_style_context_get_color(style, GTK_STATE_FLAG_NORMAL, &color); + sMenuText = GDK_RGBA_TO_NS_RGBA(color); + gtk_style_context_get_color(style, GTK_STATE_FLAG_INSENSITIVE, &color); + sMenuTextInactive = GDK_RGBA_TO_NS_RGBA(color); + + style = gtk_widget_get_style_context(menu); + gtk_style_context_get_background_color(style, GTK_STATE_FLAG_NORMAL, &color); + sMenuBackground = GDK_RGBA_TO_NS_RGBA(color); + + style = gtk_widget_get_style_context(menuitem); + gtk_style_context_get_background_color(style, GTK_STATE_FLAG_PRELIGHT, &color); + sMenuHover = GDK_RGBA_TO_NS_RGBA(color); + gtk_style_context_get_color(style, GTK_STATE_FLAG_PRELIGHT, &color); + sMenuHoverText = GDK_RGBA_TO_NS_RGBA(color); + + g_object_unref(menu); +#endif + + // button styles + GtkWidget *parent = gtk_fixed_new(); + GtkWidget *button = gtk_button_new(); + GtkWidget *label = gtk_label_new("M"); +#if (MOZ_WIDGET_GTK == 2) + GtkWidget *combobox = gtk_combo_box_new(); + GtkWidget *comboboxLabel = gtk_label_new("M"); + gtk_container_add(GTK_CONTAINER(combobox), comboboxLabel); +#else + GtkWidget *combobox = gtk_combo_box_new_with_entry(); + GtkWidget *comboboxLabel = gtk_bin_get_child(GTK_BIN(combobox)); +#endif + GtkWidget *window = gtk_window_new(GTK_WINDOW_POPUP); + GtkWidget *treeView = gtk_tree_view_new(); + GtkWidget *linkButton = gtk_link_button_new("http://example.com/"); + GtkWidget *menuBar = gtk_menu_bar_new(); + GtkWidget *menuBarItem = gtk_menu_item_new(); + GtkWidget *entry = gtk_entry_new(); + GtkWidget *textView = gtk_text_view_new(); + + gtk_container_add(GTK_CONTAINER(button), label); + gtk_container_add(GTK_CONTAINER(parent), button); + gtk_container_add(GTK_CONTAINER(parent), treeView); + gtk_container_add(GTK_CONTAINER(parent), linkButton); + gtk_container_add(GTK_CONTAINER(parent), combobox); + gtk_container_add(GTK_CONTAINER(parent), menuBar); + gtk_menu_shell_append(GTK_MENU_SHELL(menuBar), menuBarItem); + gtk_container_add(GTK_CONTAINER(window), parent); + gtk_container_add(GTK_CONTAINER(parent), entry); + gtk_container_add(GTK_CONTAINER(parent), textView); + +#if (MOZ_WIDGET_GTK == 2) + gtk_widget_set_style(button, nullptr); + gtk_widget_set_style(label, nullptr); + gtk_widget_set_style(treeView, nullptr); + gtk_widget_set_style(linkButton, nullptr); + gtk_widget_set_style(combobox, nullptr); + gtk_widget_set_style(comboboxLabel, nullptr); + gtk_widget_set_style(menuBar, nullptr); + gtk_widget_set_style(entry, nullptr); + + gtk_widget_realize(button); + gtk_widget_realize(label); + gtk_widget_realize(treeView); + gtk_widget_realize(linkButton); + gtk_widget_realize(combobox); + gtk_widget_realize(comboboxLabel); + gtk_widget_realize(menuBar); + gtk_widget_realize(entry); + + style = gtk_widget_get_style(label); + if (style) { + sButtonText = GDK_COLOR_TO_NS_RGB(style->fg[GTK_STATE_NORMAL]); + } + + style = gtk_widget_get_style(comboboxLabel); + if (style) { + sComboBoxText = GDK_COLOR_TO_NS_RGB(style->fg[GTK_STATE_NORMAL]); + } + style = gtk_widget_get_style(combobox); + if (style) { + sComboBoxBackground = GDK_COLOR_TO_NS_RGB(style->bg[GTK_STATE_NORMAL]); + } + + style = gtk_widget_get_style(menuBar); + if (style) { + sMenuBarText = GDK_COLOR_TO_NS_RGB(style->fg[GTK_STATE_NORMAL]); + sMenuBarHoverText = GDK_COLOR_TO_NS_RGB(style->fg[GTK_STATE_SELECTED]); + } + + // GTK's guide to fancy odd row background colors: + // 1) Check if a theme explicitly defines an odd row color + // 2) If not, check if it defines an even row color, and darken it + // slightly by a hardcoded value (gtkstyle.c) + // 3) If neither are defined, take the base background color and + // darken that by a hardcoded value + colorValuePtr = nullptr; + gtk_widget_style_get(treeView, + "odd-row-color", &colorValuePtr, + nullptr); + + if (colorValuePtr) { + colorValue = *colorValuePtr; + } else { + gtk_widget_style_get(treeView, + "even-row-color", &colorValuePtr, + nullptr); + if (colorValuePtr) + darken_gdk_color(colorValuePtr, &colorValue); + else + darken_gdk_color(&treeView->style->base[GTK_STATE_NORMAL], &colorValue); + } + + sOddCellBackground = GDK_COLOR_TO_NS_RGB(colorValue); + if (colorValuePtr) + gdk_color_free(colorValuePtr); + + style = gtk_widget_get_style(button); + if (style) { + sButtonBackground = GDK_COLOR_TO_NS_RGB(style->bg[GTK_STATE_NORMAL]); + sFrameOuterLightBorder = + GDK_COLOR_TO_NS_RGB(style->light[GTK_STATE_NORMAL]); + sFrameInnerDarkBorder = + GDK_COLOR_TO_NS_RGB(style->dark[GTK_STATE_NORMAL]); + } +#else + // Text colors + style = gtk_widget_get_style_context(textView); + gtk_style_context_save(style); + gtk_style_context_add_class(style, GTK_STYLE_CLASS_VIEW); + gtk_style_context_get_background_color(style, GTK_STATE_FLAG_NORMAL, &color); + sMozFieldBackground = GDK_RGBA_TO_NS_RGBA(color); + gtk_style_context_get_color(style, GTK_STATE_FLAG_NORMAL, &color); + sMozFieldText = GDK_RGBA_TO_NS_RGBA(color); + + // Selected text and background + gtk_style_context_get_background_color(style, + static_cast<GtkStateFlags>(GTK_STATE_FLAG_FOCUSED|GTK_STATE_FLAG_SELECTED), + &color); + sTextSelectedBackground = GDK_RGBA_TO_NS_RGBA(color); + gtk_style_context_get_color(style, + static_cast<GtkStateFlags>(GTK_STATE_FLAG_FOCUSED|GTK_STATE_FLAG_SELECTED), + &color); + sTextSelectedText = GDK_RGBA_TO_NS_RGBA(color); + gtk_style_context_restore(style); + + // Button text, background, border + style = gtk_widget_get_style_context(label); + gtk_style_context_get_color(style, GTK_STATE_FLAG_NORMAL, &color); + sButtonText = GDK_RGBA_TO_NS_RGBA(color); + gtk_style_context_get_color(style, GTK_STATE_FLAG_PRELIGHT, &color); + sButtonHoverText = GDK_RGBA_TO_NS_RGBA(color); + + // Combobox text color + style = gtk_widget_get_style_context(comboboxLabel); + gtk_style_context_get_color(style, GTK_STATE_FLAG_NORMAL, &color); + sComboBoxText = GDK_RGBA_TO_NS_RGBA(color); + + // Menubar text and hover text colors + style = gtk_widget_get_style_context(menuBarItem); + gtk_style_context_get_color(style, GTK_STATE_FLAG_NORMAL, &color); + sMenuBarText = GDK_RGBA_TO_NS_RGBA(color); + gtk_style_context_get_color(style, GTK_STATE_FLAG_PRELIGHT, &color); + sMenuBarHoverText = GDK_RGBA_TO_NS_RGBA(color); + + // GTK's guide to fancy odd row background colors: + // 1) Check if a theme explicitly defines an odd row color + // 2) If not, check if it defines an even row color, and darken it + // slightly by a hardcoded value (gtkstyle.c) + // 3) If neither are defined, take the base background color and + // darken that by a hardcoded value + style = gtk_widget_get_style_context(treeView); + + // Get odd row background color + gtk_style_context_save(style); + gtk_style_context_add_region(style, GTK_STYLE_REGION_ROW, GTK_REGION_ODD); + gtk_style_context_get_background_color(style, GTK_STATE_FLAG_NORMAL, &color); + sOddCellBackground = GDK_RGBA_TO_NS_RGBA(color); + gtk_style_context_restore(style); + + gtk_widget_path_free(path); + + // GtkFrame has a "border" subnode on which Adwaita draws the border. + // Some themes do not draw on this node but draw a border on the widget + // root node, so check the root node if no border is found on the border + // node. + style = ClaimStyleContext(MOZ_GTK_FRAME_BORDER); + bool themeUsesColors = + GetBorderColors(style, &sFrameOuterLightBorder, &sFrameInnerDarkBorder); + ReleaseStyleContext(style); + if (!themeUsesColors) { + style = ClaimStyleContext(MOZ_GTK_FRAME); + GetBorderColors(style, &sFrameOuterLightBorder, &sFrameInnerDarkBorder); + ReleaseStyleContext(style); + } + + // GtkInfoBar + // TODO - Use WidgetCache for it? + GtkWidget* infoBar = gtk_info_bar_new(); + GtkWidget* infoBarContent = gtk_info_bar_get_content_area(GTK_INFO_BAR(infoBar)); + GtkWidget* infoBarLabel = gtk_label_new(nullptr); + gtk_container_add(GTK_CONTAINER(parent), infoBar); + gtk_container_add(GTK_CONTAINER(infoBarContent), infoBarLabel); + style = gtk_widget_get_style_context(infoBarLabel); + gtk_style_context_add_class(style, GTK_STYLE_CLASS_INFO); + gtk_style_context_get_color(style, GTK_STATE_FLAG_NORMAL, &color); + sInfoBarText = GDK_RGBA_TO_NS_RGBA(color); +#endif + // Some themes have a unified menu bar, and support window dragging on it + gboolean supports_menubar_drag = FALSE; + GParamSpec *param_spec = + gtk_widget_class_find_style_property(GTK_WIDGET_GET_CLASS(menuBar), + "window-dragging"); + if (param_spec) { + if (g_type_is_a(G_PARAM_SPEC_VALUE_TYPE(param_spec), G_TYPE_BOOLEAN)) { + gtk_widget_style_get(menuBar, + "window-dragging", &supports_menubar_drag, + nullptr); + } + } + sMenuSupportsDrag = supports_menubar_drag; + + colorValuePtr = nullptr; + gtk_widget_style_get(linkButton, "link-color", &colorValuePtr, nullptr); + if (colorValuePtr) { + colorValue = *colorValuePtr; // we can't pass deref pointers to GDK_COLOR_TO_NS_RGB + sNativeHyperLinkText = GDK_COLOR_TO_NS_RGB(colorValue); + gdk_color_free(colorValuePtr); + } else { + sNativeHyperLinkText = NS_RGB(0x00,0x00,0xEE); + } + + // invisible character styles + guint value; + g_object_get (entry, "invisible-char", &value, nullptr); + sInvisibleCharacter = char16_t(value); + + // caret styles + gtk_widget_style_get(entry, + "cursor-aspect-ratio", &sCaretRatio, + nullptr); + + gtk_widget_destroy(window); +} + +// virtual +char16_t +nsLookAndFeel::GetPasswordCharacterImpl() +{ + return sInvisibleCharacter; +} + +void +nsLookAndFeel::RefreshImpl() +{ + nsXPLookAndFeel::RefreshImpl(); + + mDefaultFontCached = false; + mButtonFontCached = false; + mFieldFontCached = false; + mMenuFontCached = false; + +#if (MOZ_WIDGET_GTK == 2) + g_object_unref(mStyle); + mStyle = nullptr; +#else + g_object_unref(mBackgroundStyle); + g_object_unref(mButtonStyle); + + mBackgroundStyle = nullptr; + mButtonStyle = nullptr; +#endif + + Init(); +} + +bool +nsLookAndFeel::GetEchoPasswordImpl() { + return false; +} diff --git a/widget/gtk/nsLookAndFeel.h b/widget/gtk/nsLookAndFeel.h new file mode 100644 index 000000000..9058250b9 --- /dev/null +++ b/widget/gtk/nsLookAndFeel.h @@ -0,0 +1,91 @@ +/* -*- 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/. */ + +#ifndef __nsLookAndFeel +#define __nsLookAndFeel + +#include "nsXPLookAndFeel.h" +#include "nsCOMPtr.h" +#include "gfxFont.h" + +struct _GtkStyle; + +class nsLookAndFeel: public nsXPLookAndFeel { +public: + nsLookAndFeel(); + virtual ~nsLookAndFeel(); + + virtual nsresult NativeGetColor(ColorID aID, nscolor &aResult); + virtual nsresult GetIntImpl(IntID aID, int32_t &aResult); + virtual nsresult GetFloatImpl(FloatID aID, float &aResult); + virtual bool GetFontImpl(FontID aID, nsString& aFontName, + gfxFontStyle& aFontStyle, + float aDevPixPerCSSPixel); + + virtual void RefreshImpl(); + virtual char16_t GetPasswordCharacterImpl(); + virtual bool GetEchoPasswordImpl(); + +protected: +#if (MOZ_WIDGET_GTK == 2) + struct _GtkStyle *mStyle; +#else + struct _GtkStyleContext *mBackgroundStyle; + struct _GtkStyleContext *mButtonStyle; +#endif + + // Cached fonts + bool mDefaultFontCached; + bool mButtonFontCached; + bool mFieldFontCached; + bool mMenuFontCached; + nsString mDefaultFontName; + nsString mButtonFontName; + nsString mFieldFontName; + nsString mMenuFontName; + gfxFontStyle mDefaultFontStyle; + gfxFontStyle mButtonFontStyle; + gfxFontStyle mFieldFontStyle; + gfxFontStyle mMenuFontStyle; + + // Cached colors + nscolor sInfoBackground; + nscolor sInfoText; + nscolor sMenuBackground; + nscolor sMenuBarText; + nscolor sMenuBarHoverText; + nscolor sMenuText; + nscolor sMenuTextInactive; + nscolor sMenuHover; + nscolor sMenuHoverText; + nscolor sButtonText; + nscolor sButtonHoverText; + nscolor sButtonBackground; + nscolor sFrameOuterLightBorder; + nscolor sFrameInnerDarkBorder; + nscolor sOddCellBackground; + nscolor sNativeHyperLinkText; + nscolor sComboBoxText; + nscolor sComboBoxBackground; + nscolor sMozFieldText; + nscolor sMozFieldBackground; + nscolor sMozWindowText; + nscolor sMozWindowBackground; + nscolor sTextSelectedText; + nscolor sTextSelectedBackground; + nscolor sMozScrollbar; +#if (MOZ_WIDGET_GTK == 3) + nscolor sInfoBarText; +#endif + char16_t sInvisibleCharacter; + float sCaretRatio; + bool sMenuSupportsDrag; + + void Init(); +}; + +#endif diff --git a/widget/gtk/nsNativeThemeGTK.cpp b/widget/gtk/nsNativeThemeGTK.cpp new file mode 100644 index 000000000..89b8ab7dc --- /dev/null +++ b/widget/gtk/nsNativeThemeGTK.cpp @@ -0,0 +1,1994 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 "nsNativeThemeGTK.h" +#include "nsThemeConstants.h" +#include "gtkdrawing.h" +#include "nsScreenGtk.h" + +#include "gfx2DGlue.h" +#include "nsIObserverService.h" +#include "nsIServiceManager.h" +#include "nsIFrame.h" +#include "nsIPresShell.h" +#include "nsIContent.h" +#include "nsViewManager.h" +#include "nsNameSpaceManager.h" +#include "nsGfxCIID.h" +#include "nsTransform2D.h" +#include "nsMenuFrame.h" +#include "prlink.h" +#include "nsIDOMHTMLInputElement.h" +#include "nsRenderingContext.h" +#include "nsGkAtoms.h" +#include "nsAttrValueInlines.h" + +#include "mozilla/EventStates.h" +#include "mozilla/Services.h" + +#include <gdk/gdkprivate.h> +#include <gtk/gtk.h> + +#include "gfxContext.h" +#include "gfxPlatformGtk.h" +#include "gfxGdkNativeRenderer.h" +#include "mozilla/gfx/BorrowedContext.h" +#include "mozilla/gfx/HelpersCairo.h" +#include "mozilla/gfx/PathHelpers.h" + +#ifdef MOZ_X11 +# ifdef CAIRO_HAS_XLIB_SURFACE +# include "cairo-xlib.h" +# endif +# ifdef CAIRO_HAS_XLIB_XRENDER_SURFACE +# include "cairo-xlib-xrender.h" +# endif +#endif + +#include <algorithm> +#include <dlfcn.h> + +using namespace mozilla; +using namespace mozilla::gfx; + +NS_IMPL_ISUPPORTS_INHERITED(nsNativeThemeGTK, nsNativeTheme, nsITheme, + nsIObserver) + +static int gLastGdkError; + +nsNativeThemeGTK::nsNativeThemeGTK() +{ + if (moz_gtk_init() != MOZ_GTK_SUCCESS) { + memset(mDisabledWidgetTypes, 0xff, sizeof(mDisabledWidgetTypes)); + return; + } + + // We have to call moz_gtk_shutdown before the event loop stops running. + nsCOMPtr<nsIObserverService> obsServ = + mozilla::services::GetObserverService(); + obsServ->AddObserver(this, "xpcom-shutdown", false); + + memset(mDisabledWidgetTypes, 0, sizeof(mDisabledWidgetTypes)); + memset(mSafeWidgetStates, 0, sizeof(mSafeWidgetStates)); +} + +nsNativeThemeGTK::~nsNativeThemeGTK() { +} + +NS_IMETHODIMP +nsNativeThemeGTK::Observe(nsISupports *aSubject, const char *aTopic, + const char16_t *aData) +{ + if (!nsCRT::strcmp(aTopic, "xpcom-shutdown")) { + moz_gtk_shutdown(); + } else { + NS_NOTREACHED("unexpected topic"); + return NS_ERROR_UNEXPECTED; + } + + return NS_OK; +} + +void +nsNativeThemeGTK::RefreshWidgetWindow(nsIFrame* aFrame) +{ + nsIPresShell *shell = GetPresShell(aFrame); + if (!shell) + return; + + nsViewManager* vm = shell->GetViewManager(); + if (!vm) + return; + + vm->InvalidateAllViews(); +} + + +static bool IsFrameContentNodeInNamespace(nsIFrame *aFrame, uint32_t aNamespace) +{ + nsIContent *content = aFrame ? aFrame->GetContent() : nullptr; + if (!content) + return false; + return content->IsInNamespace(aNamespace); +} + +static bool IsWidgetTypeDisabled(uint8_t* aDisabledVector, uint8_t aWidgetType) { + return (aDisabledVector[aWidgetType >> 3] & (1 << (aWidgetType & 7))) != 0; +} + +static void SetWidgetTypeDisabled(uint8_t* aDisabledVector, uint8_t aWidgetType) { + aDisabledVector[aWidgetType >> 3] |= (1 << (aWidgetType & 7)); +} + +static inline uint16_t +GetWidgetStateKey(uint8_t aWidgetType, GtkWidgetState *aWidgetState) +{ + return (aWidgetState->active | + aWidgetState->focused << 1 | + aWidgetState->inHover << 2 | + aWidgetState->disabled << 3 | + aWidgetState->isDefault << 4 | + aWidgetType << 5); +} + +static bool IsWidgetStateSafe(uint8_t* aSafeVector, + uint8_t aWidgetType, + GtkWidgetState *aWidgetState) +{ + uint8_t key = GetWidgetStateKey(aWidgetType, aWidgetState); + return (aSafeVector[key >> 3] & (1 << (key & 7))) != 0; +} + +static void SetWidgetStateSafe(uint8_t *aSafeVector, + uint8_t aWidgetType, + GtkWidgetState *aWidgetState) +{ + uint8_t key = GetWidgetStateKey(aWidgetType, aWidgetState); + aSafeVector[key >> 3] |= (1 << (key & 7)); +} + +/* static */ GtkTextDirection +nsNativeThemeGTK::GetTextDirection(nsIFrame* aFrame) +{ + // IsFrameRTL() treats vertical-rl modes as right-to-left (in addition to + // horizontal text with direction=RTL), rather than just considering the + // text direction. GtkTextDirection does not have distinct values for + // vertical writing modes, but considering the block flow direction is + // important for resizers and scrollbar elements, at least. + return IsFrameRTL(aFrame) ? GTK_TEXT_DIR_RTL : GTK_TEXT_DIR_LTR; +} + +// Returns positive for negative margins (otherwise 0). +gint +nsNativeThemeGTK::GetTabMarginPixels(nsIFrame* aFrame) +{ + nscoord margin = + IsBottomTab(aFrame) ? aFrame->GetUsedMargin().top + : aFrame->GetUsedMargin().bottom; + + return std::min<gint>(MOZ_GTK_TAB_MARGIN_MASK, + std::max(0, + aFrame->PresContext()->AppUnitsToDevPixels(-margin))); +} + +static bool ShouldScrollbarButtonBeDisabled(int32_t aCurpos, int32_t aMaxpos, + uint8_t aWidgetType) +{ + return ((aCurpos == 0 && (aWidgetType == NS_THEME_SCROLLBARBUTTON_UP || + aWidgetType == NS_THEME_SCROLLBARBUTTON_LEFT)) + || (aCurpos == aMaxpos && (aWidgetType == NS_THEME_SCROLLBARBUTTON_DOWN || + aWidgetType == NS_THEME_SCROLLBARBUTTON_RIGHT))); +} + +bool +nsNativeThemeGTK::GetGtkWidgetAndState(uint8_t aWidgetType, nsIFrame* aFrame, + WidgetNodeType& aGtkWidgetType, + GtkWidgetState* aState, + gint* aWidgetFlags) +{ + if (aState) { + // For XUL checkboxes and radio buttons, the state of the parent + // determines our state. + nsIFrame *stateFrame = aFrame; + if (aFrame && ((aWidgetFlags && (aWidgetType == NS_THEME_CHECKBOX || + aWidgetType == NS_THEME_RADIO)) || + aWidgetType == NS_THEME_CHECKBOX_LABEL || + aWidgetType == NS_THEME_RADIO_LABEL)) { + + nsIAtom* atom = nullptr; + if (IsFrameContentNodeInNamespace(aFrame, kNameSpaceID_XUL)) { + if (aWidgetType == NS_THEME_CHECKBOX_LABEL || + aWidgetType == NS_THEME_RADIO_LABEL) { + // Adjust stateFrame so GetContentState finds the correct state. + stateFrame = aFrame = aFrame->GetParent()->GetParent(); + } else { + // GetContentState knows to look one frame up for radio/checkbox + // widgets, so don't adjust stateFrame here. + aFrame = aFrame->GetParent(); + } + if (aWidgetFlags) { + if (!atom) { + atom = (aWidgetType == NS_THEME_CHECKBOX || + aWidgetType == NS_THEME_CHECKBOX_LABEL) ? nsGkAtoms::checked + : nsGkAtoms::selected; + } + *aWidgetFlags = CheckBooleanAttr(aFrame, atom); + } + } else { + if (aWidgetFlags) { + nsCOMPtr<nsIDOMHTMLInputElement> inputElt(do_QueryInterface(aFrame->GetContent())); + *aWidgetFlags = 0; + if (inputElt) { + bool isHTMLChecked; + inputElt->GetChecked(&isHTMLChecked); + if (isHTMLChecked) + *aWidgetFlags |= MOZ_GTK_WIDGET_CHECKED; + } + + if (GetIndeterminate(aFrame)) + *aWidgetFlags |= MOZ_GTK_WIDGET_INCONSISTENT; + } + } + } else if (aWidgetType == NS_THEME_TOOLBARBUTTON_DROPDOWN || + aWidgetType == NS_THEME_TREEHEADERSORTARROW || + aWidgetType == NS_THEME_BUTTON_ARROW_PREVIOUS || + aWidgetType == NS_THEME_BUTTON_ARROW_NEXT || + aWidgetType == NS_THEME_BUTTON_ARROW_UP || + aWidgetType == NS_THEME_BUTTON_ARROW_DOWN) { + // The state of an arrow comes from its parent. + stateFrame = aFrame = aFrame->GetParent(); + } + + EventStates eventState = GetContentState(stateFrame, aWidgetType); + + aState->disabled = IsDisabled(aFrame, eventState) || IsReadOnly(aFrame); + aState->active = eventState.HasState(NS_EVENT_STATE_ACTIVE); + aState->focused = eventState.HasState(NS_EVENT_STATE_FOCUS); + aState->inHover = eventState.HasState(NS_EVENT_STATE_HOVER); + aState->isDefault = IsDefaultButton(aFrame); + aState->canDefault = FALSE; // XXX fix me + aState->depressed = FALSE; + + if (aWidgetType == NS_THEME_FOCUS_OUTLINE) { + aState->disabled = FALSE; + aState->active = FALSE; + aState->inHover = FALSE; + aState->isDefault = FALSE; + aState->canDefault = FALSE; + + aState->focused = TRUE; + aState->depressed = TRUE; // see moz_gtk_entry_paint() + } else if (aWidgetType == NS_THEME_BUTTON || + aWidgetType == NS_THEME_TOOLBARBUTTON || + aWidgetType == NS_THEME_DUALBUTTON || + aWidgetType == NS_THEME_TOOLBARBUTTON_DROPDOWN || + aWidgetType == NS_THEME_MENULIST || + aWidgetType == NS_THEME_MENULIST_BUTTON) { + aState->active &= aState->inHover; + } + + if (IsFrameContentNodeInNamespace(aFrame, kNameSpaceID_XUL)) { + // For these widget types, some element (either a child or parent) + // actually has element focus, so we check the focused attribute + // to see whether to draw in the focused state. + if (aWidgetType == NS_THEME_NUMBER_INPUT || + aWidgetType == NS_THEME_TEXTFIELD || + aWidgetType == NS_THEME_TEXTFIELD_MULTILINE || + aWidgetType == NS_THEME_MENULIST_TEXTFIELD || + aWidgetType == NS_THEME_SPINNER_TEXTFIELD || + aWidgetType == NS_THEME_RADIO_CONTAINER || + aWidgetType == NS_THEME_RADIO_LABEL) { + aState->focused = IsFocused(aFrame); + } else if (aWidgetType == NS_THEME_RADIO || + aWidgetType == NS_THEME_CHECKBOX) { + // In XUL, checkboxes and radios shouldn't have focus rings, their labels do + aState->focused = FALSE; + } + + if (aWidgetType == NS_THEME_SCROLLBARTHUMB_VERTICAL || + aWidgetType == NS_THEME_SCROLLBARTHUMB_HORIZONTAL) { + // for scrollbars we need to go up two to go from the thumb to + // the slider to the actual scrollbar object + nsIFrame *tmpFrame = aFrame->GetParent()->GetParent(); + + aState->curpos = CheckIntAttr(tmpFrame, nsGkAtoms::curpos, 0); + aState->maxpos = CheckIntAttr(tmpFrame, nsGkAtoms::maxpos, 100); + + if (CheckBooleanAttr(aFrame, nsGkAtoms::active)) { + aState->active = TRUE; + // Set hover state to emulate Gtk style of active scrollbar thumb + aState->inHover = TRUE; + } + } + + if (aWidgetType == NS_THEME_SCROLLBARBUTTON_UP || + aWidgetType == NS_THEME_SCROLLBARBUTTON_DOWN || + aWidgetType == NS_THEME_SCROLLBARBUTTON_LEFT || + aWidgetType == NS_THEME_SCROLLBARBUTTON_RIGHT) { + // set the state to disabled when the scrollbar is scrolled to + // the beginning or the end, depending on the button type. + int32_t curpos = CheckIntAttr(aFrame, nsGkAtoms::curpos, 0); + int32_t maxpos = CheckIntAttr(aFrame, nsGkAtoms::maxpos, 100); + if (ShouldScrollbarButtonBeDisabled(curpos, maxpos, aWidgetType)) { + aState->disabled = true; + } + + // In order to simulate native GTK scrollbar click behavior, + // we set the active attribute on the element to true if it's + // pressed with any mouse button. + // This allows us to show that it's active without setting :active + else if (CheckBooleanAttr(aFrame, nsGkAtoms::active)) + aState->active = true; + + if (aWidgetFlags) { + *aWidgetFlags = GetScrollbarButtonType(aFrame); + if (aWidgetType - NS_THEME_SCROLLBARBUTTON_UP < 2) + *aWidgetFlags |= MOZ_GTK_STEPPER_VERTICAL; + } + } + + // menu item state is determined by the attribute "_moz-menuactive", + // and not by the mouse hovering (accessibility). as a special case, + // menus which are children of a menu bar are only marked as prelight + // if they are open, not on normal hover. + + if (aWidgetType == NS_THEME_MENUITEM || + aWidgetType == NS_THEME_CHECKMENUITEM || + aWidgetType == NS_THEME_RADIOMENUITEM || + aWidgetType == NS_THEME_MENUSEPARATOR || + aWidgetType == NS_THEME_MENUARROW) { + bool isTopLevel = false; + nsMenuFrame *menuFrame = do_QueryFrame(aFrame); + if (menuFrame) { + isTopLevel = menuFrame->IsOnMenuBar(); + } + + if (isTopLevel) { + aState->inHover = menuFrame->IsOpen(); + } else { + aState->inHover = CheckBooleanAttr(aFrame, nsGkAtoms::menuactive); + } + + aState->active = FALSE; + + if (aWidgetType == NS_THEME_CHECKMENUITEM || + aWidgetType == NS_THEME_RADIOMENUITEM) { + *aWidgetFlags = 0; + if (aFrame && aFrame->GetContent()) { + *aWidgetFlags = aFrame->GetContent()-> + AttrValueIs(kNameSpaceID_None, nsGkAtoms::checked, + nsGkAtoms::_true, eIgnoreCase); + } + } + } + + // A button with drop down menu open or an activated toggle button + // should always appear depressed. + if (aWidgetType == NS_THEME_BUTTON || + aWidgetType == NS_THEME_TOOLBARBUTTON || + aWidgetType == NS_THEME_DUALBUTTON || + aWidgetType == NS_THEME_TOOLBARBUTTON_DROPDOWN || + aWidgetType == NS_THEME_MENULIST || + aWidgetType == NS_THEME_MENULIST_BUTTON) { + bool menuOpen = IsOpenButton(aFrame); + aState->depressed = IsCheckedButton(aFrame) || menuOpen; + // we must not highlight buttons with open drop down menus on hover. + aState->inHover = aState->inHover && !menuOpen; + } + + // When the input field of the drop down button has focus, some themes + // should draw focus for the drop down button as well. + if (aWidgetType == NS_THEME_MENULIST_BUTTON && aWidgetFlags) { + *aWidgetFlags = CheckBooleanAttr(aFrame, nsGkAtoms::parentfocused); + } + } + } + + switch (aWidgetType) { + case NS_THEME_BUTTON: + if (aWidgetFlags) + *aWidgetFlags = GTK_RELIEF_NORMAL; + aGtkWidgetType = MOZ_GTK_BUTTON; + break; + case NS_THEME_TOOLBARBUTTON: + case NS_THEME_DUALBUTTON: + if (aWidgetFlags) + *aWidgetFlags = GTK_RELIEF_NONE; + aGtkWidgetType = MOZ_GTK_TOOLBAR_BUTTON; + break; + case NS_THEME_FOCUS_OUTLINE: + aGtkWidgetType = MOZ_GTK_ENTRY; + break; + case NS_THEME_CHECKBOX: + case NS_THEME_RADIO: + aGtkWidgetType = (aWidgetType == NS_THEME_RADIO) ? MOZ_GTK_RADIOBUTTON : MOZ_GTK_CHECKBUTTON; + break; + case NS_THEME_SCROLLBARBUTTON_UP: + case NS_THEME_SCROLLBARBUTTON_DOWN: + case NS_THEME_SCROLLBARBUTTON_LEFT: + case NS_THEME_SCROLLBARBUTTON_RIGHT: + aGtkWidgetType = MOZ_GTK_SCROLLBAR_BUTTON; + break; + case NS_THEME_SCROLLBAR_VERTICAL: + aGtkWidgetType = MOZ_GTK_SCROLLBAR_VERTICAL; + if (GetWidgetTransparency(aFrame, aWidgetType) == eOpaque) + *aWidgetFlags = MOZ_GTK_TRACK_OPAQUE; + else + *aWidgetFlags = 0; + break; + case NS_THEME_SCROLLBAR_HORIZONTAL: + aGtkWidgetType = MOZ_GTK_SCROLLBAR_HORIZONTAL; + if (GetWidgetTransparency(aFrame, aWidgetType) == eOpaque) + *aWidgetFlags = MOZ_GTK_TRACK_OPAQUE; + else + *aWidgetFlags = 0; + break; + case NS_THEME_SCROLLBARTRACK_HORIZONTAL: + aGtkWidgetType = MOZ_GTK_SCROLLBAR_TROUGH_HORIZONTAL; + break; + case NS_THEME_SCROLLBARTRACK_VERTICAL: + aGtkWidgetType = MOZ_GTK_SCROLLBAR_TROUGH_VERTICAL; + break; + case NS_THEME_SCROLLBARTHUMB_VERTICAL: + aGtkWidgetType = MOZ_GTK_SCROLLBAR_THUMB_VERTICAL; + break; + case NS_THEME_SCROLLBARTHUMB_HORIZONTAL: + aGtkWidgetType = MOZ_GTK_SCROLLBAR_THUMB_HORIZONTAL; + break; + case NS_THEME_SPINNER: + aGtkWidgetType = MOZ_GTK_SPINBUTTON; + break; + case NS_THEME_SPINNER_UPBUTTON: + aGtkWidgetType = MOZ_GTK_SPINBUTTON_UP; + break; + case NS_THEME_SPINNER_DOWNBUTTON: + aGtkWidgetType = MOZ_GTK_SPINBUTTON_DOWN; + break; + case NS_THEME_SPINNER_TEXTFIELD: + aGtkWidgetType = MOZ_GTK_SPINBUTTON_ENTRY; + break; + case NS_THEME_RANGE: + { + if (IsRangeHorizontal(aFrame)) { + if (aWidgetFlags) + *aWidgetFlags = GTK_ORIENTATION_HORIZONTAL; + aGtkWidgetType = MOZ_GTK_SCALE_HORIZONTAL; + } else { + if (aWidgetFlags) + *aWidgetFlags = GTK_ORIENTATION_VERTICAL; + aGtkWidgetType = MOZ_GTK_SCALE_VERTICAL; + } + break; + } + case NS_THEME_RANGE_THUMB: + { + if (IsRangeHorizontal(aFrame)) { + if (aWidgetFlags) + *aWidgetFlags = GTK_ORIENTATION_HORIZONTAL; + aGtkWidgetType = MOZ_GTK_SCALE_THUMB_HORIZONTAL; + } else { + if (aWidgetFlags) + *aWidgetFlags = GTK_ORIENTATION_VERTICAL; + aGtkWidgetType = MOZ_GTK_SCALE_THUMB_VERTICAL; + } + break; + } + case NS_THEME_SCALE_HORIZONTAL: + if (aWidgetFlags) + *aWidgetFlags = GTK_ORIENTATION_HORIZONTAL; + aGtkWidgetType = MOZ_GTK_SCALE_HORIZONTAL; + break; + case NS_THEME_SCALETHUMB_HORIZONTAL: + if (aWidgetFlags) + *aWidgetFlags = GTK_ORIENTATION_HORIZONTAL; + aGtkWidgetType = MOZ_GTK_SCALE_THUMB_HORIZONTAL; + break; + case NS_THEME_SCALE_VERTICAL: + if (aWidgetFlags) + *aWidgetFlags = GTK_ORIENTATION_VERTICAL; + aGtkWidgetType = MOZ_GTK_SCALE_VERTICAL; + break; + case NS_THEME_SEPARATOR: + aGtkWidgetType = MOZ_GTK_TOOLBAR_SEPARATOR; + break; + case NS_THEME_SCALETHUMB_VERTICAL: + if (aWidgetFlags) + *aWidgetFlags = GTK_ORIENTATION_VERTICAL; + aGtkWidgetType = MOZ_GTK_SCALE_THUMB_VERTICAL; + break; + case NS_THEME_TOOLBARGRIPPER: + aGtkWidgetType = MOZ_GTK_GRIPPER; + break; + case NS_THEME_RESIZER: + aGtkWidgetType = MOZ_GTK_RESIZER; + break; + case NS_THEME_NUMBER_INPUT: + case NS_THEME_TEXTFIELD: + aGtkWidgetType = MOZ_GTK_ENTRY; + break; + case NS_THEME_TEXTFIELD_MULTILINE: +#if (MOZ_WIDGET_GTK == 3) + aGtkWidgetType = MOZ_GTK_TEXT_VIEW; +#else + aGtkWidgetType = MOZ_GTK_ENTRY; +#endif + break; + case NS_THEME_LISTBOX: + case NS_THEME_TREEVIEW: + aGtkWidgetType = MOZ_GTK_TREEVIEW; + break; + case NS_THEME_TREEHEADERCELL: + if (aWidgetFlags) { + // In this case, the flag denotes whether the header is the sorted one or not + if (GetTreeSortDirection(aFrame) == eTreeSortDirection_Natural) + *aWidgetFlags = false; + else + *aWidgetFlags = true; + } + aGtkWidgetType = MOZ_GTK_TREE_HEADER_CELL; + break; + case NS_THEME_TREEHEADERSORTARROW: + if (aWidgetFlags) { + switch (GetTreeSortDirection(aFrame)) { + case eTreeSortDirection_Ascending: + *aWidgetFlags = GTK_ARROW_DOWN; + break; + case eTreeSortDirection_Descending: + *aWidgetFlags = GTK_ARROW_UP; + break; + case eTreeSortDirection_Natural: + default: + /* This prevents the treecolums from getting smaller + * and wider when switching sort direction off and on + * */ + *aWidgetFlags = GTK_ARROW_NONE; + break; + } + } + aGtkWidgetType = MOZ_GTK_TREE_HEADER_SORTARROW; + break; + case NS_THEME_TREETWISTY: + aGtkWidgetType = MOZ_GTK_TREEVIEW_EXPANDER; + if (aWidgetFlags) + *aWidgetFlags = GTK_EXPANDER_COLLAPSED; + break; + case NS_THEME_TREETWISTYOPEN: + aGtkWidgetType = MOZ_GTK_TREEVIEW_EXPANDER; + if (aWidgetFlags) + *aWidgetFlags = GTK_EXPANDER_EXPANDED; + break; + case NS_THEME_MENULIST: + aGtkWidgetType = MOZ_GTK_DROPDOWN; + if (aWidgetFlags) + *aWidgetFlags = IsFrameContentNodeInNamespace(aFrame, kNameSpaceID_XHTML); + break; + case NS_THEME_MENULIST_TEXT: + return false; // nothing to do, but prevents the bg from being drawn + case NS_THEME_MENULIST_TEXTFIELD: + aGtkWidgetType = MOZ_GTK_DROPDOWN_ENTRY; + break; + case NS_THEME_MENULIST_BUTTON: + aGtkWidgetType = MOZ_GTK_DROPDOWN_ARROW; + break; + case NS_THEME_TOOLBARBUTTON_DROPDOWN: + case NS_THEME_BUTTON_ARROW_DOWN: + case NS_THEME_BUTTON_ARROW_UP: + case NS_THEME_BUTTON_ARROW_NEXT: + case NS_THEME_BUTTON_ARROW_PREVIOUS: + aGtkWidgetType = MOZ_GTK_TOOLBARBUTTON_ARROW; + if (aWidgetFlags) { + *aWidgetFlags = GTK_ARROW_DOWN; + + if (aWidgetType == NS_THEME_BUTTON_ARROW_UP) + *aWidgetFlags = GTK_ARROW_UP; + else if (aWidgetType == NS_THEME_BUTTON_ARROW_NEXT) + *aWidgetFlags = GTK_ARROW_RIGHT; + else if (aWidgetType == NS_THEME_BUTTON_ARROW_PREVIOUS) + *aWidgetFlags = GTK_ARROW_LEFT; + } + break; + case NS_THEME_CHECKBOX_CONTAINER: + aGtkWidgetType = MOZ_GTK_CHECKBUTTON_CONTAINER; + break; + case NS_THEME_RADIO_CONTAINER: + aGtkWidgetType = MOZ_GTK_RADIOBUTTON_CONTAINER; + break; + case NS_THEME_CHECKBOX_LABEL: + aGtkWidgetType = MOZ_GTK_CHECKBUTTON_LABEL; + break; + case NS_THEME_RADIO_LABEL: + aGtkWidgetType = MOZ_GTK_RADIOBUTTON_LABEL; + break; + case NS_THEME_TOOLBAR: + aGtkWidgetType = MOZ_GTK_TOOLBAR; + break; + case NS_THEME_TOOLTIP: + aGtkWidgetType = MOZ_GTK_TOOLTIP; + break; + case NS_THEME_STATUSBARPANEL: + case NS_THEME_RESIZERPANEL: + aGtkWidgetType = MOZ_GTK_FRAME; + break; + case NS_THEME_PROGRESSBAR: + case NS_THEME_PROGRESSBAR_VERTICAL: + aGtkWidgetType = MOZ_GTK_PROGRESSBAR; + break; + case NS_THEME_PROGRESSCHUNK: + case NS_THEME_PROGRESSCHUNK_VERTICAL: + { + nsIFrame* stateFrame = aFrame->GetParent(); + EventStates eventStates = GetContentState(stateFrame, aWidgetType); + + aGtkWidgetType = IsIndeterminateProgress(stateFrame, eventStates) + ? IsVerticalProgress(stateFrame) + ? MOZ_GTK_PROGRESS_CHUNK_VERTICAL_INDETERMINATE + : MOZ_GTK_PROGRESS_CHUNK_INDETERMINATE + : MOZ_GTK_PROGRESS_CHUNK; + } + break; + case NS_THEME_TAB_SCROLL_ARROW_BACK: + case NS_THEME_TAB_SCROLL_ARROW_FORWARD: + if (aWidgetFlags) + *aWidgetFlags = aWidgetType == NS_THEME_TAB_SCROLL_ARROW_BACK ? + GTK_ARROW_LEFT : GTK_ARROW_RIGHT; + aGtkWidgetType = MOZ_GTK_TAB_SCROLLARROW; + break; + case NS_THEME_TABPANELS: + aGtkWidgetType = MOZ_GTK_TABPANELS; + break; + case NS_THEME_TAB: + { + if (IsBottomTab(aFrame)) { + aGtkWidgetType = MOZ_GTK_TAB_BOTTOM; + } else { + aGtkWidgetType = MOZ_GTK_TAB_TOP; + } + + if (aWidgetFlags) { + /* First bits will be used to store max(0,-bmargin) where bmargin + * is the bottom margin of the tab in pixels (resp. top margin, + * for bottom tabs). */ + *aWidgetFlags = GetTabMarginPixels(aFrame); + + if (IsSelectedTab(aFrame)) + *aWidgetFlags |= MOZ_GTK_TAB_SELECTED; + + if (IsFirstTab(aFrame)) + *aWidgetFlags |= MOZ_GTK_TAB_FIRST; + } + } + break; + case NS_THEME_SPLITTER: + if (IsHorizontal(aFrame)) + aGtkWidgetType = MOZ_GTK_SPLITTER_VERTICAL; + else + aGtkWidgetType = MOZ_GTK_SPLITTER_HORIZONTAL; + break; + case NS_THEME_MENUBAR: + aGtkWidgetType = MOZ_GTK_MENUBAR; + break; + case NS_THEME_MENUPOPUP: + aGtkWidgetType = MOZ_GTK_MENUPOPUP; + break; + case NS_THEME_MENUITEM: + { + nsMenuFrame *menuFrame = do_QueryFrame(aFrame); + if (menuFrame && menuFrame->IsOnMenuBar()) { + aGtkWidgetType = MOZ_GTK_MENUBARITEM; + break; + } + } + aGtkWidgetType = MOZ_GTK_MENUITEM; + break; + case NS_THEME_MENUSEPARATOR: + aGtkWidgetType = MOZ_GTK_MENUSEPARATOR; + break; + case NS_THEME_MENUARROW: + aGtkWidgetType = MOZ_GTK_MENUARROW; + break; + case NS_THEME_CHECKMENUITEM: + aGtkWidgetType = MOZ_GTK_CHECKMENUITEM; + break; + case NS_THEME_RADIOMENUITEM: + aGtkWidgetType = MOZ_GTK_RADIOMENUITEM; + break; + case NS_THEME_WINDOW: + case NS_THEME_DIALOG: + aGtkWidgetType = MOZ_GTK_WINDOW; + break; + case NS_THEME_GTK_INFO_BAR: + aGtkWidgetType = MOZ_GTK_INFO_BAR; + break; + default: + return false; + } + + return true; +} + +#if (MOZ_WIDGET_GTK == 2) +class ThemeRenderer : public gfxGdkNativeRenderer { +public: + ThemeRenderer(GtkWidgetState aState, WidgetNodeType aGTKWidgetType, + gint aFlags, GtkTextDirection aDirection, + const GdkRectangle& aGDKRect, const GdkRectangle& aGDKClip) + : mState(aState), mGTKWidgetType(aGTKWidgetType), mFlags(aFlags), + mDirection(aDirection), mGDKRect(aGDKRect), mGDKClip(aGDKClip) {} + nsresult DrawWithGDK(GdkDrawable * drawable, gint offsetX, gint offsetY, + GdkRectangle * clipRects, uint32_t numClipRects); +private: + GtkWidgetState mState; + WidgetNodeType mGTKWidgetType; + gint mFlags; + GtkTextDirection mDirection; + const GdkRectangle& mGDKRect; + const GdkRectangle& mGDKClip; +}; + +nsresult +ThemeRenderer::DrawWithGDK(GdkDrawable * drawable, gint offsetX, + gint offsetY, GdkRectangle * clipRects, uint32_t numClipRects) +{ + GdkRectangle gdk_rect = mGDKRect; + gdk_rect.x += offsetX; + gdk_rect.y += offsetY; + + GdkRectangle gdk_clip = mGDKClip; + gdk_clip.x += offsetX; + gdk_clip.y += offsetY; + + GdkRectangle surfaceRect; + surfaceRect.x = 0; + surfaceRect.y = 0; + gdk_drawable_get_size(drawable, &surfaceRect.width, &surfaceRect.height); + gdk_rectangle_intersect(&gdk_clip, &surfaceRect, &gdk_clip); + + NS_ASSERTION(numClipRects == 0, "We don't support clipping!!!"); + moz_gtk_widget_paint(mGTKWidgetType, drawable, &gdk_rect, &gdk_clip, + &mState, mFlags, mDirection); + + return NS_OK; +} +#else +class SystemCairoClipper : public ClipExporter { +public: + explicit SystemCairoClipper(cairo_t* aContext) : mContext(aContext) + { + } + + void + BeginClip(const Matrix& aTransform) override + { + cairo_matrix_t mat; + GfxMatrixToCairoMatrix(aTransform, mat); + cairo_set_matrix(mContext, &mat); + + cairo_new_path(mContext); + } + + void + MoveTo(const Point &aPoint) override + { + cairo_move_to(mContext, aPoint.x, aPoint.y); + mCurrentPoint = aPoint; + } + + void + LineTo(const Point &aPoint) override + { + cairo_line_to(mContext, aPoint.x, aPoint.y); + mCurrentPoint = aPoint; + } + + void + BezierTo(const Point &aCP1, const Point &aCP2, const Point &aCP3) override + { + cairo_curve_to(mContext, aCP1.x, aCP1.y, aCP2.x, aCP2.y, aCP3.x, aCP3.y); + mCurrentPoint = aCP3; + } + + void + QuadraticBezierTo(const Point &aCP1, const Point &aCP2) override + { + Point CP0 = CurrentPoint(); + Point CP1 = (CP0 + aCP1 * 2.0) / 3.0; + Point CP2 = (aCP2 + aCP1 * 2.0) / 3.0; + Point CP3 = aCP2; + cairo_curve_to(mContext, CP1.x, CP1.y, CP2.x, CP2.y, CP3.x, CP3.y); + mCurrentPoint = aCP2; + } + + void + Arc(const Point &aOrigin, float aRadius, float aStartAngle, float aEndAngle, + bool aAntiClockwise) override + { + ArcToBezier(this, aOrigin, Size(aRadius, aRadius), aStartAngle, aEndAngle, + aAntiClockwise); + } + + void + Close() override + { + cairo_close_path(mContext); + } + + void + EndClip() override + { + cairo_clip(mContext); + } + + Point + CurrentPoint() const override + { + return mCurrentPoint; + } + +private: + cairo_t* mContext; + Point mCurrentPoint; +}; + +static void +DrawThemeWithCairo(gfxContext* aContext, DrawTarget* aDrawTarget, + GtkWidgetState aState, WidgetNodeType aGTKWidgetType, + gint aFlags, GtkTextDirection aDirection, gint aScaleFactor, + bool aSnapped, const Point& aDrawOrigin, const nsIntSize& aDrawSize, + GdkRectangle& aGDKRect, nsITheme::Transparency aTransparency) +{ + Point drawOffset; + Matrix transform; + if (!aSnapped) { + // If we are not snapped, we depend on the DT for translation. + drawOffset = aDrawOrigin; + transform = aDrawTarget->GetTransform().PreTranslate(aDrawOrigin); + } else { + // Otherwise, we only need to take the device offset into account. + drawOffset = aDrawOrigin - aContext->GetDeviceOffset(); + transform = Matrix::Translation(drawOffset); + } + + if (aScaleFactor != 1) + transform.PreScale(aScaleFactor, aScaleFactor); + + cairo_matrix_t mat; + GfxMatrixToCairoMatrix(transform, mat); + + nsIntSize clipSize((aDrawSize.width + aScaleFactor - 1) / aScaleFactor, + (aDrawSize.height + aScaleFactor - 1) / aScaleFactor); + +#ifndef MOZ_TREE_CAIRO + // Directly use the Cairo draw target to render the widget if using system Cairo everywhere. + BorrowedCairoContext borrowCairo(aDrawTarget); + if (borrowCairo.mCairo) { + cairo_set_matrix(borrowCairo.mCairo, &mat); + + cairo_new_path(borrowCairo.mCairo); + cairo_rectangle(borrowCairo.mCairo, 0, 0, clipSize.width, clipSize.height); + cairo_clip(borrowCairo.mCairo); + + moz_gtk_widget_paint(aGTKWidgetType, borrowCairo.mCairo, &aGDKRect, &aState, aFlags, aDirection); + + borrowCairo.Finish(); + return; + } +#endif + + // A direct Cairo draw target is not available, so we need to create a temporary one. +#if defined(MOZ_X11) && defined(CAIRO_HAS_XLIB_SURFACE) + // If using a Cairo xlib surface, then try to reuse it. + BorrowedXlibDrawable borrow(aDrawTarget); + if (borrow.GetDrawable()) { + nsIntSize size = borrow.GetSize(); + cairo_surface_t* surf = nullptr; + // Check if the surface is using XRender. +#ifdef CAIRO_HAS_XLIB_XRENDER_SURFACE + if (borrow.GetXRenderFormat()) { + surf = cairo_xlib_surface_create_with_xrender_format( + borrow.GetDisplay(), borrow.GetDrawable(), borrow.GetScreen(), + borrow.GetXRenderFormat(), size.width, size.height); + } else { +#else + if (! borrow.GetXRenderFormat()) { +#endif + surf = cairo_xlib_surface_create( + borrow.GetDisplay(), borrow.GetDrawable(), borrow.GetVisual(), + size.width, size.height); + } + if (!NS_WARN_IF(!surf)) { + Point offset = borrow.GetOffset(); + if (offset != Point()) { + cairo_surface_set_device_offset(surf, offset.x, offset.y); + } + cairo_t* cr = cairo_create(surf); + if (!NS_WARN_IF(!cr)) { + RefPtr<SystemCairoClipper> clipper = new SystemCairoClipper(cr); + aContext->ExportClip(*clipper); + + cairo_set_matrix(cr, &mat); + + cairo_new_path(cr); + cairo_rectangle(cr, 0, 0, clipSize.width, clipSize.height); + cairo_clip(cr); + + moz_gtk_widget_paint(aGTKWidgetType, cr, &aGDKRect, &aState, aFlags, aDirection); + + cairo_destroy(cr); + } + cairo_surface_destroy(surf); + } + borrow.Finish(); + return; + } +#endif + + // Check if the widget requires complex masking that must be composited. + // Try to directly write to the draw target's pixels if possible. + uint8_t* data; + nsIntSize size; + int32_t stride; + SurfaceFormat format; + IntPoint origin; + if (aDrawTarget->LockBits(&data, &size, &stride, &format, &origin)) { + // Create a Cairo image surface context the device rectangle. + cairo_surface_t* surf = + cairo_image_surface_create_for_data( + data, GfxFormatToCairoFormat(format), size.width, size.height, stride); + if (!NS_WARN_IF(!surf)) { + if (origin != IntPoint()) { + cairo_surface_set_device_offset(surf, -origin.x, -origin.y); + } + cairo_t* cr = cairo_create(surf); + if (!NS_WARN_IF(!cr)) { + RefPtr<SystemCairoClipper> clipper = new SystemCairoClipper(cr); + aContext->ExportClip(*clipper); + + cairo_set_matrix(cr, &mat); + + cairo_new_path(cr); + cairo_rectangle(cr, 0, 0, clipSize.width, clipSize.height); + cairo_clip(cr); + + moz_gtk_widget_paint(aGTKWidgetType, cr, &aGDKRect, &aState, aFlags, aDirection); + + cairo_destroy(cr); + } + cairo_surface_destroy(surf); + } + aDrawTarget->ReleaseBits(data); + } else { + // If the widget has any transparency, make sure to choose an alpha format. + format = aTransparency != nsITheme::eOpaque ? SurfaceFormat::B8G8R8A8 : aDrawTarget->GetFormat(); + // Create a temporary data surface to render the widget into. + RefPtr<DataSourceSurface> dataSurface = + Factory::CreateDataSourceSurface(aDrawSize, format, aTransparency != nsITheme::eOpaque); + DataSourceSurface::MappedSurface map; + if (!NS_WARN_IF(!(dataSurface && dataSurface->Map(DataSourceSurface::MapType::WRITE, &map)))) { + // Create a Cairo image surface wrapping the data surface. + cairo_surface_t* surf = + cairo_image_surface_create_for_data(map.mData, GfxFormatToCairoFormat(format), + aDrawSize.width, aDrawSize.height, map.mStride); + cairo_t* cr = nullptr; + if (!NS_WARN_IF(!surf)) { + cr = cairo_create(surf); + if (!NS_WARN_IF(!cr)) { + if (aScaleFactor != 1) { + cairo_scale(cr, aScaleFactor, aScaleFactor); + } + + moz_gtk_widget_paint(aGTKWidgetType, cr, &aGDKRect, &aState, aFlags, aDirection); + } + } + + // Unmap the surface before using it as a source + dataSurface->Unmap(); + + if (cr) { + if (!aSnapped || aTransparency != nsITheme::eOpaque) { + // The widget either needs to be masked or has transparency, so use the slower drawing path. + aDrawTarget->DrawSurface(dataSurface, + Rect(aSnapped ? drawOffset - aDrawTarget->GetTransform().GetTranslation() : drawOffset, + Size(aDrawSize)), + Rect(0, 0, aDrawSize.width, aDrawSize.height)); + } else { + // The widget is a simple opaque rectangle, so just copy it out. + aDrawTarget->CopySurface(dataSurface, + IntRect(0, 0, aDrawSize.width, aDrawSize.height), + TruncatedToInt(drawOffset)); + } + + cairo_destroy(cr); + } + + if (surf) { + cairo_surface_destroy(surf); + } + } + } +} +#endif + +bool +nsNativeThemeGTK::GetExtraSizeForWidget(nsIFrame* aFrame, uint8_t aWidgetType, + nsIntMargin* aExtra) +{ + *aExtra = nsIntMargin(0,0,0,0); + // Allow an extra one pixel above and below the thumb for certain + // GTK2 themes (Ximian Industrial, Bluecurve, Misty, at least); + // We modify the frame's overflow area. See bug 297508. + switch (aWidgetType) { + case NS_THEME_SCROLLBARTHUMB_VERTICAL: + aExtra->top = aExtra->bottom = 1; + break; + case NS_THEME_SCROLLBARTHUMB_HORIZONTAL: + aExtra->left = aExtra->right = 1; + break; + + // Include the indicator spacing (the padding around the control). + case NS_THEME_CHECKBOX: + case NS_THEME_RADIO: + { + gint indicator_size, indicator_spacing; + + if (aWidgetType == NS_THEME_CHECKBOX) { + moz_gtk_checkbox_get_metrics(&indicator_size, &indicator_spacing); + } else { + moz_gtk_radio_get_metrics(&indicator_size, &indicator_spacing); + } + + aExtra->top = indicator_spacing; + aExtra->right = indicator_spacing; + aExtra->bottom = indicator_spacing; + aExtra->left = indicator_spacing; + break; + } + case NS_THEME_BUTTON : + { + if (IsDefaultButton(aFrame)) { + // Some themes draw a default indicator outside the widget, + // include that in overflow + gint top, left, bottom, right; + moz_gtk_button_get_default_overflow(&top, &left, &bottom, &right); + aExtra->top = top; + aExtra->right = right; + aExtra->bottom = bottom; + aExtra->left = left; + break; + } + return false; + } + case NS_THEME_FOCUS_OUTLINE: + { + moz_gtk_get_focus_outline_size(&aExtra->left, &aExtra->top); + aExtra->right = aExtra->left; + aExtra->bottom = aExtra->top; + break; + } + case NS_THEME_TAB : + { + if (!IsSelectedTab(aFrame)) + return false; + + gint gap_height = moz_gtk_get_tab_thickness(IsBottomTab(aFrame) ? + MOZ_GTK_TAB_BOTTOM : MOZ_GTK_TAB_TOP); + if (!gap_height) + return false; + + int32_t extra = gap_height - GetTabMarginPixels(aFrame); + if (extra <= 0) + return false; + + if (IsBottomTab(aFrame)) { + aExtra->top = extra; + } else { + aExtra->bottom = extra; + } + return false; + } + default: + return false; + } + gint scale = nsScreenGtk::GetGtkMonitorScaleFactor(); + aExtra->top *= scale; + aExtra->right *= scale; + aExtra->bottom *= scale; + aExtra->left *= scale; + return true; +} + +NS_IMETHODIMP +nsNativeThemeGTK::DrawWidgetBackground(nsRenderingContext* aContext, + nsIFrame* aFrame, + uint8_t aWidgetType, + const nsRect& aRect, + const nsRect& aDirtyRect) +{ + GtkWidgetState state; + WidgetNodeType gtkWidgetType; + GtkTextDirection direction = GetTextDirection(aFrame); + gint flags; + if (!GetGtkWidgetAndState(aWidgetType, aFrame, gtkWidgetType, &state, + &flags)) + return NS_OK; + + gfxContext* ctx = aContext->ThebesContext(); + nsPresContext *presContext = aFrame->PresContext(); + + gfxRect rect = presContext->AppUnitsToGfxUnits(aRect); + gfxRect dirtyRect = presContext->AppUnitsToGfxUnits(aDirtyRect); + gint scaleFactor = nsScreenGtk::GetGtkMonitorScaleFactor(); + + // Align to device pixels where sensible + // to provide crisper and faster drawing. + // Don't snap if it's a non-unit scale factor. We're going to have to take + // slow paths then in any case. + bool snapped = ctx->UserToDevicePixelSnapped(rect); + if (snapped) { + // Leave rect in device coords but make dirtyRect consistent. + dirtyRect = ctx->UserToDevice(dirtyRect); + } + + // Translate the dirty rect so that it is wrt the widget top-left. + dirtyRect.MoveBy(-rect.TopLeft()); + // Round out the dirty rect to gdk pixels to ensure that gtk draws + // enough pixels for interpolation to device pixels. + dirtyRect.RoundOut(); + + // GTK themes can only draw an integer number of pixels + // (even when not snapped). + nsIntRect widgetRect(0, 0, NS_lround(rect.Width()), NS_lround(rect.Height())); + nsIntRect overflowRect(widgetRect); + nsIntMargin extraSize; + if (GetExtraSizeForWidget(aFrame, aWidgetType, &extraSize)) { + overflowRect.Inflate(extraSize); + } + + // This is the rectangle that will actually be drawn, in gdk pixels + nsIntRect drawingRect(int32_t(dirtyRect.X()), + int32_t(dirtyRect.Y()), + int32_t(dirtyRect.Width()), + int32_t(dirtyRect.Height())); + if (widgetRect.IsEmpty() + || !drawingRect.IntersectRect(overflowRect, drawingRect)) + return NS_OK; + + NS_ASSERTION(!IsWidgetTypeDisabled(mDisabledWidgetTypes, aWidgetType), + "Trying to render an unsafe widget!"); + + bool safeState = IsWidgetStateSafe(mSafeWidgetStates, aWidgetType, &state); + if (!safeState) { + gLastGdkError = 0; + gdk_error_trap_push (); + } + + Transparency transparency = GetWidgetTransparency(aFrame, aWidgetType); + + // gdk rectangles are wrt the drawing rect. + GdkRectangle gdk_rect = {-drawingRect.x/scaleFactor, + -drawingRect.y/scaleFactor, + widgetRect.width/scaleFactor, + widgetRect.height/scaleFactor}; + + // translate everything so (0,0) is the top left of the drawingRect + gfxPoint origin = rect.TopLeft() + drawingRect.TopLeft(); + +#if (MOZ_WIDGET_GTK == 2) + gfxContextAutoSaveRestore autoSR(ctx); + gfxMatrix matrix; + if (!snapped) { // else rects are in device coords + matrix = ctx->CurrentMatrix(); + } + matrix.Translate(origin); + matrix.Scale(scaleFactor, scaleFactor); // Draw in GDK coords + ctx->SetMatrix(matrix); + + // The gdk_clip is just advisory here, meaning "you don't + // need to draw outside this rect if you don't feel like it!" + GdkRectangle gdk_clip = {0, 0, drawingRect.width, drawingRect.height}; + + ThemeRenderer renderer(state, gtkWidgetType, flags, direction, + gdk_rect, gdk_clip); + + // Some themes (e.g. Clearlooks) just don't clip properly to any + // clip rect we provide, so we cannot advertise support for clipping within + // the widget bounds. + uint32_t rendererFlags = 0; + if (transparency == eOpaque) { + rendererFlags |= gfxGdkNativeRenderer::DRAW_IS_OPAQUE; + } + + // GtkStyles (used by the widget drawing backend) are created for a + // particular colormap/visual. + GdkColormap* colormap = moz_gtk_widget_get_colormap(); + + renderer.Draw(ctx, drawingRect.Size(), rendererFlags, colormap); +#else + DrawThemeWithCairo(ctx, aContext->GetDrawTarget(), + state, gtkWidgetType, flags, direction, scaleFactor, + snapped, ToPoint(origin), drawingRect.Size(), + gdk_rect, transparency); +#endif + + if (!safeState) { + gdk_flush(); + gLastGdkError = gdk_error_trap_pop (); + + if (gLastGdkError) { +#ifdef DEBUG + printf("GTK theme failed for widget type %d, error was %d, state was " + "[active=%d,focused=%d,inHover=%d,disabled=%d]\n", + aWidgetType, gLastGdkError, state.active, state.focused, + state.inHover, state.disabled); +#endif + NS_WARNING("GTK theme failed; disabling unsafe widget"); + SetWidgetTypeDisabled(mDisabledWidgetTypes, aWidgetType); + // force refresh of the window, because the widget was not + // successfully drawn it must be redrawn using the default look + RefreshWidgetWindow(aFrame); + } else { + SetWidgetStateSafe(mSafeWidgetStates, aWidgetType, &state); + } + } + + // Indeterminate progress bar are animated. + if (gtkWidgetType == MOZ_GTK_PROGRESS_CHUNK_INDETERMINATE || + gtkWidgetType == MOZ_GTK_PROGRESS_CHUNK_VERTICAL_INDETERMINATE) { + if (!QueueAnimatedContentForRefresh(aFrame->GetContent(), 30)) { + NS_WARNING("unable to animate widget!"); + } + } + + return NS_OK; +} + +WidgetNodeType +nsNativeThemeGTK::NativeThemeToGtkTheme(uint8_t aWidgetType, nsIFrame* aFrame) +{ + WidgetNodeType gtkWidgetType; + gint unusedFlags; + + if (!GetGtkWidgetAndState(aWidgetType, aFrame, gtkWidgetType, nullptr, + &unusedFlags)) + { + MOZ_ASSERT_UNREACHABLE("Unknown native widget to gtk widget mapping"); + return MOZ_GTK_WINDOW; + } + return gtkWidgetType; +} + +NS_IMETHODIMP +nsNativeThemeGTK::GetWidgetBorder(nsDeviceContext* aContext, nsIFrame* aFrame, + uint8_t aWidgetType, nsIntMargin* aResult) +{ + GtkTextDirection direction = GetTextDirection(aFrame); + aResult->top = aResult->left = aResult->right = aResult->bottom = 0; + switch (aWidgetType) { + case NS_THEME_TOOLBOX: + // gtk has no toolbox equivalent. So, although we map toolbox to + // gtk's 'toolbar' for purposes of painting the widget background, + // we don't use the toolbar border for toolbox. + break; + case NS_THEME_DUALBUTTON: + // TOOLBAR_DUAL_BUTTON is an interesting case. We want a border to draw + // around the entire button + dropdown, and also an inner border if you're + // over the button part. But, we want the inner button to be right up + // against the edge of the outer button so that the borders overlap. + // To make this happen, we draw a button border for the outer button, + // but don't reserve any space for it. + break; + case NS_THEME_TAB: + { + WidgetNodeType gtkWidgetType; + gint flags; + + if (!GetGtkWidgetAndState(aWidgetType, aFrame, gtkWidgetType, nullptr, + &flags)) + return NS_OK; + + moz_gtk_get_tab_border(&aResult->left, &aResult->top, + &aResult->right, &aResult->bottom, direction, + (GtkTabFlags)flags, gtkWidgetType); + } + break; + case NS_THEME_MENUITEM: + case NS_THEME_CHECKMENUITEM: + case NS_THEME_RADIOMENUITEM: + // For regular menuitems, we will be using GetWidgetPadding instead of + // GetWidgetBorder to pad up the widget's internals; other menuitems + // will need to fall through and use the default case as before. + if (IsRegularMenuItem(aFrame)) + break; + MOZ_FALLTHROUGH; + default: + { + WidgetNodeType gtkWidgetType; + gint unusedFlags; + if (GetGtkWidgetAndState(aWidgetType, aFrame, gtkWidgetType, nullptr, + &unusedFlags)) { + moz_gtk_get_widget_border(gtkWidgetType, &aResult->left, &aResult->top, + &aResult->right, &aResult->bottom, direction, + IsFrameContentNodeInNamespace(aFrame, kNameSpaceID_XHTML)); + } + } + } + + gint scale = nsScreenGtk::GetGtkMonitorScaleFactor(); + aResult->top *= scale; + aResult->right *= scale; + aResult->bottom *= scale; + aResult->left *= scale; + return NS_OK; +} + +bool +nsNativeThemeGTK::GetWidgetPadding(nsDeviceContext* aContext, + nsIFrame* aFrame, uint8_t aWidgetType, + nsIntMargin* aResult) +{ + switch (aWidgetType) { + case NS_THEME_BUTTON_FOCUS: + case NS_THEME_TOOLBARBUTTON: + case NS_THEME_DUALBUTTON: + case NS_THEME_TAB_SCROLL_ARROW_BACK: + case NS_THEME_TAB_SCROLL_ARROW_FORWARD: + case NS_THEME_MENULIST_BUTTON: + case NS_THEME_TOOLBARBUTTON_DROPDOWN: + case NS_THEME_BUTTON_ARROW_UP: + case NS_THEME_BUTTON_ARROW_DOWN: + case NS_THEME_BUTTON_ARROW_NEXT: + case NS_THEME_BUTTON_ARROW_PREVIOUS: + case NS_THEME_RANGE_THUMB: + // Radios and checkboxes return a fixed size in GetMinimumWidgetSize + // and have a meaningful baseline, so they can't have + // author-specified padding. + case NS_THEME_CHECKBOX: + case NS_THEME_RADIO: + aResult->SizeTo(0, 0, 0, 0); + return true; + case NS_THEME_MENUITEM: + case NS_THEME_CHECKMENUITEM: + case NS_THEME_RADIOMENUITEM: + { + // Menubar and menulist have their padding specified in CSS. + if (!IsRegularMenuItem(aFrame)) + return false; + + aResult->SizeTo(0, 0, 0, 0); + WidgetNodeType gtkWidgetType; + if (GetGtkWidgetAndState(aWidgetType, aFrame, gtkWidgetType, nullptr, + nullptr)) { + moz_gtk_get_widget_border(gtkWidgetType, &aResult->left, &aResult->top, + &aResult->right, &aResult->bottom, GetTextDirection(aFrame), + IsFrameContentNodeInNamespace(aFrame, kNameSpaceID_XHTML)); + } + + gint horizontal_padding; + + if (aWidgetType == NS_THEME_MENUITEM) + moz_gtk_menuitem_get_horizontal_padding(&horizontal_padding); + else + moz_gtk_checkmenuitem_get_horizontal_padding(&horizontal_padding); + + aResult->left += horizontal_padding; + aResult->right += horizontal_padding; + + gint scale = nsScreenGtk::GetGtkMonitorScaleFactor(); + aResult->top *= scale; + aResult->right *= scale; + aResult->bottom *= scale; + aResult->left *= scale; + + return true; + } + } + + return false; +} + +bool +nsNativeThemeGTK::GetWidgetOverflow(nsDeviceContext* aContext, + nsIFrame* aFrame, uint8_t aWidgetType, + nsRect* aOverflowRect) +{ + nsIntMargin extraSize; + if (!GetExtraSizeForWidget(aFrame, aWidgetType, &extraSize)) + return false; + + int32_t p2a = aContext->AppUnitsPerDevPixel(); + nsMargin m(NSIntPixelsToAppUnits(extraSize.top, p2a), + NSIntPixelsToAppUnits(extraSize.right, p2a), + NSIntPixelsToAppUnits(extraSize.bottom, p2a), + NSIntPixelsToAppUnits(extraSize.left, p2a)); + + aOverflowRect->Inflate(m); + return true; +} + +NS_IMETHODIMP +nsNativeThemeGTK::GetMinimumWidgetSize(nsPresContext* aPresContext, + nsIFrame* aFrame, uint8_t aWidgetType, + LayoutDeviceIntSize* aResult, + bool* aIsOverridable) +{ + aResult->width = aResult->height = 0; + *aIsOverridable = true; + + switch (aWidgetType) { + case NS_THEME_SCROLLBARBUTTON_UP: + case NS_THEME_SCROLLBARBUTTON_DOWN: + { + if (gtk_check_version(3,20,0) == nullptr) { + moz_gtk_get_widget_min_size(MOZ_GTK_SCROLLBAR_BUTTON, + &(aResult->width), &(aResult->height)); + } else { + MozGtkScrollbarMetrics metrics; + moz_gtk_get_scrollbar_metrics(&metrics); + + aResult->width = metrics.slider_width; + aResult->height = metrics.stepper_size; + } + + *aIsOverridable = false; + } + break; + case NS_THEME_SCROLLBARBUTTON_LEFT: + case NS_THEME_SCROLLBARBUTTON_RIGHT: + { + if (gtk_check_version(3,20,0) == nullptr) { + moz_gtk_get_widget_min_size(MOZ_GTK_SCROLLBAR_BUTTON, + &(aResult->width), &(aResult->height)); + } else { + MozGtkScrollbarMetrics metrics; + moz_gtk_get_scrollbar_metrics(&metrics); + + aResult->width = metrics.stepper_size; + aResult->height = metrics.slider_width; + } + *aIsOverridable = false; + } + break; + case NS_THEME_SPLITTER: + { + gint metrics; + if (IsHorizontal(aFrame)) { + moz_gtk_splitter_get_metrics(GTK_ORIENTATION_HORIZONTAL, &metrics); + aResult->width = metrics; + aResult->height = 0; + } else { + moz_gtk_splitter_get_metrics(GTK_ORIENTATION_VERTICAL, &metrics); + aResult->width = 0; + aResult->height = metrics; + } + *aIsOverridable = false; + } + break; + case NS_THEME_SCROLLBAR_HORIZONTAL: + case NS_THEME_SCROLLBAR_VERTICAL: + { + /* While we enforce a minimum size for the thumb, this is ignored + * for the some scrollbars if buttons are hidden (bug 513006) because + * the thumb isn't a direct child of the scrollbar, unlike the buttons + * or track. So add a minimum size to the track as well to prevent a + * 0-width scrollbar. */ + if (gtk_check_version(3,20,0) == nullptr) { + // Thumb min dimensions to start with + WidgetNodeType thumbType = aWidgetType == NS_THEME_SCROLLBAR_VERTICAL ? + MOZ_GTK_SCROLLBAR_THUMB_VERTICAL : MOZ_GTK_SCROLLBAR_THUMB_HORIZONTAL; + moz_gtk_get_widget_min_size(thumbType, &(aResult->width), &(aResult->height)); + + // Add scrollbar's borders + nsIntMargin border; + nsNativeThemeGTK::GetWidgetBorder(aFrame->PresContext()->DeviceContext(), + aFrame, aWidgetType, &border); + aResult->width += border.left + border.right; + aResult->height += border.top + border.bottom; + + // Add track's borders + uint8_t trackType = aWidgetType == NS_THEME_SCROLLBAR_VERTICAL ? + NS_THEME_SCROLLBARTRACK_VERTICAL : NS_THEME_SCROLLBARTRACK_HORIZONTAL; + nsNativeThemeGTK::GetWidgetBorder(aFrame->PresContext()->DeviceContext(), + aFrame, trackType, &border); + aResult->width += border.left + border.right; + aResult->height += border.top + border.bottom; + } else { + MozGtkScrollbarMetrics metrics; + moz_gtk_get_scrollbar_metrics(&metrics); + + // Require room for the slider in the track if we don't have buttons. + bool hasScrollbarButtons = moz_gtk_has_scrollbar_buttons(); + + if (aWidgetType == NS_THEME_SCROLLBAR_VERTICAL) { + aResult->width = metrics.slider_width + 2 * metrics.trough_border; + if (!hasScrollbarButtons) + aResult->height = metrics.min_slider_size + 2 * metrics.trough_border; + } else { + aResult->height = metrics.slider_width + 2 * metrics.trough_border; + if (!hasScrollbarButtons) + aResult->width = metrics.min_slider_size + 2 * metrics.trough_border; + } + *aIsOverridable = false; + } + + } + break; + case NS_THEME_SCROLLBARTHUMB_VERTICAL: + case NS_THEME_SCROLLBARTHUMB_HORIZONTAL: + { + if (gtk_check_version(3,20,0) == nullptr) { + moz_gtk_get_widget_min_size(NativeThemeToGtkTheme(aWidgetType, aFrame), + &(aResult->width), &(aResult->height)); + } else { + MozGtkScrollbarMetrics metrics; + moz_gtk_get_scrollbar_metrics(&metrics); + + if (aWidgetType == NS_THEME_SCROLLBARTHUMB_VERTICAL) { + aResult->width = metrics.slider_width; + aResult->height = metrics.min_slider_size; + } else { + aResult->height = metrics.slider_width; + aResult->width = metrics.min_slider_size; + } + } + *aIsOverridable = false; + } + break; + case NS_THEME_RANGE_THUMB: + { + gint thumb_length, thumb_height; + + if (IsRangeHorizontal(aFrame)) { + moz_gtk_get_scalethumb_metrics(GTK_ORIENTATION_HORIZONTAL, &thumb_length, &thumb_height); + } else { + moz_gtk_get_scalethumb_metrics(GTK_ORIENTATION_VERTICAL, &thumb_height, &thumb_length); + } + aResult->width = thumb_length; + aResult->height = thumb_height; + + *aIsOverridable = false; + } + break; + case NS_THEME_RANGE: + { + gint scale_width, scale_height; + + moz_gtk_get_scale_metrics(IsRangeHorizontal(aFrame) ? + GTK_ORIENTATION_HORIZONTAL : GTK_ORIENTATION_VERTICAL, + &scale_width, &scale_height); + aResult->width = scale_width; + aResult->height = scale_height; + + *aIsOverridable = true; + } + break; + case NS_THEME_SCALETHUMB_HORIZONTAL: + case NS_THEME_SCALETHUMB_VERTICAL: + { + gint thumb_length, thumb_height; + + if (aWidgetType == NS_THEME_SCALETHUMB_VERTICAL) { + moz_gtk_get_scalethumb_metrics(GTK_ORIENTATION_VERTICAL, &thumb_length, &thumb_height); + aResult->width = thumb_height; + aResult->height = thumb_length; + } else { + moz_gtk_get_scalethumb_metrics(GTK_ORIENTATION_HORIZONTAL, &thumb_length, &thumb_height); + aResult->width = thumb_length; + aResult->height = thumb_height; + } + + *aIsOverridable = false; + } + break; + case NS_THEME_TAB_SCROLL_ARROW_BACK: + case NS_THEME_TAB_SCROLL_ARROW_FORWARD: + { + moz_gtk_get_tab_scroll_arrow_size(&aResult->width, &aResult->height); + *aIsOverridable = false; + } + break; + case NS_THEME_MENULIST_BUTTON: + { + moz_gtk_get_combo_box_entry_button_size(&aResult->width, + &aResult->height); + *aIsOverridable = false; + } + break; + case NS_THEME_MENUSEPARATOR: + { + gint separator_height; + + moz_gtk_get_menu_separator_height(&separator_height); + aResult->height = separator_height; + + *aIsOverridable = false; + } + break; + case NS_THEME_CHECKBOX: + case NS_THEME_RADIO: + { + gint indicator_size, indicator_spacing; + + if (aWidgetType == NS_THEME_CHECKBOX) { + moz_gtk_checkbox_get_metrics(&indicator_size, &indicator_spacing); + } else { + moz_gtk_radio_get_metrics(&indicator_size, &indicator_spacing); + } + + // Include space for the indicator and the padding around it. + aResult->width = indicator_size; + aResult->height = indicator_size; + } + break; + case NS_THEME_TOOLBARBUTTON_DROPDOWN: + case NS_THEME_BUTTON_ARROW_UP: + case NS_THEME_BUTTON_ARROW_DOWN: + case NS_THEME_BUTTON_ARROW_NEXT: + case NS_THEME_BUTTON_ARROW_PREVIOUS: + { + moz_gtk_get_arrow_size(MOZ_GTK_TOOLBARBUTTON_ARROW, + &aResult->width, &aResult->height); + *aIsOverridable = false; + } + break; + case NS_THEME_CHECKBOX_CONTAINER: + case NS_THEME_RADIO_CONTAINER: + case NS_THEME_CHECKBOX_LABEL: + case NS_THEME_RADIO_LABEL: + case NS_THEME_BUTTON: + case NS_THEME_MENULIST: + case NS_THEME_TOOLBARBUTTON: + case NS_THEME_TREEHEADERCELL: + { + if (aWidgetType == NS_THEME_MENULIST) { + // Include the arrow size. + moz_gtk_get_arrow_size(MOZ_GTK_DROPDOWN, + &aResult->width, &aResult->height); + } + // else the minimum size is missing consideration of container + // descendants; the value returned here will not be helpful, but the + // box model may consider border and padding with child minimum sizes. + + nsIntMargin border; + nsNativeThemeGTK::GetWidgetBorder(aFrame->PresContext()->DeviceContext(), + aFrame, aWidgetType, &border); + aResult->width += border.left + border.right; + aResult->height += border.top + border.bottom; + } + break; +#if (MOZ_WIDGET_GTK == 3) + case NS_THEME_NUMBER_INPUT: + case NS_THEME_TEXTFIELD: + { + moz_gtk_get_entry_min_height(&aResult->height); + } + break; +#endif + case NS_THEME_SEPARATOR: + { + gint separator_width; + + moz_gtk_get_toolbar_separator_width(&separator_width); + + aResult->width = separator_width; + } + break; + case NS_THEME_SPINNER: + // hard code these sizes + aResult->width = 14; + aResult->height = 26; + break; + case NS_THEME_TREEHEADERSORTARROW: + case NS_THEME_SPINNER_UPBUTTON: + case NS_THEME_SPINNER_DOWNBUTTON: + // hard code these sizes + aResult->width = 14; + aResult->height = 13; + break; + case NS_THEME_RESIZER: + // same as Windows to make our lives easier + aResult->width = aResult->height = 15; + *aIsOverridable = false; + break; + case NS_THEME_TREETWISTY: + case NS_THEME_TREETWISTYOPEN: + { + gint expander_size; + + moz_gtk_get_treeview_expander_size(&expander_size); + aResult->width = aResult->height = expander_size; + *aIsOverridable = false; + } + break; + } + + *aResult = *aResult * nsScreenGtk::GetGtkMonitorScaleFactor(); + + return NS_OK; +} + +NS_IMETHODIMP +nsNativeThemeGTK::WidgetStateChanged(nsIFrame* aFrame, uint8_t aWidgetType, + nsIAtom* aAttribute, bool* aShouldRepaint, + const nsAttrValue* aOldValue) +{ + // Some widget types just never change state. + if (aWidgetType == NS_THEME_TOOLBOX || + aWidgetType == NS_THEME_TOOLBAR || + aWidgetType == NS_THEME_STATUSBAR || + aWidgetType == NS_THEME_STATUSBARPANEL || + aWidgetType == NS_THEME_RESIZERPANEL || + aWidgetType == NS_THEME_PROGRESSCHUNK || + aWidgetType == NS_THEME_PROGRESSCHUNK_VERTICAL || + aWidgetType == NS_THEME_PROGRESSBAR || + aWidgetType == NS_THEME_PROGRESSBAR_VERTICAL || + aWidgetType == NS_THEME_MENUBAR || + aWidgetType == NS_THEME_MENUPOPUP || + aWidgetType == NS_THEME_TOOLTIP || + aWidgetType == NS_THEME_MENUSEPARATOR || + aWidgetType == NS_THEME_WINDOW || + aWidgetType == NS_THEME_DIALOG) { + *aShouldRepaint = false; + return NS_OK; + } + + if ((aWidgetType == NS_THEME_SCROLLBARTHUMB_VERTICAL || + aWidgetType == NS_THEME_SCROLLBARTHUMB_HORIZONTAL) && + aAttribute == nsGkAtoms::active) { + *aShouldRepaint = true; + return NS_OK; + } + + if ((aWidgetType == NS_THEME_SCROLLBARBUTTON_UP || + aWidgetType == NS_THEME_SCROLLBARBUTTON_DOWN || + aWidgetType == NS_THEME_SCROLLBARBUTTON_LEFT || + aWidgetType == NS_THEME_SCROLLBARBUTTON_RIGHT) && + (aAttribute == nsGkAtoms::curpos || + aAttribute == nsGkAtoms::maxpos)) { + // If 'curpos' has changed and we are passed its old value, we can + // determine whether the button's enablement actually needs to change. + if (aAttribute == nsGkAtoms::curpos && aOldValue) { + int32_t curpos = CheckIntAttr(aFrame, nsGkAtoms::curpos, 0); + int32_t maxpos = CheckIntAttr(aFrame, nsGkAtoms::maxpos, 0); + nsAutoString str; + aOldValue->ToString(str); + nsresult err; + int32_t oldCurpos = str.ToInteger(&err); + if (str.IsEmpty() || NS_FAILED(err)) { + *aShouldRepaint = true; + } else { + bool disabledBefore = ShouldScrollbarButtonBeDisabled(oldCurpos, maxpos, aWidgetType); + bool disabledNow = ShouldScrollbarButtonBeDisabled(curpos, maxpos, aWidgetType); + *aShouldRepaint = (disabledBefore != disabledNow); + } + } else { + *aShouldRepaint = true; + } + return NS_OK; + } + + // XXXdwh Not sure what can really be done here. Can at least guess for + // specific widgets that they're highly unlikely to have certain states. + // For example, a toolbar doesn't care about any states. + if (!aAttribute) { + // Hover/focus/active changed. Always repaint. + *aShouldRepaint = true; + } + else { + // Check the attribute to see if it's relevant. + // disabled, checked, dlgtype, default, etc. + *aShouldRepaint = false; + if (aAttribute == nsGkAtoms::disabled || + aAttribute == nsGkAtoms::checked || + aAttribute == nsGkAtoms::selected || + aAttribute == nsGkAtoms::visuallyselected || + aAttribute == nsGkAtoms::focused || + aAttribute == nsGkAtoms::readonly || + aAttribute == nsGkAtoms::_default || + aAttribute == nsGkAtoms::menuactive || + aAttribute == nsGkAtoms::open || + aAttribute == nsGkAtoms::parentfocused) + *aShouldRepaint = true; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsNativeThemeGTK::ThemeChanged() +{ + memset(mDisabledWidgetTypes, 0, sizeof(mDisabledWidgetTypes)); + return NS_OK; +} + +NS_IMETHODIMP_(bool) +nsNativeThemeGTK::ThemeSupportsWidget(nsPresContext* aPresContext, + nsIFrame* aFrame, + uint8_t aWidgetType) +{ + if (IsWidgetTypeDisabled(mDisabledWidgetTypes, aWidgetType)) + return false; + + switch (aWidgetType) { + // Combobox dropdowns don't support native theming in vertical mode. + case NS_THEME_MENULIST: + case NS_THEME_MENULIST_TEXT: + case NS_THEME_MENULIST_TEXTFIELD: + if (aFrame && aFrame->GetWritingMode().IsVertical()) { + return false; + } + MOZ_FALLTHROUGH; + + case NS_THEME_BUTTON: + case NS_THEME_BUTTON_FOCUS: + case NS_THEME_RADIO: + case NS_THEME_CHECKBOX: + case NS_THEME_TOOLBOX: // N/A + case NS_THEME_TOOLBAR: + case NS_THEME_TOOLBARBUTTON: + case NS_THEME_DUALBUTTON: // so we can override the border with 0 + case NS_THEME_TOOLBARBUTTON_DROPDOWN: + case NS_THEME_BUTTON_ARROW_UP: + case NS_THEME_BUTTON_ARROW_DOWN: + case NS_THEME_BUTTON_ARROW_NEXT: + case NS_THEME_BUTTON_ARROW_PREVIOUS: + case NS_THEME_SEPARATOR: + case NS_THEME_TOOLBARGRIPPER: + case NS_THEME_STATUSBAR: + case NS_THEME_STATUSBARPANEL: + case NS_THEME_RESIZERPANEL: + case NS_THEME_RESIZER: + case NS_THEME_LISTBOX: + // case NS_THEME_LISTITEM: + case NS_THEME_TREEVIEW: + // case NS_THEME_TREEITEM: + case NS_THEME_TREETWISTY: + // case NS_THEME_TREELINE: + // case NS_THEME_TREEHEADER: + case NS_THEME_TREEHEADERCELL: + case NS_THEME_TREEHEADERSORTARROW: + case NS_THEME_TREETWISTYOPEN: + case NS_THEME_PROGRESSBAR: + case NS_THEME_PROGRESSCHUNK: + case NS_THEME_PROGRESSBAR_VERTICAL: + case NS_THEME_PROGRESSCHUNK_VERTICAL: + case NS_THEME_TAB: + // case NS_THEME_TABPANEL: + case NS_THEME_TABPANELS: + case NS_THEME_TAB_SCROLL_ARROW_BACK: + case NS_THEME_TAB_SCROLL_ARROW_FORWARD: + case NS_THEME_TOOLTIP: + case NS_THEME_SPINNER: + case NS_THEME_SPINNER_UPBUTTON: + case NS_THEME_SPINNER_DOWNBUTTON: + case NS_THEME_SPINNER_TEXTFIELD: + // case NS_THEME_SCROLLBAR: (n/a for gtk) + // case NS_THEME_SCROLLBAR_SMALL: (n/a for gtk) + case NS_THEME_SCROLLBARBUTTON_UP: + case NS_THEME_SCROLLBARBUTTON_DOWN: + case NS_THEME_SCROLLBARBUTTON_LEFT: + case NS_THEME_SCROLLBARBUTTON_RIGHT: + case NS_THEME_SCROLLBAR_HORIZONTAL: + case NS_THEME_SCROLLBAR_VERTICAL: + case NS_THEME_SCROLLBARTRACK_HORIZONTAL: + case NS_THEME_SCROLLBARTRACK_VERTICAL: + case NS_THEME_SCROLLBARTHUMB_HORIZONTAL: + case NS_THEME_SCROLLBARTHUMB_VERTICAL: + case NS_THEME_NUMBER_INPUT: + case NS_THEME_TEXTFIELD: + case NS_THEME_TEXTFIELD_MULTILINE: + case NS_THEME_RANGE: + case NS_THEME_RANGE_THUMB: + case NS_THEME_SCALE_HORIZONTAL: + case NS_THEME_SCALETHUMB_HORIZONTAL: + case NS_THEME_SCALE_VERTICAL: + case NS_THEME_SCALETHUMB_VERTICAL: + // case NS_THEME_SCALETHUMBSTART: + // case NS_THEME_SCALETHUMBEND: + // case NS_THEME_SCALETHUMBTICK: + case NS_THEME_CHECKBOX_CONTAINER: + case NS_THEME_RADIO_CONTAINER: + case NS_THEME_CHECKBOX_LABEL: + case NS_THEME_RADIO_LABEL: + case NS_THEME_MENUBAR: + case NS_THEME_MENUPOPUP: + case NS_THEME_MENUITEM: + case NS_THEME_MENUARROW: + case NS_THEME_MENUSEPARATOR: + case NS_THEME_CHECKMENUITEM: + case NS_THEME_RADIOMENUITEM: + case NS_THEME_SPLITTER: + case NS_THEME_WINDOW: + case NS_THEME_DIALOG: +#if (MOZ_WIDGET_GTK == 3) + case NS_THEME_GTK_INFO_BAR: +#endif + return !IsWidgetStyled(aPresContext, aFrame, aWidgetType); + + case NS_THEME_MENULIST_BUTTON: + if (aFrame && aFrame->GetWritingMode().IsVertical()) { + return false; + } + // "Native" dropdown buttons cause padding and margin problems, but only + // in HTML so allow them in XUL. + return (!aFrame || IsFrameContentNodeInNamespace(aFrame, kNameSpaceID_XUL)) && + !IsWidgetStyled(aPresContext, aFrame, aWidgetType); + + case NS_THEME_FOCUS_OUTLINE: + return true; + } + + return false; +} + +NS_IMETHODIMP_(bool) +nsNativeThemeGTK::WidgetIsContainer(uint8_t aWidgetType) +{ + // XXXdwh At some point flesh all of this out. + if (aWidgetType == NS_THEME_MENULIST_BUTTON || + aWidgetType == NS_THEME_RADIO || + aWidgetType == NS_THEME_RANGE_THUMB || + aWidgetType == NS_THEME_CHECKBOX || + aWidgetType == NS_THEME_TAB_SCROLL_ARROW_BACK || + aWidgetType == NS_THEME_TAB_SCROLL_ARROW_FORWARD || + aWidgetType == NS_THEME_BUTTON_ARROW_UP || + aWidgetType == NS_THEME_BUTTON_ARROW_DOWN || + aWidgetType == NS_THEME_BUTTON_ARROW_NEXT || + aWidgetType == NS_THEME_BUTTON_ARROW_PREVIOUS) + return false; + return true; +} + +bool +nsNativeThemeGTK::ThemeDrawsFocusForWidget(uint8_t aWidgetType) +{ + if (aWidgetType == NS_THEME_MENULIST || + aWidgetType == NS_THEME_BUTTON || + aWidgetType == NS_THEME_TREEHEADERCELL) + return true; + + return false; +} + +bool +nsNativeThemeGTK::ThemeNeedsComboboxDropmarker() +{ + return false; +} + +nsITheme::Transparency +nsNativeThemeGTK::GetWidgetTransparency(nsIFrame* aFrame, uint8_t aWidgetType) +{ + switch (aWidgetType) { + // These widgets always draw a default background. +#if (MOZ_WIDGET_GTK == 2) + case NS_THEME_TOOLBAR: + case NS_THEME_MENUBAR: +#endif + case NS_THEME_MENUPOPUP: + case NS_THEME_WINDOW: + case NS_THEME_DIALOG: + return eOpaque; + case NS_THEME_SCROLLBAR_VERTICAL: + case NS_THEME_SCROLLBAR_HORIZONTAL: +#if (MOZ_WIDGET_GTK == 3) + // Make scrollbar tracks opaque on the window's scroll frame to prevent + // leaf layers from overlapping. See bug 1179780. + if (!(CheckBooleanAttr(aFrame, nsGkAtoms::root_) && + aFrame->PresContext()->IsRootContentDocument() && + IsFrameContentNodeInNamespace(aFrame, kNameSpaceID_XUL))) + return eTransparent; +#endif + return eOpaque; + // Tooltips use gtk_paint_flat_box() on Gtk2 + // but are shaped on Gtk3 + case NS_THEME_TOOLTIP: +#if (MOZ_WIDGET_GTK == 2) + return eOpaque; +#else + return eTransparent; +#endif + } + + return eUnknownTransparency; +} diff --git a/widget/gtk/nsNativeThemeGTK.h b/widget/gtk/nsNativeThemeGTK.h new file mode 100644 index 000000000..56ae0317f --- /dev/null +++ b/widget/gtk/nsNativeThemeGTK.h @@ -0,0 +1,93 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#ifndef _GTK_NSNATIVETHEMEGTK_H_ +#define _GTK_NSNATIVETHEMEGTK_H_ + +#include "nsITheme.h" +#include "nsCOMPtr.h" +#include "nsIAtom.h" +#include "nsIObserver.h" +#include "nsNativeTheme.h" + +#include <gtk/gtk.h> +#include "gtkdrawing.h" + +class nsNativeThemeGTK: private nsNativeTheme, + public nsITheme, + public nsIObserver { +public: + NS_DECL_ISUPPORTS_INHERITED + + NS_DECL_NSIOBSERVER + + // The nsITheme interface. + NS_IMETHOD DrawWidgetBackground(nsRenderingContext* aContext, + nsIFrame* aFrame, uint8_t aWidgetType, + const nsRect& aRect, + const nsRect& aDirtyRect) override; + + NS_IMETHOD GetWidgetBorder(nsDeviceContext* aContext, nsIFrame* aFrame, + uint8_t aWidgetType, + nsIntMargin* aResult) override; + + virtual bool GetWidgetPadding(nsDeviceContext* aContext, + nsIFrame* aFrame, + uint8_t aWidgetType, + nsIntMargin* aResult) override; + + virtual bool GetWidgetOverflow(nsDeviceContext* aContext, + nsIFrame* aFrame, + uint8_t aWidgetType, + nsRect* aOverflowRect) override; + + NS_IMETHOD GetMinimumWidgetSize(nsPresContext* aPresContext, + nsIFrame* aFrame, uint8_t aWidgetType, + mozilla::LayoutDeviceIntSize* aResult, + bool* aIsOverridable) override; + + NS_IMETHOD WidgetStateChanged(nsIFrame* aFrame, uint8_t aWidgetType, + nsIAtom* aAttribute, + bool* aShouldRepaint, + const nsAttrValue* aOldValue) override; + + NS_IMETHOD ThemeChanged() override; + + NS_IMETHOD_(bool) ThemeSupportsWidget(nsPresContext* aPresContext, + nsIFrame* aFrame, + uint8_t aWidgetType) override; + + NS_IMETHOD_(bool) WidgetIsContainer(uint8_t aWidgetType) override; + + NS_IMETHOD_(bool) ThemeDrawsFocusForWidget(uint8_t aWidgetType) override; + + virtual bool ThemeNeedsComboboxDropmarker() override; + + virtual Transparency GetWidgetTransparency(nsIFrame* aFrame, + uint8_t aWidgetType) override; + + nsNativeThemeGTK(); + +protected: + virtual ~nsNativeThemeGTK(); + +private: + GtkTextDirection GetTextDirection(nsIFrame* aFrame); + gint GetTabMarginPixels(nsIFrame* aFrame); + bool GetGtkWidgetAndState(uint8_t aWidgetType, nsIFrame* aFrame, + WidgetNodeType& aGtkWidgetType, + GtkWidgetState* aState, gint* aWidgetFlags); + bool GetExtraSizeForWidget(nsIFrame* aFrame, uint8_t aWidgetType, + nsIntMargin* aExtra); + + void RefreshWidgetWindow(nsIFrame* aFrame); + WidgetNodeType NativeThemeToGtkTheme(uint8_t aWidgetType, nsIFrame* aFrame); + + uint8_t mDisabledWidgetTypes[32]; + uint8_t mSafeWidgetStates[1024]; // 256 widgets * 32 bits per widget + static const char* sDisabledEngines[]; +}; + +#endif diff --git a/widget/gtk/nsPSPrinters.cpp b/widget/gtk/nsPSPrinters.cpp new file mode 100644 index 000000000..2d3183e9a --- /dev/null +++ b/widget/gtk/nsPSPrinters.cpp @@ -0,0 +1,123 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* ex: set tabstop=8 softtabstop=4 shiftwidth=4 expandtab: */ +/* 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 "nscore.h" +#include "nsCUPSShim.h" +#include "nsIServiceManager.h" +#include "nsPSPrinters.h" +#include "nsReadableUtils.h" // StringBeginsWith() +#include "nsCUPSShim.h" +#include "mozilla/Preferences.h" + +#include "prlink.h" +#include "prenv.h" +#include "plstr.h" + +using namespace mozilla; + +#define NS_CUPS_PRINTER "CUPS/" +#define NS_CUPS_PRINTER_LEN (sizeof(NS_CUPS_PRINTER) - 1) + +/* dummy printer name for the gfx/src/ps driver */ +#define NS_POSTSCRIPT_DRIVER_NAME "PostScript/" + +nsCUPSShim gCupsShim; + +nsPSPrinterList::nsPSPrinterList() +{ + // Should we try cups? + if (Preferences::GetBool("print.postscript.cups.enabled", true) && + !gCupsShim.IsInitialized()) { + gCupsShim.Init(); + } +} + + +/* Check whether the PostScript module has been disabled at runtime */ +bool +nsPSPrinterList::Enabled() +{ + const char *val = PR_GetEnv("MOZILLA_POSTSCRIPT_ENABLED"); + if (val && (val[0] == '0' || !PL_strcasecmp(val, "false"))) + return false; + + // is the PS module enabled? + return Preferences::GetBool("print.postscript.enabled", true); +} + + +/* Fetch a list of printers handled by the PostsScript module */ +void +nsPSPrinterList::GetPrinterList(nsTArray<nsCString>& aList) +{ + aList.Clear(); + + // Query CUPS for a printer list. The default printer goes to the + // head of the output list; others are appended. + if (gCupsShim.IsInitialized()) { + cups_dest_t *dests; + + int num_dests = (gCupsShim.mCupsGetDests)(&dests); + if (num_dests) { + for (int i = 0; i < num_dests; i++) { + nsAutoCString fullName(NS_CUPS_PRINTER); + fullName.Append(dests[i].name); + if (dests[i].instance != nullptr) { + fullName.Append('/'); + fullName.Append(dests[i].instance); + } + if (dests[i].is_default) + aList.InsertElementAt(0, fullName); + else + aList.AppendElement(fullName); + } + } + (gCupsShim.mCupsFreeDests)(num_dests, dests); + } + + // Build the "classic" list of printers -- those accessed by running + // an opaque command. This list always contains a printer named "default". + // In addition, we look for either an environment variable + // MOZILLA_POSTSCRIPT_PRINTER_LIST or a preference setting + // print.printer_list, which contains a space-separated list of printer + // names. + aList.AppendElement( + NS_LITERAL_CSTRING(NS_POSTSCRIPT_DRIVER_NAME "default")); + + nsAutoCString list(PR_GetEnv("MOZILLA_POSTSCRIPT_PRINTER_LIST")); + if (list.IsEmpty()) { + list = Preferences::GetCString("print.printer_list"); + } + if (!list.IsEmpty()) { + // For each printer (except "default" which was already added), + // construct a string "PostScript/<name>" and append it to the list. + char *state; + + for (char *name = PL_strtok_r(list.BeginWriting(), " ", &state); + nullptr != name; + name = PL_strtok_r(nullptr, " ", &state) + ) { + if (0 != strcmp(name, "default")) { + nsAutoCString fullName(NS_POSTSCRIPT_DRIVER_NAME); + fullName.Append(name); + aList.AppendElement(fullName); + } + } + } +} + + +/* Identify the printer type */ +nsPSPrinterList::PrinterType +nsPSPrinterList::GetPrinterType(const nsACString& aName) +{ + if (StringBeginsWith(aName, NS_LITERAL_CSTRING(NS_POSTSCRIPT_DRIVER_NAME))) + return kTypePS; + else if (StringBeginsWith(aName, NS_LITERAL_CSTRING(NS_CUPS_PRINTER))) + return kTypeCUPS; + else + return kTypeUnknown; +} diff --git a/widget/gtk/nsPSPrinters.h b/widget/gtk/nsPSPrinters.h new file mode 100644 index 000000000..b584ef715 --- /dev/null +++ b/widget/gtk/nsPSPrinters.h @@ -0,0 +1,53 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* ex: set tabstop=8 softtabstop=4 shiftwidth=4 expandtab: */ +/* 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/. */ + +#ifndef nsPSPrinters_h___ +#define nsPSPrinters_h___ + +#include "nsString.h" +#include "nsTArray.h" + +class nsPSPrinterList { + public: + nsPSPrinterList(); + + /** + * Is the PostScript module enabled or disabled? + * @return true if enabled, + * false if not. + */ + bool Enabled(); + + /** + * Obtain a list of printers (print destinations) supported by the + * PostScript module, Each entry will be in the form <type>/<name>, + * where <type> is a printer type string, and <name> is the actual + * printer name. + * + * @param aList Upon return, this is populated with the list of + * printer names as described above, replacing any + * previous contents. Each entry is a UTF8 string. + * There should always be at least one entry. The + * first entry is the default print destination. + */ + void GetPrinterList(nsTArray<nsCString>& aList); + + enum PrinterType { + kTypeUnknown, // Not actually handled by the PS module + kTypePS, // Generic postscript module printer + kTypeCUPS // CUPS printer + }; + + /** + * Identify a printer's type from its name. + * @param aName The printer's full name as a UTF8 string, including + * the <type> portion as described for GetPrinterList(). + * @return The PrinterType value for this name. + */ + static PrinterType GetPrinterType(const nsACString& aName); +}; + +#endif /* nsPSPrinters_h___ */ diff --git a/widget/gtk/nsPaperPS.cpp b/widget/gtk/nsPaperPS.cpp new file mode 100644 index 000000000..7d583efac --- /dev/null +++ b/widget/gtk/nsPaperPS.cpp @@ -0,0 +1,43 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* ex: set tabstop=8 softtabstop=4 shiftwidth=4 expandtab: */ +/* 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/ArrayUtils.h" + +#include "nsPaperPS.h" +#include "plstr.h" +#include "nsCoord.h" +#include "nsMemory.h" + +using namespace mozilla; + +const nsPaperSizePS_ nsPaperSizePS::mList[] = +{ +#define SIZE_MM(x) (x) +#define SIZE_INCH(x) ((x) * MM_PER_INCH_FLOAT) + { "A5", SIZE_MM(148), SIZE_MM(210), true }, + { "A4", SIZE_MM(210), SIZE_MM(297), true }, + { "A3", SIZE_MM(297), SIZE_MM(420), true }, + { "Letter", SIZE_INCH(8.5), SIZE_INCH(11), false }, + { "Legal", SIZE_INCH(8.5), SIZE_INCH(14), false }, + { "Tabloid", SIZE_INCH(11), SIZE_INCH(17), false }, + { "Executive", SIZE_INCH(7.5), SIZE_INCH(10), false }, +#undef SIZE_INCH +#undef SIZE_MM +}; + +const unsigned int nsPaperSizePS::mCount = ArrayLength(mList); + +bool +nsPaperSizePS::Find(const char *aName) +{ + for (int i = mCount; i--; ) { + if (!PL_strcasecmp(aName, mList[i].name)) { + mCurrent = i; + return true; + } + } + return false; +} diff --git a/widget/gtk/nsPaperPS.h b/widget/gtk/nsPaperPS.h new file mode 100644 index 000000000..1acc1780c --- /dev/null +++ b/widget/gtk/nsPaperPS.h @@ -0,0 +1,93 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* ex: set tabstop=8 softtabstop=4 shiftwidth=4 expandtab: */ +/* 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/. */ + + +#ifndef _PAPERPS_H_ +#define _PAPERPS_H_ + +#include "nsDebug.h" + +struct nsPaperSizePS_ { + const char *name; + float width_mm; + float height_mm; + bool isMetric; // Present to the user in metric, if possible +}; + +class nsPaperSizePS { + public: + /** --------------------------------------------------- + * Constructor + */ + nsPaperSizePS() { mCurrent = 0; } + + /** --------------------------------------------------- + * @return true if the cursor points past the last item. + */ + bool AtEnd() { return mCurrent >= mCount; } + + /** --------------------------------------------------- + * Position the cursor at the beginning of the paper size list. + * @return VOID + */ + void First() { mCurrent = 0; } + + /** --------------------------------------------------- + * Advance the cursor to the next item. + * @return VOID + */ + void Next() { + NS_ASSERTION(!AtEnd(), "Invalid current item"); + mCurrent++; + } + + /** --------------------------------------------------- + * Point the cursor to the entry with the given paper name. + * @return true if pointing to a valid entry. + */ + bool Find(const char *aName); + + /** --------------------------------------------------- + * @return a pointer to the name of the current paper size + */ + const char *Name() { + NS_PRECONDITION(!AtEnd(), "Invalid current item"); + return mList[mCurrent].name; + } + + /** --------------------------------------------------- + * @return the width of the page in millimeters + */ + float Width_mm() { + NS_PRECONDITION(!AtEnd(), "Invalid current item"); + return mList[mCurrent].width_mm; + } + + /** --------------------------------------------------- + * @return the height of the page in millimeters + */ + float Height_mm() { + NS_PRECONDITION(!AtEnd(), "Invalid current item"); + return mList[mCurrent].height_mm; + } + + /** --------------------------------------------------- + * @return true if the paper should be presented to + * the user in metric units. + */ + bool IsMetric() { + NS_PRECONDITION(!AtEnd(), "Invalid current item"); + return mList[mCurrent].isMetric; + } + + private: + unsigned int mCurrent; + static const nsPaperSizePS_ mList[]; + static const unsigned int mCount; +}; + +#endif + diff --git a/widget/gtk/nsPrintDialogGTK.cpp b/widget/gtk/nsPrintDialogGTK.cpp new file mode 100644 index 000000000..77fa2bb69 --- /dev/null +++ b/widget/gtk/nsPrintDialogGTK.cpp @@ -0,0 +1,609 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 <gtk/gtk.h> +#include <gtk/gtkunixprint.h> +#include <stdlib.h> + +#include "mozilla/ArrayUtils.h" + +#include "mozcontainer.h" +#include "nsIPrintSettings.h" +#include "nsIWidget.h" +#include "nsPrintDialogGTK.h" +#include "nsPrintSettingsGTK.h" +#include "nsString.h" +#include "nsReadableUtils.h" +#include "nsIFile.h" +#include "nsIStringBundle.h" +#include "nsIPrintSettingsService.h" +#include "nsIDOMWindow.h" +#include "nsPIDOMWindow.h" +#include "nsIBaseWindow.h" +#include "nsIDocShellTreeItem.h" +#include "nsIDocShell.h" +#include "WidgetUtils.h" + +using namespace mozilla; +using namespace mozilla::widget; + +static const char header_footer_tags[][4] = {"", "&T", "&U", "&D", "&P", "&PT"}; + +#define CUSTOM_VALUE_INDEX gint(ArrayLength(header_footer_tags)) + +static GtkWindow * +get_gtk_window_for_nsiwidget(nsIWidget *widget) +{ + return GTK_WINDOW(widget->GetNativeData(NS_NATIVE_SHELLWIDGET)); +} + +static void +ShowCustomDialog(GtkComboBox *changed_box, gpointer user_data) +{ + if (gtk_combo_box_get_active(changed_box) != CUSTOM_VALUE_INDEX) { + g_object_set_data(G_OBJECT(changed_box), "previous-active", GINT_TO_POINTER(gtk_combo_box_get_active(changed_box))); + return; + } + + GtkWindow* printDialog = GTK_WINDOW(user_data); + nsCOMPtr<nsIStringBundleService> bundleSvc = + do_GetService(NS_STRINGBUNDLE_CONTRACTID); + + nsCOMPtr<nsIStringBundle> printBundle; + bundleSvc->CreateBundle("chrome://global/locale/printdialog.properties", getter_AddRefs(printBundle)); + nsXPIDLString intlString; + + printBundle->GetStringFromName(u"headerFooterCustom", getter_Copies(intlString)); + GtkWidget* prompt_dialog = gtk_dialog_new_with_buttons(NS_ConvertUTF16toUTF8(intlString).get(), printDialog, +#if (MOZ_WIDGET_GTK == 2) + (GtkDialogFlags)(GTK_DIALOG_MODAL | GTK_DIALOG_NO_SEPARATOR), +#else + (GtkDialogFlags)(GTK_DIALOG_MODAL), +#endif + GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT, + GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, + nullptr); + gtk_dialog_set_default_response(GTK_DIALOG(prompt_dialog), GTK_RESPONSE_ACCEPT); + gtk_dialog_set_alternative_button_order(GTK_DIALOG(prompt_dialog), + GTK_RESPONSE_ACCEPT, + GTK_RESPONSE_REJECT, + -1); + + printBundle->GetStringFromName(u"customHeaderFooterPrompt", getter_Copies(intlString)); + GtkWidget* custom_label = gtk_label_new(NS_ConvertUTF16toUTF8(intlString).get()); + GtkWidget* custom_entry = gtk_entry_new(); + GtkWidget* question_icon = gtk_image_new_from_stock(GTK_STOCK_DIALOG_QUESTION, GTK_ICON_SIZE_DIALOG); + + // To be convenient, prefill the textbox with the existing value, if any, and select it all so they can easily + // both edit it and type in a new one. + const char* current_text = (const char*) g_object_get_data(G_OBJECT(changed_box), "custom-text"); + if (current_text) { + gtk_entry_set_text(GTK_ENTRY(custom_entry), current_text); + gtk_editable_select_region(GTK_EDITABLE(custom_entry), 0, -1); + } + gtk_entry_set_activates_default(GTK_ENTRY(custom_entry), TRUE); + + GtkWidget* custom_vbox = gtk_vbox_new(TRUE, 2); + gtk_box_pack_start(GTK_BOX(custom_vbox), custom_label, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(custom_vbox), custom_entry, FALSE, FALSE, 5); // Make entry 5px underneath label + GtkWidget* custom_hbox = gtk_hbox_new(FALSE, 2); + gtk_box_pack_start(GTK_BOX(custom_hbox), question_icon, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(custom_hbox), custom_vbox, FALSE, FALSE, 10); // Make question icon 10px away from content + gtk_container_set_border_width(GTK_CONTAINER(custom_hbox), 2); + gtk_widget_show_all(custom_hbox); + + gtk_box_pack_start(GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(prompt_dialog))), + custom_hbox, FALSE, FALSE, 0); + gint diag_response = gtk_dialog_run(GTK_DIALOG(prompt_dialog)); + + if (diag_response == GTK_RESPONSE_ACCEPT) { + const gchar* response_text = gtk_entry_get_text(GTK_ENTRY(custom_entry)); + g_object_set_data_full(G_OBJECT(changed_box), "custom-text", strdup(response_text), (GDestroyNotify) free); + g_object_set_data(G_OBJECT(changed_box), "previous-active", GINT_TO_POINTER(CUSTOM_VALUE_INDEX)); + } else { + // Go back to the previous index + gint previous_active = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(changed_box), "previous-active")); + gtk_combo_box_set_active(changed_box, previous_active); + } + + gtk_widget_destroy(prompt_dialog); +} + +class nsPrintDialogWidgetGTK { + public: + nsPrintDialogWidgetGTK(nsPIDOMWindowOuter *aParent, + nsIPrintSettings *aPrintSettings); + ~nsPrintDialogWidgetGTK() { gtk_widget_destroy(dialog); } + NS_ConvertUTF16toUTF8 GetUTF8FromBundle(const char* aKey); + gint Run(); + + nsresult ImportSettings(nsIPrintSettings *aNSSettings); + nsresult ExportSettings(nsIPrintSettings *aNSSettings); + + private: + GtkWidget* dialog; + GtkWidget* radio_as_laid_out; + GtkWidget* radio_selected_frame; + GtkWidget* radio_separate_frames; + GtkWidget* shrink_to_fit_toggle; + GtkWidget* print_bg_colors_toggle; + GtkWidget* print_bg_images_toggle; + GtkWidget* selection_only_toggle; + GtkWidget* header_dropdown[3]; // {left, center, right} + GtkWidget* footer_dropdown[3]; + + nsCOMPtr<nsIStringBundle> printBundle; + + bool useNativeSelection; + + GtkWidget* ConstructHeaderFooterDropdown(const char16_t *currentString); + const char* OptionWidgetToString(GtkWidget *dropdown); + + /* Code to copy between GTK and NS print settings structures. + * In the following, + * "Import" means to copy from NS to GTK + * "Export" means to copy from GTK to NS + */ + void ExportFramePrinting(nsIPrintSettings *aNS, GtkPrintSettings *aSettings); + void ExportHeaderFooter(nsIPrintSettings *aNS); +}; + +nsPrintDialogWidgetGTK::nsPrintDialogWidgetGTK(nsPIDOMWindowOuter *aParent, + nsIPrintSettings *aSettings) +{ + nsCOMPtr<nsIWidget> widget = WidgetUtils::DOMWindowToWidget(aParent); + NS_ASSERTION(widget, "Need a widget for dialog to be modal."); + GtkWindow* gtkParent = get_gtk_window_for_nsiwidget(widget); + NS_ASSERTION(gtkParent, "Need a GTK window for dialog to be modal."); + + nsCOMPtr<nsIStringBundleService> bundleSvc = do_GetService(NS_STRINGBUNDLE_CONTRACTID); + bundleSvc->CreateBundle("chrome://global/locale/printdialog.properties", getter_AddRefs(printBundle)); + + dialog = gtk_print_unix_dialog_new(GetUTF8FromBundle("printTitleGTK").get(), gtkParent); + + gtk_print_unix_dialog_set_manual_capabilities(GTK_PRINT_UNIX_DIALOG(dialog), + GtkPrintCapabilities( + GTK_PRINT_CAPABILITY_PAGE_SET + | GTK_PRINT_CAPABILITY_COPIES + | GTK_PRINT_CAPABILITY_COLLATE + | GTK_PRINT_CAPABILITY_REVERSE + | GTK_PRINT_CAPABILITY_SCALE + | GTK_PRINT_CAPABILITY_GENERATE_PDF + | GTK_PRINT_CAPABILITY_GENERATE_PS + ) + ); + + // The vast majority of magic numbers in this widget construction are padding. e.g. for + // the set_border_width below, 12px matches that of just about every other window. + GtkWidget* custom_options_tab = gtk_vbox_new(FALSE, 0); + gtk_container_set_border_width(GTK_CONTAINER(custom_options_tab), 12); + GtkWidget* tab_label = gtk_label_new(GetUTF8FromBundle("optionsTabLabelGTK").get()); + + int16_t frameUIFlag; + aSettings->GetHowToEnableFrameUI(&frameUIFlag); + radio_as_laid_out = gtk_radio_button_new_with_mnemonic(nullptr, GetUTF8FromBundle("asLaidOut").get()); + if (frameUIFlag == nsIPrintSettings::kFrameEnableNone) + gtk_widget_set_sensitive(radio_as_laid_out, FALSE); + + radio_selected_frame = gtk_radio_button_new_with_mnemonic_from_widget(GTK_RADIO_BUTTON(radio_as_laid_out), + GetUTF8FromBundle("selectedFrame").get()); + if (frameUIFlag == nsIPrintSettings::kFrameEnableNone || + frameUIFlag == nsIPrintSettings::kFrameEnableAsIsAndEach) + gtk_widget_set_sensitive(radio_selected_frame, FALSE); + + radio_separate_frames = gtk_radio_button_new_with_mnemonic_from_widget(GTK_RADIO_BUTTON(radio_as_laid_out), + GetUTF8FromBundle("separateFrames").get()); + if (frameUIFlag == nsIPrintSettings::kFrameEnableNone) + gtk_widget_set_sensitive(radio_separate_frames, FALSE); + + // "Print Frames" options label, bold and center-aligned + GtkWidget* print_frames_label = gtk_label_new(nullptr); + char* pangoMarkup = g_markup_printf_escaped("<b>%s</b>", GetUTF8FromBundle("printFramesTitleGTK").get()); + gtk_label_set_markup(GTK_LABEL(print_frames_label), pangoMarkup); + g_free(pangoMarkup); + gtk_misc_set_alignment(GTK_MISC(print_frames_label), 0, 0); + + // Align the radio buttons slightly so they appear to fall under the aforementioned label as per the GNOME HIG + GtkWidget* frames_radio_container = gtk_alignment_new(0, 0, 0, 0); + gtk_alignment_set_padding(GTK_ALIGNMENT(frames_radio_container), 8, 0, 12, 0); + + // Radio buttons for the print frames options + GtkWidget* frames_radio_list = gtk_vbox_new(TRUE, 2); + gtk_box_pack_start(GTK_BOX(frames_radio_list), radio_as_laid_out, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(frames_radio_list), radio_selected_frame, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(frames_radio_list), radio_separate_frames, FALSE, FALSE, 0); + gtk_container_add(GTK_CONTAINER(frames_radio_container), frames_radio_list); + + // Check buttons for shrink-to-fit and print selection + GtkWidget* check_buttons_container = gtk_vbox_new(TRUE, 2); + shrink_to_fit_toggle = gtk_check_button_new_with_mnemonic(GetUTF8FromBundle("shrinkToFit").get()); + gtk_box_pack_start(GTK_BOX(check_buttons_container), shrink_to_fit_toggle, FALSE, FALSE, 0); + + // GTK+2.18 and above allow us to add a "Selection" option to the main settings screen, + // rather than adding an option on a custom tab like we must do on older versions. + bool canSelectText; + aSettings->GetPrintOptions(nsIPrintSettings::kEnableSelectionRB, &canSelectText); + if (gtk_major_version > 2 || + (gtk_major_version == 2 && gtk_minor_version >= 18)) { + useNativeSelection = true; + g_object_set(dialog, + "support-selection", TRUE, + "has-selection", canSelectText, + "embed-page-setup", TRUE, + nullptr); + } else { + useNativeSelection = false; + selection_only_toggle = gtk_check_button_new_with_mnemonic(GetUTF8FromBundle("selectionOnly").get()); + gtk_widget_set_sensitive(selection_only_toggle, canSelectText); + gtk_box_pack_start(GTK_BOX(check_buttons_container), selection_only_toggle, FALSE, FALSE, 0); + } + + // Check buttons for printing background + GtkWidget* appearance_buttons_container = gtk_vbox_new(TRUE, 2); + print_bg_colors_toggle = gtk_check_button_new_with_mnemonic(GetUTF8FromBundle("printBGColors").get()); + print_bg_images_toggle = gtk_check_button_new_with_mnemonic(GetUTF8FromBundle("printBGImages").get()); + gtk_box_pack_start(GTK_BOX(appearance_buttons_container), print_bg_colors_toggle, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(appearance_buttons_container), print_bg_images_toggle, FALSE, FALSE, 0); + + // "Appearance" options label, bold and center-aligned + GtkWidget* appearance_label = gtk_label_new(nullptr); + pangoMarkup = g_markup_printf_escaped("<b>%s</b>", GetUTF8FromBundle("printBGOptions").get()); + gtk_label_set_markup(GTK_LABEL(appearance_label), pangoMarkup); + g_free(pangoMarkup); + gtk_misc_set_alignment(GTK_MISC(appearance_label), 0, 0); + + GtkWidget* appearance_container = gtk_alignment_new(0, 0, 0, 0); + gtk_alignment_set_padding(GTK_ALIGNMENT(appearance_container), 8, 0, 12, 0); + gtk_container_add(GTK_CONTAINER(appearance_container), appearance_buttons_container); + + GtkWidget* appearance_vertical_squasher = gtk_vbox_new(FALSE, 0); + gtk_box_pack_start(GTK_BOX(appearance_vertical_squasher), appearance_label, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(appearance_vertical_squasher), appearance_container, FALSE, FALSE, 0); + + // "Header & Footer" options label, bold and center-aligned + GtkWidget* header_footer_label = gtk_label_new(nullptr); + pangoMarkup = g_markup_printf_escaped("<b>%s</b>", GetUTF8FromBundle("headerFooter").get()); + gtk_label_set_markup(GTK_LABEL(header_footer_label), pangoMarkup); + g_free(pangoMarkup); + gtk_misc_set_alignment(GTK_MISC(header_footer_label), 0, 0); + + GtkWidget* header_footer_container = gtk_alignment_new(0, 0, 0, 0); + gtk_alignment_set_padding(GTK_ALIGNMENT(header_footer_container), 8, 0, 12, 0); + + + // --- Table for making the header and footer options --- + GtkWidget* header_footer_table = gtk_table_new(3, 3, FALSE); // 3x3 table + nsXPIDLString header_footer_str[3]; + + aSettings->GetHeaderStrLeft(getter_Copies(header_footer_str[0])); + aSettings->GetHeaderStrCenter(getter_Copies(header_footer_str[1])); + aSettings->GetHeaderStrRight(getter_Copies(header_footer_str[2])); + + for (unsigned int i = 0; i < ArrayLength(header_dropdown); i++) { + header_dropdown[i] = ConstructHeaderFooterDropdown(header_footer_str[i].get()); + // Those 4 magic numbers in the middle provide the position in the table. + // The last two numbers mean 2 px padding on every side. + gtk_table_attach(GTK_TABLE(header_footer_table), header_dropdown[i], i, (i + 1), + 0, 1, (GtkAttachOptions) 0, (GtkAttachOptions) 0, 2, 2); + } + + const char labelKeys[][7] = {"left", "center", "right"}; + for (unsigned int i = 0; i < ArrayLength(labelKeys); i++) { + gtk_table_attach(GTK_TABLE(header_footer_table), + gtk_label_new(GetUTF8FromBundle(labelKeys[i]).get()), + i, (i + 1), 1, 2, (GtkAttachOptions) 0, (GtkAttachOptions) 0, 2, 2); + } + + aSettings->GetFooterStrLeft(getter_Copies(header_footer_str[0])); + aSettings->GetFooterStrCenter(getter_Copies(header_footer_str[1])); + aSettings->GetFooterStrRight(getter_Copies(header_footer_str[2])); + + for (unsigned int i = 0; i < ArrayLength(footer_dropdown); i++) { + footer_dropdown[i] = ConstructHeaderFooterDropdown(header_footer_str[i].get()); + gtk_table_attach(GTK_TABLE(header_footer_table), footer_dropdown[i], i, (i + 1), + 2, 3, (GtkAttachOptions) 0, (GtkAttachOptions) 0, 2, 2); + } + // --- + + gtk_container_add(GTK_CONTAINER(header_footer_container), header_footer_table); + + GtkWidget* header_footer_vertical_squasher = gtk_vbox_new(FALSE, 0); + gtk_box_pack_start(GTK_BOX(header_footer_vertical_squasher), header_footer_label, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(header_footer_vertical_squasher), header_footer_container, FALSE, FALSE, 0); + + // Construction of everything + gtk_box_pack_start(GTK_BOX(custom_options_tab), print_frames_label, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(custom_options_tab), frames_radio_container, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(custom_options_tab), check_buttons_container, FALSE, FALSE, 10); // 10px padding + gtk_box_pack_start(GTK_BOX(custom_options_tab), appearance_vertical_squasher, FALSE, FALSE, 10); + gtk_box_pack_start(GTK_BOX(custom_options_tab), header_footer_vertical_squasher, FALSE, FALSE, 0); + + gtk_print_unix_dialog_add_custom_tab(GTK_PRINT_UNIX_DIALOG(dialog), custom_options_tab, tab_label); + gtk_widget_show_all(custom_options_tab); +} + +NS_ConvertUTF16toUTF8 +nsPrintDialogWidgetGTK::GetUTF8FromBundle(const char *aKey) +{ + nsXPIDLString intlString; + printBundle->GetStringFromName(NS_ConvertUTF8toUTF16(aKey).get(), getter_Copies(intlString)); + return NS_ConvertUTF16toUTF8(intlString); // Return the actual object so we don't lose reference +} + +const char* +nsPrintDialogWidgetGTK::OptionWidgetToString(GtkWidget *dropdown) +{ + gint index = gtk_combo_box_get_active(GTK_COMBO_BOX(dropdown)); + + NS_ASSERTION(index <= CUSTOM_VALUE_INDEX, "Index of dropdown is higher than expected!"); + + if (index == CUSTOM_VALUE_INDEX) + return (const char*) g_object_get_data(G_OBJECT(dropdown), "custom-text"); + else + return header_footer_tags[index]; +} + +gint +nsPrintDialogWidgetGTK::Run() +{ + const gint response = gtk_dialog_run(GTK_DIALOG(dialog)); + gtk_widget_hide(dialog); + return response; +} + +void +nsPrintDialogWidgetGTK::ExportFramePrinting(nsIPrintSettings *aNS, GtkPrintSettings *aSettings) +{ + if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(radio_as_laid_out))) + aNS->SetPrintFrameType(nsIPrintSettings::kFramesAsIs); + else if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(radio_selected_frame))) + aNS->SetPrintFrameType(nsIPrintSettings::kSelectedFrame); + else if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(radio_separate_frames))) + aNS->SetPrintFrameType(nsIPrintSettings::kEachFrameSep); + else + aNS->SetPrintFrameType(nsIPrintSettings::kNoFrames); +} + +void +nsPrintDialogWidgetGTK::ExportHeaderFooter(nsIPrintSettings *aNS) +{ + const char* header_footer_str; + header_footer_str = OptionWidgetToString(header_dropdown[0]); + aNS->SetHeaderStrLeft(NS_ConvertUTF8toUTF16(header_footer_str).get()); + + header_footer_str = OptionWidgetToString(header_dropdown[1]); + aNS->SetHeaderStrCenter(NS_ConvertUTF8toUTF16(header_footer_str).get()); + + header_footer_str = OptionWidgetToString(header_dropdown[2]); + aNS->SetHeaderStrRight(NS_ConvertUTF8toUTF16(header_footer_str).get()); + + header_footer_str = OptionWidgetToString(footer_dropdown[0]); + aNS->SetFooterStrLeft(NS_ConvertUTF8toUTF16(header_footer_str).get()); + + header_footer_str = OptionWidgetToString(footer_dropdown[1]); + aNS->SetFooterStrCenter(NS_ConvertUTF8toUTF16(header_footer_str).get()); + + header_footer_str = OptionWidgetToString(footer_dropdown[2]); + aNS->SetFooterStrRight(NS_ConvertUTF8toUTF16(header_footer_str).get()); +} + +nsresult +nsPrintDialogWidgetGTK::ImportSettings(nsIPrintSettings *aNSSettings) +{ + NS_PRECONDITION(aNSSettings, "aSettings must not be null"); + NS_ENSURE_TRUE(aNSSettings, NS_ERROR_FAILURE); + + nsCOMPtr<nsPrintSettingsGTK> aNSSettingsGTK(do_QueryInterface(aNSSettings)); + if (!aNSSettingsGTK) + return NS_ERROR_FAILURE; + + GtkPrintSettings* settings = aNSSettingsGTK->GetGtkPrintSettings(); + GtkPageSetup* setup = aNSSettingsGTK->GetGtkPageSetup(); + + bool geckoBool; + aNSSettings->GetShrinkToFit(&geckoBool); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(shrink_to_fit_toggle), geckoBool); + + aNSSettings->GetPrintBGColors(&geckoBool); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(print_bg_colors_toggle), geckoBool); + + aNSSettings->GetPrintBGImages(&geckoBool); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(print_bg_images_toggle), geckoBool); + + gtk_print_unix_dialog_set_settings(GTK_PRINT_UNIX_DIALOG(dialog), settings); + gtk_print_unix_dialog_set_page_setup(GTK_PRINT_UNIX_DIALOG(dialog), setup); + + return NS_OK; +} + +nsresult +nsPrintDialogWidgetGTK::ExportSettings(nsIPrintSettings *aNSSettings) +{ + NS_PRECONDITION(aNSSettings, "aSettings must not be null"); + NS_ENSURE_TRUE(aNSSettings, NS_ERROR_FAILURE); + + GtkPrintSettings* settings = gtk_print_unix_dialog_get_settings(GTK_PRINT_UNIX_DIALOG(dialog)); + GtkPageSetup* setup = gtk_print_unix_dialog_get_page_setup(GTK_PRINT_UNIX_DIALOG(dialog)); + GtkPrinter* printer = gtk_print_unix_dialog_get_selected_printer(GTK_PRINT_UNIX_DIALOG(dialog)); + if (settings && setup && printer) { + ExportFramePrinting(aNSSettings, settings); + ExportHeaderFooter(aNSSettings); + + aNSSettings->SetOutputFormat(nsIPrintSettings::kOutputFormatNative); + + // Print-to-file is true by default. This must be turned off or else printing won't occur! + // (We manually copy the spool file when this flag is set, because we love our embedders) + // Even if it is print-to-file in GTK's case, GTK does The Right Thing when we send the job. + aNSSettings->SetPrintToFile(false); + + aNSSettings->SetShrinkToFit(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(shrink_to_fit_toggle))); + + aNSSettings->SetPrintBGColors(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(print_bg_colors_toggle))); + aNSSettings->SetPrintBGImages(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(print_bg_images_toggle))); + + // Try to save native settings in the session object + nsCOMPtr<nsPrintSettingsGTK> aNSSettingsGTK(do_QueryInterface(aNSSettings)); + if (aNSSettingsGTK) { + aNSSettingsGTK->SetGtkPrintSettings(settings); + aNSSettingsGTK->SetGtkPageSetup(setup); + aNSSettingsGTK->SetGtkPrinter(printer); + bool printSelectionOnly; + if (useNativeSelection) { + _GtkPrintPages pageSetting = (_GtkPrintPages)gtk_print_settings_get_print_pages(settings); + printSelectionOnly = (pageSetting == _GTK_PRINT_PAGES_SELECTION); + } else { + printSelectionOnly = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(selection_only_toggle)); + } + aNSSettingsGTK->SetForcePrintSelectionOnly(printSelectionOnly); + } + } + + if (settings) + g_object_unref(settings); + return NS_OK; +} + +GtkWidget* +nsPrintDialogWidgetGTK::ConstructHeaderFooterDropdown(const char16_t *currentString) +{ +#if (MOZ_WIDGET_GTK == 2) + GtkWidget* dropdown = gtk_combo_box_new_text(); +#else + GtkWidget* dropdown = gtk_combo_box_text_new(); +#endif + const char hf_options[][22] = {"headerFooterBlank", "headerFooterTitle", + "headerFooterURL", "headerFooterDate", + "headerFooterPage", "headerFooterPageTotal", + "headerFooterCustom"}; + + for (unsigned int i = 0; i < ArrayLength(hf_options); i++) { +#if (MOZ_WIDGET_GTK == 2) + gtk_combo_box_append_text(GTK_COMBO_BOX(dropdown), GetUTF8FromBundle(hf_options[i]).get()); +#else + gtk_combo_box_text_append(GTK_COMBO_BOX_TEXT(dropdown), nullptr, + GetUTF8FromBundle(hf_options[i]).get()); +#endif + } + + bool shouldBeCustom = true; + NS_ConvertUTF16toUTF8 currentStringUTF8(currentString); + + for (unsigned int i = 0; i < ArrayLength(header_footer_tags); i++) { + if (!strcmp(currentStringUTF8.get(), header_footer_tags[i])) { + gtk_combo_box_set_active(GTK_COMBO_BOX(dropdown), i); + g_object_set_data(G_OBJECT(dropdown), "previous-active", GINT_TO_POINTER(i)); + shouldBeCustom = false; + break; + } + } + + if (shouldBeCustom) { + gtk_combo_box_set_active(GTK_COMBO_BOX(dropdown), CUSTOM_VALUE_INDEX); + g_object_set_data(G_OBJECT(dropdown), "previous-active", GINT_TO_POINTER(CUSTOM_VALUE_INDEX)); + char* custom_string = strdup(currentStringUTF8.get()); + g_object_set_data_full(G_OBJECT(dropdown), "custom-text", custom_string, (GDestroyNotify) free); + } + + g_signal_connect(dropdown, "changed", (GCallback) ShowCustomDialog, dialog); + return dropdown; +} + +NS_IMPL_ISUPPORTS(nsPrintDialogServiceGTK, nsIPrintDialogService) + +nsPrintDialogServiceGTK::nsPrintDialogServiceGTK() +{ +} + +nsPrintDialogServiceGTK::~nsPrintDialogServiceGTK() +{ +} + +NS_IMETHODIMP +nsPrintDialogServiceGTK::Init() +{ + return NS_OK; +} + +NS_IMETHODIMP +nsPrintDialogServiceGTK::Show(nsPIDOMWindowOuter *aParent, + nsIPrintSettings *aSettings, + nsIWebBrowserPrint *aWebBrowserPrint) +{ + NS_PRECONDITION(aParent, "aParent must not be null"); + NS_PRECONDITION(aSettings, "aSettings must not be null"); + + nsPrintDialogWidgetGTK printDialog(aParent, aSettings); + nsresult rv = printDialog.ImportSettings(aSettings); + + NS_ENSURE_SUCCESS(rv, rv); + + const gint response = printDialog.Run(); + + // Handle the result + switch (response) { + case GTK_RESPONSE_OK: // Proceed + rv = printDialog.ExportSettings(aSettings); + break; + + case GTK_RESPONSE_CANCEL: + case GTK_RESPONSE_CLOSE: + case GTK_RESPONSE_DELETE_EVENT: + case GTK_RESPONSE_NONE: + rv = NS_ERROR_ABORT; + break; + + case GTK_RESPONSE_APPLY: // Print preview + default: + NS_WARNING("Unexpected response"); + rv = NS_ERROR_ABORT; + } + return rv; +} + +NS_IMETHODIMP +nsPrintDialogServiceGTK::ShowPageSetup(nsPIDOMWindowOuter *aParent, + nsIPrintSettings *aNSSettings) +{ + NS_PRECONDITION(aParent, "aParent must not be null"); + NS_PRECONDITION(aNSSettings, "aSettings must not be null"); + NS_ENSURE_TRUE(aNSSettings, NS_ERROR_FAILURE); + + nsCOMPtr<nsIWidget> widget = WidgetUtils::DOMWindowToWidget(aParent); + NS_ASSERTION(widget, "Need a widget for dialog to be modal."); + GtkWindow* gtkParent = get_gtk_window_for_nsiwidget(widget); + NS_ASSERTION(gtkParent, "Need a GTK window for dialog to be modal."); + + nsCOMPtr<nsPrintSettingsGTK> aNSSettingsGTK(do_QueryInterface(aNSSettings)); + if (!aNSSettingsGTK) + return NS_ERROR_FAILURE; + + // We need to init the prefs here because aNSSettings in its current form is a dummy in both uses of the word + nsCOMPtr<nsIPrintSettingsService> psService = do_GetService("@mozilla.org/gfx/printsettings-service;1"); + if (psService) { + nsXPIDLString printName; + aNSSettings->GetPrinterName(getter_Copies(printName)); + if (!printName) { + psService->GetDefaultPrinterName(getter_Copies(printName)); + aNSSettings->SetPrinterName(printName.get()); + } + psService->InitPrintSettingsFromPrefs(aNSSettings, true, nsIPrintSettings::kInitSaveAll); + } + + GtkPrintSettings* gtkSettings = aNSSettingsGTK->GetGtkPrintSettings(); + GtkPageSetup* oldPageSetup = aNSSettingsGTK->GetGtkPageSetup(); + + GtkPageSetup* newPageSetup = gtk_print_run_page_setup_dialog(gtkParent, oldPageSetup, gtkSettings); + + aNSSettingsGTK->SetGtkPageSetup(newPageSetup); + + // Now newPageSetup has a refcount of 2 (SetGtkPageSetup will addref), put it to 1 so if + // this gets replaced we don't leak. + g_object_unref(newPageSetup); + + if (psService) + psService->SavePrintSettingsToPrefs(aNSSettings, true, nsIPrintSettings::kInitSaveAll); + + return NS_OK; +} diff --git a/widget/gtk/nsPrintDialogGTK.h b/widget/gtk/nsPrintDialogGTK.h new file mode 100644 index 000000000..c60fa6a2b --- /dev/null +++ b/widget/gtk/nsPrintDialogGTK.h @@ -0,0 +1,39 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#ifndef nsPrintDialog_h__ +#define nsPrintDialog_h__ + +#include "nsIPrintDialogService.h" + +class nsIPrintSettings; + +// Copy the print pages enum here because not all versions +// have SELECTION, which we will use +typedef enum +{ + _GTK_PRINT_PAGES_ALL, + _GTK_PRINT_PAGES_CURRENT, + _GTK_PRINT_PAGES_RANGES, + _GTK_PRINT_PAGES_SELECTION +} _GtkPrintPages; + +class nsPrintDialogServiceGTK : public nsIPrintDialogService +{ + virtual ~nsPrintDialogServiceGTK(); + +public: + nsPrintDialogServiceGTK(); + + NS_DECL_ISUPPORTS + + NS_IMETHOD Init() override; + NS_IMETHOD Show(nsPIDOMWindowOuter *aParent, nsIPrintSettings *aSettings, + nsIWebBrowserPrint *aWebBrowserPrint) override; + NS_IMETHOD ShowPageSetup(nsPIDOMWindowOuter *aParent, + nsIPrintSettings *aSettings) override; +}; + +#endif diff --git a/widget/gtk/nsPrintOptionsGTK.cpp b/widget/gtk/nsPrintOptionsGTK.cpp new file mode 100644 index 000000000..ca86a1f89 --- /dev/null +++ b/widget/gtk/nsPrintOptionsGTK.cpp @@ -0,0 +1,102 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 "nsPrintOptionsGTK.h" +#include "nsPrintSettingsGTK.h" + +using namespace mozilla::embedding; + +/** --------------------------------------------------- + * See documentation in nsPrintOptionsWin.h + * @update 6/21/00 dwc + */ +nsPrintOptionsGTK::nsPrintOptionsGTK() +{ + +} + +/** --------------------------------------------------- + * See documentation in nsPrintOptionsImpl.h + * @update 6/21/00 dwc + */ +nsPrintOptionsGTK::~nsPrintOptionsGTK() +{ +} + +static void +serialize_gtk_printsettings_to_printdata(const gchar *key, + const gchar *value, + gpointer aData) +{ + PrintData* data = (PrintData*)aData; + CStringKeyValue pair; + pair.key() = key; + pair.value() = value; + data->GTKPrintSettings().AppendElement(pair); +} + +NS_IMETHODIMP +nsPrintOptionsGTK::SerializeToPrintData(nsIPrintSettings* aSettings, + nsIWebBrowserPrint* aWBP, + PrintData* data) +{ + nsresult rv = nsPrintOptions::SerializeToPrintData(aSettings, aWBP, data); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsPrintSettingsGTK> settingsGTK(do_QueryInterface(aSettings)); + NS_ENSURE_STATE(settingsGTK); + + GtkPrintSettings* gtkPrintSettings = settingsGTK->GetGtkPrintSettings(); + NS_ENSURE_STATE(gtkPrintSettings); + + gtk_print_settings_foreach( + gtkPrintSettings, + serialize_gtk_printsettings_to_printdata, + data); + + return NS_OK; +} + +NS_IMETHODIMP +nsPrintOptionsGTK::DeserializeToPrintSettings(const PrintData& data, + nsIPrintSettings* settings) +{ + nsCOMPtr<nsPrintSettingsGTK> settingsGTK(do_QueryInterface(settings)); + NS_ENSURE_STATE(settingsGTK); + + nsresult rv = nsPrintOptions::DeserializeToPrintSettings(data, settings); + NS_ENSURE_SUCCESS(rv, rv); + + // Instead of re-using the GtkPrintSettings that nsIPrintSettings is + // wrapping, we'll create a new one to deserialize to and replace it + // within nsIPrintSettings. + GtkPrintSettings* newGtkPrintSettings = gtk_print_settings_new(); + + for (uint32_t i = 0; i < data.GTKPrintSettings().Length(); ++i) { + CStringKeyValue pair = data.GTKPrintSettings()[i]; + gtk_print_settings_set(newGtkPrintSettings, + pair.key().get(), + pair.value().get()); + } + + settingsGTK->SetGtkPrintSettings(newGtkPrintSettings); + + // nsPrintSettingsGTK is holding a reference to newGtkPrintSettings + g_object_unref(newGtkPrintSettings); + newGtkPrintSettings = nullptr; + return NS_OK; +} + +nsresult nsPrintOptionsGTK::_CreatePrintSettings(nsIPrintSettings **_retval) +{ + *_retval = nullptr; + nsPrintSettingsGTK* printSettings = new nsPrintSettingsGTK(); // does not initially ref count + NS_ENSURE_TRUE(printSettings, NS_ERROR_OUT_OF_MEMORY); + + NS_ADDREF(*_retval = printSettings); // ref count + + return NS_OK; +} + diff --git a/widget/gtk/nsPrintOptionsGTK.h b/widget/gtk/nsPrintOptionsGTK.h new file mode 100644 index 000000000..d558bb800 --- /dev/null +++ b/widget/gtk/nsPrintOptionsGTK.h @@ -0,0 +1,40 @@ +/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * 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/. */ + +#ifndef nsPrintOptionsGTK_h__ +#define nsPrintOptionsGTK_h__ + +#include "nsPrintOptionsImpl.h" + +namespace mozilla +{ +namespace embedding +{ + class PrintData; +} // namespace embedding +} // namespace mozilla + +//***************************************************************************** +//*** nsPrintOptions +//***************************************************************************** +class nsPrintOptionsGTK : public nsPrintOptions +{ +public: + nsPrintOptionsGTK(); + virtual ~nsPrintOptionsGTK(); + + NS_IMETHODIMP SerializeToPrintData(nsIPrintSettings* aSettings, + nsIWebBrowserPrint* aWBP, + mozilla::embedding::PrintData* data); + NS_IMETHODIMP DeserializeToPrintSettings(const mozilla::embedding::PrintData& data, + nsIPrintSettings* settings); + + virtual nsresult _CreatePrintSettings(nsIPrintSettings **_retval); +}; + + + +#endif /* nsPrintOptions_h__ */ diff --git a/widget/gtk/nsPrintSettingsGTK.cpp b/widget/gtk/nsPrintSettingsGTK.cpp new file mode 100644 index 000000000..a8fd60bd1 --- /dev/null +++ b/widget/gtk/nsPrintSettingsGTK.cpp @@ -0,0 +1,813 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 "nsPrintSettingsGTK.h" +#include "nsIFile.h" +#include "nsNetUtil.h" +#include <stdlib.h> +#include <algorithm> + +static +gboolean ref_printer(GtkPrinter *aPrinter, gpointer aData) +{ + ((nsPrintSettingsGTK*) aData)->SetGtkPrinter(aPrinter); + return TRUE; +} + +static +gboolean printer_enumerator(GtkPrinter *aPrinter, gpointer aData) +{ + if (gtk_printer_is_default(aPrinter)) + return ref_printer(aPrinter, aData); + + return FALSE; // Keep 'em coming... +} + +static +GtkPaperSize* moz_gtk_paper_size_copy_to_new_custom(GtkPaperSize* oldPaperSize) +{ + // We make a "custom-ified" copy of the paper size so it can be changed later. + return gtk_paper_size_new_custom(gtk_paper_size_get_name(oldPaperSize), + gtk_paper_size_get_display_name(oldPaperSize), + gtk_paper_size_get_width(oldPaperSize, GTK_UNIT_INCH), + gtk_paper_size_get_height(oldPaperSize, GTK_UNIT_INCH), + GTK_UNIT_INCH); +} + +NS_IMPL_ISUPPORTS_INHERITED(nsPrintSettingsGTK, + nsPrintSettings, + nsPrintSettingsGTK) + +/** --------------------------------------------------- + */ +nsPrintSettingsGTK::nsPrintSettingsGTK() : + mPageSetup(nullptr), + mPrintSettings(nullptr), + mGTKPrinter(nullptr), + mPrintSelectionOnly(false) +{ + // The aim here is to set up the objects enough that silent printing works well. + // These will be replaced anyway if the print dialog is used. + mPrintSettings = gtk_print_settings_new(); + GtkPageSetup* pageSetup = gtk_page_setup_new(); + SetGtkPageSetup(pageSetup); + g_object_unref(pageSetup); + + SetOutputFormat(nsIPrintSettings::kOutputFormatNative); +} + +/** --------------------------------------------------- + */ +nsPrintSettingsGTK::~nsPrintSettingsGTK() +{ + if (mPageSetup) { + g_object_unref(mPageSetup); + mPageSetup = nullptr; + } + if (mPrintSettings) { + g_object_unref(mPrintSettings); + mPrintSettings = nullptr; + } + if (mGTKPrinter) { + g_object_unref(mGTKPrinter); + mGTKPrinter = nullptr; + } +} + +/** --------------------------------------------------- + */ +nsPrintSettingsGTK::nsPrintSettingsGTK(const nsPrintSettingsGTK& aPS) : + mPageSetup(nullptr), + mPrintSettings(nullptr), + mGTKPrinter(nullptr), + mPrintSelectionOnly(false) +{ + *this = aPS; +} + +/** --------------------------------------------------- + */ +nsPrintSettingsGTK& nsPrintSettingsGTK::operator=(const nsPrintSettingsGTK& rhs) +{ + if (this == &rhs) { + return *this; + } + + nsPrintSettings::operator=(rhs); + + if (mPageSetup) + g_object_unref(mPageSetup); + mPageSetup = gtk_page_setup_copy(rhs.mPageSetup); + // NOTE: No need to re-initialize mUnwriteableMargin here (even + // though mPageSetup is changing). It'll be copied correctly by + // nsPrintSettings::operator=. + + if (mPrintSettings) + g_object_unref(mPrintSettings); + mPrintSettings = gtk_print_settings_copy(rhs.mPrintSettings); + + if (mGTKPrinter) + g_object_unref(mGTKPrinter); + mGTKPrinter = (GtkPrinter*) g_object_ref(rhs.mGTKPrinter); + + mPrintSelectionOnly = rhs.mPrintSelectionOnly; + + return *this; +} + +/** ------------------------------------------- + */ +nsresult nsPrintSettingsGTK::_Clone(nsIPrintSettings **_retval) +{ + NS_ENSURE_ARG_POINTER(_retval); + *_retval = nullptr; + + nsPrintSettingsGTK *newSettings = new nsPrintSettingsGTK(*this); + if (!newSettings) + return NS_ERROR_FAILURE; + *_retval = newSettings; + NS_ADDREF(*_retval); + return NS_OK; +} + + +/** ------------------------------------------- + */ +NS_IMETHODIMP +nsPrintSettingsGTK::_Assign(nsIPrintSettings *aPS) +{ + nsPrintSettingsGTK *printSettingsGTK = static_cast<nsPrintSettingsGTK*>(aPS); + if (!printSettingsGTK) + return NS_ERROR_UNEXPECTED; + *this = *printSettingsGTK; + return NS_OK; +} + +/** --------------------------------------------------- + */ +void +nsPrintSettingsGTK::SetGtkPageSetup(GtkPageSetup *aPageSetup) +{ + if (mPageSetup) + g_object_unref(mPageSetup); + + mPageSetup = (GtkPageSetup*) g_object_ref(aPageSetup); + InitUnwriteableMargin(); + + // If the paper size is not custom, then we make a custom copy of the + // GtkPaperSize, so it can be mutable. If a GtkPaperSize wasn't made as + // custom, its properties are immutable. + GtkPaperSize* paperSize = gtk_page_setup_get_paper_size(aPageSetup); + if (!gtk_paper_size_is_custom(paperSize)) { + GtkPaperSize* customPaperSize = + moz_gtk_paper_size_copy_to_new_custom(paperSize); + gtk_page_setup_set_paper_size(mPageSetup, customPaperSize); + gtk_paper_size_free(customPaperSize); + } + SaveNewPageSize(); +} + +/** --------------------------------------------------- + */ +void +nsPrintSettingsGTK::SetGtkPrintSettings(GtkPrintSettings *aPrintSettings) +{ + if (mPrintSettings) + g_object_unref(mPrintSettings); + + mPrintSettings = (GtkPrintSettings*) g_object_ref(aPrintSettings); + + GtkPaperSize* paperSize = gtk_print_settings_get_paper_size(aPrintSettings); + if (paperSize) { + GtkPaperSize* customPaperSize = + moz_gtk_paper_size_copy_to_new_custom(paperSize); + gtk_paper_size_free(paperSize); + gtk_page_setup_set_paper_size(mPageSetup, customPaperSize); + gtk_paper_size_free(customPaperSize); + } else { + // paperSize was null, and so we add the paper size in the GtkPageSetup to + // the settings. + SaveNewPageSize(); + } +} + +/** --------------------------------------------------- + */ +void +nsPrintSettingsGTK::SetGtkPrinter(GtkPrinter *aPrinter) +{ + if (mGTKPrinter) + g_object_unref(mGTKPrinter); + + mGTKPrinter = (GtkPrinter*) g_object_ref(aPrinter); +} + +NS_IMETHODIMP nsPrintSettingsGTK::GetOutputFormat(int16_t *aOutputFormat) +{ + NS_ENSURE_ARG_POINTER(aOutputFormat); + + int16_t format; + nsresult rv = nsPrintSettings::GetOutputFormat(&format); + if (NS_FAILED(rv)) { + return rv; + } + + if (format == nsIPrintSettings::kOutputFormatNative) { + const gchar* fmtGTK = + gtk_print_settings_get(mPrintSettings, + GTK_PRINT_SETTINGS_OUTPUT_FILE_FORMAT); + if (fmtGTK) { + if (nsDependentCString(fmtGTK).EqualsIgnoreCase("pdf")) { + format = nsIPrintSettings::kOutputFormatPDF; + } else { + format = nsIPrintSettings::kOutputFormatPS; + } + } else if (GTK_IS_PRINTER(mGTKPrinter)) { + // Prior to gtk 2.24, gtk_printer_accepts_pdf() and + // gtk_printer_accepts_ps() always returned true regardless of the + // printer's capability. + bool shouldTrustGTK = + (gtk_major_version > 2 || + (gtk_major_version == 2 && gtk_minor_version >= 24)); + bool acceptsPDF = shouldTrustGTK && gtk_printer_accepts_pdf(mGTKPrinter); + + format = acceptsPDF ? nsIPrintSettings::kOutputFormatPDF + : nsIPrintSettings::kOutputFormatPS; + } + } + + *aOutputFormat = format; + return NS_OK; +} + +/** + * Reimplementation of nsPrintSettings functions so that we get the values + * from the GTK objects rather than our own variables. + */ + +NS_IMETHODIMP nsPrintSettingsGTK::GetPrintRange(int16_t *aPrintRange) +{ + NS_ENSURE_ARG_POINTER(aPrintRange); + if (mPrintSelectionOnly) { + *aPrintRange = kRangeSelection; + return NS_OK; + } + + GtkPrintPages gtkRange = gtk_print_settings_get_print_pages(mPrintSettings); + if (gtkRange == GTK_PRINT_PAGES_RANGES) + *aPrintRange = kRangeSpecifiedPageRange; + else + *aPrintRange = kRangeAllPages; + + return NS_OK; +} +NS_IMETHODIMP nsPrintSettingsGTK::SetPrintRange(int16_t aPrintRange) +{ + if (aPrintRange == kRangeSelection) { + mPrintSelectionOnly = true; + return NS_OK; + } + + mPrintSelectionOnly = false; + if (aPrintRange == kRangeSpecifiedPageRange) + gtk_print_settings_set_print_pages(mPrintSettings, GTK_PRINT_PAGES_RANGES); + else + gtk_print_settings_set_print_pages(mPrintSettings, GTK_PRINT_PAGES_ALL); + return NS_OK; +} + +NS_IMETHODIMP +nsPrintSettingsGTK::GetStartPageRange(int32_t *aStartPageRange) +{ + gint ctRanges; + GtkPageRange* lstRanges = gtk_print_settings_get_page_ranges(mPrintSettings, &ctRanges); + + // Make sure we got a range. + if (ctRanges < 1) { + *aStartPageRange = 1; + } else { + // GTK supports multiple page ranges; gecko only supports 1. So find + // the lowest start page. + int32_t start(lstRanges[0].start); + for (gint ii = 1; ii < ctRanges; ii++) { + start = std::min(lstRanges[ii].start, start); + } + *aStartPageRange = start + 1; + } + + g_free(lstRanges); + return NS_OK; +} +NS_IMETHODIMP +nsPrintSettingsGTK::SetStartPageRange(int32_t aStartPageRange) +{ + int32_t endRange; + GetEndPageRange(&endRange); + + GtkPageRange gtkRange; + gtkRange.start = aStartPageRange - 1; + gtkRange.end = endRange - 1; + + gtk_print_settings_set_page_ranges(mPrintSettings, >kRange, 1); + + return NS_OK; +} + +NS_IMETHODIMP +nsPrintSettingsGTK::GetEndPageRange(int32_t *aEndPageRange) +{ + gint ctRanges; + GtkPageRange* lstRanges = gtk_print_settings_get_page_ranges(mPrintSettings, &ctRanges); + + if (ctRanges < 1) { + *aEndPageRange = 1; + } else { + int32_t end(lstRanges[0].end); + for (gint ii = 1; ii < ctRanges; ii++) { + end = std::max(lstRanges[ii].end, end); + } + *aEndPageRange = end + 1; + } + + g_free(lstRanges); + return NS_OK; +} +NS_IMETHODIMP +nsPrintSettingsGTK::SetEndPageRange(int32_t aEndPageRange) +{ + int32_t startRange; + GetStartPageRange(&startRange); + + GtkPageRange gtkRange; + gtkRange.start = startRange - 1; + gtkRange.end = aEndPageRange - 1; + + gtk_print_settings_set_page_ranges(mPrintSettings, >kRange, 1); + + return NS_OK; +} + +NS_IMETHODIMP +nsPrintSettingsGTK::GetPrintReversed(bool *aPrintReversed) +{ + *aPrintReversed = gtk_print_settings_get_reverse(mPrintSettings); + return NS_OK; +} +NS_IMETHODIMP +nsPrintSettingsGTK::SetPrintReversed(bool aPrintReversed) +{ + gtk_print_settings_set_reverse(mPrintSettings, aPrintReversed); + return NS_OK; +} + +NS_IMETHODIMP +nsPrintSettingsGTK::GetPrintInColor(bool *aPrintInColor) +{ + *aPrintInColor = gtk_print_settings_get_use_color(mPrintSettings); + return NS_OK; +} +NS_IMETHODIMP +nsPrintSettingsGTK::SetPrintInColor(bool aPrintInColor) +{ + gtk_print_settings_set_use_color(mPrintSettings, aPrintInColor); + return NS_OK; +} + +NS_IMETHODIMP +nsPrintSettingsGTK::GetOrientation(int32_t *aOrientation) +{ + NS_ENSURE_ARG_POINTER(aOrientation); + + GtkPageOrientation gtkOrient = gtk_page_setup_get_orientation(mPageSetup); + switch (gtkOrient) { + case GTK_PAGE_ORIENTATION_LANDSCAPE: + case GTK_PAGE_ORIENTATION_REVERSE_LANDSCAPE: + *aOrientation = kLandscapeOrientation; + break; + + case GTK_PAGE_ORIENTATION_PORTRAIT: + case GTK_PAGE_ORIENTATION_REVERSE_PORTRAIT: + default: + *aOrientation = kPortraitOrientation; + } + return NS_OK; +} +NS_IMETHODIMP +nsPrintSettingsGTK::SetOrientation(int32_t aOrientation) +{ + GtkPageOrientation gtkOrient; + if (aOrientation == kLandscapeOrientation) + gtkOrient = GTK_PAGE_ORIENTATION_LANDSCAPE; + else + gtkOrient = GTK_PAGE_ORIENTATION_PORTRAIT; + + gtk_print_settings_set_orientation(mPrintSettings, gtkOrient); + gtk_page_setup_set_orientation(mPageSetup, gtkOrient); + return NS_OK; +} + +NS_IMETHODIMP +nsPrintSettingsGTK::GetToFileName(char16_t * *aToFileName) +{ + // Get the gtk output filename + const char* gtk_output_uri = gtk_print_settings_get(mPrintSettings, GTK_PRINT_SETTINGS_OUTPUT_URI); + if (!gtk_output_uri) { + *aToFileName = ToNewUnicode(mToFileName); + return NS_OK; + } + + // Convert to an nsIFile + nsCOMPtr<nsIFile> file; + nsresult rv = NS_GetFileFromURLSpec(nsDependentCString(gtk_output_uri), + getter_AddRefs(file)); + if (NS_FAILED(rv)) + return rv; + + // Extract the path + nsAutoString path; + rv = file->GetPath(path); + NS_ENSURE_SUCCESS(rv, rv); + + *aToFileName = ToNewUnicode(path); + return NS_OK; +} + +NS_IMETHODIMP +nsPrintSettingsGTK::SetToFileName(const char16_t * aToFileName) +{ + if (aToFileName[0] == 0) { + mToFileName.SetLength(0); + gtk_print_settings_set(mPrintSettings, GTK_PRINT_SETTINGS_OUTPUT_URI, + nullptr); + return NS_OK; + } + + if (StringEndsWith(nsDependentString(aToFileName), NS_LITERAL_STRING(".ps"))) { + gtk_print_settings_set(mPrintSettings, GTK_PRINT_SETTINGS_OUTPUT_FILE_FORMAT, "ps"); + } else { + gtk_print_settings_set(mPrintSettings, GTK_PRINT_SETTINGS_OUTPUT_FILE_FORMAT, "pdf"); + } + + nsCOMPtr<nsIFile> file; + nsresult rv = NS_NewLocalFile(nsDependentString(aToFileName), true, + getter_AddRefs(file)); + NS_ENSURE_SUCCESS(rv, rv); + + // Convert the nsIFile to a URL + nsAutoCString url; + rv = NS_GetURLSpecFromFile(file, url); + NS_ENSURE_SUCCESS(rv, rv); + + gtk_print_settings_set(mPrintSettings, GTK_PRINT_SETTINGS_OUTPUT_URI, url.get()); + mToFileName = aToFileName; + + return NS_OK; +} + +NS_IMETHODIMP +nsPrintSettingsGTK::GetPrinterName(char16_t * *aPrinter) +{ + const char* gtkPrintName = gtk_print_settings_get_printer(mPrintSettings); + if (!gtkPrintName) { + if (GTK_IS_PRINTER(mGTKPrinter)) { + gtkPrintName = gtk_printer_get_name(mGTKPrinter); + } else { + // This mimics what nsPrintSettingsImpl does when we try to Get before we Set + nsXPIDLString nullPrintName; + *aPrinter = ToNewUnicode(nullPrintName); + return NS_OK; + } + } + *aPrinter = UTF8ToNewUnicode(nsDependentCString(gtkPrintName)); + return NS_OK; +} + +NS_IMETHODIMP +nsPrintSettingsGTK::SetPrinterName(const char16_t * aPrinter) +{ + NS_ConvertUTF16toUTF8 gtkPrinter(aPrinter); + + if (StringBeginsWith(gtkPrinter, NS_LITERAL_CSTRING("CUPS/"))) { + // Strip off "CUPS/"; GTK might recognize the rest + gtkPrinter.Cut(0, strlen("CUPS/")); + } + + // Give mPrintSettings the passed-in printer name if either... + // - it has no printer name stored yet + // - it has an existing printer name that's different from + // the name passed to this function. + const char* oldPrinterName = gtk_print_settings_get_printer(mPrintSettings); + if (!oldPrinterName || !gtkPrinter.Equals(oldPrinterName)) { + mIsInitedFromPrinter = false; + mIsInitedFromPrefs = false; + gtk_print_settings_set_printer(mPrintSettings, gtkPrinter.get()); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsPrintSettingsGTK::GetNumCopies(int32_t *aNumCopies) +{ + NS_ENSURE_ARG_POINTER(aNumCopies); + *aNumCopies = gtk_print_settings_get_n_copies(mPrintSettings); + return NS_OK; +} +NS_IMETHODIMP +nsPrintSettingsGTK::SetNumCopies(int32_t aNumCopies) +{ + gtk_print_settings_set_n_copies(mPrintSettings, aNumCopies); + return NS_OK; +} + +NS_IMETHODIMP +nsPrintSettingsGTK::GetScaling(double *aScaling) +{ + *aScaling = gtk_print_settings_get_scale(mPrintSettings) / 100.0; + return NS_OK; +} + +NS_IMETHODIMP +nsPrintSettingsGTK::SetScaling(double aScaling) +{ + gtk_print_settings_set_scale(mPrintSettings, aScaling * 100.0); + return NS_OK; +} + +NS_IMETHODIMP +nsPrintSettingsGTK::GetPaperName(char16_t * *aPaperName) +{ + NS_ENSURE_ARG_POINTER(aPaperName); + const gchar* name = + gtk_paper_size_get_name(gtk_page_setup_get_paper_size(mPageSetup)); + *aPaperName = ToNewUnicode(NS_ConvertUTF8toUTF16(name)); + return NS_OK; +} +NS_IMETHODIMP +nsPrintSettingsGTK::SetPaperName(const char16_t * aPaperName) +{ + NS_ConvertUTF16toUTF8 gtkPaperName(aPaperName); + + // Convert these Gecko names to GTK names + if (gtkPaperName.EqualsIgnoreCase("letter")) + gtkPaperName.AssignLiteral(GTK_PAPER_NAME_LETTER); + else if (gtkPaperName.EqualsIgnoreCase("legal")) + gtkPaperName.AssignLiteral(GTK_PAPER_NAME_LEGAL); + + GtkPaperSize* oldPaperSize = gtk_page_setup_get_paper_size(mPageSetup); + gdouble width = gtk_paper_size_get_width(oldPaperSize, GTK_UNIT_INCH); + gdouble height = gtk_paper_size_get_height(oldPaperSize, GTK_UNIT_INCH); + + // Try to get the display name from the name so our paper size fits in the Page Setup dialog. + GtkPaperSize* paperSize = gtk_paper_size_new(gtkPaperName.get()); + GtkPaperSize* customPaperSize = + gtk_paper_size_new_custom(gtkPaperName.get(), + gtk_paper_size_get_display_name(paperSize), + width, height, GTK_UNIT_INCH); + gtk_paper_size_free(paperSize); + + gtk_page_setup_set_paper_size(mPageSetup, customPaperSize); + gtk_paper_size_free(customPaperSize); + SaveNewPageSize(); + return NS_OK; +} + +GtkUnit +nsPrintSettingsGTK::GetGTKUnit(int16_t aGeckoUnit) +{ + if (aGeckoUnit == kPaperSizeMillimeters) + return GTK_UNIT_MM; + else + return GTK_UNIT_INCH; +} + +void +nsPrintSettingsGTK::SaveNewPageSize() +{ + gtk_print_settings_set_paper_size(mPrintSettings, + gtk_page_setup_get_paper_size(mPageSetup)); +} + +void +nsPrintSettingsGTK::InitUnwriteableMargin() +{ + mUnwriteableMargin.SizeTo( + NS_INCHES_TO_INT_TWIPS(gtk_page_setup_get_top_margin(mPageSetup, GTK_UNIT_INCH)), + NS_INCHES_TO_INT_TWIPS(gtk_page_setup_get_right_margin(mPageSetup, GTK_UNIT_INCH)), + NS_INCHES_TO_INT_TWIPS(gtk_page_setup_get_bottom_margin(mPageSetup, GTK_UNIT_INCH)), + NS_INCHES_TO_INT_TWIPS(gtk_page_setup_get_left_margin(mPageSetup, GTK_UNIT_INCH)) + ); +} + +/** + * NOTE: Need a custom set of SetUnwriteableMargin functions, because + * whenever we change mUnwriteableMargin, we must pass the change + * down to our GTKPageSetup object. (This is needed in order for us + * to give the correct default values in nsPrintDialogGTK.) + * + * It's important that the following functions pass + * mUnwriteableMargin values rather than aUnwriteableMargin values + * to gtk_page_setup_set_[blank]_margin, because the two may not be + * the same. (Specifically, negative values of aUnwriteableMargin + * are ignored by the nsPrintSettings::SetUnwriteableMargin functions.) + */ +NS_IMETHODIMP +nsPrintSettingsGTK::SetUnwriteableMarginInTwips(nsIntMargin& aUnwriteableMargin) +{ + nsPrintSettings::SetUnwriteableMarginInTwips(aUnwriteableMargin); + gtk_page_setup_set_top_margin(mPageSetup, + NS_TWIPS_TO_INCHES(mUnwriteableMargin.top), GTK_UNIT_INCH); + gtk_page_setup_set_left_margin(mPageSetup, + NS_TWIPS_TO_INCHES(mUnwriteableMargin.left), GTK_UNIT_INCH); + gtk_page_setup_set_bottom_margin(mPageSetup, + NS_TWIPS_TO_INCHES(mUnwriteableMargin.bottom), GTK_UNIT_INCH); + gtk_page_setup_set_right_margin(mPageSetup, + NS_TWIPS_TO_INCHES(mUnwriteableMargin.right), GTK_UNIT_INCH); + return NS_OK; +} + +NS_IMETHODIMP +nsPrintSettingsGTK::SetUnwriteableMarginTop(double aUnwriteableMarginTop) +{ + nsPrintSettings::SetUnwriteableMarginTop(aUnwriteableMarginTop); + gtk_page_setup_set_top_margin(mPageSetup, + NS_TWIPS_TO_INCHES(mUnwriteableMargin.top), GTK_UNIT_INCH); + return NS_OK; +} + +NS_IMETHODIMP +nsPrintSettingsGTK::SetUnwriteableMarginLeft(double aUnwriteableMarginLeft) +{ + nsPrintSettings::SetUnwriteableMarginLeft(aUnwriteableMarginLeft); + gtk_page_setup_set_left_margin(mPageSetup, + NS_TWIPS_TO_INCHES(mUnwriteableMargin.left), GTK_UNIT_INCH); + return NS_OK; +} + +NS_IMETHODIMP +nsPrintSettingsGTK::SetUnwriteableMarginBottom(double aUnwriteableMarginBottom) +{ + nsPrintSettings::SetUnwriteableMarginBottom(aUnwriteableMarginBottom); + gtk_page_setup_set_bottom_margin(mPageSetup, + NS_TWIPS_TO_INCHES(mUnwriteableMargin.bottom), GTK_UNIT_INCH); + return NS_OK; +} + +NS_IMETHODIMP +nsPrintSettingsGTK::SetUnwriteableMarginRight(double aUnwriteableMarginRight) +{ + nsPrintSettings::SetUnwriteableMarginRight(aUnwriteableMarginRight); + gtk_page_setup_set_right_margin(mPageSetup, + NS_TWIPS_TO_INCHES(mUnwriteableMargin.right), GTK_UNIT_INCH); + return NS_OK; +} + +NS_IMETHODIMP +nsPrintSettingsGTK::GetPaperWidth(double *aPaperWidth) +{ + NS_ENSURE_ARG_POINTER(aPaperWidth); + GtkPaperSize* paperSize = gtk_page_setup_get_paper_size(mPageSetup); + *aPaperWidth = + gtk_paper_size_get_width(paperSize, GetGTKUnit(mPaperSizeUnit)); + return NS_OK; +} +NS_IMETHODIMP +nsPrintSettingsGTK::SetPaperWidth(double aPaperWidth) +{ + GtkPaperSize* paperSize = gtk_page_setup_get_paper_size(mPageSetup); + gtk_paper_size_set_size(paperSize, + aPaperWidth, + gtk_paper_size_get_height(paperSize, GetGTKUnit(mPaperSizeUnit)), + GetGTKUnit(mPaperSizeUnit)); + SaveNewPageSize(); + return NS_OK; +} + +NS_IMETHODIMP +nsPrintSettingsGTK::GetPaperHeight(double *aPaperHeight) +{ + NS_ENSURE_ARG_POINTER(aPaperHeight); + GtkPaperSize* paperSize = gtk_page_setup_get_paper_size(mPageSetup); + *aPaperHeight = + gtk_paper_size_get_height(paperSize, GetGTKUnit(mPaperSizeUnit)); + return NS_OK; +} +NS_IMETHODIMP +nsPrintSettingsGTK::SetPaperHeight(double aPaperHeight) +{ + GtkPaperSize* paperSize = gtk_page_setup_get_paper_size(mPageSetup); + gtk_paper_size_set_size(paperSize, + gtk_paper_size_get_width(paperSize, GetGTKUnit(mPaperSizeUnit)), + aPaperHeight, + GetGTKUnit(mPaperSizeUnit)); + SaveNewPageSize(); + return NS_OK; +} + +NS_IMETHODIMP +nsPrintSettingsGTK::SetPaperSizeUnit(int16_t aPaperSizeUnit) +{ + // Convert units internally. e.g. they might have set the values while we're still in mm but + // they change to inch just afterwards, expecting that their sizes are in inches. + GtkPaperSize* paperSize = gtk_page_setup_get_paper_size(mPageSetup); + gtk_paper_size_set_size(paperSize, + gtk_paper_size_get_width(paperSize, GetGTKUnit(mPaperSizeUnit)), + gtk_paper_size_get_height(paperSize, GetGTKUnit(mPaperSizeUnit)), + GetGTKUnit(aPaperSizeUnit)); + SaveNewPageSize(); + + mPaperSizeUnit = aPaperSizeUnit; + return NS_OK; +} + +NS_IMETHODIMP +nsPrintSettingsGTK::GetEffectivePageSize(double *aWidth, double *aHeight) +{ + GtkPaperSize* paperSize = gtk_page_setup_get_paper_size(mPageSetup); + *aWidth = NS_INCHES_TO_INT_TWIPS(gtk_paper_size_get_width(paperSize, GTK_UNIT_INCH)); + *aHeight = NS_INCHES_TO_INT_TWIPS(gtk_paper_size_get_height(paperSize, GTK_UNIT_INCH)); + + GtkPageOrientation gtkOrient = gtk_page_setup_get_orientation(mPageSetup); + + if (gtkOrient == GTK_PAGE_ORIENTATION_LANDSCAPE || + gtkOrient == GTK_PAGE_ORIENTATION_REVERSE_LANDSCAPE) { + double temp = *aWidth; + *aWidth = *aHeight; + *aHeight = temp; + } + return NS_OK; +} + +NS_IMETHODIMP +nsPrintSettingsGTK::SetupSilentPrinting() +{ + // We have to get a printer here, rather than when the print settings are constructed. + // This is because when we request sync, GTK makes us wait in the *event loop* while waiting + // for the enumeration to finish. We must do this when event loop runs are expected. + gtk_enumerate_printers(printer_enumerator, this, nullptr, TRUE); + + // XXX If no default printer set, get the first one. + if (!GTK_IS_PRINTER(mGTKPrinter)) + gtk_enumerate_printers(ref_printer, this, nullptr, TRUE); + + return NS_OK; +} + +NS_IMETHODIMP +nsPrintSettingsGTK::GetPageRanges(nsTArray<int32_t> &aPages) +{ + gint ctRanges; + GtkPageRange* lstRanges = gtk_print_settings_get_page_ranges(mPrintSettings, &ctRanges); + + aPages.Clear(); + + if (ctRanges > 1) { + for (gint i = 0; i < ctRanges; i++) { + aPages.AppendElement(lstRanges[i].start+1); + aPages.AppendElement(lstRanges[i].end+1); + } + } + + g_free(lstRanges); + return NS_OK; +} + +NS_IMETHODIMP +nsPrintSettingsGTK::GetResolution(int32_t *aResolution) +{ + if (!gtk_print_settings_has_key(mPrintSettings, GTK_PRINT_SETTINGS_RESOLUTION)) + return NS_ERROR_FAILURE; + *aResolution = gtk_print_settings_get_resolution(mPrintSettings); + return NS_OK; +} + +NS_IMETHODIMP +nsPrintSettingsGTK::SetResolution(int32_t aResolution) +{ + gtk_print_settings_set_resolution(mPrintSettings, aResolution); + return NS_OK; +} + +NS_IMETHODIMP +nsPrintSettingsGTK::GetDuplex(int32_t *aDuplex) +{ + if (!gtk_print_settings_has_key(mPrintSettings, GTK_PRINT_SETTINGS_DUPLEX)) { + *aDuplex = GTK_PRINT_DUPLEX_SIMPLEX; + } else { + *aDuplex = gtk_print_settings_get_duplex(mPrintSettings); + } + return NS_OK; +} + +NS_IMETHODIMP +nsPrintSettingsGTK::SetDuplex(int32_t aDuplex) +{ + MOZ_ASSERT(aDuplex >= GTK_PRINT_DUPLEX_SIMPLEX && + aDuplex <= GTK_PRINT_DUPLEX_VERTICAL, + "value is out of bounds for GtkPrintDuplex enum"); + gtk_print_settings_set_duplex(mPrintSettings, static_cast<GtkPrintDuplex>(aDuplex)); + return NS_OK; +} + diff --git a/widget/gtk/nsPrintSettingsGTK.h b/widget/gtk/nsPrintSettingsGTK.h new file mode 100644 index 000000000..21f389449 --- /dev/null +++ b/widget/gtk/nsPrintSettingsGTK.h @@ -0,0 +1,151 @@ +/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * 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/. */ + +#ifndef nsPrintSettingsGTK_h_ +#define nsPrintSettingsGTK_h_ + +#include "nsPrintSettingsImpl.h" + +extern "C" { +#include <gtk/gtk.h> +#include <gtk/gtkunixprint.h> +} + +#define NS_PRINTSETTINGSGTK_IID \ +{ 0x758df520, 0xc7c3, 0x11dc, { 0x95, 0xff, 0x08, 0x00, 0x20, 0x0c, 0x9a, 0x66 } } + + +//***************************************************************************** +//*** nsPrintSettingsGTK +//***************************************************************************** + +class nsPrintSettingsGTK : public nsPrintSettings +{ +public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECLARE_STATIC_IID_ACCESSOR(NS_PRINTSETTINGSGTK_IID) + + nsPrintSettingsGTK(); + + // We're overriding these methods because we want to read/write with GTK objects, + // not local variables. This allows a simpler settings implementation between + // Gecko and GTK. + + GtkPageSetup* GetGtkPageSetup() { return mPageSetup; }; + void SetGtkPageSetup(GtkPageSetup *aPageSetup); + + GtkPrintSettings* GetGtkPrintSettings() { return mPrintSettings; }; + void SetGtkPrintSettings(GtkPrintSettings *aPrintSettings); + + GtkPrinter* GetGtkPrinter() { return mGTKPrinter; }; + void SetGtkPrinter(GtkPrinter *aPrinter); + + bool GetForcePrintSelectionOnly() { return mPrintSelectionOnly; }; + void SetForcePrintSelectionOnly(bool aPrintSelectionOnly) { mPrintSelectionOnly = aPrintSelectionOnly; }; + + // If not printing the selection, this is stored in the GtkPrintSettings. Printing the + // selection is stored as a protected boolean (mPrintSelectionOnly). + NS_IMETHOD GetPrintRange(int16_t *aPrintRange) override; + NS_IMETHOD SetPrintRange(int16_t aPrintRange) override; + + // The page range is stored as as single range in the GtkPrintSettings object. + NS_IMETHOD GetStartPageRange(int32_t *aStartPageRange) override; + NS_IMETHOD SetStartPageRange(int32_t aStartPageRange) override; + NS_IMETHOD GetEndPageRange(int32_t *aEndPageRange) override; + NS_IMETHOD SetEndPageRange(int32_t aEndPageRange) override; + + // Reversed, color, orientation and file name are all stored in the GtkPrintSettings. + // Orientation is also stored in the GtkPageSetup and its setting takes priority when getting the orientation. + NS_IMETHOD GetPrintReversed(bool *aPrintReversed) override; + NS_IMETHOD SetPrintReversed(bool aPrintReversed) override; + + NS_IMETHOD GetPrintInColor(bool *aPrintInColor) override; + NS_IMETHOD SetPrintInColor(bool aPrintInColor) override; + + NS_IMETHOD GetOrientation(int32_t *aOrientation) override; + NS_IMETHOD SetOrientation(int32_t aOrientation) override; + + NS_IMETHOD GetToFileName(char16_t * *aToFileName) override; + NS_IMETHOD SetToFileName(const char16_t * aToFileName) override; + + // Gets/Sets the printer name in the GtkPrintSettings. If no printer name is specified there, + // you will get back the name of the current internal GtkPrinter. + NS_IMETHOD GetPrinterName(char16_t * *aPrinter) override; + NS_IMETHOD SetPrinterName(const char16_t * aPrinter) override; + + // Number of copies is stored/gotten from the GtkPrintSettings. + NS_IMETHOD GetNumCopies(int32_t *aNumCopies) override; + NS_IMETHOD SetNumCopies(int32_t aNumCopies) override; + + NS_IMETHOD GetScaling(double *aScaling) override; + NS_IMETHOD SetScaling(double aScaling) override; + + // A name recognised by GTK is strongly advised here, as this is used to create a GtkPaperSize. + NS_IMETHOD GetPaperName(char16_t * *aPaperName) override; + NS_IMETHOD SetPaperName(const char16_t * aPaperName) override; + + NS_IMETHOD SetUnwriteableMarginInTwips(nsIntMargin& aUnwriteableMargin) override; + NS_IMETHOD SetUnwriteableMarginTop(double aUnwriteableMarginTop) override; + NS_IMETHOD SetUnwriteableMarginLeft(double aUnwriteableMarginLeft) override; + NS_IMETHOD SetUnwriteableMarginBottom(double aUnwriteableMarginBottom) override; + NS_IMETHOD SetUnwriteableMarginRight(double aUnwriteableMarginRight) override; + + NS_IMETHOD GetPaperWidth(double *aPaperWidth) override; + NS_IMETHOD SetPaperWidth(double aPaperWidth) override; + + NS_IMETHOD GetPaperHeight(double *aPaperHeight) override; + NS_IMETHOD SetPaperHeight(double aPaperHeight) override; + + NS_IMETHOD SetPaperSizeUnit(int16_t aPaperSizeUnit) override; + + NS_IMETHOD GetEffectivePageSize(double *aWidth, double *aHeight) override; + + NS_IMETHOD SetupSilentPrinting() override; + + NS_IMETHOD GetPageRanges(nsTArray<int32_t> &aPages) override; + + NS_IMETHOD GetResolution(int32_t *aResolution) override; + NS_IMETHOD SetResolution(int32_t aResolution) override; + + NS_IMETHOD GetDuplex(int32_t *aDuplex) override; + NS_IMETHOD SetDuplex(int32_t aDuplex) override; + + NS_IMETHOD GetOutputFormat(int16_t *aOutputFormat) override; + +protected: + virtual ~nsPrintSettingsGTK(); + + nsPrintSettingsGTK(const nsPrintSettingsGTK& src); + nsPrintSettingsGTK& operator=(const nsPrintSettingsGTK& rhs); + + virtual nsresult _Clone(nsIPrintSettings **_retval) override; + virtual nsresult _Assign(nsIPrintSettings *aPS) override; + + GtkUnit GetGTKUnit(int16_t aGeckoUnit); + void SaveNewPageSize(); + + /** + * Re-initialize mUnwriteableMargin with values from mPageSetup. + * Should be called whenever mPageSetup is initialized or overwritten. + */ + void InitUnwriteableMargin(); + + /** + * On construction: + * - mPrintSettings and mPageSetup are just new objects with defaults determined by GTK. + * - mGTKPrinter is nullptr!!! Remember to be careful when accessing this property. + */ + GtkPageSetup* mPageSetup; + GtkPrintSettings* mPrintSettings; + GtkPrinter* mGTKPrinter; + + bool mPrintSelectionOnly; +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(nsPrintSettingsGTK, NS_PRINTSETTINGSGTK_IID) + + +#endif // nsPrintSettingsGTK_h_ diff --git a/widget/gtk/nsScreenGtk.cpp b/widget/gtk/nsScreenGtk.cpp new file mode 100644 index 000000000..61e6605b7 --- /dev/null +++ b/widget/gtk/nsScreenGtk.cpp @@ -0,0 +1,207 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 "nsScreenGtk.h" + +#include "nsIWidget.h" + +#include <gdk/gdk.h> +#ifdef MOZ_X11 +#include <gdk/gdkx.h> +#include <X11/Xatom.h> +#endif +#include <gtk/gtk.h> +#include <dlfcn.h> +#include "gfxPlatformGtk.h" + +static uint32_t sScreenId = 0; + + +nsScreenGtk :: nsScreenGtk ( ) + : mScreenNum(0), + mRect(0, 0, 0, 0), + mAvailRect(0, 0, 0, 0), + mId(++sScreenId) +{ +} + + +nsScreenGtk :: ~nsScreenGtk() +{ +} + + +NS_IMETHODIMP +nsScreenGtk :: GetId(uint32_t *aId) +{ + *aId = mId; + return NS_OK; +} // GetId + + +NS_IMETHODIMP +nsScreenGtk :: GetRect(int32_t *outLeft, int32_t *outTop, int32_t *outWidth, int32_t *outHeight) +{ + *outLeft = mRect.x; + *outTop = mRect.y; + *outWidth = mRect.width; + *outHeight = mRect.height; + + return NS_OK; + +} // GetRect + + +NS_IMETHODIMP +nsScreenGtk :: GetAvailRect(int32_t *outLeft, int32_t *outTop, int32_t *outWidth, int32_t *outHeight) +{ + *outLeft = mAvailRect.x; + *outTop = mAvailRect.y; + *outWidth = mAvailRect.width; + *outHeight = mAvailRect.height; + + return NS_OK; + +} // GetAvailRect + +gint +nsScreenGtk :: GetGtkMonitorScaleFactor() +{ +#if (MOZ_WIDGET_GTK >= 3) + // Since GDK 3.10 + static auto sGdkScreenGetMonitorScaleFactorPtr = (gint (*)(GdkScreen*, gint)) + dlsym(RTLD_DEFAULT, "gdk_screen_get_monitor_scale_factor"); + if (sGdkScreenGetMonitorScaleFactorPtr) { + // FIXME: In the future, we'll want to fix this for GTK on Wayland which + // supports a variable scale factor per display. + GdkScreen *screen = gdk_screen_get_default(); + return sGdkScreenGetMonitorScaleFactorPtr(screen, 0); + } +#endif + return 1; +} + +double +nsScreenGtk :: GetDPIScale() +{ + double dpiScale = nsIWidget::DefaultScaleOverride(); + if (dpiScale <= 0.0) { + dpiScale = GetGtkMonitorScaleFactor() * gfxPlatformGtk::GetDPIScale(); + } + return dpiScale; +} + +NS_IMETHODIMP +nsScreenGtk :: GetPixelDepth(int32_t *aPixelDepth) +{ + GdkVisual * visual = gdk_screen_get_system_visual(gdk_screen_get_default()); + *aPixelDepth = gdk_visual_get_depth(visual); + + return NS_OK; + +} // GetPixelDepth + +NS_IMETHODIMP +nsScreenGtk :: GetColorDepth(int32_t *aColorDepth) +{ + return GetPixelDepth ( aColorDepth ); + +} // GetColorDepth + +NS_IMETHODIMP +nsScreenGtk::GetDefaultCSSScaleFactor(double* aScaleFactor) +{ + *aScaleFactor = GetDPIScale(); + return NS_OK; +} + +void +nsScreenGtk :: Init (GdkWindow *aRootWindow) +{ + gint scale = nsScreenGtk::GetGtkMonitorScaleFactor(); + gint width = gdk_screen_width()*scale; + gint height = gdk_screen_height()*scale; + + // We listen for configure events on the root window to pick up + // changes to this rect. We could listen for "size_changed" signals + // on the default screen to do this, except that doesn't work with + // versions of GDK predating the GdkScreen object. See bug 256646. + mAvailRect = mRect = nsIntRect(0, 0, width, height); + +#ifdef MOZ_X11 + // We need to account for the taskbar, etc in the available rect. + // See http://freedesktop.org/Standards/wm-spec/index.html#id2767771 + + // XXX do we care about _NET_WM_STRUT_PARTIAL? That will + // add much more complexity to the code here (our screen + // could have a non-rectangular shape), but should + // lead to greater accuracy. + + long *workareas; + GdkAtom type_returned; + int format_returned; + int length_returned; + + GdkAtom cardinal_atom = gdk_x11_xatom_to_atom(XA_CARDINAL); + + gdk_error_trap_push(); + + // gdk_property_get uses (length + 3) / 4, hence G_MAXLONG - 3 here. + if (!gdk_property_get(aRootWindow, + gdk_atom_intern ("_NET_WORKAREA", FALSE), + cardinal_atom, + 0, G_MAXLONG - 3, FALSE, + &type_returned, + &format_returned, + &length_returned, + (guchar **) &workareas)) { + // This window manager doesn't support the freedesktop standard. + // Nothing we can do about it, so assume full screen size. + return; + } + + // Flush the X queue to catch errors now. + gdk_flush(); + + if (!gdk_error_trap_pop() && + type_returned == cardinal_atom && + length_returned && (length_returned % 4) == 0 && + format_returned == 32) { + int num_items = length_returned / sizeof(long); + + for (int i = 0; i < num_items; i += 4) { + nsIntRect workarea(workareas[i], workareas[i + 1], + workareas[i + 2], workareas[i + 3]); + if (!mRect.Contains(workarea)) { + // Note that we hit this when processing screen size changes, + // since we'll get the configure event before the toolbars have + // been moved. We'll end up cleaning this up when we get the + // change notification to the _NET_WORKAREA property. However, + // we still want to listen to both, so we'll handle changes + // properly for desktop environments that don't set the + // _NET_WORKAREA property. + NS_WARNING("Invalid bounds"); + continue; + } + + mAvailRect.IntersectRect(mAvailRect, workarea); + } + } + g_free (workareas); +#endif +} + +#ifdef MOZ_X11 +void +nsScreenGtk :: Init (XineramaScreenInfo *aScreenInfo) +{ + nsIntRect xineRect(aScreenInfo->x_org, aScreenInfo->y_org, + aScreenInfo->width, aScreenInfo->height); + + mScreenNum = aScreenInfo->screen_number; + + mAvailRect = mRect = xineRect; +} +#endif diff --git a/widget/gtk/nsScreenGtk.h b/widget/gtk/nsScreenGtk.h new file mode 100644 index 000000000..d58ea4b1e --- /dev/null +++ b/widget/gtk/nsScreenGtk.h @@ -0,0 +1,57 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#ifndef nsScreenGtk_h___ +#define nsScreenGtk_h___ + +#include "nsBaseScreen.h" +#include "nsRect.h" +#include "gdk/gdk.h" +#ifdef MOZ_X11 +#include <X11/Xlib.h> + +// from Xinerama.h +typedef struct { + int screen_number; + short x_org; + short y_org; + short width; + short height; +} XineramaScreenInfo; +#endif /* MOZ_X11 */ + +//------------------------------------------------------------------------ + +class nsScreenGtk : public nsBaseScreen +{ +public: + nsScreenGtk(); + ~nsScreenGtk(); + + NS_IMETHOD GetId(uint32_t* aId) override; + NS_IMETHOD GetRect(int32_t* aLeft, int32_t* aTop, + int32_t* aWidth, int32_t* aHeight) override; + NS_IMETHOD GetAvailRect(int32_t* aLeft, int32_t* aTop, + int32_t* aWidth, int32_t* aHeight) override; + NS_IMETHOD GetPixelDepth(int32_t* aPixelDepth) override; + NS_IMETHOD GetColorDepth(int32_t* aColorDepth) override; + NS_IMETHOD GetDefaultCSSScaleFactor(double* aScaleFactor) override; + + void Init(GdkWindow *aRootWindow); +#ifdef MOZ_X11 + void Init(XineramaScreenInfo *aScreenInfo); +#endif /* MOZ_X11 */ + + static gint GetGtkMonitorScaleFactor(); + static double GetDPIScale(); + +private: + uint32_t mScreenNum; + nsIntRect mRect; + nsIntRect mAvailRect; + uint32_t mId; +}; + +#endif // nsScreenGtk_h___ diff --git a/widget/gtk/nsScreenManagerGtk.cpp b/widget/gtk/nsScreenManagerGtk.cpp new file mode 100644 index 000000000..98166cc92 --- /dev/null +++ b/widget/gtk/nsScreenManagerGtk.cpp @@ -0,0 +1,366 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 "nsScreenManagerGtk.h" + +#include "mozilla/RefPtr.h" +#include "nsScreenGtk.h" +#include "nsIComponentManager.h" +#include "nsRect.h" +#include "nsGtkUtils.h" + +#define SCREEN_MANAGER_LIBRARY_LOAD_FAILED ((PRLibrary*)1) + +#ifdef MOZ_X11 +#include <gdk/gdkx.h> +// prototypes from Xinerama.h +typedef Bool (*_XnrmIsActive_fn)(Display *dpy); +typedef XineramaScreenInfo* (*_XnrmQueryScreens_fn)(Display *dpy, int *number); +#endif + +#include <gtk/gtk.h> + +void +monitors_changed(GdkScreen* aScreen, gpointer aClosure) +{ + nsScreenManagerGtk *manager = static_cast<nsScreenManagerGtk*>(aClosure); + manager->Init(); +} + +static GdkFilterReturn +root_window_event_filter(GdkXEvent *aGdkXEvent, GdkEvent *aGdkEvent, + gpointer aClosure) +{ + nsScreenManagerGtk *manager = static_cast<nsScreenManagerGtk*>(aClosure); +#ifdef MOZ_X11 + XEvent *xevent = static_cast<XEvent*>(aGdkXEvent); + + // See comments in nsScreenGtk::Init below. + switch (xevent->type) { + case PropertyNotify: + { + XPropertyEvent *propertyEvent = &xevent->xproperty; + if (propertyEvent->atom == manager->NetWorkareaAtom()) { + manager->Init(); + } + } + break; + default: + break; + } +#endif + + return GDK_FILTER_CONTINUE; +} + +nsScreenManagerGtk :: nsScreenManagerGtk ( ) + : mXineramalib(nullptr) + , mRootWindow(nullptr) + , mNetWorkareaAtom(0) +{ + // nothing else to do. I guess we could cache a bunch of information + // here, but we want to ask the device at runtime in case anything + // has changed. +} + + +nsScreenManagerGtk :: ~nsScreenManagerGtk() +{ + g_signal_handlers_disconnect_by_func(gdk_screen_get_default(), + FuncToGpointer(monitors_changed), + this); + + if (mRootWindow) { + gdk_window_remove_filter(mRootWindow, root_window_event_filter, this); + g_object_unref(mRootWindow); + mRootWindow = nullptr; + } + + /* XineramaIsActive() registers a callback function close_display() + * in X, which is to be called in XCloseDisplay(). This is the case + * if Xinerama is active, even if only with one screen. + * + * We can't unload libXinerama.so.1 here because this will make + * the address of close_display() registered in X to be invalid and + * it will crash when XCloseDisplay() is called later. */ +} + + +// addref, release, QI +NS_IMPL_ISUPPORTS(nsScreenManagerGtk, nsIScreenManager) + + +// this function will make sure that everything has been initialized. +nsresult +nsScreenManagerGtk :: EnsureInit() +{ + if (mCachedScreenArray.Count() > 0) + return NS_OK; + + mRootWindow = gdk_get_default_root_window(); + if (!mRootWindow) { + // Sometimes we don't initial X (e.g., xpcshell) + return NS_OK; + } + + g_object_ref(mRootWindow); + + // GDK_PROPERTY_CHANGE_MASK ==> PropertyChangeMask, for PropertyNotify + gdk_window_set_events(mRootWindow, + GdkEventMask(gdk_window_get_events(mRootWindow) | + GDK_PROPERTY_CHANGE_MASK)); + + g_signal_connect(gdk_screen_get_default(), "monitors-changed", + G_CALLBACK(monitors_changed), this); +#ifdef MOZ_X11 + gdk_window_add_filter(mRootWindow, root_window_event_filter, this); + if (GDK_IS_X11_DISPLAY(gdk_display_get_default())) + mNetWorkareaAtom = + XInternAtom(GDK_WINDOW_XDISPLAY(mRootWindow), "_NET_WORKAREA", False); +#endif + + return Init(); +} + +nsresult +nsScreenManagerGtk :: Init() +{ +#ifdef MOZ_X11 + XineramaScreenInfo *screenInfo = nullptr; + int numScreens; + + bool useXinerama = GDK_IS_X11_DISPLAY(gdk_display_get_default()); + + if (useXinerama && !mXineramalib) { + mXineramalib = PR_LoadLibrary("libXinerama.so.1"); + if (!mXineramalib) { + mXineramalib = SCREEN_MANAGER_LIBRARY_LOAD_FAILED; + } + } + if (mXineramalib && mXineramalib != SCREEN_MANAGER_LIBRARY_LOAD_FAILED) { + _XnrmIsActive_fn _XnrmIsActive = (_XnrmIsActive_fn) + PR_FindFunctionSymbol(mXineramalib, "XineramaIsActive"); + + _XnrmQueryScreens_fn _XnrmQueryScreens = (_XnrmQueryScreens_fn) + PR_FindFunctionSymbol(mXineramalib, "XineramaQueryScreens"); + + // get the number of screens via xinerama + Display *display = GDK_DISPLAY_XDISPLAY(gdk_display_get_default()); + if (_XnrmIsActive && _XnrmQueryScreens && _XnrmIsActive(display)) { + screenInfo = _XnrmQueryScreens(display, &numScreens); + } + } + + // screenInfo == nullptr if either Xinerama couldn't be loaded or + // isn't running on the current display + if (!screenInfo || numScreens == 1) { + numScreens = 1; +#endif + RefPtr<nsScreenGtk> screen; + + if (mCachedScreenArray.Count() > 0) { + screen = static_cast<nsScreenGtk*>(mCachedScreenArray[0]); + } else { + screen = new nsScreenGtk(); + if (!screen || !mCachedScreenArray.AppendObject(screen)) { + return NS_ERROR_OUT_OF_MEMORY; + } + } + + screen->Init(mRootWindow); +#ifdef MOZ_X11 + } + // If Xinerama is enabled and there's more than one screen, fill + // in the info for all of the screens. If that's not the case + // then nsScreenGTK() defaults to the screen width + height + else { +#ifdef DEBUG + printf("Xinerama superpowers activated for %d screens!\n", numScreens); +#endif + for (int i = 0; i < numScreens; ++i) { + RefPtr<nsScreenGtk> screen; + if (mCachedScreenArray.Count() > i) { + screen = static_cast<nsScreenGtk*>(mCachedScreenArray[i]); + } else { + screen = new nsScreenGtk(); + if (!screen || !mCachedScreenArray.AppendObject(screen)) { + return NS_ERROR_OUT_OF_MEMORY; + } + } + + // initialize this screen object + screen->Init(&screenInfo[i]); + } + } + // Remove any screens that are no longer present. + while (mCachedScreenArray.Count() > numScreens) { + mCachedScreenArray.RemoveObjectAt(mCachedScreenArray.Count() - 1); + } + + if (screenInfo) { + XFree(screenInfo); + } +#endif + + return NS_OK; +} + +NS_IMETHODIMP +nsScreenManagerGtk :: ScreenForId ( uint32_t aId, nsIScreen **outScreen ) +{ + *outScreen = nullptr; + + nsresult rv; + rv = EnsureInit(); + if (NS_FAILED(rv)) { + NS_ERROR("nsScreenManagerGtk::EnsureInit() failed from ScreenForId"); + return rv; + } + + for (int32_t i = 0, i_end = mCachedScreenArray.Count(); i < i_end; ++i) { + uint32_t id; + rv = mCachedScreenArray[i]->GetId(&id); + if (NS_SUCCEEDED(rv) && id == aId) { + NS_IF_ADDREF(*outScreen = mCachedScreenArray[i]); + return NS_OK; + } + } + + return NS_ERROR_FAILURE; +} + + +// +// ScreenForRect +// +// Returns the screen that contains the rectangle. If the rect overlaps +// multiple screens, it picks the screen with the greatest area of intersection. +// +// The coordinates are in desktop pixels. +// +NS_IMETHODIMP +nsScreenManagerGtk::ScreenForRect(int32_t aX, int32_t aY, + int32_t aWidth, int32_t aHeight, + nsIScreen **aOutScreen) +{ + nsresult rv; + rv = EnsureInit(); + if (NS_FAILED(rv)) { + NS_ERROR("nsScreenManagerGtk::EnsureInit() failed from ScreenForRect"); + return rv; + } + + // which screen ( index from zero ) should we return? + uint32_t which = 0; + // Optimize for the common case. If the number of screens is only + // one then this will fall through with which == 0 and will get the + // primary screen. + if (mCachedScreenArray.Count() > 1) { + // walk the list of screens and find the one that has the most + // surface area. + uint32_t area = 0; + nsIntRect windowRect(aX, aY, aWidth, aHeight); + for (int32_t i = 0, i_end = mCachedScreenArray.Count(); i < i_end; ++i) { + int32_t x, y, width, height; + x = y = width = height = 0; + mCachedScreenArray[i]->GetRect(&x, &y, &width, &height); + // calculate the surface area + nsIntRect screenRect(x, y, width, height); + screenRect.IntersectRect(screenRect, windowRect); + uint32_t tempArea = screenRect.width * screenRect.height; + if (tempArea >= area) { + which = i; + area = tempArea; + } + } + } + *aOutScreen = mCachedScreenArray.SafeObjectAt(which); + NS_IF_ADDREF(*aOutScreen); + return NS_OK; + +} // ScreenForRect + + +// +// GetPrimaryScreen +// +// The screen with the menubar/taskbar. This shouldn't be needed very +// often. +// +NS_IMETHODIMP +nsScreenManagerGtk :: GetPrimaryScreen(nsIScreen * *aPrimaryScreen) +{ + nsresult rv; + rv = EnsureInit(); + if (NS_FAILED(rv)) { + NS_ERROR("nsScreenManagerGtk::EnsureInit() failed from GetPrimaryScreen"); + return rv; + } + *aPrimaryScreen = mCachedScreenArray.SafeObjectAt(0); + NS_IF_ADDREF(*aPrimaryScreen); + return NS_OK; + +} // GetPrimaryScreen + + +// +// GetNumberOfScreens +// +// Returns how many physical screens are available. +// +NS_IMETHODIMP +nsScreenManagerGtk :: GetNumberOfScreens(uint32_t *aNumberOfScreens) +{ + nsresult rv; + rv = EnsureInit(); + if (NS_FAILED(rv)) { + NS_ERROR("nsScreenManagerGtk::EnsureInit() failed from GetNumberOfScreens"); + return rv; + } + *aNumberOfScreens = mCachedScreenArray.Count(); + return NS_OK; + +} // GetNumberOfScreens + +NS_IMETHODIMP +nsScreenManagerGtk::GetSystemDefaultScale(float *aDefaultScale) +{ + *aDefaultScale = nsScreenGtk::GetDPIScale(); + return NS_OK; +} + +NS_IMETHODIMP +nsScreenManagerGtk :: ScreenForNativeWidget (void *aWidget, nsIScreen **outScreen) +{ + nsresult rv; + rv = EnsureInit(); + if (NS_FAILED(rv)) { + NS_ERROR("nsScreenManagerGtk::EnsureInit() failed from ScreenForNativeWidget"); + return rv; + } + + if (mCachedScreenArray.Count() > 1) { + // I don't know how to go from GtkWindow to nsIScreen, especially + // given xinerama and stuff, so let's just do this + gint x, y, width, height; +#if (MOZ_WIDGET_GTK == 2) + gint depth; +#endif + x = y = width = height = 0; + +#if (MOZ_WIDGET_GTK == 2) + gdk_window_get_geometry(GDK_WINDOW(aWidget), &x, &y, &width, &height, + &depth); +#else + gdk_window_get_geometry(GDK_WINDOW(aWidget), &x, &y, &width, &height); +#endif + gdk_window_get_origin(GDK_WINDOW(aWidget), &x, &y); + rv = ScreenForRect(x, y, width, height, outScreen); + } else { + rv = GetPrimaryScreen(outScreen); + } + + return rv; +} diff --git a/widget/gtk/nsScreenManagerGtk.h b/widget/gtk/nsScreenManagerGtk.h new file mode 100644 index 000000000..9afb3bf22 --- /dev/null +++ b/widget/gtk/nsScreenManagerGtk.h @@ -0,0 +1,52 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#ifndef nsScreenManagerGtk_h___ +#define nsScreenManagerGtk_h___ + +#include "nsIScreenManager.h" +#include "nsIScreen.h" +#include "nsCOMPtr.h" +#include "nsCOMArray.h" +#include "prlink.h" +#include "gdk/gdk.h" +#ifdef MOZ_X11 +#include <X11/Xlib.h> +#endif + +//------------------------------------------------------------------------ + +class nsScreenManagerGtk : public nsIScreenManager +{ +public: + nsScreenManagerGtk ( ); + + NS_DECL_ISUPPORTS + NS_DECL_NSISCREENMANAGER + +#ifdef MOZ_X11 + Atom NetWorkareaAtom() { return mNetWorkareaAtom; } +#endif + + // For internal use, or reinitialization from change notification. + nsresult Init(); + +private: + virtual ~nsScreenManagerGtk(); + + nsresult EnsureInit(); + + // Cached screen array. Its length is the number of screens we have. + nsCOMArray<nsIScreen> mCachedScreenArray; + + PRLibrary *mXineramalib; + + GdkWindow *mRootWindow; +#ifdef MOZ_X11 + Atom mNetWorkareaAtom; +#endif +}; + +#endif // nsScreenManagerGtk_h___ diff --git a/widget/gtk/nsSound.cpp b/widget/gtk/nsSound.cpp new file mode 100644 index 000000000..4e81fe43f --- /dev/null +++ b/widget/gtk/nsSound.cpp @@ -0,0 +1,444 @@ +/* -*- 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 <string.h> + +#include "nscore.h" +#include "plstr.h" +#include "prlink.h" + +#include "nsSound.h" + +#include "nsIURL.h" +#include "nsIFileURL.h" +#include "nsNetUtil.h" +#include "nsIChannel.h" +#include "nsCOMPtr.h" +#include "nsString.h" +#include "nsDirectoryService.h" +#include "nsDirectoryServiceDefs.h" +#include "mozilla/FileUtils.h" +#include "mozilla/Services.h" +#include "mozilla/Unused.h" +#include "nsIStringBundle.h" +#include "nsIXULAppInfo.h" +#include "nsContentUtils.h" + +#include <stdio.h> +#include <unistd.h> + +#include <gtk/gtk.h> +static PRLibrary *libcanberra = nullptr; + +/* used to play sounds with libcanberra. */ +typedef struct _ca_context ca_context; +typedef struct _ca_proplist ca_proplist; + +typedef void (*ca_finish_callback_t) (ca_context *c, + uint32_t id, + int error_code, + void *userdata); + +typedef int (*ca_context_create_fn) (ca_context **); +typedef int (*ca_context_destroy_fn) (ca_context *); +typedef int (*ca_context_play_fn) (ca_context *c, + uint32_t id, + ...); +typedef int (*ca_context_change_props_fn) (ca_context *c, + ...); +typedef int (*ca_proplist_create_fn) (ca_proplist **); +typedef int (*ca_proplist_destroy_fn) (ca_proplist *); +typedef int (*ca_proplist_sets_fn) (ca_proplist *c, + const char *key, + const char *value); +typedef int (*ca_context_play_full_fn) (ca_context *c, + uint32_t id, + ca_proplist *p, + ca_finish_callback_t cb, + void *userdata); + +static ca_context_create_fn ca_context_create; +static ca_context_destroy_fn ca_context_destroy; +static ca_context_play_fn ca_context_play; +static ca_context_change_props_fn ca_context_change_props; +static ca_proplist_create_fn ca_proplist_create; +static ca_proplist_destroy_fn ca_proplist_destroy; +static ca_proplist_sets_fn ca_proplist_sets; +static ca_context_play_full_fn ca_context_play_full; + +struct ScopedCanberraFile { + explicit ScopedCanberraFile(nsIFile *file): mFile(file) {}; + + ~ScopedCanberraFile() { + if (mFile) { + mFile->Remove(false); + } + } + + void forget() { + mozilla::Unused << mFile.forget(); + } + nsIFile* operator->() { return mFile; } + operator nsIFile*() { return mFile; } + + nsCOMPtr<nsIFile> mFile; +}; + +static ca_context* +ca_context_get_default() +{ + // This allows us to avoid race conditions with freeing the context by handing that + // responsibility to Glib, and still use one context at a time + static GStaticPrivate ctx_static_private = G_STATIC_PRIVATE_INIT; + + ca_context* ctx = (ca_context*) g_static_private_get(&ctx_static_private); + + if (ctx) { + return ctx; + } + + ca_context_create(&ctx); + if (!ctx) { + return nullptr; + } + + g_static_private_set(&ctx_static_private, ctx, (GDestroyNotify) ca_context_destroy); + + GtkSettings* settings = gtk_settings_get_default(); + if (g_object_class_find_property(G_OBJECT_GET_CLASS(settings), + "gtk-sound-theme-name")) { + gchar* sound_theme_name = nullptr; + g_object_get(settings, "gtk-sound-theme-name", &sound_theme_name, + nullptr); + + if (sound_theme_name) { + ca_context_change_props(ctx, "canberra.xdg-theme.name", + sound_theme_name, nullptr); + g_free(sound_theme_name); + } + } + + nsCOMPtr<nsIStringBundleService> bundleService = + mozilla::services::GetStringBundleService(); + if (bundleService) { + nsCOMPtr<nsIStringBundle> brandingBundle; + bundleService->CreateBundle("chrome://branding/locale/brand.properties", + getter_AddRefs(brandingBundle)); + if (brandingBundle) { + nsAutoString wbrand; + brandingBundle->GetStringFromName(u"brandShortName", + getter_Copies(wbrand)); + NS_ConvertUTF16toUTF8 brand(wbrand); + + ca_context_change_props(ctx, "application.name", brand.get(), + nullptr); + } + } + + nsCOMPtr<nsIXULAppInfo> appInfo = do_GetService("@mozilla.org/xre/app-info;1"); + if (appInfo) { + nsAutoCString version; + appInfo->GetVersion(version); + + ca_context_change_props(ctx, "application.version", version.get(), + nullptr); + } + + ca_context_change_props(ctx, "application.icon_name", MOZ_APP_NAME, + nullptr); + + return ctx; +} + +static void +ca_finish_cb(ca_context *c, + uint32_t id, + int error_code, + void *userdata) +{ + nsIFile *file = reinterpret_cast<nsIFile *>(userdata); + if (file) { + file->Remove(false); + NS_RELEASE(file); + } +} + +NS_IMPL_ISUPPORTS(nsSound, nsISound, nsIStreamLoaderObserver) + +//////////////////////////////////////////////////////////////////////// +nsSound::nsSound() +{ + mInited = false; +} + +nsSound::~nsSound() +{ +} + +NS_IMETHODIMP +nsSound::Init() +{ + // This function is designed so that no library is compulsory, and + // one library missing doesn't cause the other(s) to not be used. + if (mInited) + return NS_OK; + + mInited = true; + + if (!libcanberra) { + libcanberra = PR_LoadLibrary("libcanberra.so.0"); + if (libcanberra) { + ca_context_create = (ca_context_create_fn) PR_FindFunctionSymbol(libcanberra, "ca_context_create"); + if (!ca_context_create) { + PR_UnloadLibrary(libcanberra); + libcanberra = nullptr; + } else { + // at this point we know we have a good libcanberra library + ca_context_destroy = (ca_context_destroy_fn) PR_FindFunctionSymbol(libcanberra, "ca_context_destroy"); + ca_context_play = (ca_context_play_fn) PR_FindFunctionSymbol(libcanberra, "ca_context_play"); + ca_context_change_props = (ca_context_change_props_fn) PR_FindFunctionSymbol(libcanberra, "ca_context_change_props"); + ca_proplist_create = (ca_proplist_create_fn) PR_FindFunctionSymbol(libcanberra, "ca_proplist_create"); + ca_proplist_destroy = (ca_proplist_destroy_fn) PR_FindFunctionSymbol(libcanberra, "ca_proplist_destroy"); + ca_proplist_sets = (ca_proplist_sets_fn) PR_FindFunctionSymbol(libcanberra, "ca_proplist_sets"); + ca_context_play_full = (ca_context_play_full_fn) PR_FindFunctionSymbol(libcanberra, "ca_context_play_full"); + } + } + } + + return NS_OK; +} + +/* static */ void +nsSound::Shutdown() +{ + if (libcanberra) { + PR_UnloadLibrary(libcanberra); + libcanberra = nullptr; + } +} + +NS_IMETHODIMP nsSound::OnStreamComplete(nsIStreamLoader *aLoader, + nsISupports *context, + nsresult aStatus, + uint32_t dataLen, + const uint8_t *data) +{ + // print a load error on bad status, and return + if (NS_FAILED(aStatus)) { +#ifdef DEBUG + if (aLoader) { + nsCOMPtr<nsIRequest> request; + aLoader->GetRequest(getter_AddRefs(request)); + if (request) { + nsCOMPtr<nsIURI> uri; + nsCOMPtr<nsIChannel> channel = do_QueryInterface(request); + if (channel) { + channel->GetURI(getter_AddRefs(uri)); + if (uri) { + printf("Failed to load %s\n", + uri->GetSpecOrDefault().get()); + } + } + } + } +#endif + return aStatus; + } + + nsCOMPtr<nsIFile> tmpFile; + nsDirectoryService::gService->Get(NS_OS_TEMP_DIR, NS_GET_IID(nsIFile), + getter_AddRefs(tmpFile)); + + nsresult rv = tmpFile->AppendNative(nsDependentCString("mozilla_audio_sample")); + if (NS_FAILED(rv)) { + return rv; + } + + rv = tmpFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, PR_IRUSR | PR_IWUSR); + if (NS_FAILED(rv)) { + return rv; + } + + ScopedCanberraFile canberraFile(tmpFile); + + mozilla::AutoFDClose fd; + rv = canberraFile->OpenNSPRFileDesc(PR_WRONLY, PR_IRUSR | PR_IWUSR, + &fd.rwget()); + if (NS_FAILED(rv)) { + return rv; + } + + // XXX: Should we do this on another thread? + uint32_t length = dataLen; + while (length > 0) { + int32_t amount = PR_Write(fd, data, length); + if (amount < 0) { + return NS_ERROR_FAILURE; + } + length -= amount; + data += amount; + } + + ca_context* ctx = ca_context_get_default(); + if (!ctx) { + return NS_ERROR_OUT_OF_MEMORY; + } + + ca_proplist *p; + ca_proplist_create(&p); + if (!p) { + return NS_ERROR_OUT_OF_MEMORY; + } + + nsAutoCString path; + rv = canberraFile->GetNativePath(path); + if (NS_FAILED(rv)) { + return rv; + } + + ca_proplist_sets(p, "media.filename", path.get()); + if (ca_context_play_full(ctx, 0, p, ca_finish_cb, canberraFile) >= 0) { + // Don't delete the temporary file here if ca_context_play_full succeeds + canberraFile.forget(); + } + ca_proplist_destroy(p); + + return NS_OK; +} + +NS_IMETHODIMP nsSound::Beep() +{ + ::gdk_beep(); + return NS_OK; +} + +NS_IMETHODIMP nsSound::Play(nsIURL *aURL) +{ + if (!mInited) + Init(); + + if (!libcanberra) + return NS_ERROR_NOT_AVAILABLE; + + bool isFile; + nsresult rv = aURL->SchemeIs("file", &isFile); + if (NS_SUCCEEDED(rv) && isFile) { + ca_context* ctx = ca_context_get_default(); + if (!ctx) { + return NS_ERROR_OUT_OF_MEMORY; + } + + nsAutoCString spec; + rv = aURL->GetSpec(spec); + if (NS_FAILED(rv)) { + return rv; + } + gchar *path = g_filename_from_uri(spec.get(), nullptr, nullptr); + if (!path) { + return NS_ERROR_FILE_UNRECOGNIZED_PATH; + } + + ca_context_play(ctx, 0, "media.filename", path, nullptr); + g_free(path); + } else { + nsCOMPtr<nsIStreamLoader> loader; + rv = NS_NewStreamLoader(getter_AddRefs(loader), + aURL, + this, // aObserver + nsContentUtils::GetSystemPrincipal(), + nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL, + nsIContentPolicy::TYPE_OTHER); + } + + return rv; +} + +NS_IMETHODIMP nsSound::PlayEventSound(uint32_t aEventId) +{ + if (!mInited) + Init(); + + if (!libcanberra) + return NS_OK; + + // Do we even want alert sounds? + GtkSettings* settings = gtk_settings_get_default(); + + if (g_object_class_find_property(G_OBJECT_GET_CLASS(settings), + "gtk-enable-event-sounds")) { + gboolean enable_sounds = TRUE; + g_object_get(settings, "gtk-enable-event-sounds", &enable_sounds, nullptr); + + if (!enable_sounds) { + return NS_OK; + } + } + + ca_context* ctx = ca_context_get_default(); + if (!ctx) { + return NS_ERROR_OUT_OF_MEMORY; + } + + switch (aEventId) { + case EVENT_ALERT_DIALOG_OPEN: + ca_context_play(ctx, 0, "event.id", "dialog-warning", nullptr); + break; + case EVENT_CONFIRM_DIALOG_OPEN: + ca_context_play(ctx, 0, "event.id", "dialog-question", nullptr); + break; + case EVENT_NEW_MAIL_RECEIVED: + ca_context_play(ctx, 0, "event.id", "message-new-email", nullptr); + break; + case EVENT_MENU_EXECUTE: + ca_context_play(ctx, 0, "event.id", "menu-click", nullptr); + break; + case EVENT_MENU_POPUP: + ca_context_play(ctx, 0, "event.id", "menu-popup", nullptr); + break; + } + return NS_OK; +} + +NS_IMETHODIMP nsSound::PlaySystemSound(const nsAString &aSoundAlias) +{ + if (NS_IsMozAliasSound(aSoundAlias)) { + NS_WARNING("nsISound::playSystemSound is called with \"_moz_\" events, they are obsolete, use nsISound::playEventSound instead"); + uint32_t eventId; + if (aSoundAlias.Equals(NS_SYSSOUND_ALERT_DIALOG)) + eventId = EVENT_ALERT_DIALOG_OPEN; + else if (aSoundAlias.Equals(NS_SYSSOUND_CONFIRM_DIALOG)) + eventId = EVENT_CONFIRM_DIALOG_OPEN; + else if (aSoundAlias.Equals(NS_SYSSOUND_MAIL_BEEP)) + eventId = EVENT_NEW_MAIL_RECEIVED; + else if (aSoundAlias.Equals(NS_SYSSOUND_MENU_EXECUTE)) + eventId = EVENT_MENU_EXECUTE; + else if (aSoundAlias.Equals(NS_SYSSOUND_MENU_POPUP)) + eventId = EVENT_MENU_POPUP; + else + return NS_OK; + return PlayEventSound(eventId); + } + + nsresult rv; + nsCOMPtr <nsIURI> fileURI; + + // create a nsIFile and then a nsIFileURL from that + nsCOMPtr <nsIFile> soundFile; + rv = NS_NewLocalFile(aSoundAlias, true, + getter_AddRefs(soundFile)); + NS_ENSURE_SUCCESS(rv,rv); + + rv = NS_NewFileURI(getter_AddRefs(fileURI), soundFile); + NS_ENSURE_SUCCESS(rv,rv); + + nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(fileURI,&rv); + NS_ENSURE_SUCCESS(rv,rv); + + rv = Play(fileURL); + + return rv; +} diff --git a/widget/gtk/nsSound.h b/widget/gtk/nsSound.h new file mode 100644 index 000000000..0039b8556 --- /dev/null +++ b/widget/gtk/nsSound.h @@ -0,0 +1,35 @@ +/* -*- 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/. */ + +#ifndef __nsSound_h__ +#define __nsSound_h__ + +#include "nsISound.h" +#include "nsIStreamLoader.h" + +#include <gtk/gtk.h> + +class nsSound : public nsISound, + public nsIStreamLoaderObserver +{ +public: + nsSound(); + + static void Shutdown(); + + NS_DECL_ISUPPORTS + NS_DECL_NSISOUND + NS_DECL_NSISTREAMLOADEROBSERVER + +private: + virtual ~nsSound(); + + bool mInited; + +}; + +#endif /* __nsSound_h__ */ diff --git a/widget/gtk/nsToolkit.cpp b/widget/gtk/nsToolkit.cpp new file mode 100644 index 000000000..41d47ff96 --- /dev/null +++ b/widget/gtk/nsToolkit.cpp @@ -0,0 +1,34 @@ +/* -*- 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 "nscore.h" // needed for 'nullptr' +#include "nsGTKToolkit.h" + +nsGTKToolkit* nsGTKToolkit::gToolkit = nullptr; + +//------------------------------------------------------------------------- +// +// constructor +// +//------------------------------------------------------------------------- +nsGTKToolkit::nsGTKToolkit() + : mFocusTimestamp(0) +{ +} + +//------------------------------------------------------------------------------- +// Return the toolkit. If a toolkit does not yet exist, then one will be created. +//------------------------------------------------------------------------------- +// static +nsGTKToolkit* nsGTKToolkit::GetToolkit() +{ + if (!gToolkit) { + gToolkit = new nsGTKToolkit(); + } + + return gToolkit; +} diff --git a/widget/gtk/nsWidgetFactory.cpp b/widget/gtk/nsWidgetFactory.cpp new file mode 100644 index 000000000..7e4274377 --- /dev/null +++ b/widget/gtk/nsWidgetFactory.cpp @@ -0,0 +1,331 @@ +/* -*- 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/ModuleUtils.h" +#include "mozilla/WidgetUtils.h" +#include "NativeKeyBindings.h" +#include "nsWidgetsCID.h" +#include "nsAppShell.h" +#include "nsAppShellSingleton.h" +#include "nsBaseWidget.h" +#include "nsGtkKeyUtils.h" +#include "nsLookAndFeel.h" +#include "nsWindow.h" +#include "nsTransferable.h" +#include "nsHTMLFormatConverter.h" +#ifdef MOZ_X11 +#include "nsClipboardHelper.h" +#include "nsClipboard.h" +#include "nsDragService.h" +#endif +#if (MOZ_WIDGET_GTK == 3) +#include "nsApplicationChooser.h" +#endif +#include "nsColorPicker.h" +#include "nsFilePicker.h" +#include "nsSound.h" +#include "nsBidiKeyboard.h" +#include "nsScreenManagerGtk.h" +#include "nsGTKToolkit.h" +#include "WakeLockListener.h" + +#ifdef NS_PRINTING +#include "nsPrintOptionsGTK.h" +#include "nsPrintSession.h" +#include "nsDeviceContextSpecG.h" +#endif + +#include "mozilla/Preferences.h" + +#include "nsImageToPixbuf.h" +#include "nsPrintDialogGTK.h" + +#if defined(MOZ_X11) +#include "nsIdleServiceGTK.h" +#include "GfxInfoX11.h" +#endif + +#include "nsNativeThemeGTK.h" + +#include "nsIComponentRegistrar.h" +#include "nsComponentManagerUtils.h" +#include "mozilla/gfx/2D.h" +#include <gtk/gtk.h> + +using namespace mozilla; +using namespace mozilla::widget; + +/* from nsFilePicker.js */ +#define XULFILEPICKER_CID \ + { 0x54ae32f8, 0x1dd2, 0x11b2, \ + { 0xa2, 0x09, 0xdf, 0x7c, 0x50, 0x53, 0x70, 0xf8} } +static NS_DEFINE_CID(kXULFilePickerCID, XULFILEPICKER_CID); + +NS_GENERIC_FACTORY_CONSTRUCTOR(nsWindow) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsChildWindow) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsTransferable) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsBidiKeyboard) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsHTMLFormatConverter) +#ifdef MOZ_X11 +NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsIdleServiceGTK, nsIdleServiceGTK::GetInstance) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsClipboardHelper) +NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsClipboard, Init) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsDragService) +#endif +NS_GENERIC_FACTORY_CONSTRUCTOR(nsSound) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsScreenManagerGtk) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsImageToPixbuf) + + +// from nsWindow.cpp +extern bool gDisableNativeTheme; + +static nsresult +nsNativeThemeGTKConstructor(nsISupports *aOuter, REFNSIID aIID, + void **aResult) +{ + nsresult rv; + nsNativeThemeGTK * inst; + + if (gDisableNativeTheme) + return NS_ERROR_NO_INTERFACE; + + *aResult = nullptr; + if (nullptr != aOuter) { + rv = NS_ERROR_NO_AGGREGATION; + return rv; + } + + inst = new nsNativeThemeGTK(); + if (nullptr == inst) { + rv = NS_ERROR_OUT_OF_MEMORY; + return rv; + } + NS_ADDREF(inst); + rv = inst->QueryInterface(aIID, aResult); + NS_RELEASE(inst); + + return rv; +} + +#if defined(MOZ_X11) +namespace mozilla { +namespace widget { +// This constructor should really be shared with all platforms. +NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(GfxInfo, Init) +} +} +#endif + +#ifdef NS_PRINTING +NS_GENERIC_FACTORY_CONSTRUCTOR(nsDeviceContextSpecGTK) +NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsPrintOptionsGTK, Init) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsPrinterEnumeratorGTK) +NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsPrintSession, Init) +NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsPrintDialogServiceGTK, Init) +#endif + +static nsresult +nsFilePickerConstructor(nsISupports *aOuter, REFNSIID aIID, + void **aResult) +{ + *aResult = nullptr; + if (aOuter != nullptr) { + return NS_ERROR_NO_AGGREGATION; + } + + bool allowPlatformPicker = + Preferences::GetBool("ui.allow_platform_file_picker", true); + + nsCOMPtr<nsIFilePicker> picker; + if (allowPlatformPicker) { + picker = new nsFilePicker; + } else { + picker = do_CreateInstance(kXULFilePickerCID); + } + + if (!picker) { + return NS_ERROR_OUT_OF_MEMORY; + } + + return picker->QueryInterface(aIID, aResult); +} + +#if (MOZ_WIDGET_GTK == 3) +static nsresult +nsApplicationChooserConstructor(nsISupports *aOuter, REFNSIID aIID, + void **aResult) +{ + *aResult = nullptr; + if (aOuter != nullptr) { + return NS_ERROR_NO_AGGREGATION; + } + nsCOMPtr<nsIApplicationChooser> chooser = new nsApplicationChooser; + + if (!chooser) { + return NS_ERROR_OUT_OF_MEMORY; + } + + return chooser->QueryInterface(aIID, aResult); +} +#endif + +static nsresult +nsColorPickerConstructor(nsISupports *aOuter, REFNSIID aIID, + void **aResult) +{ + *aResult = nullptr; + if (aOuter != nullptr) { + return NS_ERROR_NO_AGGREGATION; + } + + nsCOMPtr<nsIColorPicker> picker = new nsColorPicker; + + if (!picker) { + return NS_ERROR_OUT_OF_MEMORY; + } + + return picker->QueryInterface(aIID, aResult); +} + +NS_DEFINE_NAMED_CID(NS_WINDOW_CID); +NS_DEFINE_NAMED_CID(NS_CHILD_CID); +NS_DEFINE_NAMED_CID(NS_APPSHELL_CID); +NS_DEFINE_NAMED_CID(NS_COLORPICKER_CID); +NS_DEFINE_NAMED_CID(NS_FILEPICKER_CID); +#if (MOZ_WIDGET_GTK == 3) +NS_DEFINE_NAMED_CID(NS_APPLICATIONCHOOSER_CID); +#endif +NS_DEFINE_NAMED_CID(NS_SOUND_CID); +NS_DEFINE_NAMED_CID(NS_TRANSFERABLE_CID); +#ifdef MOZ_X11 +NS_DEFINE_NAMED_CID(NS_CLIPBOARD_CID); +NS_DEFINE_NAMED_CID(NS_CLIPBOARDHELPER_CID); +NS_DEFINE_NAMED_CID(NS_DRAGSERVICE_CID); +#endif +NS_DEFINE_NAMED_CID(NS_HTMLFORMATCONVERTER_CID); +NS_DEFINE_NAMED_CID(NS_BIDIKEYBOARD_CID); +NS_DEFINE_NAMED_CID(NS_SCREENMANAGER_CID); +NS_DEFINE_NAMED_CID(NS_THEMERENDERER_CID); +#ifdef NS_PRINTING +NS_DEFINE_NAMED_CID(NS_PRINTSETTINGSSERVICE_CID); +NS_DEFINE_NAMED_CID(NS_PRINTER_ENUMERATOR_CID); +NS_DEFINE_NAMED_CID(NS_PRINTSESSION_CID); +NS_DEFINE_NAMED_CID(NS_DEVICE_CONTEXT_SPEC_CID); +NS_DEFINE_NAMED_CID(NS_PRINTDIALOGSERVICE_CID); +#endif +NS_DEFINE_NAMED_CID(NS_IMAGE_TO_PIXBUF_CID); +#if defined(MOZ_X11) +NS_DEFINE_NAMED_CID(NS_IDLE_SERVICE_CID); +NS_DEFINE_NAMED_CID(NS_GFXINFO_CID); +#endif + + +static const mozilla::Module::CIDEntry kWidgetCIDs[] = { + { &kNS_WINDOW_CID, false, nullptr, nsWindowConstructor }, + { &kNS_CHILD_CID, false, nullptr, nsChildWindowConstructor }, + { &kNS_APPSHELL_CID, false, nullptr, nsAppShellConstructor, Module::ALLOW_IN_GPU_PROCESS }, + { &kNS_COLORPICKER_CID, false, nullptr, nsColorPickerConstructor, Module::MAIN_PROCESS_ONLY }, + { &kNS_FILEPICKER_CID, false, nullptr, nsFilePickerConstructor, Module::MAIN_PROCESS_ONLY }, +#if (MOZ_WIDGET_GTK == 3) + { &kNS_APPLICATIONCHOOSER_CID, false, nullptr, nsApplicationChooserConstructor, Module::MAIN_PROCESS_ONLY }, +#endif + { &kNS_SOUND_CID, false, nullptr, nsSoundConstructor, Module::MAIN_PROCESS_ONLY }, + { &kNS_TRANSFERABLE_CID, false, nullptr, nsTransferableConstructor }, +#ifdef MOZ_X11 + { &kNS_CLIPBOARD_CID, false, nullptr, nsClipboardConstructor, Module::MAIN_PROCESS_ONLY }, + { &kNS_CLIPBOARDHELPER_CID, false, nullptr, nsClipboardHelperConstructor }, + { &kNS_DRAGSERVICE_CID, false, nullptr, nsDragServiceConstructor, Module::MAIN_PROCESS_ONLY }, +#endif + { &kNS_HTMLFORMATCONVERTER_CID, false, nullptr, nsHTMLFormatConverterConstructor }, + { &kNS_BIDIKEYBOARD_CID, false, nullptr, nsBidiKeyboardConstructor }, + { &kNS_SCREENMANAGER_CID, false, nullptr, nsScreenManagerGtkConstructor, + Module::MAIN_PROCESS_ONLY }, + { &kNS_THEMERENDERER_CID, false, nullptr, nsNativeThemeGTKConstructor }, +#ifdef NS_PRINTING + { &kNS_PRINTSETTINGSSERVICE_CID, false, nullptr, nsPrintOptionsGTKConstructor }, + { &kNS_PRINTER_ENUMERATOR_CID, false, nullptr, nsPrinterEnumeratorGTKConstructor }, + { &kNS_PRINTSESSION_CID, false, nullptr, nsPrintSessionConstructor }, + { &kNS_DEVICE_CONTEXT_SPEC_CID, false, nullptr, nsDeviceContextSpecGTKConstructor }, + { &kNS_PRINTDIALOGSERVICE_CID, false, nullptr, nsPrintDialogServiceGTKConstructor }, +#endif + { &kNS_IMAGE_TO_PIXBUF_CID, false, nullptr, nsImageToPixbufConstructor }, +#if defined(MOZ_X11) + { &kNS_IDLE_SERVICE_CID, false, nullptr, nsIdleServiceGTKConstructor }, + { &kNS_GFXINFO_CID, false, nullptr, mozilla::widget::GfxInfoConstructor }, +#endif + { nullptr } +}; + +static const mozilla::Module::ContractIDEntry kWidgetContracts[] = { + { "@mozilla.org/widget/window/gtk;1", &kNS_WINDOW_CID }, + { "@mozilla.org/widgets/child_window/gtk;1", &kNS_CHILD_CID }, + { "@mozilla.org/widget/appshell/gtk;1", &kNS_APPSHELL_CID, Module::ALLOW_IN_GPU_PROCESS }, + { "@mozilla.org/colorpicker;1", &kNS_COLORPICKER_CID, Module::MAIN_PROCESS_ONLY }, + { "@mozilla.org/filepicker;1", &kNS_FILEPICKER_CID, Module::MAIN_PROCESS_ONLY }, +#if (MOZ_WIDGET_GTK == 3) + { "@mozilla.org/applicationchooser;1", &kNS_APPLICATIONCHOOSER_CID, Module::MAIN_PROCESS_ONLY }, +#endif + { "@mozilla.org/sound;1", &kNS_SOUND_CID, Module::MAIN_PROCESS_ONLY }, + { "@mozilla.org/widget/transferable;1", &kNS_TRANSFERABLE_CID }, +#ifdef MOZ_X11 + { "@mozilla.org/widget/clipboard;1", &kNS_CLIPBOARD_CID, Module::MAIN_PROCESS_ONLY }, + { "@mozilla.org/widget/clipboardhelper;1", &kNS_CLIPBOARDHELPER_CID }, + { "@mozilla.org/widget/dragservice;1", &kNS_DRAGSERVICE_CID, Module::MAIN_PROCESS_ONLY }, +#endif + { "@mozilla.org/widget/htmlformatconverter;1", &kNS_HTMLFORMATCONVERTER_CID }, + { "@mozilla.org/widget/bidikeyboard;1", &kNS_BIDIKEYBOARD_CID, + Module::MAIN_PROCESS_ONLY }, + { "@mozilla.org/gfx/screenmanager;1", &kNS_SCREENMANAGER_CID, + Module::MAIN_PROCESS_ONLY }, + { "@mozilla.org/chrome/chrome-native-theme;1", &kNS_THEMERENDERER_CID }, +#ifdef NS_PRINTING + { "@mozilla.org/gfx/printsettings-service;1", &kNS_PRINTSETTINGSSERVICE_CID }, + { "@mozilla.org/gfx/printerenumerator;1", &kNS_PRINTER_ENUMERATOR_CID }, + { "@mozilla.org/gfx/printsession;1", &kNS_PRINTSESSION_CID }, + { "@mozilla.org/gfx/devicecontextspec;1", &kNS_DEVICE_CONTEXT_SPEC_CID }, + { NS_PRINTDIALOGSERVICE_CONTRACTID, &kNS_PRINTDIALOGSERVICE_CID }, +#endif + { "@mozilla.org/widget/image-to-gdk-pixbuf;1", &kNS_IMAGE_TO_PIXBUF_CID }, +#if defined(MOZ_X11) + { "@mozilla.org/widget/idleservice;1", &kNS_IDLE_SERVICE_CID }, + { "@mozilla.org/gfx/info;1", &kNS_GFXINFO_CID }, +#endif + { nullptr } +}; + +static void +nsWidgetGtk2ModuleDtor() +{ + // Shutdown all XP level widget classes. + WidgetUtils::Shutdown(); + + NativeKeyBindings::Shutdown(); + nsLookAndFeel::Shutdown(); + nsFilePicker::Shutdown(); + nsSound::Shutdown(); + nsWindow::ReleaseGlobals(); + KeymapWrapper::Shutdown(); + nsGTKToolkit::Shutdown(); + nsAppShellShutdown(); +#ifdef MOZ_ENABLE_DBUS + WakeLockListener::Shutdown(); +#endif +} + +static const mozilla::Module kWidgetModule = { + mozilla::Module::kVersion, + kWidgetCIDs, + kWidgetContracts, + nullptr, + nullptr, + nsAppShellInit, + nsWidgetGtk2ModuleDtor, + Module::ALLOW_IN_GPU_PROCESS +}; + +NSMODULE_DEFN(nsWidgetGtk2Module) = &kWidgetModule; diff --git a/widget/gtk/nsWindow.cpp b/widget/gtk/nsWindow.cpp new file mode 100644 index 000000000..d97b35002 --- /dev/null +++ b/widget/gtk/nsWindow.cpp @@ -0,0 +1,7036 @@ +/* -*- 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 "nsWindow.h" + +#include "mozilla/ArrayUtils.h" +#include "mozilla/EventForwards.h" +#include "mozilla/MiscEvents.h" +#include "mozilla/MouseEvents.h" +#include "mozilla/RefPtr.h" +#include "mozilla/TextEventDispatcher.h" +#include "mozilla/TextEvents.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/TouchEvents.h" +#include "mozilla/UniquePtrExtensions.h" +#include <algorithm> + +#include "GeckoProfiler.h" + +#include "prlink.h" +#include "nsGTKToolkit.h" +#include "nsIRollupListener.h" +#include "nsIDOMNode.h" + +#include "nsWidgetsCID.h" +#include "nsDragService.h" +#include "nsIWidgetListener.h" +#include "nsIScreenManager.h" +#include "SystemTimeConverter.h" + +#include "nsGtkKeyUtils.h" +#include "nsGtkCursors.h" +#include "nsScreenGtk.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 + +#if (MOZ_WIDGET_GTK == 2) +#include "gtk2xtbin.h" +#endif +#endif /* MOZ_X11 */ +#include <gdk/gdkkeysyms.h> +#if (MOZ_WIDGET_GTK == 2) +#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/Assertions.h" +#include "mozilla/Likely.h" +#include "mozilla/Preferences.h" +#include "nsIPrefService.h" +#include "nsIGConfService.h" +#include "nsIServiceManager.h" +#include "nsIStringBundle.h" +#include "nsGfxCIID.h" +#include "nsGtkUtils.h" +#include "nsIObserverService.h" +#include "mozilla/layers/LayersTypes.h" +#include "nsIIdleServiceInternal.h" +#include "nsIPropertyBag2.h" +#include "GLContext.h" +#include "gfx2DGlue.h" +#include "nsPluginNativeWindowGtk.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 "ClientLayerManager.h" + +#include "gfxPlatformGtk.h" +#include "gfxContext.h" +#include "gfxImageSurface.h" +#include "gfxUtils.h" +#include "Layers.h" +#include "GLContextProvider.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/HelpersCairo.h" +#include "mozilla/layers/CompositorBridgeParent.h" +#include "mozilla/layers/CompositorThread.h" + +#ifdef MOZ_X11 +#include "X11CompositorWidget.h" +#include "gfxXlibSurface.h" +#include "WindowSurfaceX11Image.h" +#include "WindowSurfaceX11SHM.h" +#include "WindowSurfaceXRender.h" +#endif // MOZ_X11 + +#include "nsShmImage.h" + +#include "nsIDOMWheelEvent.h" + +#include "NativeKeyBindings.h" + +#include <dlfcn.h> + +#include "mozilla/layers/APZCTreeManager.h" + +using namespace mozilla; +using namespace mozilla::gfx; +using namespace mozilla::widget; +using namespace mozilla::layers; +using mozilla::gl::GLContext; + +// 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 | +#if GTK_CHECK_VERSION(3,4,0) + GDK_SMOOTH_SCROLL_MASK | + GDK_TOUCH_MASK | +#endif + GDK_SCROLL_MASK | + GDK_POINTER_MOTION_MASK | + GDK_PROPERTY_CHANGE_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 WidgetKeyboardEvent& 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 (MOZ_WIDGET_GTK == 2) +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 property_notify_event_cb (GtkWidget *widget, + GdkEventProperty *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 void check_resize_cb (GtkContainer* container, + gpointer user_data); + +#if (MOZ_WIDGET_GTK == 3) +static void scale_changed_cb (GtkWidget* widget, + GParamSpec* aPSpec, + gpointer aPointer); +#endif +#if GTK_CHECK_VERSION(3,4,0) +static gboolean touch_event_cb (GtkWidget* aWidget, + GdkEventTouch* aEvent); +#endif +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 SystemTimeConverter<guint32>& +TimeConverter() { + static SystemTimeConverter<guint32> sTimeConverterSingleton; + return sTimeConverterSingleton; +} + +namespace mozilla { + +class CurrentX11TimeGetter +{ +public: + explicit CurrentX11TimeGetter(GdkWindow* aWindow) + : mWindow(aWindow) + , mAsyncUpdateStart() + { + } + + guint32 GetCurrentTime() const + { + return gdk_x11_get_server_time(mWindow); + } + + void GetTimeAsyncForPossibleBackwardsSkew(const TimeStamp& aNow) + { + // Check for in-flight request + if (!mAsyncUpdateStart.IsNull()) { + return; + } + mAsyncUpdateStart = aNow; + + Display* xDisplay = GDK_WINDOW_XDISPLAY(mWindow); + Window xWindow = GDK_WINDOW_XID(mWindow); + unsigned char c = 'a'; + Atom timeStampPropAtom = TimeStampPropAtom(); + XChangeProperty(xDisplay, xWindow, timeStampPropAtom, + timeStampPropAtom, 8, PropModeReplace, &c, 1); + XFlush(xDisplay); + } + + gboolean PropertyNotifyHandler(GtkWidget* aWidget, + GdkEventProperty* aEvent) + { + if (aEvent->atom != + gdk_x11_xatom_to_atom(TimeStampPropAtom())) { + return FALSE; + } + + guint32 eventTime = aEvent->time; + TimeStamp lowerBound = mAsyncUpdateStart; + + TimeConverter().CompensateForBackwardsSkew(eventTime, lowerBound); + mAsyncUpdateStart = TimeStamp(); + return TRUE; + } + +private: + static Atom TimeStampPropAtom() { + return gdk_x11_get_xatom_by_name_for_display( + gdk_display_get_default(), "GDK_TIMESTAMP_PROP"); + } + + // This is safe because this class is stored as a member of mWindow and + // won't outlive it. + GdkWindow* mWindow; + TimeStamp mAsyncUpdateStart; +}; + +} // namespace mozilla + +static NS_DEFINE_IID(kCDragServiceCID, NS_DRAGSERVICE_CID); + +// The window from which the focus manager asks us to dispatch key events. +static nsWindow *gFocusWindow = nullptr; +static bool gBlockActivateEvent = false; +static bool gGlobalsInitialized = false; +static bool gRaiseWindows = true; +static nsWindow *gPluginFocusWindow = nullptr; + +#if GTK_CHECK_VERSION(3,4,0) +static uint32_t gLastTouchID = 0; +#endif + +#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 = nullptr; + +// Sometimes this actually also includes the state of the modifier keys, but +// only the button state bits are used. +static guint gButtonState; + +static inline int32_t +GetBitmapStride(int32_t width) +{ +#if defined(MOZ_X11) || (MOZ_WIDGET_GTK == 2) + 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; +} + +NS_IMPL_ISUPPORTS_INHERITED0(nsWindow, nsBaseWidget) + +nsWindow::nsWindow() +{ + mIsTopLevel = false; + mIsDestroyed = false; + mListenForResizes = false; + mNeedsDispatchResized = false; + mIsShown = false; + mNeedsShow = false; + mEnabled = true; + mCreated = false; +#if GTK_CHECK_VERSION(3,4,0) + mHandleTouchEvent = false; +#endif + mIsDragPopup = false; + mIsX11Display = GDK_IS_X11_DISPLAY(gdk_display_get_default()); + + mContainer = nullptr; + mGdkWindow = nullptr; + mShell = nullptr; + mPluginNativeWindow = 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; + + mXDisplay = nullptr; + mXWindow = X11None; + mXVisual = nullptr; + mXDepth = 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; + +#if GTK_CHECK_VERSION(3,4,0) + mLastScrollEventTime = GDK_CURRENT_TIME; +#endif + mPendingConfigures = 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]) { +#if (MOZ_WIDGET_GTK == 3) + g_object_unref(gCursorCache[i]); +#else + gdk_cursor_unref(gCursorCache[i]); +#endif + gCursorCache[i] = nullptr; + } + } +} + +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() +{ + mNeedsDispatchResized = false; + if (mWidgetListener) { + mWidgetListener->WindowResized(this, mBounds.width, mBounds.height); + } + if (mAttachedWidgetListener) { + mAttachedWidgetListener->WindowResized(this, + mBounds.width, mBounds.height); + } +} + +void +nsWindow::MaybeDispatchResized() +{ + if (mNeedsDispatchResized && !mIsDestroyed) { + DispatchResized(); + } +} + +nsIWidgetListener* +nsWindow::GetListener() +{ + return mAttachedWidgetListener ? mAttachedWidgetListener : mWidgetListener; +} + +nsresult +nsWindow::DispatchEvent(WidgetGUIEvent* aEvent, nsEventStatus& aStatus) +{ +#ifdef DEBUG + debug_DumpEvent(stdout, aEvent->mWidget, aEvent, + "something", 0); +#endif + aStatus = nsEventStatus_eIgnore; + nsIWidgetListener* listener = GetListener(); + 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 = nullptr; + } +} + +// 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)); + } + } + } +} + +void +nsWindow::Destroy() +{ + if (mIsDestroyed || !mCreated) + return; + + LOG(("nsWindow::Destroy [%p]\n", (void *)this)); + mIsDestroyed = true; + mCreated = false; + + /** Need to clean our LayerManager up while still alive */ + if (mLayerManager) { + mLayerManager->Destroy(); + } + 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(); + +#ifdef MOZ_X11 + // Ensure any resources assigned to the window get cleaned up first + // to avoid double-freeing. + mSurfaceProvider.CleanupResources(); +#endif + + 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, false, nullptr, 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 (mIMContext) { + mIMContext->OnDestroyWindow(this); + } + + // make sure that we remove ourself as the focus window + if (gFocusWindow == this) { + LOGFOCUS(("automatically losing focus...\n")); + gFocusWindow = nullptr; + } + +#if (MOZ_WIDGET_GTK == 2) && defined(MOZ_X11) + // make sure that we remove ourself as the plugin focus window + if (gPluginFocusWindow == this) { + gPluginFocusWindow->LoseNonXEmbedPluginFocus(); + } +#endif /* MOZ_X11 && MOZ_WIDGET_GTK == 2 && defined(MOZ_X11) */ + + GtkWidget *owningWidget = GetMozContainerWidget(); + if (mShell) { + gtk_widget_destroy(mShell); + mShell = nullptr; + mContainer = nullptr; + MOZ_ASSERT(!mGdkWindow, + "mGdkWindow should be NULL when mContainer is destroyed"); + } + else if (mContainer) { + gtk_widget_destroy(GTK_WIDGET(mContainer)); + mContainer = nullptr; + MOZ_ASSERT(!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, nullptr); + g_object_set_data(G_OBJECT(mGdkWindow), "nsWindow", nullptr); + 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(); +} + +nsIWidget * +nsWindow::GetParent(void) +{ + return mParent; +} + +float +nsWindow::GetDPI() +{ + GdkScreen *screen = gdk_display_get_default_screen(gdk_display_get_default()); + double heightInches = gdk_screen_get_height_mm(screen)/MM_PER_INCH_FLOAT; + if (heightInches < 0.25) { + // Something's broken, but we'd better not crash. + return 96.0f; + } + return float(gdk_screen_get_height(screen)/heightInches); +} + +double +nsWindow::GetDefaultScaleInternal() +{ + return GdkScaleFactor() * gfxPlatformGtk::GetDPIScale(); +} + +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. + MOZ_ASSERT(gdk_window_is_destroyed(mGdkWindow), + "live GdkWindow with no widget"); + return NS_OK; + } + + if (aNewParent) { + aNewParent->AddChild(this); + ReparentNativeWidget(aNewParent); + } else { + // aNewParent is nullptr, 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; +} + +bool +nsWindow::WidgetTypeSupportsAcceleration() +{ + return !IsSmallPopup(); +} + +void +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. + MOZ_ASSERT(gdk_window_is_destroyed(mGdkWindow), + "live GdkWindow with no widget"); + return; + } + MOZ_ASSERT(!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); +} + +void +nsWindow::ReparentNativeWidgetInternal(nsIWidget* aNewParent, + GtkWidget* aNewContainer, + GdkWindow* aNewParentWindow, + GtkWidget* aOldContainer) +{ + if (!aNewContainer) { + // The new parent GdkWindow has been destroyed. + MOZ_ASSERT(!aNewParentWindow || + gdk_window_is_destroyed(aNewParentWindow), + "live GdkWindow with no widget"); + Destroy(); + } else { + if (aNewContainer != aOldContainer) { + MOZ_ASSERT(!gdk_window_is_destroyed(aNewParentWindow), + "destroyed GdkWindow with widget"); + SetWidgetForHierarchy(mGdkWindow, aOldContainer, aNewContainer); + + if (aOldContainer == gInvisibleContainer) { + CheckDestroyInvisibleContainer(); + } + } + + if (!mIsTopLevel) { + gdk_window_reparent(mGdkWindow, aNewParentWindow, + DevicePixelsToGdkCoordRoundDown(mBounds.x), + DevicePixelsToGdkCoordRoundDown(mBounds.y)); + } + } + + nsWindow* newParent = static_cast<nsWindow*>(aNewParent); + bool parentHasMappedToplevel = + newParent && newParent->mHasMappedToplevel; + if (mHasMappedToplevel != parentHasMappedToplevel) { + SetHasMappedToplevel(parentHasMappedToplevel); + } +} + +void +nsWindow::SetModal(bool aModal) +{ + LOG(("nsWindow::SetModal [%p] %d\n", (void *)this, aModal)); + if (mIsDestroyed) + return; + if (!mIsTopLevel || !mShell) + return; + gtk_window_set_modal(GTK_WINDOW(mShell), aModal ? TRUE : FALSE); +} + +// nsIWidget method, which means IsShown. +bool +nsWindow::IsVisible() const +{ + return mIsShown; +} + +void +nsWindow::RegisterTouchWindow() +{ +#if GTK_CHECK_VERSION(3,4,0) + mHandleTouchEvent = true; + mTouches.Clear(); +#endif +} + +void +nsWindow::ConstrainPosition(bool aAllowSlop, int32_t *aX, int32_t *aY) +{ + if (!mIsTopLevel || !mShell) + return; + + double dpiScale = GetDefaultScale().scale; + + // we need to use the window size in logical screen pixels + int32_t logWidth = std::max(NSToIntRound(mBounds.width / dpiScale), 1); + int32_t logHeight = std::max(NSToIntRound(mBounds.height / dpiScale), 1); + + /* get our playing field. use the current screen, or failing that + for any reason, use device caps for the default screen. */ + nsCOMPtr<nsIScreen> screen; + nsCOMPtr<nsIScreenManager> screenmgr = do_GetService("@mozilla.org/gfx/screenmanager;1"); + if (screenmgr) { + screenmgr->ScreenForRect(*aX, *aY, logWidth, logHeight, + getter_AddRefs(screen)); + } + + // We don't have any screen so leave the coordinates as is + if (!screen) + return; + + nsIntRect screenRect; + if (mSizeMode != nsSizeMode_Fullscreen) { + // For normalized windows, use the desktop work area. + screen->GetAvailRectDisplayPix(&screenRect.x, &screenRect.y, + &screenRect.width, &screenRect.height); + } else { + // For full screen windows, use the desktop. + screen->GetRectDisplayPix(&screenRect.x, &screenRect.y, + &screenRect.width, &screenRect.height); + } + + if (aAllowSlop) { + if (*aX < screenRect.x - logWidth + kWindowPositionSlop) + *aX = screenRect.x - logWidth + kWindowPositionSlop; + else if (*aX >= screenRect.XMost() - kWindowPositionSlop) + *aX = screenRect.XMost() - kWindowPositionSlop; + + if (*aY < screenRect.y - logHeight + kWindowPositionSlop) + *aY = screenRect.y - logHeight + kWindowPositionSlop; + else if (*aY >= screenRect.YMost() - kWindowPositionSlop) + *aY = screenRect.YMost() - kWindowPositionSlop; + } else { + if (*aX < screenRect.x) + *aX = screenRect.x; + else if (*aX >= screenRect.XMost() - logWidth) + *aX = screenRect.XMost() - logWidth; + + if (*aY < screenRect.y) + *aY = screenRect.y; + else if (*aY >= screenRect.YMost() - logHeight) + *aY = screenRect.YMost() - logHeight; + } +} + +void nsWindow::SetSizeConstraints(const SizeConstraints& aConstraints) +{ + mSizeConstraints.mMinSize = GetSafeWindowSize(aConstraints.mMinSize); + mSizeConstraints.mMaxSize = GetSafeWindowSize(aConstraints.mMaxSize); + + if (mShell) { + GdkGeometry geometry; + geometry.min_width = DevicePixelsToGdkCoordRoundUp( + mSizeConstraints.mMinSize.width); + geometry.min_height = DevicePixelsToGdkCoordRoundUp( + mSizeConstraints.mMinSize.height); + geometry.max_width = DevicePixelsToGdkCoordRoundDown( + mSizeConstraints.mMaxSize.width); + geometry.max_height = DevicePixelsToGdkCoordRoundDown( + mSizeConstraints.mMaxSize.height); + + uint32_t hints = 0; + if (aConstraints.mMinSize != LayoutDeviceIntSize(0, 0)) { + hints |= GDK_HINT_MIN_SIZE; + } + if (aConstraints.mMaxSize != + LayoutDeviceIntSize(NS_MAXSIZE, NS_MAXSIZE)) { + hints |= 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; + +#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 = BoundsUseDesktopPixels() ? GetDesktopToDeviceScale().scale : 1.0; + 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; + + NativeResize(); + + NotifyRollupGeometryChange(); + ResizePluginSocketWidget(); + + // send a resize notification if this is a toplevel + if (mIsTopLevel || mListenForResizes) { + DispatchResized(); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsWindow::Resize(double aX, double aY, double aWidth, double aHeight, + bool aRepaint) +{ + double scale = BoundsUseDesktopPixels() ? GetDesktopToDeviceScale().scale : 1.0; + 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); + + if (!mCreated) + return NS_OK; + + NativeMoveResize(); + + NotifyRollupGeometryChange(); + ResizePluginSocketWidget(); + + if (mIsTopLevel || mListenForResizes) { + DispatchResized(); + } + + return NS_OK; +} + +void +nsWindow::ResizePluginSocketWidget() +{ + // e10s specific, a eWindowType_plugin_ipc_chrome holds its own + // nsPluginNativeWindowGtk wrapper. We are responsible for resizing + // the embedded socket widget. + if (mWindowType == eWindowType_plugin_ipc_chrome) { + nsPluginNativeWindowGtk* wrapper = (nsPluginNativeWindowGtk*) + GetNativeData(NS_NATIVE_PLUGIN_OBJECT_PTR); + if (wrapper) { + wrapper->width = mBounds.width; + wrapper->height = mBounds.height; + wrapper->SetAllocation(); + } + } +} + +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 = BoundsUseDesktopPixels() ? GetDesktopToDeviceScale().scale : 1.0; + 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; + + NativeMove(); + + NotifyRollupGeometryChange(); + return NS_OK; +} + + +void +nsWindow::NativeMove() +{ + GdkPoint point = DevicePixelsToGdkPointRoundDown(mBounds.TopLeft()); + + if (mIsTopLevel) { + gtk_window_move(GTK_WINDOW(mShell), point.x, point.y); + } + else if (mGdkWindow) { + gdk_window_move(mGdkWindow, point.x, point.y); + } +} + +void +nsWindow::SetZIndex(int32_t aZIndex) +{ + nsIWidget* oldPrev = GetPrevSibling(); + + nsBaseWidget::SetZIndex(aZIndex); + + if (GetPrevSibling() == oldPrev) { + return; + } + + 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); + } + } +} + +void +nsWindow::SetSizeMode(nsSizeMode aMode) +{ + LOG(("nsWindow::SetSizeMode [%p] %d\n", (void *)this, aMode)); + + // Save the requested state. + 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; + } + + 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; +} + +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) + // TODO - Implement for non-X11 Gtk backends (Bug 726479) + if (GDK_IS_X11_DISPLAY(gdk_display_get_default())) { + 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)) { + gdk_x11_window_set_user_time(gdkWindow, + sn_launchee_context_get_timestamp(ctx)); + } + + 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); + } + } + + RefPtr<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 (mIMContext) { + mIMContext->OnFocusWindow(this); + } + + LOGFOCUS((" widget now has focus in SetFocus() [%p]\n", + (void *)this)); + + return NS_OK; +} + +LayoutDeviceIntRect +nsWindow::GetScreenBounds() +{ + LayoutDeviceIntRect rect; + 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); + rect.MoveTo(GdkPointToDevicePixels({ x, y })); + } else { + rect.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. + rect.SizeTo(mBounds.Size()); + LOG(("GetScreenBounds %d,%d | %dx%d\n", + rect.x, rect.y, rect.width, rect.height)); + return rect; +} + +LayoutDeviceIntSize +nsWindow::GetClientSize() +{ + return LayoutDeviceIntSize(mBounds.width, mBounds.height); +} + +LayoutDeviceIntRect +nsWindow::GetClientBounds() +{ + // 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). + LayoutDeviceIntRect rect = GetBounds(); + rect.MoveBy(GetClientOffset()); + return rect; +} + +void +nsWindow::UpdateClientOffset() +{ + PROFILER_LABEL("nsWindow", "UpdateClientOffset", js::ProfileEntry::Category::GRAPHICS); + + if (!mIsTopLevel || !mShell || !mGdkWindow || !mIsX11Display || + gtk_window_get_window_type(GTK_WINDOW(mShell)) == GTK_WINDOW_POPUP) { + mClientOffset = nsIntPoint(0, 0); + return; + } + + GdkAtom cardinal_atom = gdk_x11_xatom_to_atom(XA_CARDINAL); + + GdkAtom type_returned; + int format_returned; + int length_returned; + long *frame_extents; + + if (!gdk_property_get(mGdkWindow, + 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) { + mClientOffset = nsIntPoint(0, 0); + return; + } + + // 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); + + mClientOffset = nsIntPoint(left, top); +} + +LayoutDeviceIntPoint +nsWindow::GetClientOffset() +{ + return LayoutDeviceIntPoint::FromUnknownPoint(mClientOffset); +} + +gboolean +nsWindow::OnPropertyNotifyEvent(GtkWidget* aWidget, GdkEventProperty* aEvent) + +{ + if (aEvent->atom == gdk_atom_intern("_NET_FRAME_EXTENTS", FALSE)) { + UpdateClientOffset(); + return FALSE; + } + + if (GetCurrentTimeGetter()->PropertyNotifyHandler(aWidget, aEvent)) { + return TRUE; + } + + return FALSE; +} + +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 || mUpdateCursor) { + GdkCursor *newCursor = nullptr; + mUpdateCursor = false; + + 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; + } +#if (MOZ_WIDGET_GTK == 3) + g_object_unref(cursor); +#else + gdk_cursor_unref(cursor); +#endif + } + + return rv; +} + +NS_IMETHODIMP +nsWindow::Invalidate(const LayoutDeviceIntRect& aRect) +{ + if (!mGdkWindow) + return NS_OK; + + GdkRectangle rect = DevicePixelsToGdkRectRoundOut(aRect); + gdk_window_invalidate_rect(mGdkWindow, &rect, FALSE); + + LOGDRAW(("Invalidate (rect) [%p]: %d %d %d %d\n", (void *)this, + rect.x, rect.y, rect.width, rect.height)); + + 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; + } + + case NS_NATIVE_PLUGIN_PORT: + return SetupPluginPort(); + + case NS_NATIVE_PLUGIN_ID: + if (!mPluginNativeWindow) { + NS_WARNING("no native plugin instance!"); + return nullptr; + } + // Return the socket widget XID + return (void*)mPluginNativeWindow->window; + + case NS_NATIVE_DISPLAY: { +#ifdef MOZ_X11 + GdkDisplay* gdkDisplay = gdk_display_get_default(); + if (GDK_IS_X11_DISPLAY(gdkDisplay)) { + return GDK_DISPLAY_XDISPLAY(gdkDisplay); + } +#endif /* MOZ_X11 */ + return nullptr; + } + case NS_NATIVE_SHELLWIDGET: + return GetToplevelWidget(); + + case NS_NATIVE_SHAREABLE_WINDOW: + return (void *) GDK_WINDOW_XID(gdk_window_get_toplevel(mGdkWindow)); + case NS_NATIVE_PLUGIN_OBJECT_PTR: + return (void *) mPluginNativeWindow; + case NS_RAW_NATIVE_IME_CONTEXT: { + void* pseudoIMEContext = GetPseudoIMEContext(); + if (pseudoIMEContext) { + return pseudoIMEContext; + } + // If IME context isn't available on this widget, we should set |this| + // instead of nullptr. + if (!mIMContext) { + return this; + } + return mIMContext.get(); + } + case NS_NATIVE_OPENGL_CONTEXT: + return nullptr; +#ifdef MOZ_X11 + case NS_NATIVE_COMPOSITOR_DISPLAY: + return gfxPlatformGtk::GetPlatform()->GetCompositorDisplay(); +#endif // MOZ_X11 + default: + NS_WARNING("nsWindow::GetNativeData called with bad value"); + return nullptr; + } +} + +void +nsWindow::SetNativeData(uint32_t aDataType, uintptr_t aVal) +{ + if (aDataType != NS_NATIVE_PLUGIN_OBJECT_PTR) { + NS_WARNING("nsWindow::SetNativeData called with bad value"); + return; + } + mPluginNativeWindow = (nsPluginNativeWindowGtk*)aVal; +} + +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(), nullptr); + 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; +} + + +LayoutDeviceIntPoint +nsWindow::WidgetToScreenOffset() +{ + gint x = 0, y = 0; + + if (mGdkWindow) { + gdk_window_get_origin(mGdkWindow, &x, &y); + } + + return GdkPointToDevicePixels({ x, y }); +} + +void +nsWindow::CaptureMouse(bool aCapture) +{ + LOG(("CaptureMouse %p\n", (void *)this)); + + if (!mGdkWindow) + return; + + if (!mContainer) + return; + + if (aCapture) { + gtk_grab_add(GTK_WIDGET(mContainer)); + GrabPointer(GetLastUserInputTime()); + } + else { + ReleaseGrabs(); + gtk_grab_remove(GTK_WIDGET(mContainer)); + } +} + +void +nsWindow::CaptureRollupEvents(nsIRollupListener *aListener, + bool aDoCapture) +{ + if (!mGdkWindow) + return; + + if (!mContainer) + return; + + LOG(("CaptureRollupEvents %p %i\n", this, int(aDoCapture))); + + if (aDoCapture) { + gRollupListener = aListener; + // Don't add a grab if a drag is in progress, or if the widget is a drag + // feedback popup. (panels with type="drag"). + if (!mIsDragPopup && !nsWindow::DragInProgress()) { + gtk_grab_add(GTK_WIDGET(mContainer)); + 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(GTK_WIDGET(mContainer)); + gRollupListener = nullptr; + } +} + +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 = false; +#ifdef MOZ_X11 + XEvent ev; + if (mIsX11Display) { + 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); + } + } +#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 (MOZ_WIDGET_GTK == 2) + gdk_window_get_geometry(aGdkWindow,nullptr,nullptr,&width,&height,nullptr); +#else + gdk_window_get_geometry(aGdkWindow,nullptr,nullptr,&width,&height); +#endif + + gdk_window_get_origin (aGdkWindow, + &x, + &y); + + gc = gdk_gc_new(gdk_get_default_root_window()); + + 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_get_default_root_window(), + 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 (MOZ_WIDGET_GTK == 2) +static bool +ExtractExposeRegion(LayoutDeviceIntRegion& aRegion, GdkEventExpose* aEvent) +{ + GdkRectangle* rects; + gint nrects; + gdk_region_get_rectangles(aEvent->region, &rects, &nrects); + + if (nrects > MAX_RECTS_IN_REGION) { + // Just use the bounding box + rects[0] = aEvent->area; + nrects = 1; + } + + for (GdkRectangle* r = rects; r < rects + nrects; r++) { + aRegion.Or(aRegion, LayoutDeviceIntRect(r->x, r->y, r->width, r->height)); + LOGDRAW(("\t%d %d %d %d\n", r->x, r->y, r->width, r->height)); + } + + g_free(rects); + return true; +} + +#else +# ifdef cairo_copy_clip_rectangle_list +# error "Looks like we're including Mozilla's cairo instead of system cairo" +# endif +static bool +ExtractExposeRegion(LayoutDeviceIntRegion& aRegion, cairo_t* cr) +{ + cairo_rectangle_list_t* rects = cairo_copy_clip_rectangle_list(cr); + if (rects->status != CAIRO_STATUS_SUCCESS) { + NS_WARNING("Failed to obtain cairo rectangle list."); + return false; + } + + for (int i = 0; i < rects->num_rectangles; i++) { + const cairo_rectangle_t& r = rects->rectangles[i]; + aRegion.Or(aRegion, LayoutDeviceIntRect::Truncate(r.x, r.y, r.width, r.height)); + LOGDRAW(("\t%d %d %d %d\n", r.x, r.y, r.width, r.height)); + } + + cairo_rectangle_list_destroy(rects); + return true; +} +#endif + +#if (MOZ_WIDGET_GTK == 2) +gboolean +nsWindow::OnExposeEvent(GdkEventExpose *aEvent) +#else +gboolean +nsWindow::OnExposeEvent(cairo_t *cr) +#endif +{ + // Send any pending resize events so that layout can update. + // May run event loop. + MaybeDispatchResized(); + + if (mIsDestroyed) { + return FALSE; + } + + // Windows that are not visible will be painted after they become visible. + if (!mGdkWindow || mIsFullyObscured || !mHasMappedToplevel) + return FALSE; + + nsIWidgetListener *listener = GetListener(); + if (!listener) + return FALSE; + + LayoutDeviceIntRegion exposeRegion; +#if (MOZ_WIDGET_GTK == 2) + if (!ExtractExposeRegion(exposeRegion, aEvent)) { +#else + if (!ExtractExposeRegion(exposeRegion, cr)) { +#endif + return FALSE; + } + + gint scale = GdkScaleFactor(); + LayoutDeviceIntRegion region = exposeRegion; + region.ScaleRoundOut(scale, scale); + + ClientLayerManager *clientLayers = GetLayerManager()->AsClientLayerManager(); + + if (clientLayers && mCompositorSession) { + // We need to paint to the screen even if nothing changed, since if we + // don't have a compositing window manager, our pixels could be stale. + clientLayers->SetNeedsComposite(true); + clientLayers->SendInvalidRegion(region.ToUnknownRegion()); + } + + RefPtr<nsWindow> strongThis(this); + + // 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 = GetListener(); + if (!listener) + return FALSE; + } + + if (clientLayers && clientLayers->NeedsComposite()) { + clientLayers->Composite(); + clientLayers->SetNeedsComposite(false); + } + + LOGDRAW(("sending expose event [%p] %p 0x%lx (rects follow):\n", + (void *)this, (void *)mGdkWindow, + gdk_x11_window_get_xid(mGdkWindow))); + + // Our bounds may have changed after calling WillPaintWindow. Clip + // to the new bounds here. The region is relative to this + // window. + region.And(region, LayoutDeviceIntRect(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)) { + AutoTArray<LayoutDeviceIntRect,1> clipRects; + kid->GetWindowClipRegion(&clipRects); + LayoutDeviceIntRect bounds = kid->GetBounds(); + for (uint32_t i = 0; i < clipRects.Length(); ++i) { + LayoutDeviceIntRect r = clipRects[i] + bounds.TopLeft(); + region.Sub(region, r); + } + } + children = children->next; + } + } + + if (region.IsEmpty()) { + return TRUE; + } + + // If this widget uses OMTC... + if (GetLayerManager()->GetBackendType() == LayersBackend::LAYERS_CLIENT) { + listener->PaintWindow(this, region); + + // Re-get the listener since the will paint notification might have + // killed it. + listener = GetListener(); + if (!listener) + return TRUE; + + listener->DidPaintWindow(); + return TRUE; + } + + BufferMode layerBuffering = BufferMode::BUFFERED; + RefPtr<DrawTarget> dt = StartRemoteDrawingInRegion(region, &layerBuffering); + if (!dt || !dt->IsValid()) { + return FALSE; + } + RefPtr<gfxContext> ctx; + IntRect boundsRect = region.GetBounds().ToUnknownRect(); + IntPoint offset(0, 0); + if (dt->GetSize() == boundsRect.Size()) { + offset = boundsRect.TopLeft(); + dt->SetTransform(Matrix::Translation(-offset)); + } + +#ifdef MOZ_X11 + 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. + dt->PushClipRect(Rect(boundsRect)); + + // 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 = BufferMode::BUFFER_NONE; + RefPtr<DrawTarget> destDT = dt->CreateSimilarDrawTarget(boundsRect.Size(), SurfaceFormat::B8G8R8A8); + if (!destDT || !destDT->IsValid()) { + return FALSE; + } + destDT->SetTransform(Matrix::Translation(-boundsRect.TopLeft())); + ctx = gfxContext::CreatePreservingTransformOrNull(destDT); + } else { + gfxUtils::ClipToRegion(dt, region.ToUnknownRegion()); + ctx = gfxContext::CreatePreservingTransformOrNull(dt); + } + MOZ_ASSERT(ctx); // checked both dt and destDT valid draw target above + +#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; + { + if (GetLayerManager()->GetBackendType() == LayersBackend::LAYERS_BASIC) { + GdkScreen *screen = gdk_window_get_screen(mGdkWindow); + if (GetTransparencyMode() == eTransparencyTransparent && + layerBuffering == BufferMode::BUFFER_NONE && + gdk_screen_is_composited(screen) && + gdk_window_get_visual(mGdkWindow) == + gdk_screen_get_rgba_visual(screen)) { + // If our draw target is unbuffered and we use an alpha channel, + // clear the image beforehand to ensure we don't get artifacts from a + // reused SHM image. See bug 1258086. + dt->ClearRect(Rect(boundsRect)); + } + AutoLayerManagerSetup setupLayerManager(this, ctx, layerBuffering); + painted = listener->PaintWindow(this, region); + + // Re-get the listener since the will paint notification might have + // killed it. + listener = GetListener(); + if (!listener) + return TRUE; + + } + } + +#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) { + RefPtr<SourceSurface> surf = ctx->GetDrawTarget()->Snapshot(); + + UpdateAlpha(surf, boundsRect); + + dt->DrawSurface(surf, Rect(boundsRect), Rect(0, 0, boundsRect.width, boundsRect.height), + DrawSurfaceOptions(SamplingFilter::POINT), + DrawOptions(1.0f, CompositionOp::OP_SOURCE)); + } + } + } + + ctx = nullptr; + dt->PopClip(); + +#endif // MOZ_X11 + + EndRemoteDrawingInRegion(dt, region); + + listener->DidPaintWindow(); + + // Synchronously flush any new dirty areas +#if (MOZ_WIDGET_GTK == 2) + 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 (MOZ_WIDGET_GTK == 2) + gdk_region_destroy(dirtyArea); +#else + cairo_region_destroy(dirtyArea); +#endif + gdk_window_process_updates(mGdkWindow, false); + } + + // check the return value! + return TRUE; +} + +void +nsWindow::UpdateAlpha(SourceSurface* aSourceSurface, nsIntRect aBoundsRect) +{ + // We need to create our own buffer to force the stride to match the + // expected stride. + int32_t stride = GetAlignedStride<4>(aBoundsRect.width, + BytesPerPixel(SurfaceFormat::A8)); + if (stride == 0) { + return; + } + int32_t bufferSize = stride * aBoundsRect.height; + auto imageBuffer = MakeUniqueFallible<uint8_t[]>(bufferSize); + { + RefPtr<DrawTarget> drawTarget = gfxPlatform::CreateDrawTargetForData( + imageBuffer.get(), + aBoundsRect.Size(), + stride, SurfaceFormat::A8); + + if (drawTarget) { + drawTarget->DrawSurface(aSourceSurface, Rect(0, 0, aBoundsRect.width, aBoundsRect.height), + Rect(0, 0, aSourceSurface->GetSize().width, aSourceSurface->GetSize().height), + DrawSurfaceOptions(SamplingFilter::POINT), + DrawOptions(1.0f, CompositionOp::OP_SOURCE)); + } + } + UpdateTranslucentWindowAlphaInternal(aBoundsRect, imageBuffer.get(), stride); +} + +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)); + + if (mPendingConfigures > 0) { + mPendingConfigures--; + } + + LayoutDeviceIntRect screenBounds = GetScreenBounds(); + + 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(). + + NS_ASSERTION(GTK_IS_WINDOW(aWidget), + "Configure event on widget that is not a GtkWindow"); + if (gtk_window_get_window_type(GTK_WINDOW(aWidget)) == 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? + NotifyWindowMoved(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", nullptr); + mGdkWindow = nullptr; + } +} + +void +nsWindow::OnSizeAllocate(GtkAllocation *aAllocation) +{ + LOG(("size_allocate [%p] %d %d %d %d\n", + (void *)this, aAllocation->x, aAllocation->y, + aAllocation->width, aAllocation->height)); + + LayoutDeviceIntSize size = GdkRectToDevicePixels(*aAllocation).Size(); + + 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 = DevicePixelsToGdkRectRoundOut( + LayoutDeviceIntRect(mBounds.width, 0, + size.width - mBounds.width, size.height)); + gdk_window_invalidate_rect(mGdkWindow, &rect, FALSE); + } + if (mBounds.height < size.height) { + GdkRectangle rect = DevicePixelsToGdkRectRoundOut( + LayoutDeviceIntRect(0, mBounds.height, + size.width, size.height - mBounds.height)); + gdk_window_invalidate_rect(mGdkWindow, &rect, FALSE); + } + + mBounds.SizeTo(size); + +#ifdef MOZ_X11 + // Notify the X11CompositorWidget of a ClientSizeChange + if (mCompositorWidgetDelegate) { + mCompositorWidgetDelegate->NotifyClientSizeChanged(GetClientSize()); + } +#endif + + // Gecko permits running nested event loops during processing of events, + // GtkWindow callers of gtk_widget_size_allocate expect the signal + // handlers to return sometime in the near future. + mNeedsDispatchResized = true; + NS_DispatchToCurrentThread(NewRunnableMethod(this, &nsWindow::MaybeDispatchResized)); +} + +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 != nullptr) + 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; + + WidgetMouseEvent event(true, eMouseEnterIntoWidget, this, + WidgetMouseEvent::eReal); + + event.mRefPoint = GdkEventCoordsToDevicePixels(aEvent->x, aEvent->y); + event.AssignEventTime(GetWidgetEventTime(aEvent->time)); + + LOG(("OnEnterNotify: %p\n", (void *)this)); + + DispatchInputEvent(&event); +} + +// 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 != nullptr) + return; + + WidgetMouseEvent event(true, eMouseExitFromWidget, this, + WidgetMouseEvent::eReal); + + event.mRefPoint = GdkEventCoordsToDevicePixels(aEvent->x, aEvent->y); + event.AssignEventTime(GetWidgetEventTime(aEvent->time)); + + event.mExitFrom = is_top_level_mouse_exit(mGdkWindow, aEvent) + ? WidgetMouseEvent::eTopLevel : WidgetMouseEvent::eChild; + + LOG(("OnLeaveNotify: %p\n", (void *)this)); + + DispatchInputEvent(&event); +} + +template <typename Event> static LayoutDeviceIntPoint +GetRefPoint(nsWindow* aWindow, Event* aEvent) +{ + if (aEvent->window == aWindow->GetGdkWindow()) { + // we are the window that the event happened on so no need for expensive WidgetToScreenOffset + return aWindow->GdkEventCoordsToDevicePixels(aEvent->x, aEvent->y); + } + // 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. + return aWindow->GdkEventCoordsToDevicePixels( + aEvent->x_root, aEvent->y_root) - aWindow->WidgetToScreenOffset(); +} + +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; + + if (mIsX11Display) { + 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 (MOZ_WIDGET_GTK == 2) + // if plugins still keeps the focus, get it back + if (gPluginFocusWindow && gPluginFocusWindow != this) { + RefPtr<nsWindow> kungFuDeathGrip = gPluginFocusWindow; + gPluginFocusWindow->LoseNonXEmbedPluginFocus(); + } +#endif /* MOZ_WIDGET_GTK == 2 */ + } +#endif /* MOZ_X11 */ + + WidgetMouseEvent event(true, eMouseMove, this, WidgetMouseEvent::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.mRefPoint.x = nscoord(xevent.xmotion.x); + event.mRefPoint.y = nscoord(xevent.xmotion.y); + + modifierState = xevent.xmotion.state; + + event.AssignEventTime(GetWidgetEventTime(xevent.xmotion.time)); +#else + event.mRefPoint = GdkEventCoordsToDevicePixels(aEvent->x, aEvent->y); + + modifierState = aEvent->state; + + event.AssignEventTime(GetWidgetEventTime(aEvent->time)); +#endif /* MOZ_X11 */ + } else { + event.mRefPoint = GetRefPoint(this, aEvent); + + modifierState = aEvent->state; + + event.AssignEventTime(GetWidgetEventTime(aEvent->time)); + } + + KeymapWrapper::InitInputEvent(event, modifierState); + + DispatchInputEvent(&event); +} + +// 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 = WidgetMouseEvent::eLeftButton; + break; + case GDK_BUTTON2_MASK: + buttonType = WidgetMouseEvent::eMiddleButton; + break; + default: + NS_ASSERTION(buttonMask == GDK_BUTTON3_MASK, + "Unexpected button mask"); + buttonType = WidgetMouseEvent::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. + WidgetMouseEvent synthEvent(true, eMouseUp, this, + WidgetMouseEvent::eSynthesized); + synthEvent.button = buttonType; + DispatchInputEvent(&synthEvent); + } + } +} + +void +nsWindow::InitButtonEvent(WidgetMouseEvent& aEvent, + GdkEventButton* aGdkEvent) +{ + aEvent.mRefPoint = GetRefPoint(this, aGdkEvent); + + guint modifierState = aGdkEvent->state; + // aEvent's state includes the button state from immediately before this + // event. If aEvent is a mousedown or mouseup event, we need to update + // the button state. + guint buttonMask = 0; + switch (aGdkEvent->button) { + case 1: + buttonMask = GDK_BUTTON1_MASK; + break; + case 2: + buttonMask = GDK_BUTTON2_MASK; + break; + case 3: + buttonMask = GDK_BUTTON3_MASK; + break; + } + if (aGdkEvent->type == GDK_BUTTON_RELEASE) { + modifierState &= ~buttonMask; + } else { + modifierState |= buttonMask; + } + + KeymapWrapper::InitInputEvent(aEvent, modifierState); + + aEvent.AssignEventTime(GetWidgetEventTime(aGdkEvent->time)); + + switch (aGdkEvent->type) { + case GDK_2BUTTON_PRESS: + aEvent.mClickCount = 2; + break; + case GDK_3BUTTON_PRESS: + aEvent.mClickCount = 3; + break; + // default is one click + default: + aEvent.mClickCount = 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)); + + // 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 = WidgetMouseEvent::eLeftButton; + break; + case 2: + domButton = WidgetMouseEvent::eMiddleButton; + break; + case 3: + domButton = WidgetMouseEvent::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); + + WidgetMouseEvent event(true, eMouseDown, this, WidgetMouseEvent::eReal); + event.button = domButton; + InitButtonEvent(event, aEvent); + event.pressure = mLastMotionPressure; + + DispatchInputEvent(&event); + + // right menu click on linux should also pop up a context menu + if (domButton == WidgetMouseEvent::eRightButton && + MOZ_LIKELY(!mIsDestroyed)) { + WidgetMouseEvent contextMenuEvent(true, eContextMenu, this, + WidgetMouseEvent::eReal); + InitButtonEvent(contextMenuEvent, aEvent); + contextMenuEvent.pressure = mLastMotionPressure; + DispatchInputEvent(&contextMenuEvent); + } +} + +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 = WidgetMouseEvent::eLeftButton; + break; + case 2: + domButton = WidgetMouseEvent::eMiddleButton; + break; + case 3: + domButton = WidgetMouseEvent::eRightButton; + break; + default: + return; + } + + gButtonState &= ~ButtonMaskFromGDKButton(aEvent->button); + + WidgetMouseEvent event(true, eMouseUp, this, + WidgetMouseEvent::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; + + DispatchInputEvent(&event); + mLastMotionPressure = pressure; +} + +void +nsWindow::OnContainerFocusInEvent(GdkEventFocus *aEvent) +{ + 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-nullptr 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 (MOZ_WIDGET_GTK == 2) && defined(MOZ_X11) + // plugin lose focus + if (gPluginFocusWindow) { + RefPtr<nsWindow> kungFuDeathGrip = gPluginFocusWindow; + gPluginFocusWindow->LoseNonXEmbedPluginFocus(); + } +#endif /* MOZ_X11 && MOZ_WIDGET_GTK == 2 */ + + if (gFocusWindow) { + RefPtr<nsWindow> kungFuDeathGrip = gFocusWindow; + if (gFocusWindow->mIMContext) { + gFocusWindow->mIMContext->OnBlurWindow(gFocusWindow); + } + gFocusWindow = nullptr; + } + + DispatchDeactivateEvent(); + + LOGFOCUS(("Done with container focus out [%p]\n", (void *)this)); +} + +bool +nsWindow::DispatchCommandEvent(nsIAtom* aCommand) +{ + nsEventStatus status; + WidgetCommandEvent event(true, nsGkAtoms::onAppCommand, aCommand, this); + DispatchEvent(&event, status); + return TRUE; +} + +bool +nsWindow::DispatchContentCommandEvent(EventMessage aMsg) +{ + nsEventStatus status; + WidgetContentCommandEvent 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; + } + + RefPtr<TextEventDispatcher> dispatcher = GetTextEventDispatcher(); + nsresult rv = dispatcher->BeginNativeInputTransaction(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return FALSE; + } + + WidgetKeyboardEvent keydownEvent(true, eKeyDown, this); + KeymapWrapper::InitKeyEvent(keydownEvent, aEvent); + nsEventStatus status = nsEventStatus_eIgnore; + bool dispatched = + dispatcher->DispatchKeyboardEvent(eKeyDown, keydownEvent, + status, aEvent); + *aCancelled = (status == nsEventStatus_eConsumeNoDefault); + return dispatched ? TRUE : FALSE; +} + +WidgetEventTime +nsWindow::GetWidgetEventTime(guint32 aEventTime) +{ + return WidgetEventTime(aEventTime, GetEventTimeStamp(aEventTime)); +} + +TimeStamp +nsWindow::GetEventTimeStamp(guint32 aEventTime) +{ + if (MOZ_UNLIKELY(!mGdkWindow)) { + // nsWindow has been Destroy()ed. + return TimeStamp::Now(); + } + if (aEventTime == 0) { + // Some X11 and GDK events may be received with a time of 0 to indicate + // that they are synthetic events. Some input method editors do this. + // In this case too, just return the current timestamp. + return TimeStamp::Now(); + } + CurrentX11TimeGetter* getCurrentTime = GetCurrentTimeGetter(); + MOZ_ASSERT(getCurrentTime, + "Null current time getter despite having a window"); + return TimeConverter().GetTimeStampFromSystemTime(aEventTime, + *getCurrentTime); +} + +mozilla::CurrentX11TimeGetter* +nsWindow::GetCurrentTimeGetter() { + MOZ_ASSERT(mGdkWindow, "Expected mGdkWindow to be set"); + if (MOZ_UNLIKELY(!mCurrentTimeGetter)) { + mCurrentTimeGetter = MakeUnique<CurrentX11TimeGetter>(mGdkWindow); + } + return mCurrentTimeGetter.get(); +} + +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. + // FYI: Don't dispatch keydown event before notifying IME of the event + // because IME may send a key event synchronously and consume the + // original event. + bool IMEWasEnabled = false; + if (mIMContext) { + IMEWasEnabled = mIMContext->IsEnabled(); + if (mIMContext->OnKeyEvent(this, aEvent)) { + return TRUE; + } + } + + // 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) || isKeyDownCancelled)) { + 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 && mIMContext && mIMContext->IsEnabled()) { + // Notice our keydown event was already dispatched. This prevents + // unnecessary DOM keydown event in the editor. + if (mIMContext->OnKeyEvent(this, aEvent, true)) { + return TRUE; + } + } + + // Look for specialized app-command keys + switch (aEvent->keyval) { + case GDK_Back: + return DispatchCommandEvent(nsGkAtoms::Back); + case GDK_Forward: + return DispatchCommandEvent(nsGkAtoms::Forward); + case GDK_Refresh: + return DispatchCommandEvent(nsGkAtoms::Reload); + case GDK_Stop: + return DispatchCommandEvent(nsGkAtoms::Stop); + case GDK_Search: + return DispatchCommandEvent(nsGkAtoms::Search); + case GDK_Favorites: + return DispatchCommandEvent(nsGkAtoms::Bookmarks); + case GDK_HomePage: + return DispatchCommandEvent(nsGkAtoms::Home); + case GDK_Copy: + case GDK_F16: // F16, F20, F18, F14 are old keysyms for Copy Cut Paste Undo + return DispatchContentCommandEvent(eContentCommandCopy); + case GDK_Cut: + case GDK_F20: + return DispatchContentCommandEvent(eContentCommandCut); + case GDK_Paste: + case GDK_F18: + return DispatchContentCommandEvent(eContentCommandPaste); + case GDK_Redo: + return DispatchContentCommandEvent(eContentCommandRedo); + case GDK_Undo: + case GDK_F14: + return DispatchContentCommandEvent(eContentCommandUndo); + } + + WidgetKeyboardEvent keypressEvent(true, eKeyPress, this); + KeymapWrapper::InitKeyEvent(keypressEvent, aEvent); + + // 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(keypressEvent)) { + WidgetMouseEvent contextMenuEvent(true, eContextMenu, this, + WidgetMouseEvent::eReal, + WidgetMouseEvent::eContextMenuKey); + + contextMenuEvent.mRefPoint = LayoutDeviceIntPoint(0, 0); + contextMenuEvent.AssignEventTime(GetWidgetEventTime(aEvent->time)); + contextMenuEvent.mClickCount = 1; + KeymapWrapper::InitInputEvent(contextMenuEvent, aEvent->state); + DispatchInputEvent(&contextMenuEvent); + } else { + RefPtr<TextEventDispatcher> dispatcher = GetTextEventDispatcher(); + nsresult rv = dispatcher->BeginNativeInputTransaction(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return TRUE; + } + + // If the character code is in the BMP, send the key press event. + // Otherwise, send a compositionchange event with the equivalent UTF-16 + // string. + // TODO: Investigate other browser's behavior in this case because + // this hack is odd for UI Events. + nsEventStatus status = nsEventStatus_eIgnore; + if (keypressEvent.mKeyNameIndex != KEY_NAME_INDEX_USE_STRING || + keypressEvent.mKeyValue.Length() == 1) { + dispatcher->MaybeDispatchKeypressEvents(keypressEvent, + status, aEvent); + } else { + WidgetEventTime eventTime = GetWidgetEventTime(aEvent->time); + dispatcher->CommitComposition(status, &keypressEvent.mKeyValue, + &eventTime); + } + } + + return TRUE; +} + +gboolean +nsWindow::OnKeyReleaseEvent(GdkEventKey *aEvent) +{ + LOGFOCUS(("OnKeyReleaseEvent [%p]\n", (void *)this)); + + if (mIMContext && mIMContext->OnKeyEvent(this, aEvent)) { + return TRUE; + } + + RefPtr<TextEventDispatcher> dispatcher = GetTextEventDispatcher(); + nsresult rv = dispatcher->BeginNativeInputTransaction(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return false; + } + + WidgetKeyboardEvent keyupEvent(true, eKeyUp, this); + KeymapWrapper::InitKeyEvent(keyupEvent, aEvent); + nsEventStatus status = nsEventStatus_eIgnore; + dispatcher->DispatchKeyboardEvent(eKeyUp, keyupEvent, status, aEvent); + + return TRUE; +} + +void +nsWindow::OnScrollEvent(GdkEventScroll *aEvent) +{ + // check to see if we should rollup + if (CheckForRollup(aEvent->x_root, aEvent->y_root, true, false)) + return; +#if GTK_CHECK_VERSION(3,4,0) + // check for duplicate legacy scroll event, see GNOME bug 726878 + if (aEvent->direction != GDK_SCROLL_SMOOTH && + mLastScrollEventTime == aEvent->time) + return; +#endif + WidgetWheelEvent wheelEvent(true, eWheel, this); + wheelEvent.mDeltaMode = nsIDOMWheelEvent::DOM_DELTA_LINE; + switch (aEvent->direction) { +#if GTK_CHECK_VERSION(3,4,0) + case GDK_SCROLL_SMOOTH: + { + // As of GTK 3.4, all directional scroll events are provided by + // the GDK_SCROLL_SMOOTH direction on XInput2 devices. + mLastScrollEventTime = aEvent->time; + // TODO - use a more appropriate scrolling unit than lines. + // Multiply event deltas by 3 to emulate legacy behaviour. + wheelEvent.mDeltaX = aEvent->delta_x * 3; + wheelEvent.mDeltaY = aEvent->delta_y * 3; + wheelEvent.mIsNoLineOrPageDelta = true; + // This next step manually unsets smooth scrolling for touch devices + // that trigger GDK_SCROLL_SMOOTH. We use the slave device, which + // represents the actual input. + GdkDevice *device = gdk_event_get_source_device((GdkEvent*)aEvent); + GdkInputSource source = gdk_device_get_source(device); + if (source == GDK_SOURCE_TOUCHSCREEN || + source == GDK_SOURCE_TOUCHPAD) { + wheelEvent.mScrollType = WidgetWheelEvent::SCROLL_ASYNCHRONOUSELY; + } + break; + } +#endif + case GDK_SCROLL_UP: + wheelEvent.mDeltaY = wheelEvent.mLineOrPageDeltaY = -3; + break; + case GDK_SCROLL_DOWN: + wheelEvent.mDeltaY = wheelEvent.mLineOrPageDeltaY = 3; + break; + case GDK_SCROLL_LEFT: + wheelEvent.mDeltaX = wheelEvent.mLineOrPageDeltaX = -1; + break; + case GDK_SCROLL_RIGHT: + wheelEvent.mDeltaX = wheelEvent.mLineOrPageDeltaX = 1; + break; + } + + wheelEvent.mRefPoint = GetRefPoint(this, aEvent); + + KeymapWrapper::InitInputEvent(wheelEvent, aEvent->state); + + wheelEvent.AssignEventTime(GetWidgetEventTime(aEvent->time)); + + DispatchInputEvent(&wheelEvent); +} + +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, nullptr, FALSE); + } + + mIsFullyObscured = false; + + // 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); + if (aEvent->changed_mask & GDK_WINDOW_STATE_FULLSCREEN) { + mWidgetListener->FullscreenChanged( + aEvent->new_window_state & GDK_WINDOW_STATE_FULLSCREEN); + } + } +} + +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 + RefPtr<nsWindow> kungFuDeathGrip = win; + win->ThemeChanged(); + } + + children = children->next; + } +} + +void +nsWindow::OnDPIChanged() +{ + if (mWidgetListener) { + nsIPresShell* presShell = mWidgetListener->GetPresShell(); + if (presShell) { + presShell->BackingScaleFactorChanged(); + // Update menu's font size etc + presShell->ThemeChanged(); + } + } +} + +void +nsWindow::OnCheckResize() +{ + mPendingConfigures++; +} + +void +nsWindow::DispatchDragEvent(EventMessage aMsg, const LayoutDeviceIntPoint& aRefPoint, + guint aTime) +{ + WidgetDragEvent event(true, aMsg, this); + + if (aMsg == eDragOver) { + InitDragEvent(event); + } + + event.mRefPoint = aRefPoint; + event.AssignEventTime(GetWidgetEventTime(aTime)); + + DispatchInputEvent(&event); +} + +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); +} + +#if GTK_CHECK_VERSION(3,4,0) +gboolean +nsWindow::OnTouchEvent(GdkEventTouch* aEvent) +{ + if (!mHandleTouchEvent) { + return FALSE; + } + + EventMessage msg; + switch (aEvent->type) { + case GDK_TOUCH_BEGIN: + msg = eTouchStart; + break; + case GDK_TOUCH_UPDATE: + msg = eTouchMove; + break; + case GDK_TOUCH_END: + msg = eTouchEnd; + break; + case GDK_TOUCH_CANCEL: + msg = eTouchCancel; + break; + default: + return FALSE; + } + + LayoutDeviceIntPoint touchPoint = GetRefPoint(this, aEvent); + + int32_t id; + RefPtr<dom::Touch> touch; + if (mTouches.Remove(aEvent->sequence, getter_AddRefs(touch))) { + id = touch->mIdentifier; + } else { + id = ++gLastTouchID & 0x7FFFFFFF; + } + + touch = new dom::Touch(id, touchPoint, LayoutDeviceIntPoint(1, 1), + 0.0f, 0.0f); + + WidgetTouchEvent event(true, msg, this); + KeymapWrapper::InitInputEvent(event, aEvent->state); + event.mTime = aEvent->time; + + if (aEvent->type == GDK_TOUCH_BEGIN || aEvent->type == GDK_TOUCH_UPDATE) { + mTouches.Put(aEvent->sequence, touch.forget()); + // add all touch points to event object + for (auto iter = mTouches.Iter(); !iter.Done(); iter.Next()) { + event.mTouches.AppendElement(new dom::Touch(*iter.UserData())); + } + } else if (aEvent->type == GDK_TOUCH_END || + aEvent->type == GDK_TOUCH_CANCEL) { + *event.mTouches.AppendElement() = touch.forget(); + } + + DispatchInputEvent(&event); + return TRUE; +} +#endif + +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( + u"brandShortName", + getter_Copies(brandName)); + + if (brandName.IsEmpty()) + brandName.AssignLiteral(u"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 (MOZ_WIDGET_GTK == 2) + 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 (MOZ_WIDGET_GTK == 2) + /* 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, nullptr, FALSE); +#endif + + return window; +} + +nsresult +nsWindow::Create(nsIWidget* aParent, + nsNativeWidget aNativeParent, + const LayoutDeviceIntRect& aRect, + 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, 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; + bool shellHasCSD = false; + + 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; + + // 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 = + mWindowType != eWindowType_popup || aInitData->mNoAutoHide ? + GTK_WINDOW_TOPLEVEL : GTK_WINDOW_POPUP; + mShell = gtk_window_new(type); + + // 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. + NativeResize(); + + if (mWindowType == eWindowType_dialog) { + 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). + NativeMove(); + + 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 (MOZ_WIDGET_GTK == 2) + 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, nullptr); +#endif + } + + GdkWindowTypeHint gtkTypeHint; + if (aInitData->mIsDragPopup) { + gtkTypeHint = GDK_WINDOW_TYPE_HINT_DND; + mIsDragPopup = true; + } + 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 + 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); + } + + // Create a container to hold child windows and child GtkWidgets. + GtkWidget *container = moz_container_new(); + mContainer = MOZ_CONTAINER(container); + +#if (MOZ_WIDGET_GTK == 3) + // "csd" style is set when widget is realized so we need to call + // it explicitly now. + gtk_widget_realize(mShell); + + // We can't draw directly to top-level window when client side + // decorations are enabled. We use container with GdkWindow instead. + GtkStyleContext* style = gtk_widget_get_style_context(mShell); + shellHasCSD = gtk_style_context_has_class(style, "csd"); +#endif + if (!shellHasCSD) { + // Use mShell's window for drawing and events. + gtk_widget_set_has_window(container, FALSE); + // Prevent GtkWindow from painting a background to flicker. + gtk_widget_set_app_paintable(mShell, TRUE); + } + // Set up event widget + eventWidget = shellHasCSD ? container : 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(eventWidget); + + 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(mGdkWindow, (GdkWMDecoration) wmd); + } + + // If the popup ignores mouse events, set an empty input shape. + if (aInitData->mMouseTransparent) { +#if (MOZ_WIDGET_GTK == 2) + GdkRectangle rect = { 0, 0, 0, 0 }; + GdkRegion *region = gdk_region_rectangle(&rect); + + gdk_window_input_shape_combine_region(mGdkWindow, region, 0, 0); + gdk_region_destroy(region); +#else + cairo_rectangle_int_t rect = { 0, 0, 0, 0 }; + cairo_region_t *region = cairo_region_create_rectangle(&rect); + + gdk_window_input_shape_combine_region(mGdkWindow, region, 0, 0); + cairo_region_destroy(region); +#endif + } + } + } + break; + case eWindowType_plugin: + case eWindowType_plugin_ipc_chrome: + case eWindowType_plugin_ipc_content: + 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), nullptr); + g_signal_connect(mShell, "delete_event", + G_CALLBACK(delete_event_cb), nullptr); + g_signal_connect(mShell, "window_state_event", + G_CALLBACK(window_state_event_cb), nullptr); + g_signal_connect(mShell, "check-resize", + G_CALLBACK(check_resize_cb), nullptr); + + 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); + } + + if (mContainer) { + // Widget signals + g_signal_connect(mContainer, "unrealize", + G_CALLBACK(container_unrealize_cb), nullptr); + g_signal_connect_after(mContainer, "size_allocate", + G_CALLBACK(size_allocate_cb), nullptr); + g_signal_connect(mContainer, "hierarchy-changed", + G_CALLBACK(hierarchy_changed_cb), nullptr); +#if (MOZ_WIDGET_GTK == 3) + g_signal_connect(mContainer, "notify::scale-factor", + G_CALLBACK(scale_changed_cb), nullptr); +#endif + // Initialize mHasMappedToplevel. + hierarchy_changed_cb(GTK_WIDGET(mContainer), nullptr); + // Expose, focus, key, and drag events are sent even to GTK_NO_WINDOW + // widgets. +#if (MOZ_WIDGET_GTK == 2) + g_signal_connect(mContainer, "expose_event", + G_CALLBACK(expose_event_cb), nullptr); +#else + g_signal_connect(G_OBJECT(mContainer), "draw", + G_CALLBACK(expose_event_cb), nullptr); +#endif + g_signal_connect(mContainer, "focus_in_event", + G_CALLBACK(focus_in_event_cb), nullptr); + g_signal_connect(mContainer, "focus_out_event", + G_CALLBACK(focus_out_event_cb), nullptr); + g_signal_connect(mContainer, "key_press_event", + G_CALLBACK(key_press_event_cb), nullptr); + g_signal_connect(mContainer, "key_release_event", + G_CALLBACK(key_release_event_cb), nullptr); + + gtk_drag_dest_set((GtkWidget *)mContainer, + (GtkDestDefaults)0, + nullptr, + 0, + (GdkDragAction)0); + + g_signal_connect(mContainer, "drag_motion", + G_CALLBACK(drag_motion_event_cb), nullptr); + g_signal_connect(mContainer, "drag_leave", + G_CALLBACK(drag_leave_event_cb), nullptr); + g_signal_connect(mContainer, "drag_drop", + G_CALLBACK(drag_drop_event_cb), nullptr); + g_signal_connect(mContainer, "drag_data_received", + G_CALLBACK(drag_data_received_event_cb), nullptr); + + GtkWidget *widgets[] = { GTK_WIDGET(mContainer), + !shellHasCSD ? mShell : nullptr }; + 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), nullptr); + // 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) { + mIMContext = new IMContextWrapper(this); + } + } else if (!mIMContext) { + nsWindow *container = GetContainerWindow(); + if (container) { + mIMContext = container->mIMContext; + } + } + + if (eventWidget) { +#if (MOZ_WIDGET_GTK == 2) + // Don't let GTK mess with the shapes of our GdkWindows + GTK_PRIVATE_SET_FLAG(eventWidget, GTK_HAS_SHAPE_MASK); +#endif + + // 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), nullptr); + g_signal_connect(eventWidget, "leave-notify-event", + G_CALLBACK(leave_notify_event_cb), nullptr); + g_signal_connect(eventWidget, "motion-notify-event", + G_CALLBACK(motion_notify_event_cb), nullptr); + g_signal_connect(eventWidget, "button-press-event", + G_CALLBACK(button_press_event_cb), nullptr); + g_signal_connect(eventWidget, "button-release-event", + G_CALLBACK(button_release_event_cb), nullptr); + g_signal_connect(eventWidget, "property-notify-event", + G_CALLBACK(property_notify_event_cb), nullptr); + g_signal_connect(eventWidget, "scroll-event", + G_CALLBACK(scroll_event_cb), nullptr); +#if GTK_CHECK_VERSION(3,4,0) + g_signal_connect(eventWidget, "touch-event", + G_CALLBACK(touch_event_cb), nullptr); +#endif + } + + 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); + +#ifdef MOZ_X11 + if (mIsX11Display && mGdkWindow) { + mXDisplay = GDK_WINDOW_XDISPLAY(mGdkWindow); + mXWindow = gdk_x11_window_get_xid(mGdkWindow); + + GdkVisual* gdkVisual = gdk_window_get_visual(mGdkWindow); + mXVisual = gdk_x11_visual_get_xvisual(gdkVisual); + mXDepth = gdk_visual_get_depth(gdkVisual); + + mSurfaceProvider.Initialize(mXDisplay, mXWindow, mXVisual, mXDepth); + } +#endif + + return NS_OK; +} + +void +nsWindow::SetWindowClass(const nsAString &xulWinType) +{ + if (!mShell) + return; + + const char *res_class = gdk_get_program_class(); + if (!res_class) + return; + + char *res_name = ToNewCString(xulWinType); + if (!res_name) + return; + + const char *role = nullptr; + + // 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; + + gdk_window_set_role(mGdkWindow, role); + +#ifdef MOZ_X11 + if (mIsX11Display) { + XClassHint *class_hint = XAllocClassHint(); + if (!class_hint) { + free(res_name); + return; + } + 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. + GdkDisplay *display = gdk_display_get_default(); + XSetClassHint(GDK_DISPLAY_XDISPLAY(display), + gdk_x11_window_get_xid(mGdkWindow), + class_hint); + XFree(class_hint); + } +#endif /* MOZ_X11 */ + + free(res_name); +} + +void +nsWindow::NativeResize() +{ + if (!AreBoundsSane()) { + // 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 && mIsShown) { + mNeedsShow = true; + NativeShow(false); + } + return; + } + + GdkRectangle size = DevicePixelsToGdkSizeRoundUp(mBounds.Size()); + + LOG(("nsWindow::NativeResize [%p] %d %d\n", (void *)this, + size.width, size.height)); + + if (mIsTopLevel) { + gtk_window_resize(GTK_WINDOW(mShell), size.width, size.height); + } + 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 = size.width; + allocation.height = size.height; + gtk_widget_size_allocate(widget, &allocation); + } + else if (mGdkWindow) { + gdk_window_resize(mGdkWindow, size.width, size.height); + } + +#ifdef MOZ_X11 + // Notify the X11CompositorWidget of a ClientSizeChange + // This is different than OnSizeAllocate to catch initial sizing + if (mCompositorWidgetDelegate) { + mCompositorWidgetDelegate->NotifyClientSizeChanged(GetClientSize()); + } +#endif + + // Does it need to be shown because bounds were previously insane? + if (mNeedsShow && mIsShown) { + NativeShow(true); + } +} + +void +nsWindow::NativeMoveResize() +{ + if (!AreBoundsSane()) { + // 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 && mIsShown) { + mNeedsShow = true; + NativeShow(false); + } + NativeMove(); + } + + GdkRectangle size = DevicePixelsToGdkSizeRoundUp(mBounds.Size()); + GdkPoint topLeft = DevicePixelsToGdkPointRoundDown(mBounds.TopLeft()); + + LOG(("nsWindow::NativeMoveResize [%p] %d %d %d %d\n", (void *)this, + topLeft.x, topLeft.y, size.width, size.height)); + + if (mIsTopLevel) { + // x and y give the position of the window manager frame top-left. + gtk_window_move(GTK_WINDOW(mShell), topLeft.x, topLeft.y); + // This sets the client window size. + gtk_window_resize(GTK_WINDOW(mShell), size.width, size.height); + } + else if (mContainer) { + GtkAllocation allocation; + allocation.x = topLeft.x; + allocation.y = topLeft.y; + allocation.width = size.width; + allocation.height = size.height; + gtk_widget_size_allocate(GTK_WIDGET(mContainer), &allocation); + } + else if (mGdkWindow) { + gdk_window_move_resize(mGdkWindow, + topLeft.x, topLeft.y, size.width, size.height); + } + +#ifdef MOZ_X11 + // Notify the X11CompositorWidget of a ClientSizeChange + // This is different than OnSizeAllocate to catch initial sizing + if (mCompositorWidgetDelegate) { + mCompositorWidgetDelegate->NotifyClientSizeChanged(GetClientSize()); + } +#endif + + // Does it need to be shown because bounds were previously insane? + if (mNeedsShow && mIsShown) { + NativeShow(true); + } +} + +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) { + // Workaround window freezes on GTK versions before 3.21.2 by + // ensuring that configure events get dispatched to windows before + // they are unmapped. See bug 1225044. + if (gtk_check_version(3, 21, 2) != nullptr && mPendingConfigures > 0) { + GtkAllocation allocation; + gtk_widget_get_allocation(GTK_WIDGET(mShell), &allocation); + + GdkEventConfigure event; + PodZero(&event); + event.type = GDK_CONFIGURE; + event.window = mGdkWindow; + event.send_event = TRUE; + event.x = allocation.x; + event.y = allocation.y; + event.width = allocation.width; + event.height = allocation.height; + + auto shellClass = GTK_WIDGET_GET_CLASS(mShell); + for (unsigned int i = 0; i < mPendingConfigures; i++) { + Unused << shellClass->configure_event(mShell, &event); + } + mPendingConfigures = 0; + } + + gtk_widget_hide(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, nullptr, 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); + } + } +} + +LayoutDeviceIntSize +nsWindow::GetSafeWindowSize(LayoutDeviceIntSize 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. + LayoutDeviceIntSize 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) +{ + // If this is a remotely updated widget we receive clipping, position, and + // size information from a source other than our owner. Don't let our parent + // update this information. + if (mWindowType == eWindowType_plugin_ipc_chrome) { + return NS_OK; + } + + for (uint32_t i = 0; i < aConfigurations.Length(); ++i) { + const Configuration& configuration = aConfigurations[i]; + nsWindow* w = static_cast<nsWindow*>(configuration.mChild.get()); + 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; +} + +nsresult +nsWindow::SetWindowClipRegion(const nsTArray<LayoutDeviceIntRect>& aRects, + bool aIntersectWithExisting) +{ + const nsTArray<LayoutDeviceIntRect>* newRects = &aRects; + + AutoTArray<LayoutDeviceIntRect,1> intersectRects; + if (aIntersectWithExisting) { + AutoTArray<LayoutDeviceIntRect,1> existingRects; + GetWindowClipRegion(&existingRects); + + LayoutDeviceIntRegion existingRegion = RegionFromArray(existingRects); + LayoutDeviceIntRegion newRegion = RegionFromArray(aRects); + LayoutDeviceIntRegion intersectRegion; + intersectRegion.And(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 && intersectRegion.IsEqual(existingRegion)) { + return NS_OK; + } + + if (!intersectRegion.IsEqual(newRegion)) { + ArrayFromRegion(intersectRegion, intersectRects); + newRects = &intersectRects; + } + } + + if (IsWindowClipRegionEqual(*newRects)) + return NS_OK; + + StoreWindowClipRegion(*newRects); + + if (!mGdkWindow) + return NS_OK; + +#if (MOZ_WIDGET_GTK == 2) + GdkRegion *region = gdk_region_new(); // aborts on OOM + for (uint32_t i = 0; i < newRects->Length(); ++i) { + const LayoutDeviceIntRect& 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 LayoutDeviceIntRect& 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 NS_OK; +} + +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. + Display* xDisplay = GDK_WINDOW_XDISPLAY(mGdkWindow); + Window xDrawable = GDK_WINDOW_XID(mGdkWindow); + Pixmap maskPixmap = XCreateBitmapFromData(xDisplay, + xDrawable, + mTransparencyBitmap, + mTransparencyBitmapWidth, + mTransparencyBitmapHeight); + XShapeCombineMask(xDisplay, xDrawable, + ShapeBounding, 0, 0, + maskPixmap, ShapeSet); + XFreePixmap(xDisplay, maskPixmap); +#else +#if (MOZ_WIDGET_GTK == 2) + gtk_widget_reset_shapes(mShell); + GdkBitmap* maskBitmap = gdk_bitmap_create_from_data(mGdkWindow, + 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_GTK == 2 +#endif // MOZ_X11 +} + +void +nsWindow::ClearTransparencyBitmap() +{ + if (!mTransparencyBitmap) + return; + + delete[] mTransparencyBitmap; + mTransparencyBitmap = nullptr; + mTransparencyBitmapWidth = 0; + mTransparencyBitmapHeight = 0; + + if (!mShell) + return; + +#ifdef MOZ_X11 + if (!mGdkWindow) + return; + + Display* xDisplay = GDK_WINDOW_XDISPLAY(mGdkWindow); + Window xWindow = gdk_x11_window_get_xid(mGdkWindow); + + XShapeCombineMask(xDisplay, xWindow, ShapeBounding, 0, 0, X11None, 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 | + GDK_POINTER_MOTION_MASK), + (GdkWindow *)nullptr, nullptr, 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. Do this with an event so that + // popups don't rollup while potentially adjusting the grab for + // this popup. + nsCOMPtr<nsIRunnable> event = + NewRunnableMethod(this, &nsWindow::CheckForRollupDuringGrab); + NS_DispatchToCurrentThread(event.forget()); + } +} + +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 nullptr; + + 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) { + RefPtr<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 (MOZ_WIDGET_GTK == 2) + 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(); +#if (MOZ_WIDGET_GTK == 3) + gdk_error_trap_pop_ignored(); +#else + gdk_error_trap_pop(); +#endif + gPluginFocusWindow = this; + gdk_window_add_filter(nullptr, 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(); +#if (MOZ_WIDGET_GTK == 3) + gdk_error_trap_pop_ignored(); +#else + gdk_error_trap_pop(); +#endif + } + gPluginFocusWindow = nullptr; + mOldFocusWindow = 0; + gdk_window_remove_filter(nullptr, 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; +} + +class FullscreenTransitionWindow final : public nsISupports +{ +public: + NS_DECL_ISUPPORTS + + explicit FullscreenTransitionWindow(GtkWidget* aWidget); + + GtkWidget* mWindow; + +private: + ~FullscreenTransitionWindow(); +}; + +NS_IMPL_ISUPPORTS0(FullscreenTransitionWindow) + +FullscreenTransitionWindow::FullscreenTransitionWindow(GtkWidget* aWidget) +{ + mWindow = gtk_window_new(GTK_WINDOW_POPUP); + GtkWindow* gtkWin = GTK_WINDOW(mWindow); + + gtk_window_set_type_hint(gtkWin, GDK_WINDOW_TYPE_HINT_SPLASHSCREEN); + gtk_window_set_transient_for(gtkWin, GTK_WINDOW(aWidget)); + gtk_window_set_decorated(gtkWin, false); + + GdkWindow* gdkWin = gtk_widget_get_window(aWidget); + GdkScreen* screen = gtk_widget_get_screen(aWidget); + gint monitorNum = gdk_screen_get_monitor_at_window(screen, gdkWin); + GdkRectangle monitorRect; + gdk_screen_get_monitor_geometry(screen, monitorNum, &monitorRect); + gtk_window_set_screen(gtkWin, screen); + gtk_window_move(gtkWin, monitorRect.x, monitorRect.y); + gtk_window_resize(gtkWin, monitorRect.width, monitorRect.height); + + GdkColor bgColor; + bgColor.red = bgColor.green = bgColor.blue = 0; + gtk_widget_modify_bg(mWindow, GTK_STATE_NORMAL, &bgColor); + + gtk_window_set_opacity(gtkWin, 0.0); + gtk_widget_show(mWindow); +} + +FullscreenTransitionWindow::~FullscreenTransitionWindow() +{ + gtk_widget_destroy(mWindow); +} + +class FullscreenTransitionData +{ +public: + FullscreenTransitionData(nsIWidget::FullscreenTransitionStage aStage, + uint16_t aDuration, nsIRunnable* aCallback, + FullscreenTransitionWindow* aWindow) + : mStage(aStage) + , mStartTime(TimeStamp::Now()) + , mDuration(TimeDuration::FromMilliseconds(aDuration)) + , mCallback(aCallback) + , mWindow(aWindow) { } + + static const guint sInterval = 1000 / 30; // 30fps + static gboolean TimeoutCallback(gpointer aData); + +private: + nsIWidget::FullscreenTransitionStage mStage; + TimeStamp mStartTime; + TimeDuration mDuration; + nsCOMPtr<nsIRunnable> mCallback; + RefPtr<FullscreenTransitionWindow> mWindow; +}; + +/* static */ gboolean +FullscreenTransitionData::TimeoutCallback(gpointer aData) +{ + bool finishing = false; + auto data = static_cast<FullscreenTransitionData*>(aData); + gdouble opacity = (TimeStamp::Now() - data->mStartTime) / data->mDuration; + if (opacity >= 1.0) { + opacity = 1.0; + finishing = true; + } + if (data->mStage == nsIWidget::eAfterFullscreenToggle) { + opacity = 1.0 - opacity; + } + gtk_window_set_opacity(GTK_WINDOW(data->mWindow->mWindow), opacity); + + if (!finishing) { + return TRUE; + } + NS_DispatchToMainThread(data->mCallback.forget()); + delete data; + return FALSE; +} + +/* virtual */ bool +nsWindow::PrepareForFullscreenTransition(nsISupports** aData) +{ + GdkScreen* screen = gtk_widget_get_screen(mShell); + if (!gdk_screen_is_composited(screen)) { + return false; + } + *aData = do_AddRef(new FullscreenTransitionWindow(mShell)).take(); + return true; +} + +/* virtual */ void +nsWindow::PerformFullscreenTransition(FullscreenTransitionStage aStage, + uint16_t aDuration, nsISupports* aData, + nsIRunnable* aCallback) +{ + auto data = static_cast<FullscreenTransitionWindow*>(aData); + // This will be released at the end of the last timeout callback for it. + auto transitionData = new FullscreenTransitionData(aStage, aDuration, + aCallback, data); + g_timeout_add_full(G_PRIORITY_HIGH, + FullscreenTransitionData::sInterval, + FullscreenTransitionData::TimeoutCallback, + transitionData, nullptr); +} + +static bool +IsFullscreenSupported(GtkWidget* aShell) +{ +#ifdef MOZ_X11 + GdkScreen* screen = gtk_widget_get_screen(aShell); + GdkAtom atom = gdk_atom_intern("_NET_WM_STATE_FULLSCREEN", FALSE); + if (!gdk_x11_screen_supports_net_wm_hint(screen, atom)) { + return false; + } +#endif + return true; +} + +nsresult +nsWindow::MakeFullScreen(bool aFullScreen, nsIScreen* aTargetScreen) +{ + LOG(("nsWindow::MakeFullScreen [%p] aFullScreen %d\n", + (void *)this, aFullScreen)); + + if (!IsFullscreenSupported(mShell)) { + return NS_ERROR_NOT_AVAILABLE; + } + + 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; + if (gdk_window_is_visible(mGdkWindow)) { + gdk_window_hide(mGdkWindow); + wasVisible = true; + } + + gint wmd; + if (aShouldHide) + wmd = 0; + else + wmd = ConvertBorderStyles(mBorderStyle); + + if (wmd != -1) + gdk_window_set_decorations(mGdkWindow, (GdkWMDecoration) wmd); + + if (wasVisible) + gdk_window_show(mGdkWindow); + + // 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 = rollupListener->ShouldConsumeOnMouseWheelEvent(); + } + // 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) { + AutoTArray<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. + bool usePoint = !aIsWheel && !aAlwaysRollup; + IntPoint point = IntPoint::Truncate(aMouseX, aMouseY); + if (rollup && rollupListener->Rollup(popupsToRollup, true, usePoint ? &point : nullptr, 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 (MOZ_WIDGET_GTK == 2) + 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 = nullptr; + 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; + } + + GdkDisplay *defaultDisplay = gdk_display_get_default(); + + // The strategy here is to use standard GDK cursors, and, if not available, + // load by standard name with gdk_cursor_new_from_name. + // Spec is here: http://www.freedesktop.org/wiki/Specifications/cursor-spec/ + switch (aCursor) { + case eCursor_standard: + gdkcursor = gdk_cursor_new_for_display(defaultDisplay, GDK_LEFT_PTR); + break; + case eCursor_wait: + gdkcursor = gdk_cursor_new_for_display(defaultDisplay, GDK_WATCH); + break; + case eCursor_select: + gdkcursor = gdk_cursor_new_for_display(defaultDisplay, GDK_XTERM); + break; + case eCursor_hyperlink: + gdkcursor = gdk_cursor_new_for_display(defaultDisplay, GDK_HAND2); + break; + case eCursor_n_resize: + gdkcursor = gdk_cursor_new_for_display(defaultDisplay, GDK_TOP_SIDE); + break; + case eCursor_s_resize: + gdkcursor = gdk_cursor_new_for_display(defaultDisplay, GDK_BOTTOM_SIDE); + break; + case eCursor_w_resize: + gdkcursor = gdk_cursor_new_for_display(defaultDisplay, GDK_LEFT_SIDE); + break; + case eCursor_e_resize: + gdkcursor = gdk_cursor_new_for_display(defaultDisplay, GDK_RIGHT_SIDE); + break; + case eCursor_nw_resize: + gdkcursor = gdk_cursor_new_for_display(defaultDisplay, + GDK_TOP_LEFT_CORNER); + break; + case eCursor_se_resize: + gdkcursor = gdk_cursor_new_for_display(defaultDisplay, + GDK_BOTTOM_RIGHT_CORNER); + break; + case eCursor_ne_resize: + gdkcursor = gdk_cursor_new_for_display(defaultDisplay, + GDK_TOP_RIGHT_CORNER); + break; + case eCursor_sw_resize: + gdkcursor = gdk_cursor_new_for_display(defaultDisplay, + GDK_BOTTOM_LEFT_CORNER); + break; + case eCursor_crosshair: + gdkcursor = gdk_cursor_new_for_display(defaultDisplay, GDK_CROSSHAIR); + break; + case eCursor_move: + gdkcursor = gdk_cursor_new_for_display(defaultDisplay, GDK_FLEUR); + break; + case eCursor_help: + gdkcursor = gdk_cursor_new_for_display(defaultDisplay, + GDK_QUESTION_ARROW); + break; + case eCursor_copy: // CSS3 + gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "copy"); + if (!gdkcursor) + newType = MOZ_CURSOR_COPY; + break; + case eCursor_alias: + gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "alias"); + if (!gdkcursor) + newType = MOZ_CURSOR_ALIAS; + break; + case eCursor_context_menu: + gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "context-menu"); + if (!gdkcursor) + newType = MOZ_CURSOR_CONTEXT_MENU; + break; + case eCursor_cell: + gdkcursor = gdk_cursor_new_for_display(defaultDisplay, GDK_PLUS); + break; + // Those two aren’t standardized. Trying both KDE’s and GNOME’s names + case eCursor_grab: + gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "openhand"); + if (!gdkcursor) + newType = MOZ_CURSOR_HAND_GRAB; + break; + case eCursor_grabbing: + gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "closedhand"); + if (!gdkcursor) + gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "grabbing"); + if (!gdkcursor) + newType = MOZ_CURSOR_HAND_GRABBING; + break; + case eCursor_spinning: + gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "progress"); + if (!gdkcursor) + 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: + gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "not-allowed"); + if (!gdkcursor) // nonstandard, yet common + gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "crossed_circle"); + if (!gdkcursor) + newType = MOZ_CURSOR_NOT_ALLOWED; + break; + case eCursor_no_drop: + gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "no-drop"); + if (!gdkcursor) // this nonstandard sequence makes it work on KDE and GNOME + gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "forbidden"); + if (!gdkcursor) + gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "circle"); + if (!gdkcursor) + newType = MOZ_CURSOR_NOT_ALLOWED; + break; + case eCursor_vertical_text: + newType = MOZ_CURSOR_VERTICAL_TEXT; + break; + case eCursor_all_scroll: + gdkcursor = gdk_cursor_new_for_display(defaultDisplay, GDK_FLEUR); + break; + case eCursor_nesw_resize: + gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "size_bdiag"); + if (!gdkcursor) + newType = MOZ_CURSOR_NESW_RESIZE; + break; + case eCursor_nwse_resize: + gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "size_fdiag"); + if (!gdkcursor) + newType = MOZ_CURSOR_NWSE_RESIZE; + break; + case eCursor_ns_resize: + gdkcursor = gdk_cursor_new_for_display(defaultDisplay, + GDK_SB_V_DOUBLE_ARROW); + break; + case eCursor_ew_resize: + gdkcursor = gdk_cursor_new_for_display(defaultDisplay, + GDK_SB_H_DOUBLE_ARROW); + break; + // Here, two better fitting cursors exist in some cursor themes. Try those first + case eCursor_row_resize: + gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "split_v"); + if (!gdkcursor) + gdkcursor = gdk_cursor_new_for_display(defaultDisplay, + GDK_SB_V_DOUBLE_ARROW); + break; + case eCursor_col_resize: + gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "split_h"); + if (!gdkcursor) + gdkcursor = gdk_cursor_new_for_display(defaultDisplay, + 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_for_display(defaultDisplay, 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(defaultDisplay, 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 nullptr; + + 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 (MOZ_WIDGET_GTK == 2) +static gboolean +expose_event_cb(GtkWidget *widget, GdkEventExpose *event) +{ + RefPtr<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)) { + RefPtr<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); + + // A strong reference is already held during "draw" signal emission, + // but GTK+ 3.4 wants the object to live a little longer than that + // (bug 1225970). + g_object_ref(widget); + g_idle_add( + [](gpointer data) -> gboolean { + g_object_unref(data); + return G_SOURCE_REMOVE; + }, + widget); + + return FALSE; +} +#endif //MOZ_WIDGET_GTK == 2 + +static gboolean +configure_event_cb(GtkWidget *widget, + GdkEventConfigure *event) +{ + RefPtr<nsWindow> window = get_window_for_gtk_widget(widget); + if (!window) + return FALSE; + + return window->OnConfigureEvent(widget, event); +} + +static void +container_unrealize_cb (GtkWidget *widget) +{ + RefPtr<nsWindow> window = get_window_for_gtk_widget(widget); + if (!window) + return; + + window->OnContainerUnrealize(); +} + +static void +size_allocate_cb (GtkWidget *widget, GtkAllocation *allocation) +{ + RefPtr<nsWindow> window = get_window_for_gtk_widget(widget); + if (!window) + return; + + window->OnSizeAllocate(allocation); +} + +static gboolean +delete_event_cb(GtkWidget *widget, GdkEventAny *event) +{ + RefPtr<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) +{ + RefPtr<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; + } + + RefPtr<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); + + 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) +{ + RefPtr<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) +{ + RefPtr<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; + + RefPtr<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 (MOZ_WIDGET_GTK == 2) + 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 (MOZ_WIDGET_GTK == 2) + 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; + + RefPtr<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); + if (GDK_IS_X11_DISPLAY(gdkDisplay)) { + 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; + + RefPtr<nsWindow> focusWindow = gFocusWindow ? gFocusWindow : window; + + return focusWindow->OnKeyReleaseEvent(event); +} + +static gboolean +property_notify_event_cb(GtkWidget* aWidget, GdkEventProperty* aEvent) +{ + RefPtr<nsWindow> window = get_window_for_gdk_window(aEvent->window); + if (!window) + return FALSE; + + return window->OnPropertyNotifyEvent(aWidget, aEvent); +} + +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) +{ + RefPtr<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 = nullptr; + event.send_event = TRUE; + window_state_event_cb(widget, &event); + } +} + +static gboolean +window_state_event_cb (GtkWidget *widget, GdkEventWindowState *event) +{ + RefPtr<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) +{ + RefPtr<nsWindow> window = data; + window->ThemeChanged(); +} + +static void +check_resize_cb (GtkContainer* container, gpointer user_data) +{ + RefPtr<nsWindow> window = get_window_for_gtk_widget(GTK_WIDGET(container)); + if (!window) { + return; + } + window->OnCheckResize(); +} + +#if (MOZ_WIDGET_GTK == 3) +static void +scale_changed_cb (GtkWidget* widget, GParamSpec* aPSpec, gpointer aPointer) +{ + RefPtr<nsWindow> window = get_window_for_gtk_widget(widget); + if (!window) { + return; + } + window->OnDPIChanged(); + + // configure_event is already fired before scale-factor signal, + // but size-allocate isn't fired by changing scale + GtkAllocation allocation; + gtk_widget_get_allocation(widget, &allocation); + window->OnSizeAllocate(&allocation); +} +#endif + +#if GTK_CHECK_VERSION(3,4,0) +static gboolean +touch_event_cb(GtkWidget* aWidget, GdkEventTouch* aEvent) +{ + UpdateLastInputEventTime(aEvent); + + nsWindow* window = GetFirstNSWindowForGDKWindow(aEvent->window); + if (!window) { + return FALSE; + } + + return window->OnTouchEvent(aEvent); +} +#endif + +////////////////////////////////////////////////////////////////////// +// These are all of our drag and drop operations + +void +nsWindow::InitDragEvent(WidgetDragEvent &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) +{ + RefPtr<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); + RefPtr<nsWindow> innerMostWindow = get_window_for_gdk_window(innerWindow); + + if (!innerMostWindow) { + innerMostWindow = window; + } + + LOGDRAG(("nsWindow drag-motion signal for %p\n", (void*)innerMostWindow)); + + LayoutDeviceIntPoint point = window->GdkPointToDevicePixels({ retx, rety }); + + return nsDragService::GetInstance()-> + ScheduleMotionEvent(innerMostWindow, aDragContext, + point, aTime); +} + +static void +drag_leave_event_cb(GtkWidget *aWidget, + GdkDragContext *aDragContext, + guint aTime, + gpointer aData) +{ + RefPtr<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) +{ + RefPtr<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); + RefPtr<nsWindow> innerMostWindow = get_window_for_gdk_window(innerWindow); + + if (!innerMostWindow) { + innerMostWindow = window; + } + + LOGDRAG(("nsWindow drag-drop signal for %p\n", (void*)innerMostWindow)); + + LayoutDeviceIntPoint point = window->GdkPointToDevicePixels({ retx, rety }); + + return nsDragService::GetInstance()-> + ScheduleDropEvent(innerMostWindow, aDragContext, + point, aTime); +} + +static void +drag_data_received_event_cb(GtkWidget *aWidget, + GdkDragContext *aDragContext, + gint aX, + gint aY, + GtkSelectionData *aSelectionData, + guint aInfo, + guint aTime, + gpointer aData) +{ + RefPtr<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 (MOZ_WIDGET_GTK == 2) + gdk_window_get_geometry(childWindow, &cx, &cy, &cw, &ch, nullptr); +#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 WidgetKeyboardEvent& aKeyEvent) +{ + return ((aKeyEvent.mKeyCode == NS_VK_F10 && aKeyEvent.IsShift() && + !aKeyEvent.IsControl() && !aKeyEvent.IsMeta() && + !aKeyEvent.IsAlt()) || + (aKeyEvent.mKeyCode == 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 = GetRootAccessible(); + } +} + +void +nsWindow::DispatchEventToRootAccessible(uint32_t aEventType) +{ + if (!a11y::ShouldA11yBeEnabled()) { + return; + } + + nsAccessibilityService* accService = GetOrCreateAccService(); + if (!accService) { + return; + } + + // Get the root document accessible and fire event to it. + a11y::Accessible* acc = GetRootAccessible(); + 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_(void) +nsWindow::SetInputContext(const InputContext& aContext, + const InputContextAction& aAction) +{ + if (!mIMContext) { + return; + } + mIMContext->SetInputContext(this, &aContext, &aAction); +} + +NS_IMETHODIMP_(InputContext) +nsWindow::GetInputContext() +{ + InputContext context; + if (!mIMContext) { + context.mIMEState.mEnabled = IMEState::DISABLED; + context.mIMEState.mOpen = IMEState::OPEN_STATE_NOT_SUPPORTED; + } else { + context = mIMContext->GetInputContext(); + } + return context; +} + +nsIMEUpdatePreference +nsWindow::GetIMEUpdatePreference() +{ + if (!mIMContext) { + return nsIMEUpdatePreference(); + } + return mIMContext->GetIMEUpdatePreference(); +} + +NS_IMETHODIMP_(TextEventDispatcherListener*) +nsWindow::GetNativeTextEventDispatcherListener() +{ + if (NS_WARN_IF(!mIMContext)) { + return nullptr; + } + return mIMContext; +} + +bool +nsWindow::ExecuteNativeKeyBindingRemapped(NativeKeyBindingsType aType, + const WidgetKeyboardEvent& aEvent, + DoCommandCallback aCallback, + void* aCallbackData, + uint32_t aGeckoKeyCode, + uint32_t aNativeKeyCode) +{ + WidgetKeyboardEvent modifiedEvent(aEvent); + modifiedEvent.mKeyCode = aGeckoKeyCode; + static_cast<GdkEventKey*>(modifiedEvent.mNativeKeyEvent)->keyval = + aNativeKeyCode; + + NativeKeyBindings* keyBindings = NativeKeyBindings::GetInstance(aType); + return keyBindings->Execute(modifiedEvent, aCallback, aCallbackData); +} + +NS_IMETHODIMP_(bool) +nsWindow::ExecuteNativeKeyBinding(NativeKeyBindingsType aType, + const WidgetKeyboardEvent& aEvent, + DoCommandCallback aCallback, + void* aCallbackData) +{ + if (aEvent.mKeyCode >= NS_VK_LEFT && aEvent.mKeyCode <= NS_VK_DOWN) { + + // Check if we're targeting content with vertical writing mode, + // and if so remap the arrow keys. + WidgetQueryContentEvent query(true, eQuerySelectedText, this); + nsEventStatus status; + DispatchEvent(&query, status); + + if (query.mSucceeded && query.mReply.mWritingMode.IsVertical()) { + uint32_t geckoCode = 0; + uint32_t gdkCode = 0; + switch (aEvent.mKeyCode) { + case NS_VK_LEFT: + if (query.mReply.mWritingMode.IsVerticalLR()) { + geckoCode = NS_VK_UP; + gdkCode = GDK_Up; + } else { + geckoCode = NS_VK_DOWN; + gdkCode = GDK_Down; + } + break; + + case NS_VK_RIGHT: + if (query.mReply.mWritingMode.IsVerticalLR()) { + geckoCode = NS_VK_DOWN; + gdkCode = GDK_Down; + } else { + geckoCode = NS_VK_UP; + gdkCode = GDK_Up; + } + break; + + case NS_VK_UP: + geckoCode = NS_VK_LEFT; + gdkCode = GDK_Left; + break; + + case NS_VK_DOWN: + geckoCode = NS_VK_RIGHT; + gdkCode = GDK_Right; + break; + } + + return ExecuteNativeKeyBindingRemapped(aType, aEvent, aCallback, + aCallbackData, + geckoCode, gdkCode); + } + } + + NativeKeyBindings* keyBindings = NativeKeyBindings::GetInstance(aType); + return keyBindings->Execute(aEvent, aCallback, aCallbackData); +} + +#if defined(MOZ_X11) && (MOZ_WIDGET_GTK == 2) +/* static */ already_AddRefed<DrawTarget> +nsWindow::GetDrawTargetForGdkDrawable(GdkDrawable* aDrawable, + const IntSize& 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); + + RefPtr<gfxASurface> surface; + + if (visual) { + Visual* xVisual = gdk_x11_visual_get_xvisual(visual); + + surface = new gfxXlibSurface(xDisplay, xDrawable, xVisual, aSize); + } else { + // no visual? we must be using an xrender format. Find a format + // for this depth. + XRenderPictFormat *pf = nullptr; + 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; + } + + surface = new gfxXlibSurface(xScreen, xDrawable, pf, aSize); + } + + RefPtr<DrawTarget> dt = + gfxPlatform::GetPlatform()->CreateDrawTargetForSurface(surface, aSize); + + if (!dt || !dt->IsValid()) { + return nullptr; + } + + return dt.forget(); +} +#endif + +already_AddRefed<DrawTarget> +nsWindow::StartRemoteDrawingInRegion(LayoutDeviceIntRegion& aInvalidRegion, BufferMode* aBufferMode) +{ + return mSurfaceProvider.StartRemoteDrawingInRegion(aInvalidRegion, aBufferMode); +} + +void +nsWindow::EndRemoteDrawingInRegion(DrawTarget* aDrawTarget, + LayoutDeviceIntRegion& aInvalidRegion) +{ + mSurfaceProvider.EndRemoteDrawingInRegion(aDrawTarget, aInvalidRegion); +} + +// Code shared begin BeginMoveDrag and BeginResizeDrag +bool +nsWindow::GetDragInfo(WidgetMouseEvent* aMouseEvent, + GdkWindow** aWindow, gint* aButton, + gint* aRootX, gint* aRootY) +{ + if (aMouseEvent->button != WidgetMouseEvent::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; + } +#ifdef DEBUG + // GDK_IS_WINDOW(...) expands to a statement-expression, and + // statement-expressions are not allowed in template-argument lists. So we + // have to make the MOZ_ASSERT condition indirect. + if (!GDK_IS_WINDOW(gdk_window)) { + MOZ_ASSERT(false, "must really be window"); + } +#endif + + // find the top-level window + gdk_window = gdk_window_get_toplevel(gdk_window); + MOZ_ASSERT(gdk_window, + "gdk_window_get_toplevel should not return null"); + *aWindow = gdk_window; + + if (!aMouseEvent->mWidget) { + 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.) + LayoutDeviceIntPoint offset = aMouseEvent->mWidget->WidgetToScreenOffset(); + *aRootX = aMouseEvent->mRefPoint.x + offset.x; + *aRootY = aMouseEvent->mRefPoint.y + offset.y; + + return true; +} + +NS_IMETHODIMP +nsWindow::BeginMoveDrag(WidgetMouseEvent* aEvent) +{ + MOZ_ASSERT(aEvent, "must have event"); + MOZ_ASSERT(aEvent->mClass == eMouseEventClass, + "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 + screenX = DevicePixelsToGdkCoordRoundDown(screenX); + screenY = DevicePixelsToGdkCoordRoundDown(screenY); + gdk_window_begin_move_drag(gdk_window, button, screenX, screenY, + aEvent->mTime); + + return NS_OK; +} + +NS_IMETHODIMP +nsWindow::BeginResizeDrag(WidgetGUIEvent* aEvent, + int32_t aHorizontal, + int32_t aVertical) +{ + NS_ENSURE_ARG_POINTER(aEvent); + + if (aEvent->mClass != eMouseEventClass) { + // you can only begin a resize drag with a mouse event + return NS_ERROR_INVALID_ARG; + } + + GdkWindow *gdk_window; + gint button, screenX, screenY; + if (!GetDragInfo(aEvent->AsMouseEvent(), &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->mTime); + + return NS_OK; +} + +nsIWidget::LayerManager* +nsWindow::GetLayerManager(PLayerTransactionChild* aShadowManager, + LayersBackend aBackendHint, + LayerManagerPersistence aPersistence) +{ + if (mIsDestroyed) { + // Prevent external code from triggering the re-creation of the LayerManager/Compositor + // during shutdown. Just return what we currently have, which is most likely null. + return mLayerManager; + } + if (!mLayerManager && eTransparencyTransparent == GetTransparencyMode()) { + mLayerManager = CreateBasicLayerManager(); + } + + return nsBaseWidget::GetLayerManager(aShadowManager, aBackendHint, aPersistence); +} + +void +nsWindow::ClearCachedResources() +{ + if (mLayerManager && + mLayerManager->GetBackendType() == mozilla::layers::LayersBackend::LAYERS_BASIC) { + mLayerManager->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(); + } + } +} + +gint +nsWindow::GdkScaleFactor() +{ +#if (MOZ_WIDGET_GTK >= 3) + // Available as of GTK 3.10+ + static auto sGdkWindowGetScaleFactorPtr = (gint (*)(GdkWindow*)) + dlsym(RTLD_DEFAULT, "gdk_window_get_scale_factor"); + if (sGdkWindowGetScaleFactorPtr && mGdkWindow) + return (*sGdkWindowGetScaleFactorPtr)(mGdkWindow); +#endif + return nsScreenGtk::GetGtkMonitorScaleFactor(); +} + + +gint +nsWindow::DevicePixelsToGdkCoordRoundUp(int pixels) { + gint scale = GdkScaleFactor(); + return (pixels + scale - 1) / scale; +} + +gint +nsWindow::DevicePixelsToGdkCoordRoundDown(int pixels) { + gint scale = GdkScaleFactor(); + return pixels / scale; +} + +GdkPoint +nsWindow::DevicePixelsToGdkPointRoundDown(LayoutDeviceIntPoint point) { + gint scale = GdkScaleFactor(); + return { point.x / scale, point.y / scale }; +} + +GdkRectangle +nsWindow::DevicePixelsToGdkRectRoundOut(LayoutDeviceIntRect rect) { + gint scale = GdkScaleFactor(); + int x = rect.x / scale; + int y = rect.y / scale; + int right = (rect.x + rect.width + scale - 1) / scale; + int bottom = (rect.y + rect.height + scale - 1) / scale; + return { x, y, right - x, bottom - y }; +} + +GdkRectangle +nsWindow::DevicePixelsToGdkSizeRoundUp(LayoutDeviceIntSize pixelSize) { + gint scale = GdkScaleFactor(); + gint width = (pixelSize.width + scale - 1) / scale; + gint height = (pixelSize.height + scale - 1) / scale; + return { 0, 0, width, height }; +} + +int +nsWindow::GdkCoordToDevicePixels(gint coord) { + return coord * GdkScaleFactor(); +} + +LayoutDeviceIntPoint +nsWindow::GdkEventCoordsToDevicePixels(gdouble x, gdouble y) +{ + gint scale = GdkScaleFactor(); + return LayoutDeviceIntPoint::Round(x * scale, y * scale); +} + +LayoutDeviceIntPoint +nsWindow::GdkPointToDevicePixels(GdkPoint point) { + gint scale = GdkScaleFactor(); + return LayoutDeviceIntPoint(point.x * scale, + point.y * scale); +} + +LayoutDeviceIntRect +nsWindow::GdkRectToDevicePixels(GdkRectangle rect) { + gint scale = GdkScaleFactor(); + return LayoutDeviceIntRect(rect.x * scale, + rect.y * scale, + rect.width * scale, + rect.height * scale); +} + +nsresult +nsWindow::SynthesizeNativeMouseEvent(LayoutDeviceIntPoint aPoint, + uint32_t aNativeMessage, + uint32_t aModifierFlags, + nsIObserver* aObserver) +{ + AutoObserverNotifier notifier(aObserver, "mouseevent"); + + if (!mGdkWindow) { + return NS_OK; + } + + GdkDisplay* display = gdk_window_get_display(mGdkWindow); + + // When a button-press/release event is requested, create it here and put it in the + // event queue. This will not emit a motion event - this needs to be done + // explicitly *before* requesting a button-press/release. You will also need to wait + // for the motion event to be dispatched before requesting a button-press/release + // event to maintain the desired event order. + if (aNativeMessage == GDK_BUTTON_PRESS || aNativeMessage == GDK_BUTTON_RELEASE) { + GdkEvent event; + memset(&event, 0, sizeof(GdkEvent)); + event.type = (GdkEventType)aNativeMessage; + event.button.button = 1; + event.button.window = mGdkWindow; + event.button.time = GDK_CURRENT_TIME; + +#if (MOZ_WIDGET_GTK == 3) + // Get device for event source + GdkDeviceManager *device_manager = gdk_display_get_device_manager(display); + event.button.device = gdk_device_manager_get_client_pointer(device_manager); +#endif + + event.button.x_root = DevicePixelsToGdkCoordRoundDown(aPoint.x); + event.button.y_root = DevicePixelsToGdkCoordRoundDown(aPoint.y); + + LayoutDeviceIntPoint pointInWindow = aPoint - WidgetToScreenOffset(); + event.button.x = DevicePixelsToGdkCoordRoundDown(pointInWindow.x); + event.button.y = DevicePixelsToGdkCoordRoundDown(pointInWindow.y); + + gdk_event_put(&event); + } else { + // We don't support specific events other than button-press/release. In all + // other cases we'll synthesize a motion event that will be emitted by + // gdk_display_warp_pointer(). + GdkScreen* screen = gdk_window_get_screen(mGdkWindow); + GdkPoint point = DevicePixelsToGdkPointRoundDown(aPoint); + gdk_display_warp_pointer(display, screen, point.x, point.y); + } + + return NS_OK; +} + +nsresult +nsWindow::SynthesizeNativeMouseScrollEvent(mozilla::LayoutDeviceIntPoint aPoint, + uint32_t aNativeMessage, + double aDeltaX, + double aDeltaY, + double aDeltaZ, + uint32_t aModifierFlags, + uint32_t aAdditionalFlags, + nsIObserver* aObserver) +{ + AutoObserverNotifier notifier(aObserver, "mousescrollevent"); + + if (!mGdkWindow) { + return NS_OK; + } + + GdkEvent event; + memset(&event, 0, sizeof(GdkEvent)); + event.type = GDK_SCROLL; + event.scroll.window = mGdkWindow; + event.scroll.time = GDK_CURRENT_TIME; +#if (MOZ_WIDGET_GTK == 3) + // Get device for event source + GdkDisplay* display = gdk_window_get_display(mGdkWindow); + GdkDeviceManager *device_manager = gdk_display_get_device_manager(display); + event.scroll.device = gdk_device_manager_get_client_pointer(device_manager); +#endif + event.scroll.x_root = DevicePixelsToGdkCoordRoundDown(aPoint.x); + event.scroll.y_root = DevicePixelsToGdkCoordRoundDown(aPoint.y); + + LayoutDeviceIntPoint pointInWindow = aPoint - WidgetToScreenOffset(); + event.scroll.x = DevicePixelsToGdkCoordRoundDown(pointInWindow.x); + event.scroll.y = DevicePixelsToGdkCoordRoundDown(pointInWindow.y); + + // The delta values are backwards on Linux compared to Windows and Cocoa, + // hence the negation. +#if GTK_CHECK_VERSION(3,4,0) + // TODO: is this correct? I don't have GTK 3.4+ so I can't check + event.scroll.direction = GDK_SCROLL_SMOOTH; + event.scroll.delta_x = -aDeltaX; + event.scroll.delta_y = -aDeltaY; +#else + if (aDeltaX < 0) { + event.scroll.direction = GDK_SCROLL_RIGHT; + } else if (aDeltaX > 0) { + event.scroll.direction = GDK_SCROLL_LEFT; + } else if (aDeltaY < 0) { + event.scroll.direction = GDK_SCROLL_DOWN; + } else if (aDeltaY > 0) { + event.scroll.direction = GDK_SCROLL_UP; + } else { + return NS_OK; + } +#endif + + gdk_event_put(&event); + + return NS_OK; +} + +#if GTK_CHECK_VERSION(3,4,0) +nsresult +nsWindow::SynthesizeNativeTouchPoint(uint32_t aPointerId, + TouchPointerState aPointerState, + LayoutDeviceIntPoint aPoint, + double aPointerPressure, + uint32_t aPointerOrientation, + nsIObserver* aObserver) +{ + AutoObserverNotifier notifier(aObserver, "touchpoint"); + + if (!mGdkWindow) { + return NS_OK; + } + + GdkEvent event; + memset(&event, 0, sizeof(GdkEvent)); + + static std::map<uint32_t, GdkEventSequence*> sKnownPointers; + + auto result = sKnownPointers.find(aPointerId); + switch (aPointerState) { + case TOUCH_CONTACT: + if (result == sKnownPointers.end()) { + // GdkEventSequence isn't a thing we can instantiate, and never gets + // dereferenced in the gtk code. It's an opaque pointer, the only + // requirement is that it be distinct from other instances of + // GdkEventSequence*. + event.touch.sequence = (GdkEventSequence*)((uintptr_t)aPointerId); + sKnownPointers[aPointerId] = event.touch.sequence; + event.type = GDK_TOUCH_BEGIN; + } else { + event.touch.sequence = result->second; + event.type = GDK_TOUCH_UPDATE; + } + break; + case TOUCH_REMOVE: + event.type = GDK_TOUCH_END; + if (result == sKnownPointers.end()) { + NS_WARNING("Tried to synthesize touch-end for unknown pointer!"); + return NS_ERROR_UNEXPECTED; + } + event.touch.sequence = result->second; + sKnownPointers.erase(result); + break; + case TOUCH_CANCEL: + event.type = GDK_TOUCH_CANCEL; + if (result == sKnownPointers.end()) { + NS_WARNING("Tried to synthesize touch-cancel for unknown pointer!"); + return NS_ERROR_UNEXPECTED; + } + event.touch.sequence = result->second; + sKnownPointers.erase(result); + break; + case TOUCH_HOVER: + default: + return NS_ERROR_NOT_IMPLEMENTED; + } + + event.touch.window = mGdkWindow; + event.touch.time = GDK_CURRENT_TIME; + + GdkDisplay* display = gdk_window_get_display(mGdkWindow); + GdkDeviceManager* device_manager = gdk_display_get_device_manager(display); + event.touch.device = gdk_device_manager_get_client_pointer(device_manager); + + event.touch.x_root = DevicePixelsToGdkCoordRoundDown(aPoint.x); + event.touch.y_root = DevicePixelsToGdkCoordRoundDown(aPoint.y); + + LayoutDeviceIntPoint pointInWindow = aPoint - WidgetToScreenOffset(); + event.touch.x = DevicePixelsToGdkCoordRoundDown(pointInWindow.x); + event.touch.y = DevicePixelsToGdkCoordRoundDown(pointInWindow.y); + + gdk_event_put(&event); + + return NS_OK; +} +#endif + +int32_t +nsWindow::RoundsWidgetCoordinatesTo() +{ + return GdkScaleFactor(); +} + +void nsWindow::GetCompositorWidgetInitData(mozilla::widget::CompositorWidgetInitData* aInitData) +{ + #ifdef MOZ_X11 + *aInitData = mozilla::widget::CompositorWidgetInitData( + mXWindow, + nsCString(XDisplayString(mXDisplay)), + GetClientSize()); + #endif +} diff --git a/widget/gtk/nsWindow.h b/widget/gtk/nsWindow.h new file mode 100644 index 000000000..49a8d4baf --- /dev/null +++ b/widget/gtk/nsWindow.h @@ -0,0 +1,580 @@ +/* -*- 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/. */ + +#ifndef __nsWindow_h__ +#define __nsWindow_h__ + +#include "mozcontainer.h" +#include "mozilla/RefPtr.h" +#include "mozilla/UniquePtr.h" +#include "nsIDragService.h" +#include "nsITimer.h" +#include "nsGkAtoms.h" +#include "nsRefPtrHashtable.h" + +#include "nsBaseWidget.h" +#include <gdk/gdk.h> +#include <gtk/gtk.h> + +#ifdef MOZ_X11 +#include <gdk/gdkx.h> +#endif /* MOZ_X11 */ + +#include "mozilla/widget/WindowSurface.h" +#include "mozilla/widget/WindowSurfaceProvider.h" + +#ifdef ACCESSIBILITY +#include "mozilla/a11y/Accessible.h" +#endif +#include "mozilla/EventForwards.h" +#include "mozilla/TouchEvents.h" + +#include "IMContextWrapper.h" + +#undef LOG +#ifdef MOZ_LOGGING + +#include "mozilla/Logging.h" +#include "nsTArray.h" +#include "Units.h" + +extern PRLogModuleInfo *gWidgetLog; +extern PRLogModuleInfo *gWidgetFocusLog; +extern PRLogModuleInfo *gWidgetDragLog; +extern PRLogModuleInfo *gWidgetDrawLog; + +#define LOG(args) MOZ_LOG(gWidgetLog, mozilla::LogLevel::Debug, args) +#define LOGFOCUS(args) MOZ_LOG(gWidgetFocusLog, mozilla::LogLevel::Debug, args) +#define LOGDRAG(args) MOZ_LOG(gWidgetDragLog, mozilla::LogLevel::Debug, args) +#define LOGDRAW(args) MOZ_LOG(gWidgetDrawLog, mozilla::LogLevel::Debug, args) + +#else + +#define LOG(args) +#define LOGFOCUS(args) +#define LOGDRAG(args) +#define LOGDRAW(args) + +#endif /* MOZ_LOGGING */ + +class gfxPattern; +class nsPluginNativeWindowGtk; + +namespace mozilla { +class TimeStamp; +class CurrentX11TimeGetter; +} + +class nsWindow : public nsBaseWidget +{ +public: + typedef mozilla::gfx::DrawTarget DrawTarget; + typedef mozilla::WidgetEventTime WidgetEventTime; + + nsWindow(); + + static void ReleaseGlobals(); + + NS_DECL_ISUPPORTS_INHERITED + + void CommonCreate(nsIWidget *aParent, bool aListenForResizes); + + virtual nsresult DispatchEvent(mozilla::WidgetGUIEvent* aEvent, + nsEventStatus& aStatus) override; + + // called when we are destroyed + virtual void OnDestroy(void) override; + + // called to check and see if a widget's dimensions are sane + bool AreBoundsSane(void); + + // nsIWidget + using nsBaseWidget::Create; // for Create signature not overridden here + virtual MOZ_MUST_USE nsresult Create(nsIWidget* aParent, + nsNativeWidget aNativeParent, + const LayoutDeviceIntRect& aRect, + nsWidgetInitData* aInitData) override; + virtual void Destroy() override; + virtual nsIWidget *GetParent() override; + virtual float GetDPI() override; + virtual double GetDefaultScaleInternal() override; + // Under Gtk, we manage windows using device pixels so no scaling is needed: + mozilla::DesktopToLayoutDeviceScale GetDesktopToDeviceScale() final + { + return mozilla::DesktopToLayoutDeviceScale(1.0); + } + virtual nsresult SetParent(nsIWidget* aNewParent) override; + virtual void SetModal(bool aModal) override; + virtual bool IsVisible() const override; + virtual void ConstrainPosition(bool aAllowSlop, + int32_t *aX, + int32_t *aY) override; + virtual void SetSizeConstraints(const SizeConstraints& aConstraints) override; + NS_IMETHOD Move(double aX, + double aY) override; + NS_IMETHOD Show (bool aState) override; + NS_IMETHOD Resize (double aWidth, + double aHeight, + bool aRepaint) override; + NS_IMETHOD Resize (double aX, + double aY, + double aWidth, + double aHeight, + bool aRepaint) override; + virtual bool IsEnabled() const override; + + void SetZIndex(int32_t aZIndex) override; + virtual void SetSizeMode(nsSizeMode aMode) override; + NS_IMETHOD Enable(bool aState) override; + NS_IMETHOD SetFocus(bool aRaise = false) override; + virtual LayoutDeviceIntRect GetScreenBounds() override; + virtual LayoutDeviceIntRect GetClientBounds() override; + virtual LayoutDeviceIntSize GetClientSize() override; + virtual LayoutDeviceIntPoint GetClientOffset() override; + NS_IMETHOD SetCursor(nsCursor aCursor) override; + NS_IMETHOD SetCursor(imgIContainer* aCursor, + uint32_t aHotspotX, uint32_t aHotspotY) override; + NS_IMETHOD Invalidate(const LayoutDeviceIntRect& aRect) override; + virtual void* GetNativeData(uint32_t aDataType) override; + void SetNativeData(uint32_t aDataType, uintptr_t aVal) override; + NS_IMETHOD SetTitle(const nsAString& aTitle) override; + NS_IMETHOD SetIcon(const nsAString& aIconSpec) override; + virtual void SetWindowClass(const nsAString& xulWinType) override; + virtual LayoutDeviceIntPoint WidgetToScreenOffset() override; + virtual void CaptureMouse(bool aCapture) override; + virtual void CaptureRollupEvents(nsIRollupListener *aListener, + bool aDoCapture) override; + NS_IMETHOD GetAttention(int32_t aCycleCount) override; + virtual nsresult SetWindowClipRegion(const nsTArray<LayoutDeviceIntRect>& aRects, + bool aIntersectWithExisting) override; + virtual bool HasPendingInputEvent() override; + + virtual bool PrepareForFullscreenTransition(nsISupports** aData) override; + virtual void PerformFullscreenTransition(FullscreenTransitionStage aStage, + uint16_t aDuration, + nsISupports* aData, + nsIRunnable* aCallback) override; + virtual nsresult MakeFullScreen(bool aFullScreen, + nsIScreen* aTargetScreen = nullptr) override; + NS_IMETHOD HideWindowChrome(bool aShouldHide) override; + + /** + * GetLastUserInputTime returns a timestamp for the most recent user input + * event. This is intended for pointer grab requests (including drags). + */ + static guint32 GetLastUserInputTime(); + + // utility method, -1 if no change should be made, otherwise returns a + // value that can be passed to gdk_window_set_decorations + gint ConvertBorderStyles(nsBorderStyle aStyle); + + GdkRectangle DevicePixelsToGdkRectRoundOut(LayoutDeviceIntRect aRect); + + // event callbacks +#if (MOZ_WIDGET_GTK == 2) + gboolean OnExposeEvent(GdkEventExpose *aEvent); +#else + gboolean OnExposeEvent(cairo_t *cr); +#endif + gboolean OnConfigureEvent(GtkWidget *aWidget, + GdkEventConfigure *aEvent); + void OnContainerUnrealize(); + void OnSizeAllocate(GtkAllocation *aAllocation); + void OnDeleteEvent(); + void OnEnterNotifyEvent(GdkEventCrossing *aEvent); + void OnLeaveNotifyEvent(GdkEventCrossing *aEvent); + void OnMotionNotifyEvent(GdkEventMotion *aEvent); + void OnButtonPressEvent(GdkEventButton *aEvent); + void OnButtonReleaseEvent(GdkEventButton *aEvent); + void OnContainerFocusInEvent(GdkEventFocus *aEvent); + void OnContainerFocusOutEvent(GdkEventFocus *aEvent); + gboolean OnKeyPressEvent(GdkEventKey *aEvent); + gboolean OnKeyReleaseEvent(GdkEventKey *aEvent); + void OnScrollEvent(GdkEventScroll *aEvent); + void OnVisibilityNotifyEvent(GdkEventVisibility *aEvent); + void OnWindowStateEvent(GtkWidget *aWidget, + GdkEventWindowState *aEvent); + void OnDragDataReceivedEvent(GtkWidget *aWidget, + GdkDragContext *aDragContext, + gint aX, + gint aY, + GtkSelectionData*aSelectionData, + guint aInfo, + guint aTime, + gpointer aData); + gboolean OnPropertyNotifyEvent(GtkWidget *aWidget, + GdkEventProperty *aEvent); +#if GTK_CHECK_VERSION(3,4,0) + gboolean OnTouchEvent(GdkEventTouch* aEvent); +#endif + + virtual already_AddRefed<mozilla::gfx::DrawTarget> + StartRemoteDrawingInRegion(LayoutDeviceIntRegion& aInvalidRegion, + mozilla::layers::BufferMode* aBufferMode) override; + virtual void EndRemoteDrawingInRegion(mozilla::gfx::DrawTarget* aDrawTarget, + LayoutDeviceIntRegion& aInvalidRegion) override; + +private: + void UpdateAlpha(mozilla::gfx::SourceSurface* aSourceSurface, nsIntRect aBoundsRect); + + void NativeMove(); + void NativeResize(); + void NativeMoveResize(); + + void NativeShow (bool aAction); + void SetHasMappedToplevel(bool aState); + LayoutDeviceIntSize GetSafeWindowSize(LayoutDeviceIntSize aSize); + + void EnsureGrabs (void); + void GrabPointer (guint32 aTime); + void ReleaseGrabs (void); + + void UpdateClientOffset(); + +public: + enum PluginType { + PluginType_NONE = 0, /* do not have any plugin */ + PluginType_XEMBED, /* the plugin support xembed */ + PluginType_NONXEMBED /* the plugin does not support xembed */ + }; + + void SetPluginType(PluginType aPluginType); +#ifdef MOZ_X11 + void SetNonXEmbedPluginFocus(void); + void LoseNonXEmbedPluginFocus(void); +#endif /* MOZ_X11 */ + + void ThemeChanged(void); + void OnDPIChanged(void); + void OnCheckResize(void); + +#ifdef MOZ_X11 + Window mOldFocusWindow; +#endif /* MOZ_X11 */ + + static guint32 sLastButtonPressTime; + + NS_IMETHOD BeginResizeDrag(mozilla::WidgetGUIEvent* aEvent, + int32_t aHorizontal, + int32_t aVertical) override; + NS_IMETHOD BeginMoveDrag(mozilla::WidgetMouseEvent* aEvent) override; + + MozContainer* GetMozContainer() { return mContainer; } + // GetMozContainerWidget returns the MozContainer even for undestroyed + // descendant windows + GtkWidget* GetMozContainerWidget(); + GdkWindow* GetGdkWindow() { return mGdkWindow; } + bool IsDestroyed() { return mIsDestroyed; } + + void DispatchDragEvent(mozilla::EventMessage aMsg, + const LayoutDeviceIntPoint& aRefPoint, + guint aTime); + static void UpdateDragStatus (GdkDragContext *aDragContext, + nsIDragService *aDragService); + // If this dispatched the keydown event actually, this returns TRUE, + // otherwise, FALSE. + bool DispatchKeyDownEvent(GdkEventKey *aEvent, + bool *aIsCancelled); + WidgetEventTime GetWidgetEventTime(guint32 aEventTime); + mozilla::TimeStamp GetEventTimeStamp(guint32 aEventTime); + mozilla::CurrentX11TimeGetter* GetCurrentTimeGetter(); + + NS_IMETHOD_(void) SetInputContext(const InputContext& aContext, + const InputContextAction& aAction) override; + NS_IMETHOD_(InputContext) GetInputContext() override; + virtual nsIMEUpdatePreference GetIMEUpdatePreference() override; + NS_IMETHOD_(TextEventDispatcherListener*) + GetNativeTextEventDispatcherListener() override; + bool ExecuteNativeKeyBindingRemapped( + NativeKeyBindingsType aType, + const mozilla::WidgetKeyboardEvent& aEvent, + DoCommandCallback aCallback, + void* aCallbackData, + uint32_t aGeckoKeyCode, + uint32_t aNativeKeyCode); + NS_IMETHOD_(bool) ExecuteNativeKeyBinding( + NativeKeyBindingsType aType, + const mozilla::WidgetKeyboardEvent& aEvent, + DoCommandCallback aCallback, + void* aCallbackData) override; + + // These methods are for toplevel windows only. + void ResizeTransparencyBitmap(); + void ApplyTransparencyBitmap(); + void ClearTransparencyBitmap(); + + virtual void SetTransparencyMode(nsTransparencyMode aMode) override; + virtual nsTransparencyMode GetTransparencyMode() override; + virtual nsresult ConfigureChildren(const nsTArray<Configuration>& aConfigurations) override; + nsresult UpdateTranslucentWindowAlphaInternal(const nsIntRect& aRect, + uint8_t* aAlphas, int32_t aStride); + +#if (MOZ_WIDGET_GTK == 2) + static already_AddRefed<DrawTarget> GetDrawTargetForGdkDrawable(GdkDrawable* aDrawable, + const mozilla::gfx::IntSize& aSize); +#endif + virtual void ReparentNativeWidget(nsIWidget* aNewParent) override; + + virtual nsresult SynthesizeNativeMouseEvent(LayoutDeviceIntPoint aPoint, + uint32_t aNativeMessage, + uint32_t aModifierFlags, + nsIObserver* aObserver) override; + + virtual nsresult SynthesizeNativeMouseMove(LayoutDeviceIntPoint aPoint, + nsIObserver* aObserver) override + { return SynthesizeNativeMouseEvent(aPoint, GDK_MOTION_NOTIFY, 0, aObserver); } + + virtual nsresult SynthesizeNativeMouseScrollEvent(LayoutDeviceIntPoint aPoint, + uint32_t aNativeMessage, + double aDeltaX, + double aDeltaY, + double aDeltaZ, + uint32_t aModifierFlags, + uint32_t aAdditionalFlags, + nsIObserver* aObserver) override; + +#if GTK_CHECK_VERSION(3,4,0) + virtual nsresult SynthesizeNativeTouchPoint(uint32_t aPointerId, + TouchPointerState aPointerState, + LayoutDeviceIntPoint aPoint, + double aPointerPressure, + uint32_t aPointerOrientation, + nsIObserver* aObserver) override; +#endif + +#ifdef MOZ_X11 + Display* XDisplay() { return mXDisplay; } +#endif + virtual void GetCompositorWidgetInitData(mozilla::widget::CompositorWidgetInitData* aInitData) override; + + // HiDPI scale conversion + gint GdkScaleFactor(); + + // To GDK + gint DevicePixelsToGdkCoordRoundUp(int pixels); + gint DevicePixelsToGdkCoordRoundDown(int pixels); + GdkPoint DevicePixelsToGdkPointRoundDown(LayoutDeviceIntPoint point); + GdkRectangle DevicePixelsToGdkSizeRoundUp(LayoutDeviceIntSize pixelSize); + + // From GDK + int GdkCoordToDevicePixels(gint coord); + LayoutDeviceIntPoint GdkPointToDevicePixels(GdkPoint point); + LayoutDeviceIntPoint GdkEventCoordsToDevicePixels(gdouble x, gdouble y); + LayoutDeviceIntRect GdkRectToDevicePixels(GdkRectangle rect); + + virtual bool WidgetTypeSupportsAcceleration() override; +protected: + virtual ~nsWindow(); + + // event handling code + void DispatchActivateEvent(void); + void DispatchDeactivateEvent(void); + void DispatchResized(); + void MaybeDispatchResized(); + + // Helper for SetParent and ReparentNativeWidget. + void ReparentNativeWidgetInternal(nsIWidget* aNewParent, + GtkWidget* aNewContainer, + GdkWindow* aNewParentWindow, + GtkWidget* aOldContainer); + + virtual void RegisterTouchWindow() override; + + nsCOMPtr<nsIWidget> mParent; + // Is this a toplevel window? + bool mIsTopLevel; + // Has this widget been destroyed yet? + bool mIsDestroyed; + + // Should we send resize events on all resizes? + bool mListenForResizes; + // Does WindowResized need to be called on listeners? + bool mNeedsDispatchResized; + // This flag tracks if we're hidden or shown. + bool mIsShown; + bool mNeedsShow; + // is this widget enabled? + bool mEnabled; + // has the native window for this been created yet? + bool mCreated; +#if GTK_CHECK_VERSION(3,4,0) + // whether we handle touch event + bool mHandleTouchEvent; +#endif + // true if this is a drag and drop feedback popup + bool mIsDragPopup; + // Can we access X? + bool mIsX11Display; + +private: + void DestroyChildWindows(); + GtkWidget *GetToplevelWidget(); + nsWindow *GetContainerWindow(); + void SetUrgencyHint(GtkWidget *top_window, bool state); + void *SetupPluginPort(void); + void SetDefaultIcon(void); + void InitButtonEvent(mozilla::WidgetMouseEvent& aEvent, + GdkEventButton* aGdkEvent); + bool DispatchCommandEvent(nsIAtom* aCommand); + bool DispatchContentCommandEvent(mozilla::EventMessage aMsg); + bool CheckForRollup(gdouble aMouseX, gdouble aMouseY, + bool aIsWheel, bool aAlwaysRollup); + void CheckForRollupDuringGrab() + { + CheckForRollup(0, 0, false, true); + } + + bool GetDragInfo(mozilla::WidgetMouseEvent* aMouseEvent, + GdkWindow** aWindow, gint* aButton, + gint* aRootX, gint* aRootY); + void ClearCachedResources(); + nsIWidgetListener* GetListener(); + + GtkWidget *mShell; + MozContainer *mContainer; + GdkWindow *mGdkWindow; + + uint32_t mHasMappedToplevel : 1, + mIsFullyObscured : 1, + mRetryPointerGrab : 1; + nsSizeMode mSizeState; + PluginType mPluginType; + + int32_t mTransparencyBitmapWidth; + int32_t mTransparencyBitmapHeight; + + nsIntPoint mClientOffset; + +#if GTK_CHECK_VERSION(3,4,0) + // This field omits duplicate scroll events caused by GNOME bug 726878. + guint32 mLastScrollEventTime; + + // for touch event handling + nsRefPtrHashtable<nsPtrHashKey<GdkEventSequence>, mozilla::dom::Touch> mTouches; +#endif + +#ifdef MOZ_X11 + Display* mXDisplay; + Window mXWindow; + Visual* mXVisual; + int mXDepth; + mozilla::widget::WindowSurfaceProvider mSurfaceProvider; +#endif + + // Upper bound on pending ConfigureNotify events to be dispatched to the + // window. See bug 1225044. + unsigned int mPendingConfigures; + +#ifdef ACCESSIBILITY + RefPtr<mozilla::a11y::Accessible> mRootAccessible; + + /** + * Request to create the accessible for this window if it is top level. + */ + void CreateRootAccessible(); + + /** + * Dispatch accessible event for the top level window accessible. + * + * @param aEventType [in] the accessible event type to dispatch + */ + void DispatchEventToRootAccessible(uint32_t aEventType); + + /** + * Dispatch accessible window activate event for the top level window + * accessible. + */ + void DispatchActivateEventAccessible(); + + /** + * Dispatch accessible window deactivate event for the top level window + * accessible. + */ + void DispatchDeactivateEventAccessible(); + + /** + * Dispatch accessible window maximize event for the top level window + * accessible. + */ + void DispatchMaximizeEventAccessible(); + + /** + * Dispatch accessible window minize event for the top level window + * accessible. + */ + void DispatchMinimizeEventAccessible(); + + /** + * Dispatch accessible window restore event for the top level window + * accessible. + */ + void DispatchRestoreEventAccessible(); +#endif + + // Updates the bounds of the socket widget we manage for remote plugins. + void ResizePluginSocketWidget(); + + // e10s specific - for managing the socket widget this window hosts. + nsPluginNativeWindowGtk* mPluginNativeWindow; + + // The cursor cache + static GdkCursor *gsGtkCursorCache[eCursorCount]; + + // Transparency + bool mIsTransparent; + // This bitmap tracks which pixels are transparent. We don't support + // full translucency at this time; each pixel is either fully opaque + // or fully transparent. + gchar* mTransparencyBitmap; + + // all of our DND stuff + void InitDragEvent(mozilla::WidgetDragEvent& aEvent); + + float mLastMotionPressure; + + // Remember the last sizemode so that we can restore it when + // leaving fullscreen + nsSizeMode mLastSizeMode; + + static bool DragInProgress(void); + + void DispatchMissedButtonReleases(GdkEventCrossing *aGdkEvent); + + // nsBaseWidget + virtual LayerManager* GetLayerManager(PLayerTransactionChild* aShadowManager = nullptr, + LayersBackend aBackendHint = mozilla::layers::LayersBackend::LAYERS_NONE, + LayerManagerPersistence aPersistence = LAYER_MANAGER_CURRENT) override; + + void CleanLayerManagerRecursive(); + + virtual int32_t RoundsWidgetCoordinatesTo() override; + + /** + * |mIMContext| takes all IME related stuff. + * + * This is owned by the top-level nsWindow or the topmost child + * nsWindow embedded in a non-Gecko widget. + * + * The instance is created when the top level widget is created. And when + * the widget is destroyed, it's released. All child windows refer its + * ancestor widget's instance. So, one set of IM contexts is created for + * all windows in a hierarchy. If the children are released after the top + * level window is released, the children still have a valid pointer, + * however, IME doesn't work at that time. + */ + RefPtr<mozilla::widget::IMContextWrapper> mIMContext; + + mozilla::UniquePtr<mozilla::CurrentX11TimeGetter> mCurrentTimeGetter; +}; + +class nsChildWindow : public nsWindow { +public: + nsChildWindow(); + ~nsChildWindow(); +}; + +#endif /* __nsWindow_h__ */ |