/* 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 "nsWindowX11.h" #include #include #include #include #include #include "GLContextEGL.h" // for GLContextEGL::FindVisual() #include "GLContextGLX.h" // for GLContextGLX::FindVisual() #include "WindowSurfaceX11Image.h" #include "WindowSurfaceX11SHM.h" #include "gfxXlibSurface.h" #include "mozilla/PodOperations.h" #include "mozilla/gfx/gfxVars.h" #include "nsWindow.h" using namespace mozilla; using namespace mozilla::gfx; using namespace mozilla::layers; using namespace mozilla::widget; using mozilla::gl::GLContextEGL; using mozilla::gl::GLContextGLX; void nsWindowX11::GetWorkspaceID(nsAString& workspaceID) { workspaceID.Truncate(); if (!mShell) { return; } LOG("nsWindow::GetWorkspaceID()\n"); // Get the gdk window for this widget. GdkWindow* gdk_window = GetToplevelGdkWindow(); if (!gdk_window) { LOG(" missing Gdk window, quit."); return; } if (WorkspaceManagementDisabled()) { LOG(" WorkspaceManagementDisabled, quit."); return; } GdkAtom cardinal_atom = gdk_x11_xatom_to_atom(XA_CARDINAL); GdkAtom type_returned; int format_returned; int length_returned; long* wm_desktop; if (!gdk_property_get(gdk_window, gdk_atom_intern("_NET_WM_DESKTOP", FALSE), cardinal_atom, 0, // offset INT32_MAX, // length FALSE, // delete &type_returned, &format_returned, &length_returned, (guchar**)&wm_desktop)) { LOG(" gdk_property_get() failed, quit."); return; } LOG(" got workspace ID %d", (int32_t)wm_desktop[0]); workspaceID.AppendInt((int32_t)wm_desktop[0]); g_free(wm_desktop); } void nsWindowX11::MoveToWorkspace(const nsAString& workspaceIDStr) { nsresult rv = NS_OK; int32_t workspaceID = workspaceIDStr.ToInteger(&rv); LOG("nsWindow::MoveToWorkspace() ID %d", workspaceID); if (NS_FAILED(rv) || !workspaceID || !mShell) { LOG(" MoveToWorkspace disabled, quit"); return; } // Get the gdk window for this widget. GdkWindow* gdk_window = GetToplevelGdkWindow(); if (!gdk_window) { LOG(" failed to get GdkWindow, quit."); return; } // This code is inspired by some found in the 'gxtuner' project. // https://github.com/brummer10/gxtuner/blob/792d453da0f3a599408008f0f1107823939d730d/deskpager.cpp#L50 XEvent xevent; Display* xdisplay = gdk_x11_get_default_xdisplay(); GdkScreen* screen = gdk_window_get_screen(gdk_window); Window root_win = GDK_WINDOW_XID(gdk_screen_get_root_window(screen)); GdkDisplay* display = gdk_window_get_display(gdk_window); Atom type = gdk_x11_get_xatom_by_name_for_display(display, "_NET_WM_DESKTOP"); xevent.type = ClientMessage; xevent.xclient.type = ClientMessage; xevent.xclient.serial = 0; xevent.xclient.send_event = TRUE; xevent.xclient.display = xdisplay; xevent.xclient.window = GDK_WINDOW_XID(gdk_window); xevent.xclient.message_type = type; xevent.xclient.format = 32; xevent.xclient.data.l[0] = workspaceID; xevent.xclient.data.l[1] = X11CurrentTime; xevent.xclient.data.l[2] = 0; xevent.xclient.data.l[3] = 0; xevent.xclient.data.l[4] = 0; XSendEvent(xdisplay, root_win, FALSE, SubstructureNotifyMask | SubstructureRedirectMask, &xevent); XFlush(xdisplay); LOG(" moved to workspace"); } Window nsWindowX11::GetX11Window() { return gdk_x11_window_get_xid(mGdkWindow); } // Configure GL visual on X11. bool nsWindowX11::ConfigureX11GLVisual() { auto* screen = gtk_widget_get_screen(mShell); int visualId = 0; bool haveVisual = false; if (gfxVars::UseEGL()) { haveVisual = GLContextEGL::FindVisual(&visualId); } // We are on GLX or use it as a fallback on Mesa, see // https://gitlab.freedesktop.org/mesa/mesa/-/issues/149 if (!haveVisual) { auto* display = GDK_DISPLAY_XDISPLAY(gtk_widget_get_display(mShell)); int screenNumber = GDK_SCREEN_XNUMBER(screen); haveVisual = GLContextGLX::FindVisual(display, screenNumber, &visualId); } GdkVisual* gdkVisual = nullptr; if (haveVisual) { // If we're using CSD, rendering will go through mContainer, but // it will inherit this visual as it is a child of mShell. gdkVisual = gdk_x11_screen_lookup_visual(screen, visualId); } if (!gdkVisual) { NS_WARNING("We're missing X11 Visual!"); // We try to use a fallback alpha visual GdkScreen* screen = gtk_widget_get_screen(mShell); gdkVisual = gdk_screen_get_rgba_visual(screen); } if (gdkVisual) { gtk_widget_set_visual(mShell, gdkVisual); mHasAlphaVisual = true; return true; } return false; } void nsWindowX11::CreateNative() { #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdeprecated-declarations" gtk_widget_set_double_buffered(GTK_WIDGET(mContainer), FALSE); #pragma GCC diagnostic pop mSurfaceProvider.Initialize(GetX11Window()); } void nsWindowX11::ConfigureToplevelWindowNative() { // Set window manager hint to keep fullscreen windows composited. // // If the window were to get unredirected, there could be visible // tearing because Gecko does not align its framebuffer updates with // vblank. // // This must be (re-)applied whenever the shell's X window is created, // including after CSD-triggered re-realize in SetCustomTitlebar(). SetCompositorHint(GTK_WIDGET_COMPOSITED_ENABLED); } void nsWindowX11::DestroyNative() { UnlockNativePointer(); } void nsWindowX11::SetCompositorHint(WindowComposeRequest aState) { gulong value = aState; GdkAtom cardinal_atom = gdk_x11_xatom_to_atom(XA_CARDINAL); gdk_property_change(GetToplevelGdkWindow(), gdk_atom_intern("_NET_WM_BYPASS_COMPOSITOR", FALSE), cardinal_atom, 32, // format GDK_PROP_MODE_REPLACE, (guchar*)&value, 1); } /* XApp progress support currently works by setting a property * on a window with this Atom name. A supporting window manager * will notice this and pass it along to whatever handling has * been implemented on that end (e.g. passing it on to a taskbar * widget.) There is no issue if WM support is lacking, this is * simply ignored in that case. * * See https://github.com/linuxmint/xapps/blob/master/libxapp/xapp-gtk-window.c * for further details. */ #define PROGRESS_HINT "_NET_WM_XAPP_PROGRESS" static void set_window_hint_cardinal(Window xid, const gchar* atom_name, gulong cardinal) { GdkDisplay* display; display = gdk_display_get_default(); if (cardinal > 0) { XChangeProperty(GDK_DISPLAY_XDISPLAY(display), xid, gdk_x11_get_xatom_by_name_for_display(display, atom_name), XA_CARDINAL, 32, PropModeReplace, (guchar*)&cardinal, 1); } else { XDeleteProperty(GDK_DISPLAY_XDISPLAY(display), xid, gdk_x11_get_xatom_by_name_for_display(display, atom_name)); } } void nsWindowX11::SetProgress(unsigned long progressPercent) { progressPercent = MIN(progressPercent, 100); set_window_hint_cardinal(GDK_WINDOW_XID(GetToplevelGdkWindow()), PROGRESS_HINT, progressPercent); } static bool SupportsPointerBarriers(Display* aDisplay) { MOZ_ASSERT(StaticPrefs::dom_pointer_lock_native_lock_enabled()); // XXX We expect the capability doesn't change as we use only one display // everywhere. static const bool sSupported = [&] { int eventBase = -1; int errorBase = -1; if (!XFixesQueryExtension(aDisplay, &eventBase, &errorBase)) { return false; } int major = 0; int minor = 0; if (!XFixesQueryVersion(aDisplay, &major, &minor)) { return false; } return major >= 5; }(); return sSupported; } void nsWindowX11::UpdateNativePointerBarriers() { if (!StaticPrefs::dom_pointer_lock_native_lock_enabled()) { MOZ_ASSERT(!mIsNativePointerLocked); MOZ_ASSERT(!mNativePointerBarriers); return; } if (NS_WARN_IF(!mGdkWindow)) { return; } Display* display = GDK_DISPLAY_XDISPLAY(gdk_window_get_display(mGdkWindow)); if (NS_WARN_IF(!SupportsPointerBarriers(display))) { MOZ_ASSERT(!mNativePointerBarriers); return; } if (mNativePointerBarriers) { XFixesDestroyPointerBarrier(display, mNativePointerBarriers->mLeft); XFixesDestroyPointerBarrier(display, mNativePointerBarriers->mRight); XFixesDestroyPointerBarrier(display, mNativePointerBarriers->mTop); XFixesDestroyPointerBarrier(display, mNativePointerBarriers->mBottom); mNativePointerBarriers.reset(); } if (mIsNativePointerLocked) { Window window = GetX11Window(); mNativePointerBarriers.emplace( XFixesCreatePointerBarrier( display, window, mClientArea.X(), mClientArea.Y(), mClientArea.X(), mClientArea.YMost(), BarrierPositiveX, 0, nullptr), XFixesCreatePointerBarrier(display, window, mClientArea.XMost(), mClientArea.Y(), mClientArea.XMost(), mClientArea.YMost(), BarrierNegativeX, 0, nullptr), XFixesCreatePointerBarrier( display, window, mClientArea.X(), mClientArea.Y(), mClientArea.XMost(), mClientArea.Y(), BarrierPositiveY, 0, nullptr), XFixesCreatePointerBarrier(display, window, mClientArea.X(), mClientArea.YMost(), mClientArea.XMost(), mClientArea.YMost(), BarrierNegativeY, 0, nullptr)); } } void nsWindowX11::LockNativePointer( NativePointerLockMode aNativePointerLockMode) { if (!StaticPrefs::dom_pointer_lock_native_lock_enabled()) { MOZ_ASSERT(!mIsNativePointerLocked); MOZ_ASSERT(!mNativePointerBarriers); return; } if (mIsNativePointerLocked) { MOZ_ASSERT(mNativePointerBarriers); return; } mIsNativePointerLocked = true; UpdateNativePointerBarriers(); } void nsWindowX11::UnlockNativePointer() { if (!mIsNativePointerLocked) { MOZ_ASSERT(!mNativePointerBarriers); return; } MOZ_ASSERT(StaticPrefs::dom_pointer_lock_native_lock_enabled()); mIsNativePointerLocked = false; UpdateNativePointerBarriers(); } void nsWindowX11::NativeShow(bool aAction) { if (aAction) { // unset our flag now that our window has been shown mNeedsShow = true; auto removeShow = MakeScopeExit([&] { mNeedsShow = false; }); LOG("nsWindowX11::NativeShow show\n"); // Set up usertime/startupID metadata for the created window. // On X11 we use gtk_window_set_startup_id() so we need to call it // before show. SetUserTimeAndStartupTokenForActivatedWindow(); LOG(" calling gtk_widget_show(mShell)\n"); gtk_widget_show(mShell); if (mX11HiddenPopupPositioned) { LOG(" re-position hidden popup window [%d, %d]", mClientArea.x, mClientArea.y); gtk_window_move(GTK_WINDOW(mShell), mClientArea.x, mClientArea.y); mX11HiddenPopupPositioned = false; } } else { LOG("nsWindow::NativeShow hide\n"); // 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++) { (void)shellClass->configure_event(mShell, &event); } mPendingConfigures = 0; } gtk_widget_hide(mShell); } }