/* -*- 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/.
*
* This file incorporates work covered by the following license notice:
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed
* with this work for additional information regarding copyright
* ownership. The ASF licenses this file to you under the Apache
* License, Version 2.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of
* the License at http://www.apache.org/licenses/LICENSE-2.0 .
*/
#include <sal/config.h>
#include <sal/log.hxx>
#include <cstdlib>
#include <thread>
#include <unx/saldisp.hxx>
#include <unistd.h>
#include <string.h>
#include <sys/time.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/XKBlib.h>
#include <X11/Xatom.h>
#include <X11/keysym.h>
#include <poll.h>
#include <sal/macros.h>
#include "X11_selection.hxx"
#include "X11_clipboard.hxx"
#include "X11_transferable.hxx"
#include "X11_dndcontext.hxx"
#include "bmp.hxx"
#include <vcl/svapp.hxx>
#include <o3tl/string_view.hxx>
// pointer bitmaps
#include "copydata_curs.h"
#include "copydata_mask.h"
#include "movedata_curs.h"
#include "movedata_mask.h"
#include "linkdata_curs.h"
#include "linkdata_mask.h"
#include "nodrop_curs.h"
#include "nodrop_mask.h"
#include <com/sun/star/datatransfer/dnd/DNDConstants.hpp>
#include <com/sun/star/awt/MouseEvent.hpp>
#include <com/sun/star/awt/MouseButton.hpp>
#include <com/sun/star/frame/Desktop.hpp>
#include <rtl/tencinfo.h>
#include <rtl/ustrbuf.hxx>
#include <comphelper/processfactory.hxx>
#include <comphelper/solarmutex.hxx>
#include <cppuhelper/supportsservice.hxx>
#include <algorithm>
constexpr
auto DRAG_EVENT_MASK = ButtonPressMask |
ButtonReleaseMask |
PointerMotionMask |
EnterWindowMask |
LeaveWindowMask;
using namespace com::sun::star::datatransfer;
using namespace com::sun::star::datatransfer::dnd;
using namespace com::sun::star::lang;
using namespace com::sun::star::awt;
using namespace com::sun::star::uno;
using namespace com::sun::star::frame;
using namespace cppu;
using namespace x11;
// stubs to satisfy solaris compiler's rather rigid linking warning
extern "C"
{
static void call_SelectionManager_run(
void * pMgr )
{
SelectionManager::run( pMgr );
}
static void call_SelectionManager_runDragExecute(
void * pMgr )
{
osl_setThreadName(
"SelectionManager::runDragExecute()" );
SelectionManager::runDragExecute( pMgr );
}
}
const tools::
Long nXdndProtocolRevision = 5;
namespace {
// mapping between mime types (or what the office thinks of mime types)
// and X convention types
struct NativeTypeEntry
{
Atom nAtom;
const char * pType;
// Mime encoding on our side
const char * pNativeType;
// string corresponding to nAtom for the case of nAtom being uninitialized
int nFormat;
// the corresponding format
};
}
// the convention for Xdnd is mime types as specified by the corresponding
// RFC's with the addition that text/plain without charset tag contains iso8859-1
// sadly some applications (e.g. gtk) do not honor the mimetype only rule,
// so for compatibility add UTF8_STRING
static NativeTypeEntry aXdndConversionTab[] =
{
{ 0,
"text/plain;charset=iso8859-1" ,
"text/plain" , 8 },
{ 0,
"text/plain;charset=utf-8" ,
"UTF8_STRING" , 8 }
};
// for clipboard and primary selections there is only a convention for text
// that the encoding name of the text is taken as type in all capitalized letters
static NativeTypeEntry aNativeConversionTab[] =
{
{ 0,
"text/plain;charset=utf-16" ,
"ISO10646-1" , 16 },
{ 0,
"text/plain;charset=utf-8" ,
"UTF8_STRING" , 8 },
{ 0,
"text/plain;charset=utf-8" ,
"UTF-8" , 8 },
{ 0,
"text/plain;charset=utf-8" ,
"text/plain;charset=UTF-8" , 8 },
// ISO encodings
{ 0,
"text/plain;charset=iso8859-2" ,
"ISO8859-2" , 8 },
{ 0,
"text/plain;charset=iso8859-3" ,
"ISO8859-3" , 8 },
{ 0,
"text/plain;charset=iso8859-4" ,
"ISO8859-4" , 8 },
{ 0,
"text/plain;charset=iso8859-5" ,
"ISO8859-5" , 8 },
{ 0,
"text/plain;charset=iso8859-6" ,
"ISO8859-6" , 8 },
{ 0,
"text/plain;charset=iso8859-7" ,
"ISO8859-7" , 8 },
{ 0,
"text/plain;charset=iso8859-8" ,
"ISO8859-8" , 8 },
{ 0,
"text/plain;charset=iso8859-9" ,
"ISO8859-9" , 8 },
{ 0,
"text/plain;charset=iso8859-10" ,
"ISO8859-10" , 8 },
{ 0,
"text/plain;charset=iso8859-13" ,
"ISO8859-13" , 8 },
{ 0,
"text/plain;charset=iso8859-14" ,
"ISO8859-14" , 8 },
{ 0,
"text/plain;charset=iso8859-15" ,
"ISO8859-15" , 8 },
// asian encodings
{ 0,
"text/plain;charset=jisx0201.1976-0" ,
"JISX0201.1976-0" , 8 },
{ 0,
"text/plain;charset=jisx0208.1983-0" ,
"JISX0208.1983-0" , 8 },
{ 0,
"text/plain;charset=jisx0208.1990-0" ,
"JISX0208.1990-0" , 8 },
{ 0,
"text/plain;charset=jisx0212.1990-0" ,
"JISX0212.1990-0" , 8 },
{ 0,
"text/plain;charset=gb2312.1980-0" ,
"GB2312.1980-0" , 8 },
{ 0,
"text/plain;charset=ksc5601.1992-0" ,
"KSC5601.1992-0" , 8 },
// eastern european encodings
{ 0,
"text/plain;charset=koi8-r" ,
"KOI8-R" , 8 },
{ 0,
"text/plain;charset=koi8-u" ,
"KOI8-U" , 8 },
// String (== iso8859-1)
{ XA_STRING,
"text/plain;charset=iso8859-1" ,
"STRING" , 8 },
// special for compound text
{ 0,
"text/plain;charset=compound_text" ,
"COMPOUND_TEXT" , 8 },
// PIXMAP
{ XA_PIXMAP,
"image/bmp" ,
"PIXMAP" , 32 }
};
rtl_TextEncoding x11::getTextPlainEncoding(
const OUString& rMimeType )
{
rtl_TextEncoding aEncoding = RTL_TEXTENCODING_DONTKNOW;
OUString aMimeType( rMimeType.toAsciiLowerCase() );
sal_Int32 nIndex = 0;
if ( o3tl::getToken(aMimeType, 0,
';' , nIndex ) == u
"text/plain" )
{
if ( aMimeType.getLength() == 10 )
// only "text/plain"
aEncoding = RTL_TEXTENCODING_ISO_8859_1;
else
{
while ( nIndex != -1 )
{
OUString aToken = aMimeType.getToken( 0,
';' , nIndex );
sal_Int32 nPos = 0;
if ( o3tl::getToken(aToken, 0,
'=' , nPos ) == u
"charset" )
{
OString aEncToken = OUStringToOString( o3tl::getToken(aToken, 0,
'=' , nPos ), RTL_TEXTENC
ODING_ISO_8859_1 );
aEncoding = rtl_getTextEncodingFromUnixCharset( aEncToken.getStr() );
if ( aEncoding == RTL_TEXTENCODING_DONTKNOW )
{
if ( aEncToken.equalsIgnoreAsciiCase( "utf-8" ) )
aEncoding = RTL_TEXTENCODING_UTF8;
}
if ( aEncoding != RTL_TEXTENCODING_DONTKNOW )
break ;
}
}
}
}
#if OSL_DEBUG_LEVEL > 1
SAL_WARN_IF(aEncoding == RTL_TEXTENCODING_DONTKNOW,
"vcl.unx.dtrans" , "getTextPlainEncoding( "
<< rMimeType << " ) failed." );
#endif
return aEncoding;
}
std::unordered_map< OUString, SelectionManager* >& SelectionManager::getInstances()
{
static std::unordered_map< OUString, SelectionManager* > aInstances;
return aInstances;
}
SelectionManager::SelectionManager() :
m_nIncrementalThreshold( 15*1024 ),
m_pDisplay( nullptr ),
m_aThread( nullptr ),
m_aDragExecuteThread( nullptr ),
m_aWindow( None ),
m_nSelectionTimeout( 0 ),
m_nSelectionTimestamp( CurrentTime ),
m_bDropEnterSent( true ),
m_aCurrentDropWindow( None ),
m_nDropTime( None ),
m_nLastDropAction( 0 ),
m_nLastX( 0 ),
m_nLastY( 0 ),
m_bDropWaitingForCompletion( false ),
m_aDropWindow( None ),
m_aDropProxy( None ),
m_aDragSourceWindow( None ),
m_nLastDragX( 0 ),
m_nLastDragY( 0 ),
m_nNoPosX( 0 ),
m_nNoPosY( 0 ),
m_nNoPosWidth( 0 ),
m_nNoPosHeight( 0 ),
m_nDragButton( 0 ),
m_nUserDragAction( 0 ),
m_nTargetAcceptAction( 0 ),
m_nSourceActions( 0 ),
m_bLastDropAccepted( false ),
m_bDropSuccess( false ),
m_bDropSent( false ),
m_nDropTimeout( 0 ),
m_bWaitingForPrimaryConversion( false ),
m_aMoveCursor( None ),
m_aCopyCursor( None ),
m_aLinkCursor( None ),
m_aNoneCursor( None ),
m_aCurrentCursor( None ),
m_nCurrentProtocolVersion( nXdndProtocolRevision ),
m_nTARGETSAtom( None ),
m_nTIMESTAMPAtom( None ),
m_nTEXTAtom( None ),
m_nINCRAtom( None ),
m_nCOMPOUNDAtom( None ),
m_nMULTIPLEAtom( None ),
m_nImageBmpAtom( None ),
m_nXdndAware( None ),
m_nXdndEnter( None ),
m_nXdndLeave( None ),
m_nXdndPosition( None ),
m_nXdndStatus( None ),
m_nXdndDrop( None ),
m_nXdndFinished( None ),
m_nXdndSelection( None ),
m_nXdndTypeList( None ),
m_nXdndProxy( None ),
m_nXdndActionCopy( None ),
m_nXdndActionMove( None ),
m_nXdndActionLink( None ),
m_nXdndActionAsk( None ),
m_bShutDown( false )
{
memset(&m_aDropEnterEvent, 0, sizeof (m_aDropEnterEvent));
m_EndThreadPipe[0] = 0;
m_EndThreadPipe[1] = 0;
m_aDragRunning.reset();
}
Cursor SelectionManager::createCursor( const unsigned char * pPointerData, const unsigned char * pMaskData, int width, int height, int hotX, int hotY )
{
Pixmap aPointer;
Pixmap aMask;
XColor aBlack, aWhite;
aBlack.pixel = BlackPixel( m_pDisplay, 0 );
aBlack.red = aBlack.green = aBlack.blue = 0;
aBlack.flags = DoRed | DoGreen | DoBlue;
aWhite.pixel = WhitePixel( m_pDisplay, 0 );
aWhite.red = aWhite.green = aWhite.blue = 0xffff;
aWhite.flags = DoRed | DoGreen | DoBlue;
aPointer =
XCreateBitmapFromData( m_pDisplay,
m_aWindow,
reinterpret_cast <const char *>(pPointerData),
width,
height );
aMask
= XCreateBitmapFromData( m_pDisplay,
m_aWindow,
reinterpret_cast <const char *>(pMaskData),
width,
height );
Cursor aCursor =
XCreatePixmapCursor( m_pDisplay, aPointer, aMask,
&aBlack, &aWhite,
hotX,
hotY );
XFreePixmap( m_pDisplay, aPointer );
XFreePixmap( m_pDisplay, aMask );
return aCursor;
}
void SelectionManager::initialize( const Sequence< Any >& arguments )
{
osl::MutexGuard aGuard(m_aMutex);
if ( ! m_xDisplayConnection.is() )
{
/*
* first argument must be a css::awt::XDisplayConnection
* from this we will get the XEvents of the vcl event loop by
* registering us as XEventHandler on it.
*
* implementor's note:
* FIXME:
* finally the clipboard and XDND service is back in the module it belongs
* now cleanup and sharing of resources with the normal vcl event loop
* needs to be added. The display used would be that of the normal event loop
* and synchronization should be done via the SolarMutex.
*/
if ( arguments.hasElements() )
arguments.getConstArray()[0] >>= m_xDisplayConnection;
if ( ! m_xDisplayConnection.is() )
{
}
else
m_xDisplayConnection->addEventHandler( Any(), this , ~0 );
}
if ( m_pDisplay )
return ;
OUString aUDisplay;
if ( m_xDisplayConnection.is() )
{
Any aIdentifier = m_xDisplayConnection->getIdentifier();
aIdentifier >>= aUDisplay;
}
OString aDisplayName( OUStringToOString( aUDisplay, RTL_TEXTENCODING_ISO_8859_1 ) );
m_pDisplay = XOpenDisplay( aDisplayName.isEmpty() ? nullptr : aDisplayName.getStr());
if ( !m_pDisplay )
return ;
#ifdef SYNCHRONIZE
XSynchronize( m_pDisplay, True );
#endif
// special targets
m_nTARGETSAtom = getAtom( u"TARGETS" _ustr );
m_nTIMESTAMPAtom = getAtom( u"TIMESTAMP" _ustr );
m_nTEXTAtom = getAtom( u"TEXT" _ustr );
m_nINCRAtom = getAtom( u"INCR" _ustr );
m_nCOMPOUNDAtom = getAtom( u"COMPOUND_TEXT" _ustr );
m_nMULTIPLEAtom = getAtom( u"MULTIPLE" _ustr );
m_nImageBmpAtom = getAtom( u"image/bmp" _ustr );
// Atoms for Xdnd protocol
m_nXdndAware = getAtom( u"XdndAware" _ustr );
m_nXdndEnter = getAtom( u"XdndEnter" _ustr );
m_nXdndLeave = getAtom( u"XdndLeave" _ustr );
m_nXdndPosition = getAtom( u"XdndPosition" _ustr );
m_nXdndStatus = getAtom( u"XdndStatus" _ustr );
m_nXdndDrop = getAtom( u"XdndDrop" _ustr );
m_nXdndFinished = getAtom( u"XdndFinished" _ustr );
m_nXdndSelection = getAtom( u"XdndSelection" _ustr );
m_nXdndTypeList = getAtom( u"XdndTypeList" _ustr );
m_nXdndProxy = getAtom( u"XdndProxy" _ustr );
m_nXdndActionCopy = getAtom( u"XdndActionCopy" _ustr );
m_nXdndActionMove = getAtom( u"XdndActionMove" _ustr );
m_nXdndActionLink = getAtom( u"XdndActionLink" _ustr );
m_nXdndActionAsk = getAtom( u"XdndActionAsk" _ustr );
// initialize map with member none
m_aAtomToString[ 0 ]= "None" ;
m_aAtomToString[ XA_PRIMARY ] = "PRIMARY" ;
// create a (invisible) message window
m_aWindow = XCreateSimpleWindow( m_pDisplay, DefaultRootWindow( m_pDisplay ),
10, 10, 10, 10, 0, 0, 1 );
// initialize threshold for incremental transfers
// ICCCM says it should be smaller that the max request size
// which in turn is guaranteed to be at least 16k bytes
m_nIncrementalThreshold = XMaxRequestSize( m_pDisplay ) - 1024;
if ( !m_aWindow )
return ;
// initialize default cursors
m_aMoveCursor = createCursor( movedata_curs_bits,
movedata_mask_bits,
movedata_curs_width,
movedata_curs_height,
movedata_curs_x_hot,
movedata_curs_y_hot );
m_aCopyCursor = createCursor( copydata_curs_bits,
copydata_mask_bits,
copydata_curs_width,
copydata_curs_height,
copydata_curs_x_hot,
copydata_curs_y_hot );
m_aLinkCursor = createCursor( linkdata_curs_bits,
linkdata_mask_bits,
linkdata_curs_width,
linkdata_curs_height,
linkdata_curs_x_hot,
linkdata_curs_y_hot );
m_aNoneCursor = createCursor( nodrop_curs_bits,
nodrop_mask_bits,
nodrop_curs_width,
nodrop_curs_height,
nodrop_curs_x_hot,
nodrop_curs_y_hot );
// just interested in SelectionClear/Notify/Request and PropertyChange
XSelectInput( m_pDisplay, m_aWindow, PropertyChangeMask );
// create the transferable for Drag operations
m_xDropTransferable = new X11Transferable( *this , m_nXdndSelection );
registerHandler( m_nXdndSelection, *this );
m_aThread = osl_createSuspendedThread( call_SelectionManager_run, this );
if ( m_aThread )
osl_resumeThread( m_aThread );
#if OSL_DEBUG_LEVEL > 1
else
SAL_WARN("vcl.unx.dtrans" , "SelectionManager::initialize: "
<< "creation of dispatch thread failed !." );
#endif
if (pipe(m_EndThreadPipe) != 0) {
#if OSL_DEBUG_LEVEL > 1
SAL_WARN("vcl.unx.dtrans" , "Failed to create endThreadPipe." );
#endif
m_EndThreadPipe[0] = m_EndThreadPipe[1] = 0;
}
}
SelectionManager::~SelectionManager()
{
#if OSL_DEBUG_LEVEL > 1
SAL_INFO("vcl.unx.dtrans" , "SelectionManager::~SelectionManager ("
<< (m_pDisplay ? DisplayString(m_pDisplay) : "no display" )
<< ")." );
#endif
{
osl::MutexGuard aGuard( *osl::Mutex::getGlobalMutex() );
auto it = std::find_if(getInstances().begin(), getInstances().end(),
[&](const std::pair< OUString, SelectionManager* >& rInstance) { return rInstance.second == this ; });
if ( it != getInstances().end() )
getInstances().erase( it );
}
if ( m_aThread )
{
osl_terminateThread( m_aThread );
osl_joinWithThread( m_aThread );
osl_destroyThread( m_aThread );
}
if ( m_aDragExecuteThread )
{
osl_terminateThread( m_aDragExecuteThread );
osl_joinWithThread( m_aDragExecuteThread );
m_aDragExecuteThread = nullptr;
// thread handle is freed in dragDoDispatch()
}
osl::MutexGuard aGuard(m_aMutex);
#if OSL_DEBUG_LEVEL > 1
SAL_INFO("vcl.unx.dtrans" , "shutting down SelectionManager." );
#endif
if ( !m_pDisplay )
return ;
deregisterHandler( m_nXdndSelection );
// destroy message window
if ( m_aWindow )
XDestroyWindow( m_pDisplay, m_aWindow );
// release cursors
if (m_aMoveCursor != None)
XFreeCursor(m_pDisplay, m_aMoveCursor);
if (m_aCopyCursor != None)
XFreeCursor(m_pDisplay, m_aCopyCursor);
if (m_aLinkCursor != None)
XFreeCursor(m_pDisplay, m_aLinkCursor);
if (m_aNoneCursor != None)
XFreeCursor(m_pDisplay, m_aNoneCursor);
// paranoia setting, the drag thread should have
// done that already
XUngrabPointer( m_pDisplay, CurrentTime );
XUngrabKeyboard( m_pDisplay, CurrentTime );
XCloseDisplay( m_pDisplay );
}
SelectionAdaptor* SelectionManager::getAdaptor( Atom selection )
{
std::unordered_map< Atom, Selection* >::iterator it =
m_aSelections.find( selection );
return it != m_aSelections.end() ? it->second->m_pAdaptor : nullptr;
}
OUString SelectionManager::convertFromCompound( const char * pText, int nLen )
{
osl::MutexGuard aGuard( m_aMutex );
OUStringBuffer aRet;
if ( nLen < 0 )
nLen = strlen( pText );
char ** pTextList = nullptr;
int nTexts = 0;
XTextProperty aProp;
aProp.value = reinterpret_cast <unsigned char *>(const_cast <char *>(pText));
aProp.encoding = m_nCOMPOUNDAtom;
aProp.format = 8;
aProp.nitems = nLen;
XmbTextPropertyToTextList( m_pDisplay,
&aProp,
&pTextList,
&nTexts );
rtl_TextEncoding aEncoding = osl_getThreadTextEncoding();
for ( int i = 0; i < nTexts; i++ )
aRet.append(OStringToOUString( pTextList[i], aEncoding ));
if ( pTextList )
XFreeStringList( pTextList );
return aRet.makeStringAndClear();
}
OString SelectionManager::convertToCompound( const OUString& rText )
{
osl::MutexGuard aGuard( m_aMutex );
XTextProperty aProp;
aProp.value = nullptr;
aProp.encoding = XA_STRING;
aProp.format = 8;
aProp.nitems = 0;
OString aRet( rText.getStr(), rText.getLength(), osl_getThreadTextEncoding() );
char * pT = const_cast <char *>(aRet.getStr());
XmbTextListToTextProperty( m_pDisplay,
&pT,
1,
XCompoundTextStyle,
&aProp );
if ( aProp.value )
{
aRet = reinterpret_cast <char *>(aProp.value);
XFree( aProp.value );
#ifdef __sun
/*
* for currently unknown reasons XmbTextListToTextProperty on Solaris returns
* no data in ISO8859-n encodings (at least for n = 1, 15)
* in these encodings the directly converted text does the
* trick, also.
*/
if ( aRet.isEmpty() && !rText.isEmpty() )
aRet = OUStringToOString( rText, osl_getThreadTextEncoding() );
#endif
}
else
aRet.clear();
return aRet;
}
bool SelectionManager::convertData(
const css::uno::Reference< XTransferable >& xTransferable,
Atom nType,
Atom nSelection,
int & rFormat,
Sequence< sal_Int8 >& rData )
{
bool bSuccess = false ;
if ( ! xTransferable.is() )
return bSuccess;
try
{
DataFlavor aFlavor;
aFlavor.MimeType = convertTypeFromNative( nType, nSelection, rFormat );
sal_Int32 nIndex = 0;
if ( o3tl::getToken(aFlavor.MimeType, 0, ';' , nIndex ) == u"text/plain" )
{
if ( o3tl::getToken(aFlavor.MimeType, 0, ';' , nIndex ) == u"charset=utf-16" )
aFlavor.DataType = cppu::UnoType<OUString>::get();
else
aFlavor.DataType = cppu::UnoType<Sequence< sal_Int8 >>::get();
}
else
aFlavor.DataType = cppu::UnoType<Sequence< sal_Int8 >>::get();
if ( xTransferable->isDataFlavorSupported( aFlavor ) )
{
Any aValue( xTransferable->getTransferData( aFlavor ) );
if ( aValue.getValueTypeClass() == TypeClass_STRING )
{
OUString aString;
aValue >>= aString;
rData = Sequence< sal_Int8 >( reinterpret_cast <sal_Int8 const *>(aString.getStr()), aString.getLength() * sizeof ( sal_Unicode ) );
bSuccess = true ;
}
else if ( aValue.getValueType() == cppu::UnoType<Sequence< sal_Int8 >>::get() )
{
aValue >>= rData;
bSuccess = true ;
}
}
else if ( aFlavor.MimeType.startsWith("text/plain" ) )
{
rtl_TextEncoding aEncoding = RTL_TEXTENCODING_DONTKNOW;
bool bCompoundText = false ;
if ( nType == m_nCOMPOUNDAtom )
bCompoundText = true ;
else
aEncoding = getTextPlainEncoding( aFlavor.MimeType );
if ( aEncoding != RTL_TEXTENCODING_DONTKNOW || bCompoundText )
{
aFlavor.MimeType = "text/plain;charset=utf-16" ;
aFlavor.DataType = cppu::UnoType<OUString>::get();
if ( xTransferable->isDataFlavorSupported( aFlavor ) )
{
Any aValue( xTransferable->getTransferData( aFlavor ) );
OUString aString;
aValue >>= aString;
OString aByteString( bCompoundText ? convertToCompound( aString ) : OUStringToOString( aString, aEncoding ) );
rData = Sequence< sal_Int8 >( reinterpret_cast <sal_Int8 const *>(aByteString.getStr()), aByteString.getLength() * sizeof ( char ) );
bSuccess = true ;
}
}
}
}
// various exceptions possible ... which all lead to a failed conversion
// so simplify here to a catch all
catch (...)
{
}
return bSuccess;
}
SelectionManager& SelectionManager::get()
{
osl::MutexGuard aGuard( *osl::Mutex::getGlobalMutex() );
OUString aDisplayName;
if (auto const env = getenv( "DISPLAY" ))
aDisplayName = OStringToOUString( env, RTL_TEXTENCODING_ISO_8859_1 );
SelectionManager* pInstance = nullptr;
std::unordered_map< OUString, SelectionManager* >::iterator it = getInstances().find( aDisplayName );
if ( it != getInstances().end() )
pInstance = it->second;
else pInstance = getInstances()[ aDisplayName ] = new SelectionManager();
return *pInstance;
}
OUString SelectionManager::getString( Atom aAtom )
{
osl::MutexGuard aGuard(m_aMutex);
if ( m_aAtomToString.find( aAtom ) == m_aAtomToString.end() )
{
char * pAtom = m_pDisplay ? XGetAtomName( m_pDisplay, aAtom ) : nullptr;
if ( ! pAtom )
return OUString();
OUString aString( OStringToOUString( pAtom, RTL_TEXTENCODING_ISO_8859_1 ) );
XFree( pAtom );
m_aStringToAtom[ aString ] = aAtom;
m_aAtomToString[ aAtom ] = aString;
}
return m_aAtomToString[ aAtom ];
}
Atom SelectionManager::getAtom( const OUString& rString )
{
osl::MutexGuard aGuard(m_aMutex);
if ( m_aStringToAtom.find( rString ) == m_aStringToAtom.end() )
{
static Atom nNoDisplayAtoms = 1;
Atom aAtom = m_pDisplay ? XInternAtom( m_pDisplay, OUStringToOString( rString, RTL_TEXTENCODING_ISO_8859_1 ).getStr(), False ) : nNoDisplayAtoms++;
m_aStringToAtom[ rString ] = aAtom;
m_aAtomToString[ aAtom ] = rString;
}
return m_aStringToAtom[ rString ];
}
bool SelectionManager::requestOwnership( Atom selection )
{
bool bSuccess = false ;
if ( m_pDisplay && m_aWindow )
{
osl::MutexGuard aGuard(m_aMutex);
SelectionAdaptor* pAdaptor = getAdaptor( selection );
if ( pAdaptor )
{
XSetSelectionOwner( m_pDisplay, selection, m_aWindow, CurrentTime );
if ( XGetSelectionOwner( m_pDisplay, selection ) == m_aWindow )
bSuccess = true ;
#if OSL_DEBUG_LEVEL > 1
SAL_INFO("vcl.unx.dtrans" ,
(bSuccess ? "acquired" : "failed to acquire" )
<< " ownership for selection "
<< getString( selection ));
#endif
Selection* pSel = m_aSelections[ selection ];
pSel->m_bOwner = bSuccess;
delete pSel->m_pPixmap;
pSel->m_pPixmap = nullptr;
pSel->m_nOrigTimestamp = m_nSelectionTimestamp;
}
#if OSL_DEBUG_LEVEL > 1
else
SAL_WARN("vcl.unx.dtrans" , "no adaptor for selection "
<< getString( selection ));
if ( pAdaptor->getTransferable().is() )
{
Sequence< DataFlavor > aTypes = pAdaptor->getTransferable()->getTransferDataFlavors();
for ( int i = 0; i < aTypes.getLength(); i++ )
{
SAL_INFO("vcl.unx.dtrans" , " " << aTypes.getConstArray()[i].MimeType);
}
}
#endif
}
return bSuccess;
}
void SelectionManager::convertTypeToNative( const OUString& rType, Atom selection, int & rFormat, ::std::list< Atom >& rConversions, bool bPushFront )
{
NativeTypeEntry* pTab = selection == m_nXdndSelection ? aXdndConversionTab : aNativeConversionTab;
int nTabEntries = selection == m_nXdndSelection ? SAL_N_ELEMENTS(aXdndConversionTab) : SAL_N_ELEMENTS(aNativeConversionTab);
OString aType( OUStringToOString( rType, RTL_TEXTENCODING_ISO_8859_1 ) );
SAL_INFO( "vcl.unx.dtrans" , "convertTypeToNative " << aType );
rFormat = 0;
for ( int i = 0; i < nTabEntries; i++ )
{
if ( aType.equalsIgnoreAsciiCase( pTab[i].pType ) )
{
if ( ! pTab[i].nAtom )
pTab[i].nAtom = getAtom( OStringToOUString( pTab[i].pNativeType, RTL_TEXTENCODING_ISO_8859_1 ) );
rFormat = pTab[i].nFormat;
if ( bPushFront )
rConversions.push_front( pTab[i].nAtom );
else
rConversions.push_back( pTab[i].nAtom );
if ( pTab[i].nFormat == XA_PIXMAP )
{
if ( bPushFront )
{
rConversions.push_front( XA_VISUALID );
rConversions.push_front( XA_COLORMAP );
}
else
{
rConversions.push_back( XA_VISUALID );
rConversions.push_back( XA_COLORMAP );
}
}
}
}
if ( ! rFormat )
rFormat = 8; // byte buffer
if ( bPushFront )
rConversions.push_front( getAtom( rType ) );
else
rConversions.push_back( getAtom( rType ) );
};
void SelectionManager::getNativeTypeList( const Sequence< DataFlavor >& rTypes, std::list< Atom >& rOutTypeList, Atom targetselection )
{
rOutTypeList.clear();
int nFormat;
bool bHaveText = false ;
for ( const auto & rFlavor : rTypes )
{
if ( rFlavor.MimeType.startsWith("text/plain" ))
bHaveText = true ;
else
convertTypeToNative( rFlavor.MimeType, targetselection, nFormat, rOutTypeList );
}
if ( bHaveText )
{
if ( targetselection != m_nXdndSelection )
{
// only mimetypes should go into Xdnd type list
rOutTypeList.push_front( XA_STRING );
rOutTypeList.push_front( m_nCOMPOUNDAtom );
}
convertTypeToNative( u"text/plain;charset=utf-8" _ustr, targetselection, nFormat, rOutTypeList, true );
}
if ( targetselection != m_nXdndSelection )
rOutTypeList.push_back( m_nMULTIPLEAtom );
}
OUString SelectionManager::convertTypeFromNative( Atom nType, Atom selection, int & rFormat )
{
NativeTypeEntry* pTab = (selection == m_nXdndSelection) ? aXdndConversionTab : aNativeConversionTab;
int nTabEntries = (selection == m_nXdndSelection) ? SAL_N_ELEMENTS(aXdndConversionTab) : SAL_N_ELEMENTS(aNativeConversionTab);
for ( int i = 0; i < nTabEntries; i++ )
{
if ( ! pTab[i].nAtom )
pTab[i].nAtom = getAtom( OStringToOUString( pTab[i].pNativeType, RTL_TEXTENCODING_ISO_8859_1 ) );
if ( nType == pTab[i].nAtom )
{
rFormat = pTab[i].nFormat;
return OStringToOUString( pTab[i].pType, RTL_TEXTENCODING_ISO_8859_1 );
}
}
rFormat = 8;
return getString( nType );
}
bool SelectionManager::getPasteData( Atom selection, Atom type, Sequence< sal_Int8 >& rData )
{
osl::ResettableMutexGuard aGuard(m_aMutex);
std::unordered_map< Atom, Selection* >::iterator it;
bool bSuccess = false ;
#if OSL_DEBUG_LEVEL > 1
SAL_INFO("vcl.unx.dtrans" , "getPasteData( " << getString( selection )
<< ", native: " << getString( type ) << " )." );
#endif
if ( ! m_pDisplay )
return false ;
it = m_aSelections.find( selection );
if ( it == m_aSelections.end() )
return false ;
::Window aSelectionOwner = XGetSelectionOwner( m_pDisplay, selection );
if ( aSelectionOwner == None )
return false ;
if ( aSelectionOwner == m_aWindow )
{
// probably bad timing led us here
#if OSL_DEBUG_LEVEL > 1
SAL_WARN("vcl.unx.dtrans" , "Innere Nabelschau." );
#endif
return false ;
}
// ICCCM recommends to destroy property before convert request unless
// parameters are transported; we do only in case of MULTIPLE,
// so destroy property unless target is MULTIPLE
if ( type != m_nMULTIPLEAtom )
XDeleteProperty( m_pDisplay, m_aWindow, selection );
XConvertSelection( m_pDisplay, selection, type, selection, m_aWindow, selection == m_nXdndSelection ? m_nDropTime : CurrentTime );
it->second->m_eState = Selection::WaitingForResponse;
it->second->m_aRequestedType = type;
it->second->m_aData = Sequence< sal_Int8 >();
it->second->m_aDataArrived.reset();
// really start the request; if we don't flush the
// queue the request won't leave it because there are no more
// X calls after this until the data arrived or timeout
XFlush( m_pDisplay );
// do a reschedule
struct timeval tv_last, tv_current;
gettimeofday( &tv_last, nullptr );
tv_current = tv_last;
XEvent aEvent;
do
{
bool bAdjustTime = false ;
{
bool bHandle = false ;
if ( XCheckTypedEvent( m_pDisplay,
PropertyNotify,
&aEvent
) )
{
bHandle = true ;
if ( aEvent.xproperty.window == m_aWindow
&& aEvent.xproperty.atom == selection )
bAdjustTime = true ;
}
else if ( XCheckTypedEvent( m_pDisplay,
SelectionClear,
&aEvent
) )
{
bHandle = true ;
}
else if ( XCheckTypedEvent( m_pDisplay,
SelectionRequest,
&aEvent
) )
{
bHandle = true ;
}
else if ( XCheckTypedEvent( m_pDisplay,
SelectionNotify,
&aEvent
) )
{
bHandle = true ;
if ( aEvent.xselection.selection == selection
&& ( aEvent.xselection.requestor == m_aWindow ||
aEvent.xselection.requestor == m_aCurrentDropWindow )
)
bAdjustTime = true ;
}
else
{
aGuard.clear();
std::this_thread::sleep_for(std::chrono::milliseconds(100));
aGuard.reset();
}
if ( bHandle )
{
aGuard.clear();
handleXEvent( aEvent );
aGuard.reset();
}
}
gettimeofday( &tv_current, nullptr );
if ( bAdjustTime )
tv_last = tv_current;
} while ( ! it->second->m_aDataArrived.check() && (tv_current.tv_sec - tv_last.tv_sec) < getSelectionTimeout() );
#if OSL_DEBUG_LEVEL > 1
SAL_WARN_IF((tv_current.tv_sec - tv_last.tv_sec) > getSelectionTimeout(),
"vcl.unx.dtrans" , "timed out." );
#endif
if ( it->second->m_aDataArrived.check() &&
it->second->m_aData.getLength() )
{
rData = it->second->m_aData;
bSuccess = true ;
}
#if OSL_DEBUG_LEVEL > 1
else
SAL_WARN("vcl.unx.dtrans" , "conversion unsuccessful." );
#endif
return bSuccess;
}
bool SelectionManager::getPasteData( Atom selection, const OUString& rType, Sequence< sal_Int8 >& rData )
{
bool bSuccess = false ;
std::unordered_map< Atom, Selection* >::iterator it;
{
osl::MutexGuard aGuard(m_aMutex);
it = m_aSelections.find( selection );
if ( it == m_aSelections.end() )
return false ;
}
if ( it->second->m_aTypes.getLength() == 0 )
{
Sequence< DataFlavor > aFlavors;
getPasteDataTypes( selection, aFlavors );
if ( it->second->m_aTypes.getLength() == 0 )
return false ;
}
const Sequence< DataFlavor >& rTypes( it->second->m_aTypes );
const std::vector< Atom >& rNativeTypes( it->second->m_aNativeTypes );
#if OSL_DEBUG_LEVEL > 1
SAL_INFO("vcl.unx.dtrans" , "getPasteData( \" "
<< getString( selection )
<< "\" , \""
<< rType << "\" ).");
#endif
if ( rType == "text/plain;charset=utf-16" )
{
// let's see if we have UTF16 else try to find something convertible
if ( it->second->m_aTypes.getLength() && ! it->second->m_bHaveUTF16 )
{
Sequence< sal_Int8 > aData;
if ( it->second->m_aUTF8Type != None &&
getPasteData( selection,
it->second->m_aUTF8Type,
aData )
)
{
OUString aRet( reinterpret_cast <const char *>(aData.getConstArray()), aData.getLength(), RTL_TEXTENCODING_UTF8 );
rData = Sequence< sal_Int8 >( reinterpret_cast <sal_Int8 const *>(aRet.getStr()), (aRet.getLength()+1)*sizeof ( sal_Unicode ) );
bSuccess = true ;
}
else if ( it->second->m_bHaveCompound &&
getPasteData( selection,
m_nCOMPOUNDAtom,
aData )
)
{
OUString aRet( convertFromCompound( reinterpret_cast <const char *>(aData.getConstArray()), aData.getLength() ) );
rData = Sequence< sal_Int8 >( reinterpret_cast <sal_Int8 const *>(aRet.getStr()), (aRet.getLength()+1)*sizeof ( sal_Unicode ) );
bSuccess = true ;
}
else
{
for ( int i = 0; i < rTypes.getLength(); i++ )
{
rtl_TextEncoding aEncoding = getTextPlainEncoding( rTypes.getConstArray()[i].MimeType );
if ( aEncoding != RTL_TEXTENCODING_DONTKNOW &&
aEncoding != RTL_TEXTENCODING_UNICODE &&
getPasteData( selection,
rNativeTypes[i],
aData )
)
{
#if OSL_DEBUG_LEVEL > 1
SAL_INFO("vcl.unx.dtrans" , "using \" "
<< rTypes.getConstArray()[i].MimeType
<< "\" instead of \""
<< rType
<< "\" .");
#endif
OString aConvert( reinterpret_cast <char const *>(aData.getConstArray()), aData.getLength() );
OUString aUTF( OStringToOUString( aConvert, aEncoding ) );
rData = Sequence< sal_Int8 >( reinterpret_cast <sal_Int8 const *>(aUTF.getStr()), (aUTF.getLength()+1)*sizeof ( sal_Unicode ) );
bSuccess = true ;
break ;
}
}
}
}
}
else if ( rType == "image/bmp" )
{
// #i83376# try if someone has the data in image/bmp already before
// doing the PIXMAP stuff (e.g. the Gimp has this)
bSuccess = getPasteData( selection, m_nImageBmpAtom, rData );
#if OSL_DEBUG_LEVEL > 1
SAL_INFO_IF(bSuccess, "vcl.unx.dtrans" ,
"got " << (int ) rData.getLength() << " bytes of image/bmp." );
#endif
if ( ! bSuccess )
{
Pixmap aPixmap = None;
Colormap aColormap = None;
// prepare property for MULTIPLE request
Sequence< sal_Int8 > aData;
Atom const pTypes[4] = { XA_PIXMAP, XA_PIXMAP, XA_COLORMAP, XA_COLORMAP };
{
osl::MutexGuard aGuard(m_aMutex);
XChangeProperty( m_pDisplay,
m_aWindow,
selection,
XA_ATOM,
32,
PropModeReplace,
reinterpret_cast <unsigned char const *>(pTypes),
4 );
}
// try MULTIPLE request
if ( getPasteData( selection, m_nMULTIPLEAtom, aData ) )
{
Atom* pReturnedTypes = reinterpret_cast <Atom*>(aData.getArray());
if ( pReturnedTypes[0] == XA_PIXMAP && pReturnedTypes[1] == XA_PIXMAP )
{
osl::MutexGuard aGuard(m_aMutex);
Atom type = None;
int format = 0;
unsigned long nItems = 0;
unsigned long nBytes = 0;
unsigned char * pReturn = nullptr;
XGetWindowProperty( m_pDisplay, m_aWindow, XA_PIXMAP, 0, 1, True , XA_PIXMAP, &type, &format, &nItems, &nBytes, &pReturn );
if ( pReturn )
{
if ( type == XA_PIXMAP )
aPixmap = *reinterpret_cast <Pixmap*>(pReturn);
XFree( pReturn );
pReturn = nullptr;
if ( pReturnedTypes[2] == XA_COLORMAP && pReturnedTypes[3] == XA_COLORMAP )
{
XGetWindowProperty( m_pDisplay, m_aWindow, XA_COLORMAP, 0, 1, True , XA_COLORMAP, &type, &format, &nItems, &nBytes, &pReturn );
if ( pReturn )
{
if ( type == XA_COLORMAP )
aColormap = *reinterpret_cast <Colormap*>(pReturn);
XFree( pReturn );
}
}
}
#if OSL_DEBUG_LEVEL > 1
else
{
SAL_WARN("vcl.unx.dtrans" , "could not get PIXMAP property: type="
<< getString( type )
<< ", format=" << format
<< ", items=" << nItems
<< ", bytes=" << nBytes
<< ", ret=0x" << pReturn);
}
#endif
}
}
if ( aPixmap == None )
{
// perhaps two normal requests will work
if ( getPasteData( selection, XA_PIXMAP, aData ) )
{
aPixmap = *reinterpret_cast <Pixmap*>(aData.getArray());
if ( aColormap == None && getPasteData( selection, XA_COLORMAP, aData ) )
aColormap = *reinterpret_cast <Colormap*>(aData.getArray());
}
}
// convert data if possible
if ( aPixmap != None )
{
osl::MutexGuard aGuard(m_aMutex);
sal_Int32 nOutSize = 0;
sal_uInt8* pBytes = X11_getBmpFromPixmap( m_pDisplay, aPixmap, aColormap, nOutSize );
if ( pBytes )
{
if ( nOutSize )
{
rData = Sequence< sal_Int8 >( nOutSize );
memcpy( rData.getArray(), pBytes, nOutSize );
bSuccess = true ;
}
std::free( pBytes );
}
}
}
}
if ( ! bSuccess )
{
int nFormat;
::std::list< Atom > aTypes;
convertTypeToNative( rType, selection, nFormat, aTypes );
Atom nSelectedType = None;
for (auto const & type : aTypes)
{
for ( auto const & nativeType: rNativeTypes )
if (nativeType == type)
{
nSelectedType = type;
if (nSelectedType != None)
break ;
}
}
if ( nSelectedType != None )
bSuccess = getPasteData( selection, nSelectedType, rData );
}
#if OSL_DEBUG_LEVEL > 1
SAL_INFO("vcl.unx.dtrans" , "getPasteData for selection "
<< getString( selection )
<< " and data type "
<< rType
<< " returns "
<< ( bSuccess ? "true" : "false" )
<< ", returned sequence has length "
<< rData.getLength() << "." );
#endif
return bSuccess;
}
bool SelectionManager::getPasteDataTypes( Atom selection, Sequence< DataFlavor >& rTypes )
{
std::unordered_map< Atom, Selection* >::iterator it;
{
osl::MutexGuard aGuard(m_aMutex);
it = m_aSelections.find( selection );
if ( it != m_aSelections.end() &&
it->second->m_aTypes.getLength() &&
std::abs( it->second->m_nLastTimestamp - time( nullptr ) ) < 2
)
{
rTypes = it->second->m_aTypes;
return true ;
}
}
bool bSuccess = false ;
bool bHaveUTF16 = false ;
Atom aUTF8Type = None;
bool bHaveCompound = false ;
Sequence< sal_Int8 > aAtoms;
if ( selection == m_nXdndSelection )
{
// xdnd sends first three types with XdndEnter
// if more than three types are supported then the XDndTypeList
// property on the source window is used
if ( m_aDropEnterEvent.data.l[0] && m_aCurrentDropWindow )
{
if ( m_aDropEnterEvent.data.l[1] & 1 )
{
const unsigned int atomcount = 256;
// more than three types; look in property
osl::MutexGuard aGuard(m_aMutex);
Atom nType;
int nFormat;
unsigned long nItems, nBytes;
unsigned char * pBytes = nullptr;
XGetWindowProperty( m_pDisplay, m_aDropEnterEvent.data.l[0],
m_nXdndTypeList, 0, atomcount, False ,
XA_ATOM,
&nType, &nFormat, &nItems, &nBytes, &pBytes );
#if OSL_DEBUG_LEVEL > 1
SAL_INFO("vcl.unx.dtrans" , "have "
<< nItems
<< " data types in XdndTypeList." );
#endif
if ( nItems == atomcount && nBytes > 0 )
{
// wow ... more than 256 types !
aAtoms.realloc( sizeof ( Atom )*atomcount+nBytes );
memcpy( aAtoms.getArray(), pBytes, sizeof ( Atom )*atomcount );
XFree( pBytes );
pBytes = nullptr;
XGetWindowProperty( m_pDisplay, m_aDropEnterEvent.data.l[0],
m_nXdndTypeList, atomcount, nBytes/sizeof (Atom),
False , XA_ATOM,
&nType, &nFormat, &nItems, &nBytes, &pBytes );
{
memcpy( aAtoms.getArray()+atomcount*sizeof (Atom), pBytes, nItems*sizeof (Atom) );
XFree( pBytes );
}
}
else
{
aAtoms.realloc( sizeof (Atom)*nItems );
memcpy( aAtoms.getArray(), pBytes, nItems*sizeof (Atom) );
XFree( pBytes );
}
}
else
{
// one to three types
int n = 0, i;
for ( i = 0; i < 3; i++ )
if ( m_aDropEnterEvent.data.l[2+i] )
n++;
#if OSL_DEBUG_LEVEL > 1
SAL_INFO("vcl.unx.dtrans" , "have "
<< n
<< " data types in XdndEnter." );
#endif
aAtoms.realloc( sizeof (Atom)*n );
for ( i = 0, n = 0; i < 3; i++ )
if ( m_aDropEnterEvent.data.l[2+i] )
reinterpret_cast <Atom*>(aAtoms.getArray())[n++] = m_aDropEnterEvent.data.l[2+i];
}
}
}
// get data of type TARGETS
else if ( ! getPasteData( selection, m_nTARGETSAtom, aAtoms ) )
aAtoms = Sequence< sal_Int8 >();
std::vector< Atom > aNativeTypes;
if ( aAtoms.hasElements() )
{
sal_Int32 nAtoms = aAtoms.getLength() / sizeof (Atom);
Atom* pAtoms = reinterpret_cast <Atom*>(aAtoms.getArray());
rTypes.realloc( nAtoms );
aNativeTypes.resize( nAtoms );
DataFlavor* pFlavors = rTypes.getArray();
sal_Int32 nNativeTypesIndex = 0;
bool bHaveText = false ;
while ( nAtoms-- )
{
SAL_INFO_IF(*pAtoms && *pAtoms < 0x01000000, "vcl.unx.dtrans" ,
"getPasteDataTypes: available: \" " << getString(*pAtoms) << " \"" );
if ( *pAtoms == m_nCOMPOUNDAtom )
bHaveText = bHaveCompound = true ;
else if ( *pAtoms && *pAtoms < 0x01000000 )
{
int nFormat;
pFlavors->MimeType = convertTypeFromNative( *pAtoms, selection, nFormat );
pFlavors->DataType = cppu::UnoType<Sequence< sal_Int8 >>::get();
sal_Int32 nIndex = 0;
if ( o3tl::getToken(pFlavors->MimeType, 0, ';' , nIndex ) == u"text/plain" )
{
std::u16string_view aToken(o3tl::getToken(pFlavors->MimeType, 0, ';' , nIndex ));
// omit text/plain;charset=unicode since it is not well defined
if ( aToken == u"charset=unicode" )
{
pAtoms++;
continue ;
}
bHaveText = true ;
if ( aToken == u"charset=utf-16" )
{
bHaveUTF16 = true ;
pFlavors->DataType = cppu::UnoType<OUString>::get();
}
else if ( aToken == u"charset=utf-8" )
{
aUTF8Type = *pAtoms;
}
}
pFlavors++;
aNativeTypes[ nNativeTypesIndex ] = *pAtoms;
nNativeTypesIndex++;
}
pAtoms++;
}
if ( (pFlavors - rTypes.getArray()) < rTypes.getLength() )
rTypes.realloc(pFlavors - rTypes.getArray());
bSuccess = rTypes.hasElements();
if ( bHaveText && ! bHaveUTF16 )
{
int i = 0;
int nNewFlavors = rTypes.getLength()+1;
Sequence< DataFlavor > aTemp( nNewFlavors );
for ( i = 0; i < nNewFlavors-1; i++ )
aTemp.getArray()[i+1] = rTypes.getConstArray()[i];
aTemp.getArray()[0].MimeType = "text/plain;charset=utf-16" ;
aTemp.getArray()[0].DataType = cppu::UnoType<OUString>::get();
rTypes = std::move(aTemp);
std::vector< Atom > aNativeTemp( nNewFlavors );
for ( i = 0; i < nNewFlavors-1; i++ )
aNativeTemp[ i + 1 ] = aNativeTypes[ i ];
aNativeTemp[0] = None;
aNativeTypes = std::move(aNativeTemp);
}
}
{
osl::MutexGuard aGuard(m_aMutex);
it = m_aSelections.find( selection );
if ( it != m_aSelections.end() )
{
if ( bSuccess )
{
it->second->m_aTypes = rTypes;
it->second->m_aNativeTypes = std::move(aNativeTypes);
it->second->m_nLastTimestamp = time( nullptr );
it->second->m_bHaveUTF16 = bHaveUTF16;
it->second->m_aUTF8Type = aUTF8Type;
it->second->m_bHaveCompound = bHaveCompound;
}
else
{
it->second->m_aTypes = Sequence< DataFlavor >();
it->second->m_aNativeTypes = std::vector< Atom >();
it->second->m_nLastTimestamp = 0;
it->second->m_bHaveUTF16 = false ;
it->second->m_aUTF8Type = None;
it->second->m_bHaveCompound = false ;
}
}
}
#if OSL_DEBUG_LEVEL > 1
{
SAL_INFO("vcl.unx.dtrans" , "SelectionManager::getPasteDataTypes( "
<< getString( selection )
<< " ) = "
<< (bSuccess ? "true" : "false" ));
for ( int i = 0; i < rTypes.getLength(); i++ )
SAL_INFO("vcl.unx.dtrans" , "type: " << rTypes.getConstArray()[i].MimeType);
}
#endif
return bSuccess;
}
PixmapHolder* SelectionManager::getPixmapHolder( Atom selection )
{
std::unordered_map< Atom, Selection* >::const_iterator it = m_aSelections.find( selection );
if ( it == m_aSelections.end() )
return nullptr;
if ( ! it->second->m_pPixmap )
it->second->m_pPixmap = new PixmapHolder( m_pDisplay );
return it->second->m_pPixmap;
}
static std::size_t GetTrueFormatSize(int nFormat)
{
// http://mail.gnome.org/archives/wm-spec-list/2003-March/msg00067.html
return nFormat == 32 ? sizeof (long ) : nFormat/8;
}
bool SelectionManager::sendData( SelectionAdaptor* pAdaptor,
::Window requestor,
Atom target,
Atom property,
Atom selection )
{
osl::ResettableMutexGuard aGuard( m_aMutex );
// handle targets related to image/bmp
if ( target == XA_COLORMAP || target == XA_PIXMAP || target == XA_BITMAP || target == XA_VISUALID )
{
PixmapHolder* pPixmap = getPixmapHolder( selection );
if ( ! pPixmap ) return false ;
XID nValue = None;
// handle colormap request
if ( target == XA_COLORMAP )
nValue = static_cast <XID>(pPixmap->getColormap());
else if ( target == XA_VISUALID )
nValue = static_cast <XID>(pPixmap->getVisualID());
else if ( target == XA_PIXMAP || target == XA_BITMAP )
{
nValue = static_cast <XID>(pPixmap->getPixmap());
if ( nValue == None )
{
// first conversion
Sequence< sal_Int8 > aData;
int nFormat;
aGuard.clear();
bool bConverted = convertData( pAdaptor->getTransferable(), target, selection, nFormat, aData );
aGuard.reset();
if ( bConverted )
{
// get pixmap again since clearing the guard could have invalidated
// the pixmap in another thread
pPixmap = getPixmapHolder( selection );
// conversion succeeded, so aData contains image/bmp now
if ( pPixmap->needsConversion( reinterpret_cast <const sal_uInt8*>(aData.getConstArray()) ) )
{
SAL_INFO( "vcl.unx.dtrans" , "trying bitmap conversion" );
int depth = pPixmap->getDepth();
aGuard.clear();
aData = convertBitmapDepth(aData, depth);
aGuard.reset();
}
// get pixmap again since clearing the guard could have invalidated
// the pixmap in another thread
pPixmap = getPixmapHolder( selection );
nValue = static_cast <XID>(pPixmap->setBitmapData( reinterpret_cast <const sal_uInt8*>(aData.getConstArray()) ));
}
if ( nValue == None )
return false ;
}
if ( target == XA_BITMAP )
nValue = static_cast <XID>(pPixmap->getBitmap());
}
XChangeProperty( m_pDisplay,
requestor,
property,
target,
32,
PropModeReplace,
reinterpret_cast <const unsigned char *>(&nValue),
1);
return true ;
}
/*
* special target TEXT allows us to transfer
* the data in an encoding of our choice
* COMPOUND_TEXT will work with most applications
*/
if ( target == m_nTEXTAtom )
target = m_nCOMPOUNDAtom;
Sequence< sal_Int8 > aData;
int nFormat;
aGuard.clear();
bool bConverted = convertData( pAdaptor->getTransferable(), target, selection, nFormat, aData );
aGuard.reset();
if ( bConverted )
{
// conversion succeeded
if ( aData.getLength() > m_nIncrementalThreshold )
{
#if OSL_DEBUG_LEVEL > 1
SAL_INFO("vcl.unx.dtrans" , "using INCR protocol." );
std::unordered_map< ::Window, std::unordered_map< Atom, IncrementalTransfer > >::const_iterator win_it = m_aIncrementals.find( requestor );
if ( win_it != m_aIncrementals.end() )
{
std::unordered_map< Atom, IncrementalTransfer >::const_iterator inc_it = win_it->second.find( property );
if ( inc_it != win_it->second.end() )
{
const IncrementalTransfer& rInc = inc_it->second;
SAL_INFO("vcl.unx.dtrans" , "premature end and new start for INCR transfer for window "
<< std::showbase << std::hex
<< rInc.m_aRequestor
<< ", property "
<< getString( rInc.m_aProperty )
<< ", type "
<< getString( rInc.m_aTarget ));
}
}
#endif
// insert IncrementalTransfer
IncrementalTransfer& rInc = m_aIncrementals[ requestor ][ property ];
rInc.m_aData = std::move(aData);
rInc.m_nBufferPos = 0;
rInc.m_aRequestor = requestor;
rInc.m_aProperty = property;
rInc.m_aTarget = target;
rInc.m_nFormat = nFormat;
rInc.m_nTransferStartTime = time( nullptr );
// use incr protocol, signal start to requestor
tools::Long nMinSize = m_nIncrementalThreshold;
XSelectInput( m_pDisplay, requestor, PropertyChangeMask );
XChangeProperty( m_pDisplay, requestor, property,
m_nINCRAtom, 32, PropModeReplace, reinterpret_cast <unsigned char *>(&nMinSize), 1 );
XFlush( m_pDisplay );
}
else
{
std::size_t nUnitSize = GetTrueFormatSize(nFormat);
XChangeProperty( m_pDisplay,
requestor,
property,
target,
nFormat,
PropModeReplace,
reinterpret_cast <const unsigned char *>(aData.getConstArray()),
aData.getLength()/nUnitSize );
}
}
#if OSL_DEBUG_LEVEL > 1
else
SAL_WARN("vcl.unx.dtrans" , "convertData failed for type: "
<< convertTypeFromNative( target, selection, nFormat ));
#endif
return bConverted;
}
bool SelectionManager::handleSelectionRequest( XSelectionRequestEvent& rRequest )
{
osl::ResettableMutexGuard aGuard( m_aMutex );
#if OSL_DEBUG_LEVEL > 1
SAL_INFO("vcl.unx.dtrans" , "handleSelectionRequest for selection "
<< getString( rRequest.selection )
<< " and target "
<< getString( rRequest.target ));
#endif
XEvent aNotify;
aNotify.type = SelectionNotify;
aNotify.xselection.display = rRequest.display;
aNotify.xselection.send_event = True ;
aNotify.xselection.requestor = rRequest.requestor;
aNotify.xselection.selection = rRequest.selection;
aNotify.xselection.time = rRequest.time;
aNotify.xselection.target = rRequest.target;
aNotify.xselection.property = None;
SelectionAdaptor* pAdaptor = getAdaptor( rRequest.selection );
// ensure that we still own that selection
if ( pAdaptor &&
XGetSelectionOwner( m_pDisplay, rRequest.selection ) == m_aWindow )
{
css::uno::Reference< XTransferable > xTrans( pAdaptor->getTransferable() );
if ( rRequest.target == m_nTARGETSAtom )
{
// someone requests our types
if ( xTrans.is() )
{
aGuard.clear();
Sequence< DataFlavor > aFlavors = xTrans->getTransferDataFlavors();
aGuard.reset();
::std::list< Atom > aConversions;
getNativeTypeList( aFlavors, aConversions, rRequest.selection );
int i, nTypes = aConversions.size();
Atom* pTypes = static_cast <Atom*>(alloca( nTypes * sizeof ( Atom ) ));
std::list< Atom >::const_iterator it;
for ( i = 0, it = aConversions.begin(); i < nTypes; i++, ++it )
pTypes[i] = *it;
XChangeProperty( m_pDisplay, rRequest.requestor, rRequest.property,
XA_ATOM, 32, PropModeReplace, reinterpret_cast <unsigned char *>(pTypes), nTypes );
aNotify.xselection.property = rRequest.property;
#if OSL_DEBUG_LEVEL > 1
SAL_INFO("vcl.unx.dtrans" , "sending type list:" );
for ( int k = 0; k < nTypes; k++ )
SAL_INFO("vcl.unx.dtrans" , " "
<< (pTypes[k] ? XGetAtomName( m_pDisplay, pTypes[k] ) :
"" ));
#endif
}
}
else if ( rRequest.target == m_nTIMESTAMPAtom )
{
tools::Long nTimeStamp = static_cast <tools::Long >(m_aSelections[rRequest.selection]->m_nOrigTimestamp);
XChangeProperty( m_pDisplay, rRequest.requestor, rRequest.property,
XA_INTEGER, 32, PropModeReplace, reinterpret_cast <unsigned char *>(&nTimeStamp), 1 );
aNotify.xselection.property = rRequest.property;
#if OSL_DEBUG_LEVEL > 1
SAL_INFO("vcl.unx.dtrans" , "sending timestamp: " << (int )nTimeStamp);
#endif
}
else
{
bool bEventSuccess = false ;
if ( rRequest.target == m_nMULTIPLEAtom )
{
// get all targets
Atom nType = None;
int nFormat = 0;
unsigned long nItems = 0, nBytes = 0;
unsigned char * pData = nullptr;
// get number of atoms
XGetWindowProperty( m_pDisplay,
rRequest.requestor,
rRequest.property,
0, 0,
False ,
AnyPropertyType,
&nType, &nFormat,
&nItems, &nBytes,
&pData );
if ( nFormat == 32 && nBytes/4 )
{
if ( pData ) // ?? should not happen
{
XFree( pData );
pData = nullptr;
}
XGetWindowProperty( m_pDisplay,
rRequest.requestor,
rRequest.property,
0, nBytes/4,
False ,
nType,
&nType, &nFormat,
&nItems, &nBytes,
&pData );
if ( pData && nItems )
{
#if OSL_DEBUG_LEVEL > 1
SAL_INFO("vcl.unx.dtrans" , "found "
<< nItems
<< " atoms in MULTIPLE request." );
#endif
bEventSuccess = true ;
bool bResetAtoms = false ;
Atom* pAtoms = reinterpret_cast <Atom*>(pData);
aGuard.clear();
for ( unsigned long i = 0; i < nItems; i += 2 )
{
#if OSL_DEBUG_LEVEL > 1
std::ostringstream oss;
oss << " "
<< getString( pAtoms[i] )
<< " => "
<< getString( pAtoms[i+1] )
<< ": " ;
#endif
bool bSuccess = sendData( pAdaptor, rRequest.requestor, pAtoms[i], pAtoms[i+1], rRequest.selection );
#if OSL_DEBUG_LEVEL > 1
oss << (bSuccess ? "succeeded" : "failed" );
SAL_INFO("vcl.unx.dtrans" , oss.str());
#endif
if ( ! bSuccess )
{
pAtoms[i] = None;
bResetAtoms = true ;
}
}
aGuard.reset();
if ( bResetAtoms )
XChangeProperty( m_pDisplay,
rRequest.requestor,
rRequest.property,
XA_ATOM,
32,
PropModeReplace,
pData,
nBytes/4 );
}
if ( pData )
XFree( pData );
}
#if OSL_DEBUG_LEVEL > 1
else
{
std::ostringstream oss;
oss << "could not get type list from \" "
<< getString( rRequest.property )
<< "\" of type \""
<< getString( nType )
<< "\" on requestor "
<< std::showbase << std::hex
<< rRequest.requestor
<< ", requestor has properties:" ;
--> --------------------
--> maximum size reached
--> --------------------
Messung V0.5 C=94 H=95 G=94
¤ Dauer der Verarbeitung: 0.21 Sekunden
(vorverarbeitet)
¤
*© Formatika GbR, Deutschland