/* -*- 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& 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(true , false );
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.83 Sekunden
¤
*© Formatika GbR, Deutschland