Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/LibreOffice/vcl/unx/gtk3/   (Office von Apache Version 25.8.3.2©)  Datei vom 5.10.2025 mit Größe 887 kB image not shown  

Quelle  gtkinst.cxx   Sprache: C

 
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * 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 <sal/config.h>

#include <deque>
#include <optional>
#include <stack>
#include <string.h>
#include <string_view>

#include <dndhelper.hxx>
#include <o3tl/test_info.hxx>
#include <osl/process.h>
#include <osl/file.hxx>
#include <unx/gtk/gtkdata.hxx>
#include <unx/gtk/gtkinst.hxx>
#include <unx/genprn.h>
#include <unx/salobj.h>
#include <unx/gtk/gtkgdi.hxx>
#include <unx/gtk/gtkframe.hxx>
#include <unx/gtk/gtkobject.hxx>
#include <unx/gtk/gtksalmenu.hxx>
#include <headless/svpvd.hxx>
#include <headless/svpbmp.hxx>
#include <utility>
#include <vcl/builder.hxx>
#include <vcl/inputtypes.hxx>
#include <vcl/specialchars.hxx>
#include <vcl/sysdata.hxx>
#include <vcl/transfer.hxx>
#include <vcl/toolkit/floatwin.hxx>
#include <unx/genpspgraphics.h>
#include <rtl/strbuf.hxx>
#include <sal/log.hxx>
#include <rtl/uri.hxx>

#include <basegfx/numeric/ftools.hxx>
#include <vcl/settings.hxx>

#include <dlfcn.h>
#include <fcntl.h>
#include <unistd.h>

#if !GTK_CHECK_VERSION(4, 0, 0)
#include "a11y/atkwrapper.hxx"
#endif
#include <com/sun/star/accessibility/XAccessibleContext2.hpp>
#include <com/sun/star/awt/XVclWindowPeer.hpp>
#include <com/sun/star/datatransfer/XTransferable.hpp>
#include <com/sun/star/datatransfer/clipboard/XClipboard.hpp>
#include <com/sun/star/datatransfer/clipboard/XClipboardEx.hpp>
#include <com/sun/star/datatransfer/clipboard/XClipboardNotifier.hpp>
#include <com/sun/star/datatransfer/clipboard/XClipboardListener.hpp>
#include <com/sun/star/datatransfer/clipboard/XFlushableClipboard.hpp>
#include <com/sun/star/datatransfer/clipboard/XSystemClipboard.hpp>
#include <com/sun/star/datatransfer/dnd/DNDConstants.hpp>
#include <com/sun/star/lang/IllegalArgumentException.hpp>
#include <com/sun/star/lang/XMultiServiceFactory.hpp>
#include <com/sun/star/lang/XServiceInfo.hpp>
#include <com/sun/star/lang/XSingleServiceFactory.hpp>
#include <com/sun/star/lang/XInitialization.hpp>
#include <comphelper/lok.hxx>
#include <comphelper/processfactory.hxx>
#include <comphelper/propertyvalue.hxx>
#include <comphelper/sequence.hxx>
#include <comphelper/string.hxx>
#include <cppuhelper/compbase.hxx>
#include <cppuhelper/implbase.hxx>
#include <cppuhelper/supportsservice.hxx>
#include <officecfg/Office/Common.hxx>
#include <rtl/bootstrap.hxx>
#include <o3tl/unreachable.hxx>
#include <o3tl/string_view.hxx>
#include <svl/zforlist.hxx>
#include <svl/zformat.hxx>
#include <tools/helpers.hxx>
#include <tools/fract.hxx>
#include <tools/stream.hxx>
#include <unotools/resmgr.hxx>
#include <unotools/tempfile.hxx>
#include <unx/gstsink.hxx>
#include <vcl/ImageTree.hxx>
#include <vcl/abstdlg.hxx>
#include <vcl/event.hxx>
#include <vcl/i18nhelp.hxx>
#include <vcl/quickselectionengine.hxx>
#include <vcl/mnemonic.hxx>
#include <vcl/filter/PngImageWriter.hxx>
#include <vcl/stdtext.hxx>
#include <vcl/syswin.hxx>
#include <vcl/virdev.hxx>
#include <vcl/weld.hxx>
#include <vcl/wrkwin.hxx>
#include "customcellrenderer.hxx"
#include <strings.hrc>
#include <window.h>
#include <numeric>
#include <boost/property_tree/ptree.hpp>
#include <opengl/zone.hxx>

using namespace com::sun::star;
using namespace com::sun::star::uno;
using namespace com::sun::star::lang;

extern "C"
{
    #define GET_YIELD_MUTEX() static_cast<GtkYieldMutex*>(GetSalInstance()->GetYieldMutex())
#if !GTK_CHECK_VERSION(4, 0, 0)
    static void GdkThreadsEnter()
    {
        GtkYieldMutex *pYieldMutex = GET_YIELD_MUTEX();
        pYieldMutex->ThreadsEnter();
    }
    static void GdkThreadsLeave()
    {
        GtkYieldMutex *pYieldMutex = GET_YIELD_MUTEX();
        pYieldMutex->ThreadsLeave();
    }
#endif

    VCLPLUG_GTK_PUBLIC SalInstance* create_SalInstance()
    {
        SAL_INFO(
            "vcl.gtk",
            "create vcl plugin instance with gtk version " << gtk_get_major_version()
                << " " << gtk_get_minor_version() << " " << gtk_get_micro_version());

        if (gtk_get_major_version() == 3 && gtk_get_minor_version() < 18)
        {
            g_warning("require gtk >= 3.18 for theme expectations");
            return nullptr;
        }

        // for gtk2 it is always built with X support, so this is always called
        // for gtk3 it is normally built with X and Wayland support, if
        // X is supported GDK_WINDOWING_X11 is defined and this is always
        // called, regardless of if we're running under X or Wayland.
        // We can't use (DLSYM_GDK_IS_X11_DISPLAY(pDisplay)) to only do it under
        // X, because we need to do it earlier than we have a display
#if defined(GDK_WINDOWING_X11)
        /* #i92121# workaround deadlocks in the X11 implementation
        */

        static const char* pNoXInitThreads = getenv( "SAL_NO_XINITTHREADS" );
        /* #i90094#
           from now on we know that an X connection will be
           established, so protect X against itself
        */

        if( ! ( pNoXInitThreads && *pNoXInitThreads ) )
            XInitThreads();
#endif

#if !GTK_CHECK_VERSION(4, 0, 0)
        gdk_threads_set_lock_functions (GdkThreadsEnter, GdkThreadsLeave);
        SAL_INFO("vcl.gtk""Hooked gdk threads locks");
#endif

        auto pYieldMutex = std::make_unique<GtkYieldMutex>();

#if !GTK_CHECK_VERSION(4, 0, 0)
        gdk_threads_init();
#endif

        GtkInstance* pInstance = new GtkInstance( std::move(pYieldMutex) );
        SAL_INFO("vcl.gtk""creating GtkInstance " << pInstance);

        // Create SalData, this does not leak
        new GtkSalData();

        return pInstance;
    }
}

#if !GTK_CHECK_VERSION(4, 0, 0)
static VclInputFlags categorizeEvent(const GdkEvent *pEvent)
{
    VclInputFlags nType = VclInputFlags::NONE;
    switch (gdk_event_get_event_type(pEvent))
    {
    case GDK_MOTION_NOTIFY:
    case GDK_BUTTON_PRESS:
#if !GTK_CHECK_VERSION(4, 0, 0)
    case GDK_2BUTTON_PRESS:
    case GDK_3BUTTON_PRESS:
#endif
    case GDK_BUTTON_RELEASE:
    case GDK_ENTER_NOTIFY:
    case GDK_LEAVE_NOTIFY:
    case GDK_SCROLL:
        nType = VclInputFlags::MOUSE;
        break;
    case GDK_KEY_PRESS:
    // case GDK_KEY_RELEASE: //similar to the X11SalInstance one
        nType = VclInputFlags::KEYBOARD;
        break;
#if !GTK_CHECK_VERSION(4, 0, 0)
    case GDK_EXPOSE:
        nType = VclInputFlags::PAINT;
        break;
#endif
    default:
        nType = VclInputFlags::OTHER;
        break;
    }
    return nType;
}
#endif

GtkInstance::GtkInstance( std::unique_ptr<SalYieldMutex> pMutex )
    : SvpSalInstance( std::move(pMutex) )
    , m_pTimer(nullptr)
    , bNeedsInit(true)
    , m_pLastCairoFontOptions(nullptr)
{
    m_bSupportsOpenGL = true;
}

//We want to defer initializing gtk until we are after uno has been
//bootstrapped so we can ask the config what the UI language is so that we can
//force that in as $LANGUAGE to get gtk to render widgets RTL if we have a RTL
//UI in a LTR locale
void GtkInstance::AfterAppInit()
{
    EnsureInit();
}

void GtkInstance::EnsureInit()
{
    if (!bNeedsInit)
        return;
    // initialize SalData
    GtkSalData *pSalData = GetGtkSalData();
    pSalData->Init();
    GtkSalData::initNWF();

    ImplSVData* pSVData = ImplGetSVData();
#ifdef GTK_TOOLKIT_NAME
    // [-loplugin:ostr] if we use a literal here, we get use-after-free on shutdown
    pSVData->maAppData.mxToolkitName = OUString(GTK_TOOLKIT_NAME);
#else
    // [-loplugin:ostr] if we use a literal here, we get use-after-free on shutdown
    pSVData->maAppData.mxToolkitName = OUString("gtk3");
#endif

    bNeedsInit = false;
}

GtkInstance::~GtkInstance()
{
    assert( nullptr == m_pTimer );
    ResetLastSeenCairoFontOptions(nullptr);
}

SalFrame* GtkInstance::CreateFrame( SalFrame* pParent, SalFrameStyleFlags nStyle )
{
    EnsureInit();
    return new GtkSalFrame( pParent, nStyle );
}

SalFrame* GtkInstance::CreateChildFrame( SystemParentData* pParentData, SalFrameStyleFlags )
{
    EnsureInit();
    return new GtkSalFrame( pParentData );
}

SalObject* GtkInstance::CreateObject( SalFrame* pParent, SystemWindowData* pWindowData, bool bShow )
{
    EnsureInit();
    //FIXME: Missing CreateObject functionality ...
    if (pWindowData && pWindowData->bClipUsingNativeWidget)
        return new GtkSalObjectWidgetClip(static_cast<GtkSalFrame*>(pParent), bShow);
    return new GtkSalObject(static_cast<GtkSalFrame*>(pParent), bShow);
}

extern "C"
{
    typedef void*(* getDefaultFnc)();
    typedef void(* addItemFnc)(void *, const char *);
}

void GtkInstance::AddToRecentDocumentList(const OUString& rFileUrl, const OUString&, const OUString&)
{
    EnsureInit();
    OString sGtkURL;
    rtl_TextEncoding aSystemEnc = osl_getThreadTextEncoding();
    if ((aSystemEnc == RTL_TEXTENCODING_UTF8) || !rFileUrl.startsWith( "file://" ))
        sGtkURL = OUStringToOString(rFileUrl, RTL_TEXTENCODING_UTF8);
    else
    {
        //Non-utf8 locales are a bad idea if trying to work with non-ascii filenames
        //Decode %XX components
        OUString sDecodedUri = rtl::Uri::decode(rFileUrl.copy(7), rtl_UriDecodeToIuri, RTL_TEXTENCODING_UTF8);
        //Convert back to system locale encoding
        OString sSystemUrl = OUStringToOString(sDecodedUri, aSystemEnc);
        //Encode to an escaped ASCII-encoded URI
        gchar *g_uri = g_filename_to_uri(sSystemUrl.getStr(), nullptr, nullptr);
        sGtkURL = OString(g_uri);
        g_free(g_uri);
    }
    GtkRecentManager *manager = gtk_recent_manager_get_default ();
    gtk_recent_manager_add_item (manager, sGtkURL.getStr());
}

SalInfoPrinter* GtkInstance::CreateInfoPrinter( SalPrinterQueueInfo* pQueueInfo,
    ImplJobSetup* pSetupData )
{
    EnsureInit();
    mbPrinterInit = true;
    // create and initialize SalInfoPrinter
    PspSalInfoPrinter* pPrinter = new PspSalInfoPrinter;
    configurePspInfoPrinter(pPrinter, pQueueInfo, pSetupData);
    return pPrinter;
}

std::unique_ptr<SalPrinter> GtkInstance::CreatePrinter( SalInfoPrinter* pInfoPrinter )
{
    EnsureInit();
    mbPrinterInit = true;
    return std::unique_ptr<SalPrinter>(new PspSalPrinter(pInfoPrinter));
}

/*
 * These methods always occur in pairs
 * A ThreadsEnter is followed by a ThreadsLeave
 * We need to queue up the recursive lock count
 * for each pair, so we can accurately restore
 * it later.
 */

thread_local std::stack<sal_uInt32> GtkYieldMutex::yieldCounts;

void GtkYieldMutex::ThreadsEnter()
{
    acquire();
    if (yieldCounts.empty())
        return;
    auto n = yieldCounts.top();
    yieldCounts.pop();

    const bool bUndoingLeaveWithoutEnter = n == 0;
    // if the ThreadsLeave bLeaveWithoutEnter of true condition occurred to
    // create this entry then return early undoing the initial acquire of the
    // function
    if G_UNLIKELY(bUndoingLeaveWithoutEnter)
    {
        release();
        return;
    }

    assert(n > 0);
    n--;
    if (n > 0)
        acquire(n);
}

void GtkYieldMutex::ThreadsLeave()
{
    const bool bLeaveWithoutEnter = m_nCount == 0;
    SAL_WARN_IF(bLeaveWithoutEnter, "vcl.gtk""gdk_threads_leave without matching gdk_threads_enter");
    yieldCounts.push(m_nCount);
    if G_UNLIKELY(bLeaveWithoutEnter) // this ideally shouldn't happen, but can due to the gtk3 file dialog
        return;
    release(true);
}

std::unique_ptr<SalVirtualDevice> GtkInstance::CreateVirtualDevice( SalGraphics &rG,
                                                    tools::Long nDX, tools::Long nDY,
                                                    DeviceFormat /*eFormat*/ )
{
    EnsureInit();
    SvpSalGraphics *pSvpSalGraphics = dynamic_cast<SvpSalGraphics*>(&rG);
    assert(pSvpSalGraphics);
    // tdf#127529 see SvpSalInstance::CreateVirtualDevice for the rare case of a non-null pPreExistingTarget
    std::unique_ptr<SalVirtualDevice> xNew(new SvpSalVirtualDevice(pSvpSalGraphics->getSurface(), /*pPreExistingTarget*/nullptr));
    if (!xNew->SetSize(nDX, nDY))
        xNew.reset();
    return xNew;
}

std::unique_ptr<SalVirtualDevice> GtkInstance::CreateVirtualDevice( SalGraphics &rG,
                                                    tools::Long &nDX, tools::Long &nDY,
                                                    DeviceFormat /*eFormat*/,
                                                    const SystemGraphicsData& rGd )
{
    EnsureInit();
    SvpSalGraphics *pSvpSalGraphics = dynamic_cast<SvpSalGraphics*>(&rG);
    assert(pSvpSalGraphics);
    // tdf#127529 see SvpSalInstance::CreateVirtualDevice for the rare case of a non-null pPreExistingTarget
    cairo_surface_t* pPreExistingTarget = static_cast<cairo_surface_t*>(rGd.pSurface);
    std::unique_ptr<SalVirtualDevice> xNew(new SvpSalVirtualDevice(pSvpSalGraphics->getSurface(), pPreExistingTarget));
    if (!xNew->SetSize(nDX, nDY))
        xNew.reset();
    return xNew;
}

std::shared_ptr<SalBitmap> GtkInstance::CreateSalBitmap()
{
    EnsureInit();
    return SvpSalInstance::CreateSalBitmap();
}

std::unique_ptr<SalMenu> GtkInstance::CreateMenu( bool bMenuBar, Menu* pVCLMenu )
{
    EnsureInit();
    GtkSalMenu* pSalMenu = new GtkSalMenu( bMenuBar );
    pSalMenu->SetMenu( pVCLMenu );
    return std::unique_ptr<SalMenu>(pSalMenu);
}

std::unique_ptr<SalMenuItem> GtkInstance::CreateMenuItem( const SalItemParams & rItemData )
{
    EnsureInit();
    return std::unique_ptr<SalMenuItem>(new GtkSalMenuItem( &rItemData ));
}

SalTimer* GtkInstance::CreateSalTimer()
{
    EnsureInit();
    assert( nullptr == m_pTimer );
    if ( nullptr == m_pTimer )
        m_pTimer = new GtkSalTimer();
    return m_pTimer;
}

void GtkInstance::RemoveTimer ()
{
    EnsureInit();
    m_pTimer = nullptr;
}

bool GtkInstance::DoYield(bool bWait, bool bHandleAllCurrentEvents)
{
    EnsureInit();
    return GetGtkSalData()->Yield( bWait, bHandleAllCurrentEvents );
}

bool GtkInstance::IsTimerExpired()
{
    EnsureInit();
    return (m_pTimer && m_pTimer->Expired());
}

namespace
{
    bool DisplayHasAnyInput()
    {
        GdkDisplay* pDisplay = gdk_display_get_default();
#if defined(GDK_WINDOWING_WAYLAND)
        if (DLSYM_GDK_IS_WAYLAND_DISPLAY(pDisplay))
        {
            bool bRet = false;
            wl_display* pWLDisplay = gdk_wayland_display_get_wl_display(pDisplay);
            static auto wayland_display_get_fd = reinterpret_cast<int (*) (wl_display*)>(dlsym(nullptr, "wl_display_get_fd"));
            if (wayland_display_get_fd)
            {
                GPollFD aPollFD;
                aPollFD.fd = wayland_display_get_fd(pWLDisplay);
                aPollFD.events = G_IO_IN | G_IO_ERR | G_IO_HUP;
                bRet = g_poll(&aPollFD, 1, 0) > 0;
            }
            return bRet;
        }
#endif
#if defined(GDK_WINDOWING_X11)
        if (DLSYM_GDK_IS_X11_DISPLAY(pDisplay))
        {
            GPollFD aPollFD;
            aPollFD.fd = ConnectionNumber(gdk_x11_display_get_xdisplay(pDisplay));
            aPollFD.events = G_IO_IN;
            return g_poll(&aPollFD, 1, 0) > 0;
        }
#endif
        return false;
    }
}

bool GtkInstance::AnyInput( VclInputFlags nType )
{
    EnsureInit();
    if( (nType & VclInputFlags::TIMER) && IsTimerExpired() )
        return true;

    // strip timer bits now
    nType = nType & ~VclInputFlags::TIMER;

    static constexpr VclInputFlags ANY_INPUT_EXCLUDING_TIMER = VCL_INPUT_ANY & ~VclInputFlags::TIMER;

    const bool bCheckForAnyInput = nType == ANY_INPUT_EXCLUDING_TIMER;

    bool bRet = false;

    if (bCheckForAnyInput)
        bRet = DisplayHasAnyInput();

#if !GTK_CHECK_VERSION(4, 0, 0)
    GdkDisplay* pDisplay = gdk_display_get_default();
    if (!gdk_display_has_pending(pDisplay))
        return bRet;

    if (bCheckForAnyInput)
        return true;

    std::deque<GdkEvent*> aEvents;
    GdkEvent *pEvent = nullptr;
    while ((pEvent = gdk_display_get_event(pDisplay)))
    {
        aEvents.push_back(pEvent);
        VclInputFlags nEventType = categorizeEvent(pEvent);
        if ( (nEventType & nType) || ( nEventType == VclInputFlags::NONE && (nType & VclInputFlags::OTHER) ) )
        {
            bRet = true;
        }
    }

    while (!aEvents.empty())
    {
        pEvent = aEvents.front();
        gdk_display_put_event(pDisplay, pEvent);
        gdk_event_free(pEvent);
        aEvents.pop_front();
    }
#endif

    return bRet;
}

std::unique_ptr<GenPspGraphics> GtkInstance::CreatePrintGraphics()
{
    EnsureInit();
    return std::make_unique<GenPspGraphics>();
}

const cairo_font_options_t* GtkInstance::GetCairoFontOptions()
{
#if !GTK_CHECK_VERSION(4, 0, 0)
    const cairo_font_options_t* pCairoFontOptions = gdk_screen_get_font_options(gdk_screen_get_default());
#else
    auto pDefaultWin = ImplGetDefaultWindow();
    assert(pDefaultWin);
    SalFrame* pDefaultFrame = pDefaultWin->ImplGetFrame();
    GtkSalFrame* pGtkFrame = dynamic_cast<GtkSalFrame*>(pDefaultFrame);
    assert(pGtkFrame);
    const cairo_font_options_t* pCairoFontOptions = pGtkFrame->get_font_options();
#endif
    if (!m_pLastCairoFontOptions && pCairoFontOptions)
        m_pLastCairoFontOptions = cairo_font_options_copy(pCairoFontOptions);
    return pCairoFontOptions;
}

const cairo_font_options_t* GtkInstance::GetLastSeenCairoFontOptions() const
{
    return m_pLastCairoFontOptions;
}

void GtkInstance::ResetLastSeenCairoFontOptions(const cairo_font_options_t* pCairoFontOptions)
{
    if (m_pLastCairoFontOptions)
        cairo_font_options_destroy(m_pLastCairoFontOptions);
    if (pCairoFontOptions)
        m_pLastCairoFontOptions = cairo_font_options_copy(pCairoFontOptions);
    else
        m_pLastCairoFontOptions = nullptr;
}


namespace
{
    struct TypeEntry
    {
        const char*     pNativeType;        // string corresponding to nAtom for the case of nAtom being uninitialized
        const char*     pType;              // Mime encoding on our side
    };

    const TypeEntry aConversionTab[] =
    {
        { "ISO10646-1""text/plain;charset=utf-16" },
        { "UTF8_STRING""text/plain;charset=utf-8" },
        { "UTF-8""text/plain;charset=utf-8" },
        { "text/plain;charset=UTF-8""text/plain;charset=utf-8" },
        // ISO encodings
        { "ISO8859-2""text/plain;charset=iso8859-2" },
        { "ISO8859-3""text/plain;charset=iso8859-3" },
        { "ISO8859-4""text/plain;charset=iso8859-4" },
        { "ISO8859-5""text/plain;charset=iso8859-5" },
        { "ISO8859-6""text/plain;charset=iso8859-6" },
        { "ISO8859-7""text/plain;charset=iso8859-7" },
        { "ISO8859-8""text/plain;charset=iso8859-8" },
        { "ISO8859-9""text/plain;charset=iso8859-9" },
        { "ISO8859-10""text/plain;charset=iso8859-10" },
        { "ISO8859-13""text/plain;charset=iso8859-13" },
        { "ISO8859-14""text/plain;charset=iso8859-14" },
        { "ISO8859-15""text/plain;charset=iso8859-15" },
        // asian encodings
        { "JISX0201.1976-0""text/plain;charset=jisx0201.1976-0" },
        { "JISX0208.1983-0""text/plain;charset=jisx0208.1983-0" },
        { "JISX0208.1990-0""text/plain;charset=jisx0208.1990-0" },
        { "JISX0212.1990-0""text/plain;charset=jisx0212.1990-0" },
        { "GB2312.1980-0""text/plain;charset=gb2312.1980-0" },
        { "KSC5601.1992-0""text/plain;charset=ksc5601.1992-0" },
        // eastern european encodings
        { "KOI8-R""text/plain;charset=koi8-r" },
        { "KOI8-U""text/plain;charset=koi8-u" },
        // String (== iso8859-1)
        { "STRING""text/plain;charset=iso8859-1" },
        // special for compound text
        { "COMPOUND_TEXT""text/plain;charset=compound_text" },

        // PIXMAP
        { "PIXMAP""image/bmp" }
    };

    class DataFlavorEq
    {
    private:
        const css::datatransfer::DataFlavor& m_rData;
    public:
        explicit DataFlavorEq(const css::datatransfer::DataFlavor& rData) : m_rData(rData) {}
        bool operator() (const css::datatransfer::DataFlavor& rData) const
        {
            return rData.MimeType == m_rData.MimeType &&
                   rData.DataType  == m_rData.DataType;
        }
    };
}

#if GTK_CHECK_VERSION(4, 0, 0)
std::vector<css::datatransfer::DataFlavor> GtkTransferable::getTransferDataFlavorsAsVector(const char * const *targets, gint n_targets)
#else
std::vector<css::datatransfer::DataFlavor> GtkTransferable::getTransferDataFlavorsAsVector(GdkAtom *targets, gint n_targets)
#endif
{
    std::vector<css::datatransfer::DataFlavor> aVector;

    bool bHaveText = false, bHaveUTF16 = false;

    for (gint i = 0; i < n_targets; ++i)
    {
#if GTK_CHECK_VERSION(4, 0, 0)
        const gchar* pName = targets[i];
#else
        gchar* pName = gdk_atom_name(targets[i]);
#endif
        const char* pFinalName = pName;
        css::datatransfer::DataFlavor aFlavor;

        // omit text/plain;charset=unicode since it is not well defined
        if (rtl_str_compare(pName, "text/plain;charset=unicode") == 0)
        {
#if !GTK_CHECK_VERSION(4, 0, 0)
            g_free(pName);
#endif
            continue;
        }

        for (size_t j = 0; j < SAL_N_ELEMENTS(aConversionTab); ++j)
        {
            if (rtl_str_compare(pName, aConversionTab[j].pNativeType) == 0)
            {
                pFinalName = aConversionTab[j].pType;
                break;
            }
        }

        // There are more non-MIME-types reported that are not translated by
        // aConversionTab, like "SAVE_TARGETS", "INTEGER", "ATOM"; just filter
        // them out for now before they confuse this code's clients:
        if (rtl_str_indexOfChar(pFinalName, '/') == -1)
        {
#if !GTK_CHECK_VERSION(4, 0, 0)
            g_free(pName);
#endif
            continue;
        }

        aFlavor.MimeType = OUString(pFinalName,
                                    strlen(pFinalName),
                                    RTL_TEXTENCODING_UTF8);

        m_aMimeTypeToGtkType[aFlavor.MimeType] = targets[i];

        aFlavor.DataType = cppu::UnoType<Sequence< sal_Int8 >>::get();

        sal_Int32 nIndex(0);
        if (o3tl::getToken(aFlavor.MimeType, 0, ';', nIndex) == u"text/plain")
        {
            bHaveText = true;
            std::u16string_view aToken(o3tl::getToken(aFlavor.MimeType, 0, ';', nIndex));
            if (aToken == u"charset=utf-16")
            {
                bHaveUTF16 = true;
                aFlavor.DataType = cppu::UnoType<OUString>::get();
            }
        }
        aVector.push_back(aFlavor);
#if !GTK_CHECK_VERSION(4, 0, 0)
        g_free(pName);
#endif
    }

    //If we have text, but no UTF-16 format which is basically the only
    //text-format LibreOffice supports for cnp then claim we do and we
    //will convert on demand
    if (bHaveText && !bHaveUTF16)
    {
        css::datatransfer::DataFlavor aFlavor;
        aFlavor.MimeType = "text/plain;charset=utf-16";
        aFlavor.DataType = cppu::UnoType<OUString>::get();
        aVector.push_back(aFlavor);
    }

    return aVector;
}

css::uno::Sequence<css::datatransfer::DataFlavor> SAL_CALL GtkTransferable::getTransferDataFlavors()
{
    return comphelper::containerToSequence(getTransferDataFlavorsAsVector());
}

sal_Bool SAL_CALL GtkTransferable::isDataFlavorSupported(const css::datatransfer::DataFlavor& rFlavor)
{
    const std::vector<css::datatransfer::DataFlavor> aAll =
        getTransferDataFlavorsAsVector();

    return std::any_of(aAll.begin(), aAll.end(), DataFlavorEq(rFlavor));
}

#if GTK_CHECK_VERSION(4, 0, 0)
void read_transfer_result::read_block_async_completed(GObject* source, GAsyncResult* res, gpointer user_data)
{
    GInputStream* stream = G_INPUT_STREAM(source);
    read_transfer_result* pRes = static_cast<read_transfer_result*>(user_data);

    gsize bytes_read = g_input_stream_read_finish(stream, res, nullptr);

    bool bFinished = bytes_read == 0;

    if (bFinished)
    {
        g_object_unref(stream);
        pRes->aVector.resize(pRes->nRead);
        pRes->bDone = true;
        g_main_context_wakeup(nullptr);
        return;
    }

    pRes->nRead += bytes_read;

    pRes->aVector.resize(pRes->nRead + read_transfer_result::BlockSize);

    g_input_stream_read_async(stream,
                              pRes->aVector.data() + pRes->nRead,
                              read_transfer_result::BlockSize,
                              G_PRIORITY_DEFAULT,
                              nullptr,
                              read_block_async_completed,
                              user_data);
}

OUString read_transfer_result::get_as_string() const
{
    const char* pStr = reinterpret_cast<const char*>(aVector.data());
    return OUString(pStr, aVector.size(), RTL_TEXTENCODING_UTF8).replaceAll("\r\n""\n");
}

css::uno::Sequence<sal_Int8> read_transfer_result::get_as_sequence() const
{
    return css::uno::Sequence<sal_Int8>(aVector.data(), aVector.size());
}
#endif

namespace {

GdkClipboard* clipboard_get(SelectionType eSelection)
{
#if GTK_CHECK_VERSION(4, 0, 0)
    if (eSelection == SELECTION_CLIPBOARD)
        return gdk_display_get_clipboard(gdk_display_get_default());
    return gdk_display_get_primary_clipboard(gdk_display_get_default());
#else
    return gtk_clipboard_get(eSelection == SELECTION_CLIPBOARD ? GDK_SELECTION_CLIPBOARD : GDK_SELECTION_PRIMARY);
#endif
}

#if GTK_CHECK_VERSION(4, 0, 0)

void read_clipboard_async_completed(GObject* source, GAsyncResult* res, gpointer user_data)
{
    GdkClipboard* clipboard = GDK_CLIPBOARD(source);
    read_transfer_result* pRes = static_cast<read_transfer_result*>(user_data);

    GInputStream* pResult = gdk_clipboard_read_finish(clipboard, res, nullptr, nullptr);

    if (!pResult)
    {
        pRes->bDone = true;
        g_main_context_wakeup(nullptr);
        return;
    }

    pRes->aVector.resize(read_transfer_result::BlockSize);

    g_input_stream_read_async(pResult,
                              pRes->aVector.data(),
                              pRes->aVector.size(),
                              G_PRIORITY_DEFAULT,
                              nullptr,
                              read_transfer_result::read_block_async_completed,
                              user_data);
}

#endif

class GtkClipboardTransferable : public GtkTransferable
{
private:
    SelectionType m_eSelection;

public:

    explicit GtkClipboardTransferable(SelectionType eSelection)
        : m_eSelection(eSelection)
    {
    }

    /*
     * XTransferable
     */


    virtual css::uno::Any SAL_CALL getTransferData(const css::datatransfer::DataFlavor&&nbsp;rFlavor) override
    {
        css::uno::Any aRet;

        css::datatransfer::DataFlavor aFlavor(rFlavor);
        if (aFlavor.MimeType == "text/plain;charset=utf-16")
            aFlavor.MimeType = "text/plain;charset=utf-8";

        GdkClipboard* clipboard = clipboard_get(m_eSelection);

#if !GTK_CHECK_VERSION(4, 0, 0)
        if (aFlavor.MimeType == "text/plain;charset=utf-8")
        {
            gchar *pText = gtk_clipboard_wait_for_text(clipboard);
            OUString aStr(pText, pText ? strlen(pText) : 0, RTL_TEXTENCODING_UTF8);
            g_free(pText);
            aRet <<= aStr.replaceAll("\r\n""\n");
            return aRet;
        }
#endif

        auto it = m_aMimeTypeToGtkType.find(aFlavor.MimeType);
        if (it == m_aMimeTypeToGtkType.end())
            return css::uno::Any();

#if !GTK_CHECK_VERSION(4, 0, 0)
        GtkSelectionData* data = gtk_clipboard_wait_for_contents(clipboard,
                                                                 it->second);
        if (!data)
        {
            return css::uno::Any();
        }
        gint length;
        const guchar *rawdata = gtk_selection_data_get_data_with_length(data,
                                                                        &length);
        Sequence<sal_Int8> aSeq(reinterpret_cast<const sal_Int8*>(rawdata), length);
        gtk_selection_data_free(data);
        aRet <<= aSeq;
#else
        SalInstance* pInstance = GetSalInstance();
        read_transfer_result aRes;
        const char *mime_types[] = { it->second.getStr(), nullptr };

        gdk_clipboard_read_async(clipboard,
                                 mime_types,
                                 G_PRIORITY_DEFAULT,
                                 nullptr,
                                 read_clipboard_async_completed,
                                 &aRes);

        while (!aRes.bDone)
            pInstance->DoYield(truefalse);

        if (aFlavor.MimeType == "text/plain;charset=utf-8")
            aRet <<= aRes.get_as_string();
        else
            aRet <<= aRes.get_as_sequence();
#endif
        return aRet;
    }

    std::vector<css::datatransfer::DataFlavor> getTransferDataFlavorsAsVector()
        override
    {
        std::vector<css::datatransfer::DataFlavor> aVector;

        GdkClipboard* clipboard = clipboard_get(m_eSelection);

#if GTK_CHECK_VERSION(4, 0, 0)
        GdkContentFormats* pFormats = gdk_clipboard_get_formats(clipboard);
        gsize n_targets;
        const char * const *targets = gdk_content_formats_get_mime_types(pFormats, &n_targets);
        aVector = GtkTransferable::getTransferDataFlavorsAsVector(targets, n_targets);
#else
        GdkAtom *targets;
        gint n_targets;
        if (gtk_clipboard_wait_for_targets(clipboard, &targets, &n_targets))
        {
            aVector = GtkTransferable::getTransferDataFlavorsAsVector(targets, n_targets);
            g_free(targets);
        }
#endif

        return aVector;
    }
};

class VclGtkClipboard :
        public cppu::WeakComponentImplHelper<
        datatransfer::clipboard::XSystemClipboard,
        datatransfer::clipboard::XFlushableClipboard,
        XServiceInfo>
{
    SelectionType                                            m_eSelection;
    osl::Mutex                                               m_aMutex;
    gulong                                                   m_nOwnerChangedSignalId;
    ImplSVEvent*                                             m_pSetClipboardEvent;
    Reference<css::datatransfer::XTransferable>              m_aContents;
    Reference<css::datatransfer::clipboard::XClipboardOwner> m_aOwner;
    std::vector< Reference<css::datatransfer::clipboard::XClipboardListener> > m_aListeners;
#if GTK_CHECK_VERSION(4, 0, 0)
    std::vector<OString> m_aGtkTargets;
    TransferableContent* m_pClipboardContent;
#else
    std::vector<GtkTargetEntry> m_aGtkTargets;
#endif
    VclToGtkHelper m_aConversionHelper;

    DECL_LINK(AsyncSetGtkClipboard, void*, void);

#if GTK_CHECK_VERSION(4, 0, 0)
    DECL_LINK(DetachClipboard, void*, void);
#endif

public:

    explicit VclGtkClipboard(SelectionType eSelection);
    virtual ~VclGtkClipboard() override;

    /*
     * XServiceInfo
     */


    virtual OUString SAL_CALL getImplementationName() override;
    virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override;
    virtual Sequence< OUString > SAL_CALL getSupportedServiceNames() override;

    /*
     * XClipboard
     */


    virtual Reference< css::datatransfer::XTransferable > SAL_CALL getContents() override;

    virtual void SAL_CALL setContents(
        const Reference< css::datatransfer::XTransferable >& xTrans,
        const Reference< css::datatransfer::clipboard::XClipboardOwner >& xClipboardOwner ) override;

    virtual OUString SAL_CALL getName() override;

    /*
     * XClipboardEx
     */


    virtual sal_Int8 SAL_CALL getRenderingCapabilities() override;

    /*
     * XFlushableClipboard
     */

    virtual void SAL_CALL flushClipboard() override;

    /*
     * XClipboardNotifier
     */

    virtual void SAL_CALL addClipboardListener(
        const Reference< css::datatransfer::clipboard::XClipboardListener >& listener ) override;

    virtual void SAL_CALL removeClipboardListener(
        const Reference< css::datatransfer::clipboard::XClipboardListener >& listener ) override;

#if !GTK_CHECK_VERSION(4, 0, 0)
    void ClipboardGet(GtkSelectionData *selection_data, guint info);
#endif
    void OwnerPossiblyChanged(GdkClipboard *clipboard);
    void ClipboardClear();
    void SetGtkClipboard();
    void SyncGtkClipboard();
};

}

OUString VclGtkClipboard::getImplementationName()
{
    return u"com.sun.star.datatransfer.VclGtkClipboard"_ustr;
}

Sequence< OUString > VclGtkClipboard::getSupportedServiceNames()
{
    Sequence<OUString> aRet { u"com.sun.star.datatransfer.clipboard.SystemClipboard"_ustr };
    return aRet;
}

sal_Bool VclGtkClipboard::supportsService( const OUString& ServiceName )
{
    return cppu::supportsService(this, ServiceName);
}

Reference< css::datatransfer::XTransferable > VclGtkClipboard::getContents()
{
    if (!m_aContents.is())
    {
        //tdf#93887 This is the system clipboard/selection. We fetch it when we are not
        //the owner of the clipboard and have not already fetched it.
        m_aContents = new GtkClipboardTransferable(m_eSelection);
#if GTK_CHECK_VERSION(4, 0, 0)
        if (m_pClipboardContent)
            transerable_content_set_transferable(m_pClipboardContent, m_aContents.get());
#endif
    }
    return m_aContents;
}

#if !GTK_CHECK_VERSION(4, 0, 0)
void VclGtkClipboard::ClipboardGet(GtkSelectionData *selection_data, guint info)
{
    if (!m_aContents.is())
        return;
    // tdf#129809 take a reference in case m_aContents is replaced during this
    // call
    Reference<datatransfer::XTransferable> xCurrentContents(m_aContents);
    m_aConversionHelper.setSelectionData(xCurrentContents, selection_data, info);
}

namespace
{
    const OString& getPID()
    {
        static OString sPID;
        if (!sPID.getLength())
        {
            oslProcessIdentifier aProcessId = 0;
            oslProcessInfo info;
            info.Size = sizeof (oslProcessInfo);
            if (osl_getProcessInfo(nullptr, osl_Process_IDENTIFIER, &info) == osl_Process_E_None)
                aProcessId = info.Ident;
            sPID = OString::number(aProcessId);
        }
        return sPID;
    }

    void ClipboardGetFunc(GdkClipboard* /*clipboard*/, GtkSelectionData *selection_data,
                          guint info,
                          gpointer user_data_or_owner)
    {
        VclGtkClipboard* pThis = static_cast<VclGtkClipboard*>(user_data_or_owner);
        pThis->ClipboardGet(selection_data, info);
    }

    void ClipboardClearFunc(GdkClipboard* /*clipboard*/, gpointer user_data_or_owner)
    {
        VclGtkClipboard* pThis = static_cast<VclGtkClipboard*>(user_data_or_owner);
        pThis->ClipboardClear();
    }
}
#endif

namespace
{
#if GTK_CHECK_VERSION(4, 0, 0)
    void handle_owner_change(GdkClipboard *clipboard, gpointer user_data)
    {
        VclGtkClipboard* pThis = static_cast<VclGtkClipboard*>(user_data);
        pThis->OwnerPossiblyChanged(clipboard);
    }
#else
    void handle_owner_change(GdkClipboard *clipboard, GdkEvent* /*event*/, gpointer user_data)
    {
        VclGtkClipboard* pThis = static_cast<VclGtkClipboard*>(user_data);
        pThis->OwnerPossiblyChanged(clipboard);
    }
#endif
}

void VclGtkClipboard::OwnerPossiblyChanged(GdkClipboard* clipboard)
{
    SyncGtkClipboard(); // tdf#138183 do any pending SetGtkClipboard calls
    if (!m_aContents.is())
        return;

#if GTK_CHECK_VERSION(4, 0, 0)
    bool bSelf = gdk_clipboard_is_local(clipboard);
#else
    //if gdk_display_supports_selection_notification is not supported, e.g. like
    //right now under wayland, then you only get owner-changed notifications at
    //opportune times when the selection might have changed. So here
    //we see if the selection supports a dummy selection type identifying
    //our pid, in which case it's us.
    bool bSelf = false;

    //disconnect and reconnect after gtk_clipboard_wait_for_targets to
    //avoid possible recursion
    g_signal_handler_disconnect(clipboard, m_nOwnerChangedSignalId);

    OString sTunnel = "application/x-libreoffice-internal-id-" + getPID();
    GdkAtom *targets;
    gint n_targets;
    if (gtk_clipboard_wait_for_targets(clipboard, &targets, &n_targets))
    {
        for (gint i = 0; i < n_targets && !bSelf; ++i)
        {
            gchar* pName = gdk_atom_name(targets[i]);
            if (strcmp(pName, sTunnel.getStr()) == 0)
            {
                bSelf = true;
            }
            g_free(pName);
        }

        g_free(targets);
    }

    m_nOwnerChangedSignalId = g_signal_connect(clipboard, "owner-change",
                                               G_CALLBACK(handle_owner_change), this);
#endif

    if (!bSelf)
    {
        //null out m_aContents to return control to the system-one which
        //will be retrieved if getContents is called again
        setContents(Reference<css::datatransfer::XTransferable>(),
                    Reference<css::datatransfer::clipboard::XClipboardOwner>());
    }
}

void VclGtkClipboard::ClipboardClear()
{
    if (m_pSetClipboardEvent)
    {
        Application::RemoveUserEvent(m_pSetClipboardEvent);
        m_pSetClipboardEvent = nullptr;
    }
#if !GTK_CHECK_VERSION(4, 0, 0)
    for (auto &a : m_aGtkTargets)
        g_free(a.target);
#endif
    m_aGtkTargets.clear();
}

#if GTK_CHECK_VERSION(4, 0, 0)
IMPL_LINK_NOARG(VclGtkClipboard, DetachClipboard, void*, void)
{
    ClipboardClear();
}

OString VclToGtkHelper::makeGtkTargetEntry(const css::datatransfer::DataFlavor& ;rFlavor)
{
    OString aEntry = OUStringToOString(rFlavor.MimeType, RTL_TEXTENCODING_UTF8);
    auto it = std::find_if(aInfoToFlavor.begin(), aInfoToFlavor.end(),
                           DataFlavorEq(rFlavor));
    if (it == aInfoToFlavor.end())
        aInfoToFlavor.push_back(rFlavor);
    return aEntry;
}
#else
GtkTargetEntry VclToGtkHelper::makeGtkTargetEntry(const css::datatransfer::DataFlavor& rFlavor)
{
    GtkTargetEntry aEntry;
    aEntry.target =
        g_strdup(OUStringToOString(rFlavor.MimeType, RTL_TEXTENCODING_UTF8).getStr());
    aEntry.flags = 0;
    auto it = std::find_if(aInfoToFlavor.begin(), aInfoToFlavor.end(),
                        DataFlavorEq(rFlavor));
    if (it != aInfoToFlavor.end())
        aEntry.info = std::distance(aInfoToFlavor.begin(), it);
    else
    {
        aEntry.info = aInfoToFlavor.size();
        aInfoToFlavor.push_back(rFlavor);
    }
    return aEntry;
}
#endif

#if GTK_CHECK_VERSION(4, 0, 0)

namespace
{
    void write_mime_type_done(GObject* pStream, GAsyncResult* pResult, gpointer pTaskPtr)
    {
        GTask* pTask = static_cast<GTask*>(pTaskPtr);

        GError* pError = nullptr;
        if (!g_output_stream_write_all_finish(G_OUTPUT_STREAM(pStream),
                                              pResult, nullptr, &pError))
        {
            g_task_return_error(pTask, pError);
        }
        else
        {
            g_task_return_boolean(pTask, true);
        }

        g_object_unref(pTask);
    }

    class MimeTypeEq
    {
    private:
        const OUString& m_rMimeType;
    public:
        explicit MimeTypeEq(const OUString& rMimeType) : m_rMimeType(rMimeType) {}
        bool operator() (const css::datatransfer::DataFlavor& rData) const
        {
            return rData.MimeType == m_rMimeType;
        }
    };
}

void VclToGtkHelper::setSelectionData(const Reference<css::datatransfer::XTransferable> &rTrans,
                                      GdkContentProvider* provider,
                                      const char* mime_type,
                                      GOutputStream* stream,
                                      int io_priority,
                                      GCancellable* cancellable,
                                      GAsyncReadyCallback callback,
                                      gpointer user_data)
{
    GTask *task = g_task_new(provider, cancellable, callback, user_data);
    g_task_set_priority(task, io_priority);

    OUString sMimeType(mime_type, strlen(mime_type), RTL_TEXTENCODING_UTF8);

    auto it = std::find_if(aInfoToFlavor.begin(), aInfoToFlavor.end(),
                           MimeTypeEq(sMimeType));
    if (it == aInfoToFlavor.end())
    {
        SAL_WARN( "vcl.gtk""unknown mime-type request from clipboard");
        g_task_return_new_error(task, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
            "unknown mime-type “%s” request from clipboard", mime_type);
        g_object_unref(task);
        return;
    }

    css::datatransfer::DataFlavor aFlavor(*it);
    if (aFlavor.MimeType == "UTF8_STRING" || aFlavor.MimeType == "STRING")
        aFlavor.MimeType = "text/plain;charset=utf-8";

    Sequence<sal_Int8> aData;
    Any aValue;

    try
    {
        aValue = rTrans->getTransferData(aFlavor);
    }
    catch (...)
    {
    }

    if (aValue.getValueTypeClass() == TypeClass_STRING)
    {
        OUString aString;
        aValue >>= aString;
        aData = Sequence< sal_Int8 >( reinterpret_cast<sal_Int8 const *>(aString.getStr()), aString.getLength() * sizeof( sal_Unicode ) );
    }
    else if (aValue.getValueType() == cppu::UnoType<Sequence< sal_Int8 >>::get())
    {
        aValue >>= aData;
    }
    else if (aFlavor.MimeType == "text/plain;charset=utf-8")
    {
        //didn't have utf-8, try utf-16 and convert
        aFlavor.MimeType = "text/plain;charset=utf-16";
        aFlavor.DataType = cppu::UnoType<OUString>::get();
        try
        {
            aValue = rTrans->getTransferData(aFlavor);
        }
        catch (...)
        {
        }
        OUString aString;
        aValue >>= aString;
        OString aUTF8String(OUStringToOString(aString, RTL_TEXTENCODING_UTF8));

        g_output_stream_write_all_async(stream, aUTF8String.getStr(), aUTF8String.getLength(),
                                        io_priority, cancellable, write_mime_type_done, task);
        return;
    }

    g_output_stream_write_all_async(stream, aData.getArray(), aData.getLength(),
                                    io_priority, cancellable, write_mime_type_done, task);
}
#else
void VclToGtkHelper::setSelectionData(const Reference<css::datatransfer::XTransferable> &rTrans,
                                      GtkSelectionData *selection_data, guint info)
{
    GdkAtom type(gdk_atom_intern(OUStringToOString(aInfoToFlavor[info].MimeType,
                                                   RTL_TEXTENCODING_UTF8).getStr(),
                                 false));

    css::datatransfer::DataFlavor aFlavor(aInfoToFlavor[info]);
    if (aFlavor.MimeType == "UTF8_STRING" || aFlavor.MimeType == "STRING")
        aFlavor.MimeType = "text/plain;charset=utf-8";

    Sequence<sal_Int8> aData;
    Any aValue;

    try
    {
        aValue = rTrans->getTransferData(aFlavor);
    }
    catch (...)
    {
    }

    if (aValue.getValueTypeClass() == TypeClass_STRING)
    {
        OUString aString;
        aValue >>= aString;
        aData = Sequence< sal_Int8 >( reinterpret_cast<sal_Int8 const *>(aString.getStr()), aString.getLength() * sizeof( sal_Unicode ) );
    }
    else if (aValue.getValueType() == cppu::UnoType<Sequence< sal_Int8 >>::get())
    {
        aValue >>= aData;
    }
    else if (aFlavor.MimeType == "text/plain;charset=utf-8")
    {
        //didn't have utf-8, try utf-16 and convert
        aFlavor.MimeType = "text/plain;charset=utf-16";
        aFlavor.DataType = cppu::UnoType<OUString>::get();
        try
        {
            aValue = rTrans->getTransferData(aFlavor);
        }
        catch (...)
        {
        }
        OUString aString;
        aValue >>= aString;
        OString aUTF8String(OUStringToOString(aString, RTL_TEXTENCODING_UTF8));
        gtk_selection_data_set(selection_data, type, 8,
                               reinterpret_cast<const guchar *>(aUTF8String.getStr()),
                               aUTF8String.getLength());
        return;
    }

    gtk_selection_data_set(selection_data, type, 8,
                           reinterpret_cast<const guchar *>(aData.getArray()),
                           aData.getLength());
}
#endif

VclGtkClipboard::VclGtkClipboard(SelectionType eSelection)
    : cppu::WeakComponentImplHelper<datatransfer::clipboard::XSystemClipboard,
                                    datatransfer::clipboard::XFlushableClipboard, XServiceInfo>
        (m_aMutex)
    , m_eSelection(eSelection)
    , m_pSetClipboardEvent(nullptr)
#if GTK_CHECK_VERSION(4, 0, 0)
    , m_pClipboardContent(nullptr)
#endif
{
    GdkClipboard* clipboard = clipboard_get(m_eSelection);
#if GTK_CHECK_VERSION(4, 0, 0)
    m_nOwnerChangedSignalId = g_signal_connect(clipboard, "changed",
                                               G_CALLBACK(handle_owner_change), this);
#else
    m_nOwnerChangedSignalId = g_signal_connect(clipboard, "owner-change",
                                               G_CALLBACK(handle_owner_change), this);
#endif
}

void VclGtkClipboard::flushClipboard()
{
#if !GTK_CHECK_VERSION(4, 0, 0)
    SolarMutexGuard aGuard;

    if (m_eSelection != SELECTION_CLIPBOARD)
        return;

    GdkClipboard* clipboard = clipboard_get(m_eSelection);
    gtk_clipboard_store(clipboard);
#endif
}

VclGtkClipboard::~VclGtkClipboard()
{
    GdkClipboard* clipboard = clipboard_get(m_eSelection);
    g_signal_handler_disconnect(clipboard, m_nOwnerChangedSignalId);
    if (!m_aGtkTargets.empty())
    {
#if GTK_CHECK_VERSION(4, 0, 0)
        gdk_clipboard_set_content(clipboard, nullptr);
        m_pClipboardContent = nullptr;
#else
        gtk_clipboard_clear(clipboard);
#endif
        ClipboardClear();
    }
    assert(!m_pSetClipboardEvent);
    assert(m_aGtkTargets.empty());
}

#if GTK_CHECK_VERSION(4, 0, 0)
std::vector<OString> VclToGtkHelper::FormatsToGtk(const css::uno::Sequence<css::datatransfer::DataFlavor> &rFormats)
#else
std::vector<GtkTargetEntry> VclToGtkHelper::FormatsToGtk(const css::uno::Sequence<css::datatransfer::DataFlavor> &rFormats)
#endif
{
#if GTK_CHECK_VERSION(4, 0, 0)
    std::vector<OString> aGtkTargets;
#else
    std::vector<GtkTargetEntry> aGtkTargets;
#endif

    bool bHaveText(false), bHaveUTF8(false);
    for (const css::datatransfer::DataFlavor& rFlavor : rFormats)
    {
        sal_Int32 nIndex(0);
        if (o3tl::getToken(rFlavor.MimeType, 0, ';', nIndex) == u"text/plain")
        {
            bHaveText = true;
            std::u16string_view aToken(o3tl::getToken(rFlavor.MimeType, 0, ';', nIndex));
            if (aToken == u"charset=utf-8")
            {
                bHaveUTF8 = true;
            }
        }
        aGtkTargets.push_back(makeGtkTargetEntry(rFlavor));
    }

    if (bHaveText)
    {
        css::datatransfer::DataFlavor aFlavor;
        aFlavor.DataType = cppu::UnoType<Sequence< sal_Int8 >>::get();
        if (!bHaveUTF8)
        {
            aFlavor.MimeType = "text/plain;charset=utf-8";
            aGtkTargets.push_back(makeGtkTargetEntry(aFlavor));
        }
        aFlavor.MimeType = "UTF8_STRING";
        aGtkTargets.push_back(makeGtkTargetEntry(aFlavor));
        aFlavor.MimeType = "STRING";
        aGtkTargets.push_back(makeGtkTargetEntry(aFlavor));
    }

    return aGtkTargets;
}

IMPL_LINK_NOARG(VclGtkClipboard, AsyncSetGtkClipboard, void*, void)
{
    osl::Guard aGuard( m_aMutex );
    m_pSetClipboardEvent = nullptr;
    SetGtkClipboard();
}

void VclGtkClipboard::SyncGtkClipboard()
{
    osl::Guard aGuard(m_aMutex);
    if (m_pSetClipboardEvent)
    {
        Application::RemoveUserEvent(m_pSetClipboardEvent);
        m_pSetClipboardEvent = nullptr;
        SetGtkClipboard();
    }
}

void VclGtkClipboard::SetGtkClipboard()
{
    GdkClipboard* clipboard = clipboard_get(m_eSelection);
#if GTK_CHECK_VERSION(4, 0, 0)
    m_pClipboardContent = TRANSFERABLE_CONTENT(transerable_content_new(&m_aConversionHelper, m_aContents.get()));
    transerable_content_set_detach_clipboard_link(m_pClipboardContent, LINK(this, VclGtkClipboard, DetachClipboard));
    gdk_clipboard_set_content(clipboard, GDK_CONTENT_PROVIDER(m_pClipboardContent));
#else
    gtk_clipboard_set_with_data(clipboard, m_aGtkTargets.data(), m_aGtkTargets.size(),
                                ClipboardGetFunc, ClipboardClearFunc, this);
    gtk_clipboard_set_can_store(clipboard, m_aGtkTargets.data(), m_aGtkTargets.size());
#endif
}

void VclGtkClipboard::setContents(
        const Reference< css::datatransfer::XTransferable >& xTrans,
        const Reference< css::datatransfer::clipboard::XClipboardOwner >& xClipboardOwner )
{
    css::uno::Sequence<css::datatransfer::DataFlavor> aFormats;
    if (xTrans.is())
    {
        aFormats = xTrans->getTransferDataFlavors();
    }

    osl::ClearableMutexGuard aGuard( m_aMutex );
    Reference< datatransfer::clipboard::XClipboardOwner > xOldOwner( m_aOwner );
    Reference< datatransfer::XTransferable > xOldContents( m_aContents );
    m_aContents = xTrans;
#if GTK_CHECK_VERSION(4, 0, 0)
    if (m_pClipboardContent)
        transerable_content_set_transferable(m_pClipboardContent, m_aContents.get());
#endif
    m_aOwner = xClipboardOwner;

    std::vector< Reference< datatransfer::clipboard::XClipboardListener > > aListeners( m_aListeners );
    datatransfer::clipboard::ClipboardEvent aEv;

    GdkClipboard* clipboard = clipboard_get(m_eSelection);
    if (!m_aGtkTargets.empty())
    {
#if GTK_CHECK_VERSION(4, 0, 0)
        gdk_clipboard_set_content(clipboard, nullptr);
        m_pClipboardContent = nullptr;
#else
        gtk_clipboard_clear(clipboard);
#endif
        ClipboardClear();
    }
    assert(m_aGtkTargets.empty());
    if (m_aContents.is())
    {
#if GTK_CHECK_VERSION(4, 0, 0)
        std::vector<OString> aGtkTargets(m_aConversionHelper.FormatsToGtk(aFormats));
#else
        std::vector<GtkTargetEntry> aGtkTargets(m_aConversionHelper.FormatsToGtk(aFormats));
#endif
        if (!aGtkTargets.empty())
        {
#if !GTK_CHECK_VERSION(4, 0, 0)
            GtkTargetEntry aEntry;
            OString sTunnel = "application/x-libreoffice-internal-id-" + getPID();
            aEntry.target = g_strdup(sTunnel.getStr());
            aEntry.flags = 0;
            aEntry.info = 0;
            aGtkTargets.push_back(aEntry);
#endif
            m_aGtkTargets = std::move(aGtkTargets);

            if (!m_pSetClipboardEvent)
                m_pSetClipboardEvent = Application::PostUserEvent(LINK(this, VclGtkClipboard, AsyncSetGtkClipboard));
        }
    }

    aEv.Contents = getContents();

    aGuard.clear();

    if (xOldOwner.is() && xOldOwner != xClipboardOwner)
        xOldOwner->lostOwnership( this, xOldContents );
    for (auto const& listener : aListeners)
    {
        listener->changedContents( aEv );
    }
}

OUString VclGtkClipboard::getName()
{
    return (m_eSelection == SELECTION_CLIPBOARD) ? u"CLIPBOARD"_ustr : u"PRIMARY"_ustr;
}

sal_Int8 VclGtkClipboard::getRenderingCapabilities()
{
    return 0;
}

void VclGtkClipboard::addClipboardListener( const Reference< datatransfer::clipboard::XClipboardListener >& listener )
{
    osl::Guard aGuard( m_aMutex );

    m_aListeners.push_back( listener );
}

void VclGtkClipboard::removeClipboardListener( const Reference< datatransfer::clipboard::XClipboardListener >& listener )
{
    osl::Guard aGuard( m_aMutex );

    std::erase(m_aListeners, listener);
}

Reference<css::datatransfer::clipboard::XClipboard>
GtkInstance::CreateClipboard(const Sequence<Any>& arguments)
{
    if ( o3tl::IsRunningUnitTest() || o3tl::IsRunningUITest() )
        return SalInstance::CreateClipboard( arguments );

    OUString sel;
    if (!arguments.hasElements()) {
        sel = "CLIPBOARD";
    } else if (arguments.getLength() != 1 || !(arguments[0] >>= sel)) {
        throw css::lang::IllegalArgumentException(
            u"bad GtkInstance::CreateClipboard arguments"_ustr,
            css::uno::Reference<css::uno::XInterface>(), -1);
    }

    SelectionType eSelection = (sel == "CLIPBOARD") ? SELECTION_CLIPBOARD : SELECTION_PRIMARY;

    if (m_aClipboards[eSelection].is())
        return m_aClipboards[eSelection];

    Reference<css::datatransfer::clipboard::XClipboard> xClipboard(new VclGtkClipboard(eSelection));
    m_aClipboards[eSelection] = xClipboard;
    return xClipboard;
}

GtkInstDropTarget::GtkInstDropTarget()
    : WeakComponentImplHelper(m_aMutex)
    , m_pFrame(nullptr)
    , m_pFormatConversionRequest(nullptr)
    , m_bActive(false)
#if !GTK_CHECK_VERSION(4, 0, 0)
    , m_bInDrag(false)
#endif
    , m_nDefaultActions(0)
{
}

GtkInstDropTarget::GtkInstDropTarget(GtkSalFrame* pFrame)
    : GtkInstDropTarget()
{
    assert(pFrame && "missing SalFrame");

    m_pFrame = pFrame;
    m_pFrame->registerDropTarget(this);
    m_bActive = true;
}

OUString SAL_CALL GtkInstDropTarget::getImplementationName()
{
    return u"com.sun.star.datatransfer.dnd.VclGtkDropTarget"_ustr;
}

sal_Bool SAL_CALL GtkInstDropTarget::supportsService(OUString const & ServiceName)
{
    return cppu::supportsService(this, ServiceName);
}

css::uno::Sequence<OUString> SAL_CALL GtkInstDropTarget::getSupportedServiceNames()
{
    Sequence<OUString> aRet { u"com.sun.star.datatransfer.dnd.GtkDropTarget"_ustr };
    return aRet;
}

GtkInstDropTarget::~GtkInstDropTarget()
{
    if (m_pFrame)
        m_pFrame->deregisterDropTarget(this);
}

void GtkInstDropTarget::deinitialize()
{
    m_pFrame = nullptr;
    m_bActive = false;
}

void GtkInstDropTarget::addDropTargetListener( const Reference< css::datatransfer::dnd::XDropTargetListener >& xListener)
{
    ::osl::Guard< ::osl::Mutex > aGuard( m_aMutex );

    m_aListeners.push_back( xListener );
}

void GtkInstDropTarget::removeDropTargetListener( const Reference< css::datatransfer::dnd::XDropTargetListener >& xListener)
{
    ::osl::Guard< ::osl::Mutex > aGuard( m_aMutex );

    std::erase(m_aListeners, xListener);
}

void GtkInstDropTarget::fire_drop(const css::datatransfer::dnd::DropTargetDropEvent& dtde)
{
    osl::ClearableGuard<osl::Mutex> aGuard( m_aMutex );
    std::vector<css::uno::Reference<css::datatransfer::dnd::XDropTargetListener>> aListeners(m_aListeners);
    aGuard.clear();

    for (auto const& listener : aListeners)
    {
        listener->drop( dtde );
    }
}

void GtkInstDropTarget::fire_dragEnter(const css::datatransfer::dnd::DropTargetDragEnterEvent& dtde)
{
    osl::ClearableGuard< ::osl::Mutex > aGuard( m_aMutex );
    std::vector<css::uno::Reference<css::datatransfer::dnd::XDropTargetListener>> aListeners(m_aListeners);
    aGuard.clear();

    for (auto const& listener : aListeners)
    {
        listener->dragEnter( dtde );
    }
}

void GtkInstDropTarget::fire_dragOver(const css::datatransfer::dnd::DropTargetDragEvent& dtde)
{
    osl::ClearableGuard< ::osl::Mutex > aGuard( m_aMutex );
    std::vector<css::uno::Reference<css::datatransfer::dnd::XDropTargetListener>> aListeners(m_aListeners);
    aGuard.clear();

    for (auto const& listener : aListeners)
    {
        listener->dragOver( dtde );
    }
}

void GtkInstDropTarget::fire_dragExit(const css::datatransfer::dnd::DropTargetEvent& dte)
{
    osl::ClearableGuard< ::osl::Mutex > aGuard( m_aMutex );
    std::vector<css::uno::Reference<css::datatransfer::dnd::XDropTargetListener>> aListeners(m_aListeners);
    aGuard.clear();

    for (auto const& listener : aListeners)
    {
        listener->dragExit( dte );
    }
}

sal_Bool GtkInstDropTarget::isActive()
{
    return m_bActive;
}

void GtkInstDropTarget::setActive(sal_Bool bActive)
{
    m_bActive = bActive;
}

sal_Int8 GtkInstDropTarget::getDefaultActions()
{
    return m_nDefaultActions;
}

void GtkInstDropTarget::setDefaultActions(sal_Int8 nDefaultActions)
{
    m_nDefaultActions = nDefaultActions;
}

css::uno::Reference<css::datatransfer::dnd::XDropTarget>
GtkInstance::ImplCreateDropTarget(const SystemEnvData* pSysEnv)
{
    return new GtkInstDropTarget(static_cast<GtkSalFrame*>(pSysEnv->pSalFrame));
}

GtkInstDragSource::GtkInstDragSource(GtkSalFrame* pFrame)
    : GtkInstDragSource()
{
    assert(pFrame && "missing SalFrame");

    m_pFrame = pFrame;
    m_pFrame->registerDragSource(this);
}

GtkInstDragSource::~GtkInstDragSource()
{
    if (m_pFrame)
        m_pFrame->deregisterDragSource(this);

    if (GtkInstDragSource::g_ActiveDragSource == this)
    {
        SAL_WARN( "vcl.gtk", "dragEnd should have been called on GtkInstDragSource before dtor");
        GtkInstDragSource::g_ActiveDragSource = nullptr;
    }
}

void GtkInstDragSource::deinitialize()
{
    m_pFrame = nullptr;
}

sal_Bool GtkInstDragSource::isDragImageSupported()
{
    return true;
}

sal_Int32 GtkInstDragSource::getDefaultCursor( sal_Int8 )
{
    return 0;
}

OUString SAL_CALL GtkInstDragSource::getImplementationName()
{
    return u"com.sun.star.datatransfer.dnd.VclGtkDragSource"_ustr;
}

sal_Bool SAL_CALL GtkInstDragSource::supportsService(OUString const & ServiceName)
{
    return cppu::supportsService(this, ServiceName);
}

css::uno::Sequence<OUString> SAL_CALL GtkInstDragSource::getSupportedServiceNames()
{
    Sequence<OUString> aRet { u"com.sun.star.datatransfer.dnd.GtkDragSource"_ustr };
    return aRet;
}

css::uno::Reference<css::datatransfer::dnd::XDragSource>
GtkInstance::ImplCreateDragSource(const SystemEnvData* pSysEnv)
{
    return new GtkInstDragSource(static_cast<GtkSalFrame*>(pSysEnv->pSalFrame));
}

namespace {

class GtkOpenGLContext : public OpenGLContext
{
    GLWindow m_aGLWin;
    GtkWidget *m_pGLArea;
    GdkGLContext *m_pContext;
    gulong m_nDestroySignalId;
    gulong m_nRenderSignalId;
    guint m_nAreaFrameBuffer;
    guint m_nFrameBuffer;
    guint m_nRenderBuffer;
    guint m_nDepthBuffer;
    guint m_nFrameScratchBuffer;
    guint m_nRenderScratchBuffer;
    guint m_nDepthScratchBuffer;

public:
    GtkOpenGLContext()
        : m_pGLArea(nullptr)
        , m_pContext(nullptr)
        , m_nDestroySignalId(0)
        , m_nRenderSignalId(0)
        , m_nAreaFrameBuffer(0)
        , m_nFrameBuffer(0)
        , m_nRenderBuffer(0)
        , m_nDepthBuffer(0)
        , m_nFrameScratchBuffer(0)
        , m_nRenderScratchBuffer(0)
        , m_nDepthScratchBuffer(0)
    {
    }

    virtual void initWindow() override
    {
        if( !m_pChildWindow )
        {
            SystemWindowData winData = generateWinData(mpWindow, mbRequestLegacyContext);
            m_pChildWindow = VclPtr<SystemChildWindow>::Create(mpWindow, 0, &winData, false);
        }

        if (m_pChildWindow)
        {
            InitChildWindow(m_pChildWindow.get());
        }
    }

private:
    virtual const GLWindow& getOpenGLWindow() const override { return m_aGLWin; }
    virtual GLWindow& getModifiableOpenGLWindow() override { return m_aGLWin; }

    static void signalDestroy(GtkWidget*, gpointer context)
    {
        GtkOpenGLContext* pThis = static_cast<GtkOpenGLContext*>(context);
        pThis->m_pGLArea = nullptr;
        pThis->m_nDestroySignalId = 0;
        pThis->m_nRenderSignalId = 0;
    }

    static gboolean signalRender(GtkGLArea*, GdkGLContext*, gpointer window)
    {
        GtkOpenGLContext* pThis = static_cast<GtkOpenGLContext*>(window);

        int scale = gtk_widget_get_scale_factor(pThis->m_pGLArea);
        int width = pThis->m_aGLWin.Width * scale;
        int height = pThis->m_aGLWin.Height * scale;

        glDrawBuffer(GL_COLOR_ATTACHMENT0_EXT);

        glBindFramebuffer(GL_READ_FRAMEBUFFER, pThis->m_nAreaFrameBuffer);
        glReadBuffer(GL_COLOR_ATTACHMENT0_EXT);

        glBlitFramebuffer(0, 0, width, height, 0, 0, width, height,
                          GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT, GL_NEAREST);

        gdk_gl_context_make_current(pThis->m_pContext);
        return true;
    }

    virtual void adjustToNewSize() override
    {
        if (!m_pGLArea)
            return;

        int scale = gtk_widget_get_scale_factor(m_pGLArea);
        int width = m_aGLWin.Width * scale;
        int height = m_aGLWin.Height * scale;

        // seen in tdf#124729 width/height of 0 leading to GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT
        int allocwidth = std::max(width, 1);
        int allocheight = std::max(height, 1);

        gtk_gl_area_make_current(GTK_GL_AREA(m_pGLArea));
        if (GError *pError = gtk_gl_area_get_error(GTK_GL_AREA(m_pGLArea)))
        {
            SAL_WARN("vcl.gtk", "gtk gl area error: " << pError->message);
            return;
        }

        glBindRenderbuffer(GL_RENDERBUFFER, m_nRenderBuffer);
        glRenderbufferStorage(GL_RENDERBUFFER, GL_RGB8, allocwidth, allocheight);
        glBindRenderbuffer(GL_RENDERBUFFER, m_nDepthBuffer);
        glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, allocwidth, allocheight);
        glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_nAreaFrameBuffer);

        glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
                                     GL_RENDERBUFFER_EXT, m_nRenderBuffer);
        glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT,
                                     GL_RENDERBUFFER_EXT, m_nDepthBuffer);

        gdk_gl_context_make_current(m_pContext);
        glBindRenderbuffer(GL_RENDERBUFFER, m_nRenderBuffer);
        glBindRenderbuffer(GL_RENDERBUFFER, m_nDepthBuffer);
        glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_nFrameBuffer);

        glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
                                     GL_RENDERBUFFER_EXT, m_nRenderBuffer);
        glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT,
                                     GL_RENDERBUFFER_EXT, m_nDepthBuffer);
        glViewport(0, 0, width, height);

        glBindRenderbuffer(GL_RENDERBUFFER, m_nRenderScratchBuffer);
        glRenderbufferStorage(GL_RENDERBUFFER, GL_RGB8, allocwidth, allocheight);
        glBindRenderbuffer(GL_RENDERBUFFER, m_nDepthScratchBuffer);
        glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, allocwidth, allocheight);
        glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_nFrameScratchBuffer);

        glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
                                     GL_RENDERBUFFER_EXT, m_nRenderScratchBuffer);
        glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT,
                                     GL_RENDERBUFFER_EXT, m_nDepthScratchBuffer);

        glViewport(0, 0, width, height);
    }

    // Use a throw away toplevel to determine the OpenGL version because once
    // an GdkGLContext is created for a window then it seems that
    // glGenVertexArrays will always be called when the window gets rendered.
    static int GetOpenGLVersion()
    {
        int nMajorGLVersion(0);

        GtkWidget* pWindow;
#if !GTK_CHECK_VERSION(4,0,0)
        pWindow = gtk_window_new(GTK_WINDOW_TOPLEVEL);
#else
        pWindow = gtk_window_new();
#endif

        gtk_widget_realize(pWindow);

        if (GdkSurface* pSurface = widget_get_surface(pWindow))
        {
            if (GdkGLContext* pContext = surface_create_gl_context(pSurface))
            {
                if (gdk_gl_context_realize(pContext, nullptr))
                {
                    OpenGLZone aZone;
                    gdk_gl_context_make_current(pContext);
                    gdk_gl_context_get_version(pContext, &nMajorGLVersion, nullptr);
                    gdk_gl_context_clear_current();
                }
                g_object_unref(pContext);
            }
        }

#if !GTK_CHECK_VERSION(4,0,0)
        gtk_widget_destroy(pWindow);
#else
        gtk_window_destroy(GTK_WINDOW(pWindow));
#endif
        return nMajorGLVersion;
    }

    virtual bool ImplInit() override
    {
        static int nOpenGLVersion = GetOpenGLVersion();
        if (nOpenGLVersion < 3)
        {
            SAL_WARN("vcl.gtk", "gtk GL requires glGenVertexArrays which is OpenGL 3, while system provides: " << nOpenGLVersion);
            return false;
        }

        const SystemEnvData* pEnvData = m_pChildWindow->GetSystemData();
        GtkWidget *pParent = static_cast<GtkWidget*>(pEnvData->pWidget);
        m_pGLArea = gtk_gl_area_new();
        m_nDestroySignalId = g_signal_connect(G_OBJECT(m_pGLArea), "destroy", G_CALLBACK(signalDestroy), this);
        m_nRenderSignalId = g_signal_connect(G_OBJECT(m_pGLArea), "render", G_CALLBACK(signalRender), this);
        gtk_gl_area_set_has_depth_buffer(GTK_GL_AREA(m_pGLArea), true);
        gtk_gl_area_set_auto_render(GTK_GL_AREA(m_pGLArea), false);
        gtk_widget_set_hexpand(m_pGLArea, true);
        gtk_widget_set_vexpand(m_pGLArea, true);
#if !GTK_CHECK_VERSION(4, 0, 0)
        gtk_container_add(GTK_CONTAINER(pParent), m_pGLArea);
        gtk_widget_show_all(pParent);
#else
        gtk_grid_attach(GTK_GRID(pParent), m_pGLArea, 0, 0, 1, 1);
        gtk_widget_set_visible(pParent, true);
        gtk_widget_set_visible(m_pGLArea, true);
#endif

        gtk_gl_area_make_current(GTK_GL_AREA(m_pGLArea));
        if (GError *pError = gtk_gl_area_get_error(GTK_GL_AREA(m_pGLArea)))
        {
            SAL_WARN("vcl.gtk", "gtk gl area error: " << pError->message);
            return false;
        }

        gtk_gl_area_attach_buffers(GTK_GL_AREA(m_pGLArea));
        glGenFramebuffersEXT(1, &m_nAreaFrameBuffer);

        GdkSurface* pWindow = widget_get_surface(pParent);
        m_pContext = surface_create_gl_context(pWindow);
        if (!m_pContext)
            return false;

        if (!gdk_gl_context_realize(m_pContext, nullptr))
--> --------------------

--> maximum size reached

--> --------------------

Messung V0.5
C=95 H=93 G=93

¤ Dauer der Verarbeitung: 0.17 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.