/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=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 "nsWaylandDisplay.h" #include "DMABufFormats.h" #include "base/message_loop.h" // for MessageLoop #include "base/task.h" // for NewRunnableMethod, etc #include "mozilla/gfx/Logging.h" // for gfxCriticalNote #include "mozilla/StaticMutex.h" #include "mozilla/Array.h" #include "mozilla/StaticPtr.h" #include "mozilla/ThreadLocal.h" #include "mozilla/StaticPrefs_widget.h" #include "mozilla/StaticPrefs_general.h" #include "mozilla/Sprintf.h" #include "WidgetUtilsGtk.h" #include "nsGtkKeyUtils.h" #include "nsWindow.h" #include "wayland-proxy.h" namespace mozilla::widget { static nsWaylandDisplay* gWaylandDisplay; void WaylandDisplayRelease() { MOZ_RELEASE_ASSERT(NS_IsMainThread(), "WaylandDisplay can be released in main thread only!"); if (!gWaylandDisplay) { return; } delete gWaylandDisplay; gWaylandDisplay = nullptr; } wl_display* WaylandDisplayGetWLDisplay() { GdkDisplay* disp = gdk_display_get_default(); if (!GdkIsWaylandDisplay(disp)) { return nullptr; } return gdk_wayland_display_get_wl_display(disp); } nsWaylandDisplay* WaylandDisplayGet() { if (!gWaylandDisplay) { MOZ_RELEASE_ASSERT(NS_IsMainThread(), "WaylandDisplay can be created in main thread only!"); wl_display* waylandDisplay = WaylandDisplayGetWLDisplay(); if (!waylandDisplay) { return nullptr; } // We're setting Wayland client buffer size here (i.e. our write buffer). // Server buffer size is set by compositor and we may use the same buffer // sizes on both sides. Mutter uses 1024 * 1024 (1M) so let's use the same // value. wl_display_set_max_buffer_size(waylandDisplay, 1024 * 1024); gWaylandDisplay = new nsWaylandDisplay(waylandDisplay); } return gWaylandDisplay; } void nsWaylandDisplay::SetShm(wl_shm* aShm) { mShm = aShm; } class WaylandPointerEvent { public: RefPtr TakeWindow(wl_surface* aSurface) { if (!aSurface) { mWindow = nullptr; } else { GdkWindow* window = static_cast(wl_surface_get_user_data(aSurface)); mWindow = window ? static_cast( g_object_get_data(G_OBJECT(window), "nsWindow")) : nullptr; } return mWindow; } already_AddRefed GetAndClearWindow() { return mWindow.forget(); } RefPtr GetWindow() { return mWindow; } void SetSource(int32_t aSource) { mSource = aSource; } void SetDelta120(uint32_t aAxis, int32_t aDelta) { switch (aAxis) { case WL_POINTER_AXIS_VERTICAL_SCROLL: mDeltaY = aDelta / 120.0f; break; case WL_POINTER_AXIS_HORIZONTAL_SCROLL: mDeltaX = aDelta / 120.0f; break; default: NS_WARNING("WaylandPointerEvent::SetDelta120(): wrong axis!"); break; } } void SetTime(uint32_t aTime) { mTime = aTime; } void SendScrollEvent() { if (!mWindow || !StaticPrefs::general_smoothScroll()) { return; } // nsWindow::OnSmoothScrollEvent() may spin event loop so // mWindow/mSource/delta may be replaced. int32_t source = mSource; float deltaX = mDeltaX; float deltaY = mDeltaY; mSource = -1; mDeltaX = mDeltaY = 0.0f; // We process wheel events only now. if (source != WL_POINTER_AXIS_SOURCE_WHEEL) { return; } RefPtr win = mWindow; uint32_t eventTime = mTime; win->OnSmoothScrollEvent(eventTime, deltaX, deltaY); } void Clear() { mWindow = nullptr; } WaylandPointerEvent() { Clear(); } private: StaticRefPtr mWindow; uint32_t mTime = 0; int32_t mSource = 0; float mDeltaX = 0; float mDeltaY = 0; }; MOZ_RUNINIT static WaylandPointerEvent sHoldGesture; static void gesture_hold_begin(void* data, struct zwp_pointer_gesture_hold_v1* hold, uint32_t serial, uint32_t time, struct wl_surface* surface, uint32_t fingers) { RefPtr window = sHoldGesture.TakeWindow(surface); if (!window) { return; } window->OnTouchpadHoldEvent(GDK_TOUCHPAD_GESTURE_PHASE_BEGIN, time, fingers); } static void gesture_hold_end(void* data, struct zwp_pointer_gesture_hold_v1* hold, uint32_t serial, uint32_t time, int32_t cancelled) { RefPtr window = sHoldGesture.GetAndClearWindow(); if (!window) { return; } window->OnTouchpadHoldEvent(cancelled ? GDK_TOUCHPAD_GESTURE_PHASE_CANCEL : GDK_TOUCHPAD_GESTURE_PHASE_END, time, 0); } static const struct zwp_pointer_gesture_hold_v1_listener gesture_hold_listener = {gesture_hold_begin, gesture_hold_end}; MOZ_RUNINIT static WaylandPointerEvent sScrollEvent; static void pointer_handle_enter(void* data, struct wl_pointer* pointer, uint32_t serial, struct wl_surface* surface, wl_fixed_t sx, wl_fixed_t sy) { sScrollEvent.TakeWindow(surface); } static void pointer_handle_leave(void* data, struct wl_pointer* pointer, uint32_t serial, struct wl_surface* surface) { sScrollEvent.Clear(); } static void pointer_handle_motion(void* data, struct wl_pointer* pointer, uint32_t time, wl_fixed_t sx, wl_fixed_t sy) { } static void pointer_handle_button(void* data, struct wl_pointer* pointer, uint32_t serial, uint32_t time, uint32_t button, uint32_t state) {} static void pointer_handle_axis(void* data, struct wl_pointer* pointer, uint32_t time, uint32_t axis, wl_fixed_t value) { sScrollEvent.SetTime(time); } static void pointer_handle_frame(void* data, struct wl_pointer* pointer) { sScrollEvent.SendScrollEvent(); } static void pointer_handle_axis_source( void* data, struct wl_pointer* pointer, /*enum wl_pointer_axis_source */ uint32_t source) { sScrollEvent.SetSource(source); } static void pointer_handle_axis_stop(void* data, struct wl_pointer* pointer, uint32_t time, uint32_t axis) {} static void pointer_handle_axis_discrete(void* data, struct wl_pointer* pointer, uint32_t axis, int32_t value) {} static void pointer_handle_axis_value120(void* data, struct wl_pointer* pointer, uint32_t axis, int32_t value) { sScrollEvent.SetDelta120(axis, value); } /* * Example of scroll events we get for various devices. Note that * even three different devices has the same wl_pointer. * * Standard mouse wheel: * * pointer_handle_axis_source pointer 0x7fd14fd4bac0 source 0 * pointer_handle_axis_value120 pointer 0x7fd14fd4bac0 value 120 * pointer_handle_axis pointer 0x7fd14fd4bac0 time 9470441 value 10.000000 * pointer_handle_frame * * Hi-res mouse wheel: * * pointer_handle_axis_source pointer 0x7fd14fd4bac0 source 0 * pointer_handle_axis_value120 pointer 0x7fd14fd4bac0 value -24 * pointer_handle_axis pointer 0x7fd14fd4bac0 time 9593205 value -1.992188 * pointer_handle_frame * * Touchpad: * * pointer_handle_axis_source pointer 0x7fd14fd4bac0 source 1 * pointer_handle_axis pointer 0x7fd14fd4bac0 time 9431830 value 0.312500 * pointer_handle_axis pointer 0x7fd14fd4bac0 time 9431830 value -1.015625 * pointer_handle_frame */ static const struct moz_wl_pointer_listener pointer_listener = { pointer_handle_enter, pointer_handle_leave, pointer_handle_motion, pointer_handle_button, pointer_handle_axis, pointer_handle_frame, pointer_handle_axis_source, pointer_handle_axis_stop, pointer_handle_axis_discrete, pointer_handle_axis_value120, }; void nsWaylandDisplay::SetPointer(wl_pointer* aPointer) { // Don't even try on such old interface if (wl_proxy_get_version((struct wl_proxy*)aPointer) < WL_POINTER_RELEASE_SINCE_VERSION) { return; } MOZ_DIAGNOSTIC_ASSERT(!mPointer); mPointer = aPointer; // We're interested in pointer_handle_axis_value120() only for now. if (wl_proxy_get_version((struct wl_proxy*)aPointer) >= WL_POINTER_AXIS_VALUE120_SINCE_VERSION) { wl_pointer_add_listener( mPointer, (const wl_pointer_listener*)&pointer_listener, this); } // mPointerGestures is set by zwp_pointer_gestures_v1 if we have it. if (mPointerGestures) { mPointerGestureHold = zwp_pointer_gestures_v1_get_hold_gesture(mPointerGestures, mPointer); zwp_pointer_gesture_hold_v1_set_user_data(mPointerGestureHold, this); zwp_pointer_gesture_hold_v1_add_listener(mPointerGestureHold, &gesture_hold_listener, this); } } void nsWaylandDisplay::RemovePointer() { wl_pointer_release(mPointer); mPointer = nullptr; } static void seat_handle_capabilities(void* data, struct wl_seat* seat, unsigned int caps) { auto* display = static_cast(data); if (!display) { return; } if ((caps & WL_SEAT_CAPABILITY_POINTER) && !display->GetPointer()) { display->SetPointer(wl_seat_get_pointer(seat)); } else if (!(caps & WL_SEAT_CAPABILITY_POINTER) && display->GetPointer()) { display->RemovePointer(); } wl_keyboard* keyboard = display->GetKeyboard(); if ((caps & WL_SEAT_CAPABILITY_KEYBOARD) && !keyboard) { display->SetKeyboard(wl_seat_get_keyboard(seat)); } else if (!(caps & WL_SEAT_CAPABILITY_KEYBOARD) && keyboard) { display->ClearKeyboard(); } } static void seat_handle_name(void* data, struct wl_seat* seat, const char* name) { /* We don't care about the name. */ } static const struct wl_seat_listener seat_listener = { seat_handle_capabilities, seat_handle_name, }; void nsWaylandDisplay::SetSeat(wl_seat* aSeat, int aSeatId) { mSeat = aSeat; mSeatId = aSeatId; wl_seat_add_listener(aSeat, &seat_listener, this); } void nsWaylandDisplay::RemoveSeat(int aSeatId) { if (mSeatId == aSeatId) { mSeat = nullptr; mSeatId = -1; } } /* This keymap routine is derived from weston-2.0.0/clients/simple-im.c */ static void keyboard_handle_keymap(void* data, struct wl_keyboard* wl_keyboard, uint32_t format, int fd, uint32_t size) { KeymapWrapper::HandleKeymap(format, fd, size); } static void keyboard_handle_enter(void* data, struct wl_keyboard* keyboard, uint32_t serial, struct wl_surface* surface, struct wl_array* keys) { KeymapWrapper::SetFocusIn(surface, serial); } static void keyboard_handle_leave(void* data, struct wl_keyboard* keyboard, uint32_t serial, struct wl_surface* surface) { KeymapWrapper::SetFocusOut(surface); } static void keyboard_handle_key(void* data, struct wl_keyboard* keyboard, uint32_t serial, uint32_t time, uint32_t key, uint32_t state) { // hardware key code is +8. // https://gitlab.gnome.org/GNOME/gtk/-/blob/3.24.41/gdk/wayland/gdkdevice-wayland.c#L2341 KeymapWrapper::KeyboardHandlerForWayland(serial, key + 8, state); } static void keyboard_handle_modifiers(void* data, struct wl_keyboard* keyboard, uint32_t serial, uint32_t mods_depressed, uint32_t mods_latched, uint32_t mods_locked, uint32_t group) {} static void keyboard_handle_repeat_info(void* data, struct wl_keyboard* keyboard, int32_t rate, int32_t delay) {} static const struct wl_keyboard_listener keyboard_listener = { keyboard_handle_keymap, keyboard_handle_enter, keyboard_handle_leave, keyboard_handle_key, keyboard_handle_modifiers, keyboard_handle_repeat_info}; void nsWaylandDisplay::SetKeyboard(wl_keyboard* aKeyboard) { MOZ_ASSERT(aKeyboard); MOZ_DIAGNOSTIC_ASSERT(!mKeyboard); mKeyboard = aKeyboard; wl_keyboard_add_listener(mKeyboard, &keyboard_listener, nullptr); } void nsWaylandDisplay::ClearKeyboard() { if (mKeyboard) { wl_keyboard_destroy(mKeyboard); mKeyboard = nullptr; KeymapWrapper::ClearKeymap(); } } void nsWaylandDisplay::SetCompositor(wl_compositor* aCompositor) { mCompositor = aCompositor; } void nsWaylandDisplay::SetSubcompositor(wl_subcompositor* aSubcompositor) { mSubcompositor = aSubcompositor; } void nsWaylandDisplay::SetIdleInhibitManager( zwp_idle_inhibit_manager_v1* aIdleInhibitManager) { mIdleInhibitManager = aIdleInhibitManager; } void nsWaylandDisplay::SetViewporter(wp_viewporter* aViewporter) { mViewporter = aViewporter; } void nsWaylandDisplay::SetRelativePointerManager( zwp_relative_pointer_manager_v1* aRelativePointerManager) { mRelativePointerManager = aRelativePointerManager; } void nsWaylandDisplay::SetPointerConstraints( zwp_pointer_constraints_v1* aPointerConstraints) { mPointerConstraints = aPointerConstraints; } void nsWaylandDisplay::SetPointerGestures( zwp_pointer_gestures_v1* aPointerGestures) { mPointerGestures = aPointerGestures; } void nsWaylandDisplay::SetDmabuf(zwp_linux_dmabuf_v1* aDmabuf, int aVersion) { if (!aDmabuf || aVersion < ZWP_LINUX_DMABUF_V1_MODIFIER_SINCE_VERSION) { return; } mDmabuf = aDmabuf; mDmabufIsFeedback = (aVersion >= ZWP_LINUX_DMABUF_V1_GET_DEFAULT_FEEDBACK_SINCE_VERSION); if (mDmabufIsFeedback) { mFormats->InitFeedback(mDmabuf, nullptr); } else { mFormats->InitV3(mDmabuf); } } void nsWaylandDisplay::EnsureDMABufFormats() { if (mDmabuf && !mDmabufIsFeedback) { mFormats->InitV3Done(); } mFormats->EnsureBasicFormats(); } void nsWaylandDisplay::SetXdgActivation(xdg_activation_v1* aXdgActivation) { mXdgActivation = aXdgActivation; } void nsWaylandDisplay::SetAppMenuManager( org_kde_kwin_appmenu_manager* aAppMenuManager) { mAppMenuManager = aAppMenuManager; } void nsWaylandDisplay::SetCMSupportedFeature(uint32_t aFeature) { switch (aFeature) { case WP_COLOR_MANAGER_V1_FEATURE_ICC_V2_V4: mColorManagerSupportedFeature.mICC = true; break; case WP_COLOR_MANAGER_V1_FEATURE_PARAMETRIC: mColorManagerSupportedFeature.mParametric = true; break; case WP_COLOR_MANAGER_V1_FEATURE_SET_PRIMARIES: mColorManagerSupportedFeature.mPrimaries = true; break; case WP_COLOR_MANAGER_V1_FEATURE_SET_TF_POWER: mColorManagerSupportedFeature.mFTPower = true; break; case WP_COLOR_MANAGER_V1_FEATURE_SET_LUMINANCES: mColorManagerSupportedFeature.mLuminances = true; break; case WP_COLOR_MANAGER_V1_FEATURE_SET_MASTERING_DISPLAY_PRIMARIES: mColorManagerSupportedFeature.mDisplayPrimaries = true; break; } } void nsWaylandDisplay::SetCMSupportedTFNamed(uint32_t aTF) { if (aTF < sColorTransfersNum) { mSupportedTransfer[aTF] = aTF; } else { NS_WARNING("Unknow color transfer function!"); } } void nsWaylandDisplay::SetCMSupportedPrimariesNamed(uint32_t aPrimaries) { if (aPrimaries < sColorPrimariesNum) { mSupportedPrimaries[aPrimaries] = aPrimaries; } else { NS_WARNING("Unknown color primaries!"); } } static void supported_intent(void* data, struct wp_color_manager_v1* color_manager, uint32_t render_intent) {} static void supported_feature(void* data, struct wp_color_manager_v1* color_manager, uint32_t feature) { auto* display = static_cast(data); display->SetCMSupportedFeature(feature); } static void supported_tf_named(void* data, struct wp_color_manager_v1* color_manager, uint32_t tf) { auto* display = static_cast(data); display->SetCMSupportedTFNamed(tf); } static void supported_primaries_named(void* data, struct wp_color_manager_v1* color_manager, uint32_t primaries) { auto* display = static_cast(data); display->SetCMSupportedPrimariesNamed(primaries); } static void supported_done(void* data, struct wp_color_manager_v1* wp_color_manager_v1) {} static const struct wp_color_manager_v1_listener color_manager_listener = { supported_intent, supported_feature, supported_tf_named, supported_primaries_named, supported_done, }; void nsWaylandDisplay::SetColorManager(wp_color_manager_v1* aColorManager) { mColorManager = aColorManager; if (mColorManager) { wp_color_manager_v1_add_listener(mColorManager, &color_manager_listener, this); } } static void global_registry_handler(void* data, wl_registry* registry, uint32_t id, const char* interface, uint32_t version) { auto* display = static_cast(data); if (!display) { return; } nsDependentCString iface(interface); if (iface.EqualsLiteral("wl_shm")) { auto* shm = WaylandRegistryBind(registry, id, &wl_shm_interface, 1); display->SetShm(shm); } else if (iface.EqualsLiteral("zwp_idle_inhibit_manager_v1")) { auto* idle_inhibit_manager = WaylandRegistryBind( registry, id, &zwp_idle_inhibit_manager_v1_interface, 1); display->SetIdleInhibitManager(idle_inhibit_manager); } else if (iface.EqualsLiteral("zwp_relative_pointer_manager_v1")) { auto* relative_pointer_manager = WaylandRegistryBind( registry, id, &zwp_relative_pointer_manager_v1_interface, 1); display->SetRelativePointerManager(relative_pointer_manager); } else if (iface.EqualsLiteral("zwp_pointer_constraints_v1")) { auto* pointer_constraints = WaylandRegistryBind( registry, id, &zwp_pointer_constraints_v1_interface, 1); display->SetPointerConstraints(pointer_constraints); } else if (iface.EqualsLiteral("wl_compositor") && version >= WL_SURFACE_DAMAGE_BUFFER_SINCE_VERSION) { auto* compositor = WaylandRegistryBind( registry, id, &wl_compositor_interface, WL_SURFACE_DAMAGE_BUFFER_SINCE_VERSION); display->SetCompositor(compositor); } else if (iface.EqualsLiteral("wl_subcompositor")) { auto* subcompositor = WaylandRegistryBind( registry, id, &wl_subcompositor_interface, 1); display->SetSubcompositor(subcompositor); } else if (iface.EqualsLiteral("wp_viewporter")) { auto* viewporter = WaylandRegistryBind( registry, id, &wp_viewporter_interface, 1); display->SetViewporter(viewporter); } else if (iface.EqualsLiteral("zwp_linux_dmabuf_v1")) { if (version < ZWP_LINUX_DMABUF_V1_MODIFIER_SINCE_VERSION) { return; } int vers = MIN(version, ZWP_LINUX_DMABUF_V1_GET_DEFAULT_FEEDBACK_SINCE_VERSION); auto* dmabuf = WaylandRegistryBind( registry, id, &zwp_linux_dmabuf_v1_interface, vers); display->SetDmabuf(dmabuf, vers); } else if (iface.EqualsLiteral("xdg_activation_v1")) { auto* activation = WaylandRegistryBind( registry, id, &xdg_activation_v1_interface, 1); display->SetXdgActivation(activation); } else if (iface.EqualsLiteral("org_kde_kwin_appmenu_manager")) { auto* appMenuManager = WaylandRegistryBind( registry, id, &org_kde_kwin_appmenu_manager_interface, MIN(version, 2)); display->SetAppMenuManager(appMenuManager); } else if (iface.EqualsLiteral("wl_seat") && version >= WL_POINTER_RELEASE_SINCE_VERSION) { auto* seat = WaylandRegistryBind( registry, id, &wl_seat_interface, MIN(version, WL_POINTER_AXIS_VALUE120_SINCE_VERSION)); display->SetSeat(seat, id); } else if (iface.EqualsLiteral("wp_fractional_scale_manager_v1")) { auto* manager = WaylandRegistryBind( registry, id, &wp_fractional_scale_manager_v1_interface, 1); display->SetFractionalScaleManager(manager); } else if (iface.EqualsLiteral("gtk_primary_selection_device_manager") || iface.EqualsLiteral("zwp_primary_selection_device_manager_v1")) { display->EnablePrimarySelection(); } else if (iface.EqualsLiteral("zwp_pointer_gestures_v1") && version >= ZWP_POINTER_GESTURES_V1_GET_HOLD_GESTURE_SINCE_VERSION) { auto* gestures = WaylandRegistryBind( registry, id, &zwp_pointer_gestures_v1_interface, ZWP_POINTER_GESTURES_V1_GET_HOLD_GESTURE_SINCE_VERSION); display->SetPointerGestures(gestures); } else if (iface.EqualsLiteral("wp_color_manager_v1")) { auto* colorManager = WaylandRegistryBind( registry, id, &wp_color_manager_v1_interface, version); display->SetColorManager(colorManager); } } static void global_registry_remover(void* data, wl_registry* registry, uint32_t id) { auto* display = static_cast(data); if (!display) { return; } display->RemoveSeat(id); } static const struct wl_registry_listener registry_listener = { global_registry_handler, global_registry_remover}; nsWaylandDisplay::~nsWaylandDisplay() = default; static void WlLogHandler(const char* format, va_list args) { char error[1000]; VsprintfLiteral(error, format, args); gfxCriticalNote << "(" << GetDesktopEnvironmentIdentifier().get() << ") Wayland protocol error: " << error; // See Bug 1826583 and Bug 1844653 for reference. // "warning: queue %p destroyed while proxies still attached" and variants // like "zwp_linux_dmabuf_feedback_v1@%d still attached" are exceptions on // Wayland and non-fatal. They are triggered in certain versions of Mesa or // the proprietary Nvidia driver and we don't want to crash because of them. if (strstr(error, "still attached")) { return; } MOZ_CRASH_UNSAFE_PRINTF("(%s) %s Proxy: %s", GetDesktopEnvironmentIdentifier().get(), error, WaylandProxy::GetState()); } void WlCompositorCrashHandler() { gfxCriticalNote << "Wayland protocol error: Compositor (" << GetDesktopEnvironmentIdentifier().get() << ") crashed, proxy: " << WaylandProxy::GetState(); MOZ_CRASH_UNSAFE_PRINTF("Compositor crashed (%s) proxy: %s", GetDesktopEnvironmentIdentifier().get(), WaylandProxy::GetState()); } nsWaylandDisplay::nsWaylandDisplay(wl_display* aDisplay) : mThreadId(PR_GetCurrentThread()), mDisplay(aDisplay) { MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess()); // GTK sets the log handler on display creation, thus we overwrite it here // in a similar fashion wl_log_set_handler_client(WlLogHandler); mFormats = new DMABufFormats(); mRegistry = wl_display_get_registry(mDisplay); wl_registry_add_listener(mRegistry, ®istry_listener, this); wl_display_roundtrip(mDisplay); wl_display_roundtrip(mDisplay); EnsureDMABufFormats(); for (auto& e : mSupportedTransfer) { e = -1; }; for (auto& e : mSupportedPrimaries) { e = -1; }; // Check we have critical Wayland interfaces. // Missing ones indicates a compositor bug and we can't continue. MOZ_RELEASE_ASSERT(GetShm(), "We're missing shm interface!"); MOZ_RELEASE_ASSERT(GetCompositor(), "We're missing compositor interface!"); MOZ_RELEASE_ASSERT(GetSubcompositor(), "We're missing subcompositor interface!"); } } // namespace mozilla::widget