diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:06:44 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:06:44 +0000 |
commit | ed5640d8b587fbcfed7dd7967f3de04b37a76f26 (patch) | |
tree | 7a5f7c6c9d02226d7471cb3cc8fbbf631b415303 /vcl/unx/generic | |
parent | Initial commit. (diff) | |
download | libreoffice-ed5640d8b587fbcfed7dd7967f3de04b37a76f26.tar.xz libreoffice-ed5640d8b587fbcfed7dd7967f3de04b37a76f26.zip |
Adding upstream version 4:7.4.7.upstream/4%7.4.7upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'vcl/unx/generic')
85 files changed, 44234 insertions, 0 deletions
diff --git a/vcl/unx/generic/app/gendata.cxx b/vcl/unx/generic/app/gendata.cxx new file mode 100644 index 000000000..444d65302 --- /dev/null +++ b/vcl/unx/generic/app/gendata.cxx @@ -0,0 +1,54 @@ +/* -*- 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 <unx/gendata.hxx> + +#include <unx/fontmanager.hxx> +#include <unx/glyphcache.hxx> +#include <printerinfomanager.hxx> + +SalData::SalData() { SetSalData(this); } + +SalData::~SalData() {} + +GenericUnixSalData::GenericUnixSalData() + : m_pDisplay(nullptr) +{ +} + +GenericUnixSalData::~GenericUnixSalData() +{ + // at least for InitPrintFontManager the sequence is important + m_pPrintFontManager.reset(); + m_pFreetypeManager.reset(); + m_pPrinterInfoManager.reset(); +} + +void GenericUnixSalData::Dispose() {} + +void GenericUnixSalData::InitFreetypeManager() { m_pFreetypeManager.reset(new FreetypeManager); } + +void GenericUnixSalData::InitPrintFontManager() +{ + GetFreetypeManager(); + m_pPrintFontManager.reset(new psp::PrintFontManager); + m_pPrintFontManager->initialize(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/generic/app/gendisp.cxx b/vcl/unx/generic/app/gendisp.cxx new file mode 100644 index 000000000..b1dbef3f5 --- /dev/null +++ b/vcl/unx/generic/app/gendisp.cxx @@ -0,0 +1,69 @@ +/* -*- 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 <salframe.hxx> +#include <unx/gendisp.hxx> + +SalGenericDisplay::SalGenericDisplay() +{ + m_pCapture = nullptr; +} + +SalGenericDisplay::~SalGenericDisplay() +{ +} + +void SalGenericDisplay::registerFrame( SalFrame* pFrame ) +{ + insertFrame( pFrame ); +} + +void SalGenericDisplay::deregisterFrame( SalFrame* pFrame ) +{ + eraseFrame( pFrame ); +} + +void SalGenericDisplay::emitDisplayChanged() +{ + SalFrame *pAnyFrame = anyFrame(); + if( pAnyFrame ) + pAnyFrame->CallCallback( SalEvent::DisplayChanged, nullptr ); +} + +bool SalGenericDisplay::DispatchInternalEvent( bool bHandleAllCurrentEvent ) +{ + return DispatchUserEvents( bHandleAllCurrentEvent ); +} + +void SalGenericDisplay::SendInternalEvent( SalFrame* pFrame, void* pData, SalEvent nEvent ) +{ + PostEvent( pFrame, pData, nEvent ); +} + +void SalGenericDisplay::CancelInternalEvent( SalFrame* pFrame, void* pData, SalEvent nEvent ) +{ + RemoveEvent( pFrame, pData, nEvent ); +} + +void SalGenericDisplay::ProcessEvent( SalUserEvent aEvent ) +{ + aEvent.m_pFrame->CallCallback( aEvent.m_nEvent, aEvent.m_pData ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/generic/app/geninst.cxx b/vcl/unx/generic/app/geninst.cxx new file mode 100644 index 000000000..191d87ca7 --- /dev/null +++ b/vcl/unx/generic/app/geninst.cxx @@ -0,0 +1,99 @@ +/* -*- 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> + +#if defined(LINUX) +# include <stdio.h> +#endif +#if defined(__FreeBSD__) +# include <sys/utsname.h> +#endif + +#include <config_features.h> +#if HAVE_FEATURE_OPENGL +#include <vcl/opengl/OpenGLContext.hxx> +#endif +#include <unx/geninst.h> +#include <o3tl/string_view.hxx> + +// SalYieldMutex + +SalYieldMutex::SalYieldMutex() +{ +#if HAVE_FEATURE_OPENGL + SetBeforeReleaseHandler( &OpenGLContext::prepareForYield ); +#endif +} + +SalYieldMutex::~SalYieldMutex() +{ +} + +SalGenericInstance::~SalGenericInstance() +{ +} + +OUString SalGenericInstance::getOSVersion() +{ + OUString aKernelVer = "unknown"; +#if defined(LINUX) + FILE* pVersion = fopen( "/proc/version", "r" ); + if ( pVersion ) + { + char aVerBuffer[512]; + if ( fgets ( aVerBuffer, 511, pVersion ) ) + { + aKernelVer = OUString::createFromAscii( aVerBuffer ); + // "Linux version 3.16.7-29-desktop ..." + std::u16string_view aVers = o3tl::getToken(aKernelVer, 2, ' ' ); + // "3.16.7-29-desktop ..." + size_t nTooDetailed = aVers.find( '.', 2); + if (nTooDetailed < 1 || nTooDetailed > 8) + aKernelVer = "Linux (misparsed version)"; + else // "3.16.7-29-desktop ..." + aKernelVer = OUString::Concat("Linux ") + aVers.substr(0, nTooDetailed); + } + fclose( pVersion ); + } +#elif defined(__FreeBSD__) + struct utsname stName; + if ( uname( &stName ) != 0 ) + return aKernelVer; + + sal_Int32 nDots = 0; + sal_Int32 nIndex = 0; + aKernelVer = OUString::createFromAscii( stName.release ); + while ( nIndex++ < aKernelVer.getLength() ) + { + const char c = stName.release[ nIndex ]; + if ( c == ' ' || c == '-' || ( c == '.' && nDots++ > 0 ) ) + break; + } + aKernelVer = OUString::createFromAscii(stName.sysname) + " " + aKernelVer.copy(0, nIndex); +#elif defined(EMSCRIPTEN) +#define str(s) #s +#define xstr(s) str(s) + aKernelVer = "Emscripten " xstr(__EMSCRIPTEN_major__) + "." xstr(__EMSCRIPTEN_minor__) "." xstr(__EMSCRIPTEN_tiny__); +#endif + return aKernelVer; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/generic/app/gensys.cxx b/vcl/unx/generic/app/gensys.cxx new file mode 100644 index 000000000..98371c548 --- /dev/null +++ b/vcl/unx/generic/app/gensys.cxx @@ -0,0 +1,116 @@ +/* -*- 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 <config_folders.h> + +#include <unx/gensys.h> + +#include <svdata.hxx> + +#include <rtl/strbuf.hxx> +#include <rtl/bootstrap.hxx> +#include <osl/process.h> +#include <osl/thread.h> +#include <unotools/configmgr.hxx> + +using namespace com::sun::star; + +SalGenericSystem::SalGenericSystem() +{ +} + +SalGenericSystem::~SalGenericSystem() +{ +} + +int SalGenericSystem::ShowNativeMessageBox( const OUString& rTitle, const OUString& rMessage ) +{ + std::vector< OUString > aButtons; + int nButtonIds[5] = {0}, nBut = 0; + + ImplHideSplash(); + + aButtons.push_back( "OK" ); + nButtonIds[nBut++] = SALSYSTEM_SHOWNATIVEMSGBOX_BTN_OK; + int nResult = ShowNativeDialog( rTitle, rMessage, aButtons ); + + return nResult != -1 ? nButtonIds[ nResult ] : 0; +} + +#if !defined(ANDROID) && !defined(IOS) + +// X11-specific + +const char* SalGenericSystem::getFrameResName() +{ + /* according to ICCCM: + * first search command line for -name parameter + * then try RESOURCE_NAME environment variable + * then use argv[0] stripped by directories + */ + static OStringBuffer aResName; + if( aResName.isEmpty() ) + { + int nArgs = osl_getCommandArgCount(); + for( int n = 0; n < nArgs-1; n++ ) + { + OUString aArg; + osl_getCommandArg( n, &aArg.pData ); + if( aArg.equalsIgnoreAsciiCase("-name") ) + { + osl_getCommandArg( n+1, &aArg.pData ); + aResName.append( OUStringToOString( aArg, osl_getThreadTextEncoding() ) ); + break; + } + } + if( aResName.isEmpty() ) + { + const char* pEnv = getenv( "RESOURCE_NAME" ); + if( pEnv && *pEnv ) + aResName.append( pEnv ); + } + if( aResName.isEmpty() ) + aResName.append( OUStringToOString( utl::ConfigManager::getProductName().toAsciiLowerCase(), + osl_getThreadTextEncoding())); + } + return aResName.getStr(); +} + +const char* SalGenericSystem::getFrameClassName() +{ + static OStringBuffer aClassName; + if( aClassName.isEmpty() ) + { + OUString aIni, aProduct; + rtl::Bootstrap::get( "BRAND_BASE_DIR", aIni ); + aIni += "/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE( "bootstrap" ); + rtl::Bootstrap aBootstrap( aIni ); + aBootstrap.getFrom( "ProductKey", aProduct ); + + if( !aProduct.isEmpty() ) + aClassName.append( OUStringToOString( aProduct, osl_getThreadTextEncoding() ) ); + else + aClassName.append( OUStringToOString( utl::ConfigManager::getProductName(), osl_getThreadTextEncoding())); + } + return aClassName.getStr(); +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/generic/app/i18n_cb.cxx b/vcl/unx/generic/app/i18n_cb.cxx new file mode 100644 index 000000000..c17c01a4d --- /dev/null +++ b/vcl/unx/generic/app/i18n_cb.cxx @@ -0,0 +1,494 @@ +/* -*- 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 <stdio.h> +#include <string.h> + +#include <o3tl/safeint.hxx> +#include <osl/thread.h> +#include <sal/log.hxx> + +#include <X11/Xlib.h> + +#include <vcl/commandevent.hxx> +#include <unx/i18n_cb.hxx> +#include <unx/i18n_ic.hxx> +#include <unx/i18n_im.hxx> +#include <salframe.hxx> + +// i. preedit start callback + +int +PreeditStartCallback ( XIC, XPointer client_data, XPointer ) +{ + preedit_data_t* pPreeditData = reinterpret_cast<preedit_data_t*>(client_data); + if ( pPreeditData->eState == PreeditStatus::ActivationRequired ) + { + pPreeditData->eState = PreeditStatus::Active; + pPreeditData->aText.nLength = 0; + } + + return -1; +} + +// ii. preedit done callback + +void +PreeditDoneCallback ( XIC, XPointer client_data, XPointer ) +{ + preedit_data_t* pPreeditData = reinterpret_cast<preedit_data_t*>(client_data); + if (pPreeditData->eState == PreeditStatus::Active ) + { + if( pPreeditData->pFrame ) + pPreeditData->pFrame->CallCallback( SalEvent::EndExtTextInput, nullptr ); + } + pPreeditData->eState = PreeditStatus::StartPending; +} + +// iii. preedit draw callback + +// Handle deletion of text in a preedit_draw_callback +// from and howmuch are guaranteed to be nonnegative + +static void +Preedit_DeleteText(preedit_text_t *ptext, int from, int howmuch) +{ + // If we've been asked to delete no text then just set + // nLength correctly and return + if (ptext->nLength == 0) + { + ptext->nLength = from; + return; + } + + int to = from + howmuch; + + if (to == static_cast<int>(ptext->nLength)) + { + // delete from the end of the text + ptext->nLength = from; + } + else if (to < static_cast<int>(ptext->nLength)) + { + // cut out of the middle of the text + memmove( static_cast<void*>(ptext->pUnicodeBuffer + from), + static_cast<void*>(ptext->pUnicodeBuffer + to), + (ptext->nLength - to) * sizeof(sal_Unicode)); + memmove( static_cast<void*>(ptext->pCharStyle + from), + static_cast<void*>(ptext->pCharStyle + to), + (ptext->nLength - to) * sizeof(XIMFeedback)); + ptext->nLength -= howmuch; + } + else + { + // XXX this indicates an error, are we out of sync ? + SAL_INFO("vcl.app", "Preedit_DeleteText( from=" << from + << " to=" << to + << " length=" << ptext->nLength + << " )."); + fprintf (stderr, "\t XXX internal error, out of sync XXX\n"); + + ptext->nLength = from; + } + + // NULL-terminate the string + ptext->pUnicodeBuffer[ptext->nLength] = u'\0'; +} + +// reallocate the textbuffer with sufficiently large size 2^x +// nnewlimit is presupposed to be larger than ptext->size +static void +enlarge_buffer ( preedit_text_t *ptext, int nnewlimit ) +{ + size_t nnewsize = ptext->nSize; + + while ( nnewsize <= o3tl::make_unsigned(nnewlimit) ) + nnewsize *= 2; + + ptext->nSize = nnewsize; + ptext->pUnicodeBuffer = static_cast<sal_Unicode*>(realloc(static_cast<void*>(ptext->pUnicodeBuffer), + nnewsize * sizeof(sal_Unicode))); + ptext->pCharStyle = static_cast<XIMFeedback*>(realloc(static_cast<void*>(ptext->pCharStyle), + nnewsize * sizeof(XIMFeedback))); +} + +// Handle insertion of text in a preedit_draw_callback +// string field of XIMText struct is guaranteed to be != NULL + +static void +Preedit_InsertText(preedit_text_t *pText, XIMText *pInsertText, int where) +{ + sal_Unicode *pInsertTextString; + int nInsertTextLength = 0; + XIMFeedback *pInsertTextCharStyle = pInsertText->feedback; + + nInsertTextLength = pInsertText->length; + + // can't handle wchar_t strings, so convert to multibyte chars first + char *pMBString; + size_t nMBLength; + if (pInsertText->encoding_is_wchar) + { + wchar_t *pWCString = pInsertText->string.wide_char; + size_t nBytes = wcstombs ( nullptr, pWCString, 0 /* don't care */); + pMBString = static_cast<char*>(alloca( nBytes + 1 )); + nMBLength = wcstombs ( pMBString, pWCString, nBytes + 1); + } + else + { + pMBString = pInsertText->string.multi_byte; + nMBLength = strlen(pMBString); // xxx + } + + // convert multibyte chars to unicode + rtl_TextEncoding nEncoding = osl_getThreadTextEncoding(); + + if (nEncoding != RTL_TEXTENCODING_UNICODE) + { + rtl_TextToUnicodeConverter aConverter = + rtl_createTextToUnicodeConverter( nEncoding ); + rtl_TextToUnicodeContext aContext = + rtl_createTextToUnicodeContext(aConverter); + + sal_Size nBufferSize = nInsertTextLength * 2; + + pInsertTextString = static_cast<sal_Unicode*>(alloca(nBufferSize)); + + sal_uInt32 nConversionInfo; + sal_Size nConvertedChars; + + rtl_convertTextToUnicode( aConverter, aContext, + pMBString, nMBLength, + pInsertTextString, nBufferSize, + RTL_TEXTTOUNICODE_FLAGS_UNDEFINED_IGNORE + | RTL_TEXTTOUNICODE_FLAGS_INVALID_IGNORE, + &nConversionInfo, &nConvertedChars ); + + rtl_destroyTextToUnicodeContext(aConverter, aContext); + rtl_destroyTextToUnicodeConverter(aConverter); + + } + else + { + pInsertTextString = reinterpret_cast<sal_Unicode*>(pMBString); + } + + // enlarge target text-buffer if necessary + if (pText->nSize <= (pText->nLength + nInsertTextLength)) + enlarge_buffer(pText, pText->nLength + nInsertTextLength); + + // insert text: displace old mem and put new bytes in + int from = where; + int to = where + nInsertTextLength; + int howmany = pText->nLength - where; + + memmove(static_cast<void*>(pText->pUnicodeBuffer + to), + static_cast<void*>(pText->pUnicodeBuffer + from), + howmany * sizeof(sal_Unicode)); + memmove(static_cast<void*>(pText->pCharStyle + to), + static_cast<void*>(pText->pCharStyle + from), + howmany * sizeof(XIMFeedback)); + + to = from; + howmany = nInsertTextLength; + + memcpy(static_cast<void*>(pText->pUnicodeBuffer + to), static_cast<void*>(pInsertTextString), + howmany * sizeof(sal_Unicode)); + memcpy(static_cast<void*>(pText->pCharStyle + to), static_cast<void*>(pInsertTextCharStyle), + howmany * sizeof(XIMFeedback)); + + pText->nLength += howmany; + + // NULL-terminate the string + pText->pUnicodeBuffer[pText->nLength] = u'\0'; +} + +// Handle the change of attributes in a preedit_draw_callback + +static void +Preedit_UpdateAttributes ( preedit_text_t* ptext, XIMFeedback const * feedback, + int from, int amount ) +{ + if ( (from + amount) > static_cast<int>(ptext->nLength) ) + { + // XXX this indicates an error, are we out of sync ? + SAL_INFO("vcl.app", "Preedit_UpdateAttributes( " + << from << " + " << amount << " > " << ptext->nLength + << " )."); + fprintf (stderr, "\t XXX internal error, out of sync XXX\n"); + + return; + } + + memcpy ( ptext->pCharStyle + from, + feedback, amount * sizeof(XIMFeedback) ); +} + +// Convert the XIM feedback values into appropriate VCL +// EXTTEXTINPUT_ATTR values +// returns an allocate list of attributes, which must be freed by caller +static ExtTextInputAttr* +Preedit_FeedbackToSAL ( const XIMFeedback* pfeedback, int nlength, std::vector<ExtTextInputAttr>& rSalAttr ) +{ + ExtTextInputAttr *psalattr; + ExtTextInputAttr nval; + ExtTextInputAttr noldval = ExtTextInputAttr::NONE; + XIMFeedback nfeedback; + + // only work with reasonable length + if (nlength > 0 && nlength > sal::static_int_cast<int>(rSalAttr.size()) ) + { + rSalAttr.reserve( nlength ); + psalattr = rSalAttr.data(); + } + else + return nullptr; + + for (int npos = 0; npos < nlength; npos++) + { + nval = ExtTextInputAttr::NONE; + nfeedback = pfeedback[npos]; + + // means to use the feedback of the previous char + if (nfeedback == 0) + { + nval = noldval; + } + // convert feedback to attributes + else + { + if (nfeedback & XIMReverse) + nval |= ExtTextInputAttr::Highlight; + if (nfeedback & XIMUnderline) + nval |= ExtTextInputAttr::Underline; + if (nfeedback & XIMHighlight) + nval |= ExtTextInputAttr::Highlight; + if (nfeedback & XIMPrimary) + nval |= ExtTextInputAttr::DottedUnderline; + if (nfeedback & XIMSecondary) + nval |= ExtTextInputAttr::DashDotUnderline; + if (nfeedback & XIMTertiary) // same as 2ery + nval |= ExtTextInputAttr::DashDotUnderline; + + } + // copy in list + psalattr[npos] = nval; + noldval = nval; + } + // return list of sal attributes + return psalattr; +} + +void +PreeditDrawCallback(XIC ic, XPointer client_data, + XIMPreeditDrawCallbackStruct *call_data) +{ + preedit_data_t* pPreeditData = reinterpret_cast<preedit_data_t*>(client_data); + + // if there's nothing to change then change nothing + if ( ( (call_data->text == nullptr) && (call_data->chg_length == 0) ) + || pPreeditData->pFrame == nullptr ) + return; + + // Solaris 7 deletes the preedit buffer after commit + // since the next call to preeditstart will have the same effect just skip this. + // if (pPreeditData->eState == ePreeditStatusStartPending && call_data->text == NULL) + // return; + + if ( pPreeditData->eState == PreeditStatus::StartPending ) + pPreeditData->eState = PreeditStatus::ActivationRequired; + PreeditStartCallback( ic, client_data, nullptr ); + + // Edit the internal textbuffer as indicated by the call_data, + // chg_first and chg_length are guaranteed to be nonnegative + + // handle text deletion + if (call_data->text == nullptr) + { + Preedit_DeleteText(&(pPreeditData->aText), + call_data->chg_first, call_data->chg_length ); + } + else + { + // handle text insertion + if ( (call_data->chg_length == 0) + && (call_data->text->string.wide_char != nullptr)) + { + Preedit_InsertText(&(pPreeditData->aText), call_data->text, + call_data->chg_first); + } + else if ( (call_data->chg_length != 0) + && (call_data->text->string.wide_char != nullptr)) + { + // handle text replacement by deletion and insertion of text, + // not smart, just good enough + + Preedit_DeleteText(&(pPreeditData->aText), + call_data->chg_first, call_data->chg_length); + Preedit_InsertText(&(pPreeditData->aText), call_data->text, + call_data->chg_first); + } + else if ( (call_data->chg_length != 0) + && (call_data->text->string.wide_char == nullptr)) + { + // not really a text update, only attributes are concerned + Preedit_UpdateAttributes(&(pPreeditData->aText), + call_data->text->feedback, + call_data->chg_first, call_data->chg_length); + } + } + + // build the SalExtTextInputEvent and send it up + + pPreeditData->aInputEv.mpTextAttr = Preedit_FeedbackToSAL( + pPreeditData->aText.pCharStyle, pPreeditData->aText.nLength, pPreeditData->aInputFlags); + pPreeditData->aInputEv.mnCursorPos = call_data->caret; + pPreeditData->aInputEv.maText = OUString(pPreeditData->aText.pUnicodeBuffer, + pPreeditData->aText.nLength); + pPreeditData->aInputEv.mnCursorFlags = 0; // default: make cursor visible + + if ( pPreeditData->eState == PreeditStatus::Active && pPreeditData->pFrame ) + pPreeditData->pFrame->CallCallback(SalEvent::ExtTextInput, static_cast<void*>(&pPreeditData->aInputEv)); + if (pPreeditData->aText.nLength == 0 && pPreeditData->pFrame ) + pPreeditData->pFrame->CallCallback( SalEvent::EndExtTextInput, nullptr ); + + if (pPreeditData->aText.nLength == 0) + pPreeditData->eState = PreeditStatus::StartPending; + + GetPreeditSpotLocation(ic, reinterpret_cast<XPointer>(pPreeditData)); +} + +void +GetPreeditSpotLocation(XIC ic, XPointer client_data) +{ + + // Send SalEventExtTextInputPos event to get spotlocation + + SalExtTextInputPosEvent aPosEvent; + preedit_data_t* pPreeditData = reinterpret_cast<preedit_data_t*>(client_data); + + if( pPreeditData->pFrame ) + pPreeditData->pFrame->CallCallback(SalEvent::ExtTextInputPos, static_cast<void*>(&aPosEvent)); + + XPoint point; + point.x = aPosEvent.mnX + aPosEvent.mnWidth; + point.y = aPosEvent.mnY + aPosEvent.mnHeight; + + XVaNestedList preedit_attr; + preedit_attr = XVaCreateNestedList(0, XNSpotLocation, &point, nullptr); + XSetICValues(ic, XNPreeditAttributes, preedit_attr, nullptr); + XFree(preedit_attr); +} + +// iv. preedit caret callback + +#if OSL_DEBUG_LEVEL > 1 +void +PreeditCaretCallback ( XIC ic, XPointer client_data, + XIMPreeditCaretCallbackStruct *call_data ) +{ + // XXX PreeditCaretCallback is pure debug code for now + const char *direction = "?"; + const char *style = "?"; + + switch ( call_data->style ) + { + case XIMIsInvisible: style = "Invisible"; break; + case XIMIsPrimary: style = "Primary"; break; + case XIMIsSecondary: style = "Secondary"; break; + } + switch ( call_data->direction ) + { + case XIMForwardChar: direction = "Forward char"; break; + case XIMBackwardChar: direction = "Backward char"; break; + case XIMForwardWord: direction = "Forward word"; break; + case XIMBackwardWord: direction = "Backward word"; break; + case XIMCaretUp: direction = "Caret up"; break; + case XIMCaretDown: direction = "Caret down"; break; + case XIMNextLine: direction = "Next line"; break; + case XIMPreviousLine: direction = "Previous line"; break; + case XIMLineStart: direction = "Line start"; break; + case XIMLineEnd: direction = "Line end"; break; + case XIMAbsolutePosition: direction = "Absolute"; break; + case XIMDontChange: direction = "Don't change"; break; + } + + SAL_INFO("vcl.app", "PreeditCaretCallback( ic=" << ic + << ", client=" << client_data + << ","); + SAL_INFO("vcl.app", "\t position=" << call_data->position + << ", direction=\"" << direction + << "\", style=\"" << style + << "\" )."); +} +#else +void +PreeditCaretCallback ( XIC, XPointer, XIMPreeditCaretCallbackStruct* ) +{ +} +#endif + +// v. commit string callback: convert an extended text input (iiimp ... ) +// into an ordinary key-event + +Bool +IsControlCode(sal_Unicode nChar) +{ + if ( nChar <= 0x1F /* C0 controls */ ) + return True; + else + return False; +} + +// vi. status callbacks: for now these are empty, they are just needed for turbo linux + +void +StatusStartCallback (XIC, XPointer, XPointer) +{ +} + +void +StatusDoneCallback (XIC, XPointer, XPointer) +{ +} + +void +StatusDrawCallback (XIC, XPointer, XIMStatusDrawCallbackStruct *) +{ +} + +// vii. destroy callbacks: internally disable all IC/IM calls + +void +IC_IMDestroyCallback (XIM, XPointer client_data, XPointer) +{ + SalI18N_InputContext *pContext = reinterpret_cast<SalI18N_InputContext*>(client_data); + if (pContext != nullptr) + pContext->HandleDestroyIM(); +} + +void +IM_IMDestroyCallback (XIM, XPointer client_data, XPointer) +{ + SalI18N_InputMethod *pMethod = reinterpret_cast<SalI18N_InputMethod*>(client_data); + if (pMethod != nullptr) + pMethod->HandleDestroyIM(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/generic/app/i18n_ic.cxx b/vcl/unx/generic/app/i18n_ic.cxx new file mode 100644 index 000000000..32390a888 --- /dev/null +++ b/vcl/unx/generic/app/i18n_ic.cxx @@ -0,0 +1,604 @@ +/* -*- 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 <X11/Xlib.h> + +#include <unx/i18n_ic.hxx> +#include <unx/i18n_im.hxx> + +#include <unx/salframe.h> +#include <unx/saldisp.hxx> + +#include <sal/log.hxx> + +using namespace vcl; + +static void sendEmptyCommit( SalFrame* pFrame ) +{ + vcl::DeletionListener aDel( pFrame ); + + SalExtTextInputEvent aEmptyEv; + aEmptyEv.mpTextAttr = nullptr; + aEmptyEv.maText.clear(); + aEmptyEv.mnCursorPos = 0; + aEmptyEv.mnCursorFlags = 0; + pFrame->CallCallback( SalEvent::ExtTextInput, static_cast<void*>(&aEmptyEv) ); + if( ! aDel.isDeleted() ) + pFrame->CallCallback( SalEvent::EndExtTextInput, nullptr ); +} + +// Constructor / Destructor, the InputContext is bound to the SalFrame, as it +// needs the shell window as a focus window + +SalI18N_InputContext::~SalI18N_InputContext() +{ + if ( maContext != nullptr ) + XDestroyIC( maContext ); + if ( mpAttributes != nullptr ) + XFree( mpAttributes ); + if ( mpStatusAttributes != nullptr ) + XFree( mpStatusAttributes ); + if ( mpPreeditAttributes != nullptr ) + XFree( mpPreeditAttributes ); + + if (maClientData.aText.pUnicodeBuffer != nullptr) + free(maClientData.aText.pUnicodeBuffer); + if (maClientData.aText.pCharStyle != nullptr) + free(maClientData.aText.pCharStyle); +} + +// convenience routine to add items to a XVaNestedList + +static XVaNestedList +XVaAddToNestedList( XVaNestedList a_srclist, char* name, XPointer value ) +{ + XVaNestedList a_dstlist; + + // if ( value == NULL ) + // return a_srclist; + + if ( a_srclist == nullptr ) + { + a_dstlist = XVaCreateNestedList( + 0, + name, value, + nullptr ); + } + else + { + a_dstlist = XVaCreateNestedList( + 0, + XNVaNestedList, a_srclist, + name, value, + nullptr ); + } + + return a_dstlist != nullptr ? a_dstlist : a_srclist ; +} + +// convenience routine to create a fontset + +static XFontSet +get_font_set( Display *p_display ) +{ + static XFontSet p_font_set = nullptr; + + if (p_font_set == nullptr) + { + char **pp_missing_list; + int n_missing_count; + char *p_default_string; + + p_font_set = XCreateFontSet(p_display, "-*", + &pp_missing_list, &n_missing_count, &p_default_string); + } + + return p_font_set; +} + +const XIMStyle g_nSupportedStatusStyle( + XIMStatusCallbacks | + XIMStatusNothing | + XIMStatusNone + ); + +// Constructor for an InputContext (IC) + +SalI18N_InputContext::SalI18N_InputContext ( SalFrame *pFrame ) : + mbUseable( True ), + maContext( nullptr ), + mnSupportedPreeditStyle( + XIMPreeditCallbacks | + XIMPreeditNothing | + XIMPreeditNone + ), + mnStatusStyle( 0 ), + mnPreeditStyle( 0 ), + mpAttributes( nullptr ), + mpStatusAttributes( nullptr ), + mpPreeditAttributes( nullptr ) +{ +#ifdef __sun + static const char* pIIIMPEnable = getenv( "SAL_DISABLE_OWN_IM_STATUS" ); + if( pIIIMPEnable && *pIIIMPEnable ) + mnSupportedStatusStyle &= ~XIMStatusCallbacks; +#endif + + memset(&maPreeditStartCallback, 0, sizeof(maPreeditStartCallback)); + memset(&maPreeditDoneCallback, 0, sizeof(maPreeditDoneCallback)); + memset(&maPreeditDrawCallback, 0, sizeof(maPreeditDrawCallback)); + memset(&maPreeditCaretCallback, 0, sizeof(maPreeditCaretCallback)); + memset(&maCommitStringCallback, 0, sizeof(maCommitStringCallback)); + memset(&maSwitchIMCallback, 0, sizeof(maSwitchIMCallback)); + memset(&maDestroyCallback, 0, sizeof(maDestroyCallback)); + + maClientData.aText.pUnicodeBuffer = nullptr; + maClientData.aText.pCharStyle = nullptr; + maClientData.aInputEv.mpTextAttr = nullptr; + maClientData.aInputEv.mnCursorPos = 0; + maClientData.aInputEv.mnCursorFlags = 0; + + SalI18N_InputMethod *pInputMethod; + pInputMethod = vcl_sal::getSalDisplay(GetGenericUnixSalData())->GetInputMethod(); + + mnSupportedPreeditStyle = XIMPreeditCallbacks | XIMPreeditPosition + | XIMPreeditNothing | XIMPreeditNone; + if (pInputMethod->UseMethod() + && SupportInputMethodStyle( pInputMethod->GetSupportedStyles() ) ) + { + const SystemEnvData* pEnv = pFrame->GetSystemData(); + ::Window aClientWindow = pEnv->aShellWindow; + ::Window aFocusWindow = pEnv->GetWindowHandle(pFrame); + + // for status callbacks and commit string callbacks +#define PREEDIT_BUFSZ 16 + maClientData.eState = PreeditStatus::StartPending; + maClientData.pFrame = pFrame; + maClientData.aText.pUnicodeBuffer = + static_cast<sal_Unicode*>(malloc(PREEDIT_BUFSZ * sizeof(sal_Unicode))); + maClientData.aText.pCharStyle = + static_cast<XIMFeedback*>(malloc(PREEDIT_BUFSZ * sizeof(XIMFeedback))); + maClientData.aText.nSize = PREEDIT_BUFSZ; + maClientData.aText.nLength = 0; + + // Status attributes + + switch ( mnStatusStyle ) + { + case XIMStatusCallbacks: + { + static XIMCallback aStatusStartCallback; + static XIMCallback aStatusDoneCallback; + static XIMCallback aStatusDrawCallback; + + aStatusStartCallback.callback = reinterpret_cast<XIMProc>(StatusStartCallback); + aStatusStartCallback.client_data = reinterpret_cast<XPointer>(&maClientData); + aStatusDoneCallback.callback = reinterpret_cast<XIMProc>(StatusDoneCallback); + aStatusDoneCallback.client_data = reinterpret_cast<XPointer>(&maClientData); + aStatusDrawCallback.callback = reinterpret_cast<XIMProc>(StatusDrawCallback); + aStatusDrawCallback.client_data = reinterpret_cast<XPointer>(&maClientData); + + mpStatusAttributes = XVaCreateNestedList ( + 0, + XNStatusStartCallback, &aStatusStartCallback, + XNStatusDoneCallback, &aStatusDoneCallback, + XNStatusDrawCallback, &aStatusDrawCallback, + nullptr ); + + break; + } + + case XIMStatusArea: + /* not supported */ + break; + + case XIMStatusNone: + case XIMStatusNothing: + default: + /* no arguments needed */ + break; + } + + // set preedit attributes + + switch ( mnPreeditStyle ) + { + case XIMPreeditCallbacks: + + maPreeditCaretCallback.callback = reinterpret_cast<XIMProc>(PreeditCaretCallback); + maPreeditStartCallback.callback = reinterpret_cast<XIMProc>(PreeditStartCallback); + maPreeditDoneCallback.callback = reinterpret_cast<XIMProc>(PreeditDoneCallback); + maPreeditDrawCallback.callback = reinterpret_cast<XIMProc>(PreeditDrawCallback); + maPreeditCaretCallback.client_data = reinterpret_cast<XPointer>(&maClientData); + maPreeditStartCallback.client_data = reinterpret_cast<XPointer>(&maClientData); + maPreeditDoneCallback.client_data = reinterpret_cast<XPointer>(&maClientData); + maPreeditDrawCallback.client_data = reinterpret_cast<XPointer>(&maClientData); + + mpPreeditAttributes = XVaCreateNestedList ( + 0, + XNPreeditStartCallback, &maPreeditStartCallback, + XNPreeditDoneCallback, &maPreeditDoneCallback, + XNPreeditDrawCallback, &maPreeditDrawCallback, + XNPreeditCaretCallback, &maPreeditCaretCallback, + nullptr ); + + break; + + case XIMPreeditArea: + /* not supported */ + break; + + case XIMPreeditPosition: + { + // spot location + SalExtTextInputPosEvent aPosEvent; + pFrame->CallCallback(SalEvent::ExtTextInputPos, static_cast<void*>(&aPosEvent)); + + static XPoint aSpot; + aSpot.x = aPosEvent.mnX + aPosEvent.mnWidth; + aSpot.y = aPosEvent.mnY + aPosEvent.mnHeight; + + // create attributes for preedit position style + mpPreeditAttributes = XVaCreateNestedList ( + 0, + XNSpotLocation, &aSpot, + nullptr ); + + // XCreateIC() fails on Redflag Linux 2.0 if there is no + // fontset though the data itself is not evaluated nor is + // it required according to the X specs. + Display* pDisplay = vcl_sal::getSalDisplay(GetGenericUnixSalData())->GetDisplay(); + XFontSet pFontSet = get_font_set(pDisplay); + + if (pFontSet != nullptr) + { + mpPreeditAttributes = XVaAddToNestedList( mpPreeditAttributes, + const_cast<char*>(XNFontSet), reinterpret_cast<XPointer>(pFontSet)); + } + + break; + } + + case XIMPreeditNone: + case XIMPreeditNothing: + default: + /* no arguments needed */ + break; + } + + // Create the InputContext by giving it exactly the information it + // deserves, because inappropriate attributes + // let XCreateIC fail on Solaris (eg. for C locale) + + mpAttributes = XVaCreateNestedList( + 0, + XNFocusWindow, aFocusWindow, + XNClientWindow, aClientWindow, + XNInputStyle, mnPreeditStyle | mnStatusStyle, + nullptr ); + + if ( mnPreeditStyle != XIMPreeditNone ) + { +#if defined LINUX || defined FREEBSD || defined NETBSD || defined OPENBSD || defined DRAGONFLY + if ( mpPreeditAttributes != nullptr ) +#endif + mpAttributes = XVaAddToNestedList( mpAttributes, + const_cast<char*>(XNPreeditAttributes), static_cast<XPointer>(mpPreeditAttributes) ); + } + if ( mnStatusStyle != XIMStatusNone ) + { +#if defined LINUX || defined FREEBSD || defined NETBSD || defined OPENBSD || defined DRAGONFLY + if ( mpStatusAttributes != nullptr ) +#endif + mpAttributes = XVaAddToNestedList( mpAttributes, + const_cast<char*>(XNStatusAttributes), static_cast<XPointer>(mpStatusAttributes) ); + } + maContext = XCreateIC( pInputMethod->GetMethod(), + XNVaNestedList, mpAttributes, + nullptr ); + } + + if ( maContext == nullptr ) + { +#if OSL_DEBUG_LEVEL > 1 + SAL_WARN("vcl.app", "input context creation failed."); +#endif + + mbUseable = False; + + if ( mpAttributes != nullptr ) + XFree( mpAttributes ); + if ( mpStatusAttributes != nullptr ) + XFree( mpStatusAttributes ); + if ( mpPreeditAttributes != nullptr ) + XFree( mpPreeditAttributes ); + if ( maClientData.aText.pUnicodeBuffer != nullptr ) + free ( maClientData.aText.pUnicodeBuffer ); + if ( maClientData.aText.pCharStyle != nullptr ) + free ( maClientData.aText.pCharStyle ); + + mpAttributes = nullptr; + mpStatusAttributes = nullptr; + mpPreeditAttributes = nullptr; + maClientData.aText.pUnicodeBuffer = nullptr; + maClientData.aText.pCharStyle = nullptr; + } + + if ( maContext != nullptr) + { + maDestroyCallback.callback = IC_IMDestroyCallback; + maDestroyCallback.client_data = reinterpret_cast<XPointer>(this); + XSetICValues( maContext, + XNDestroyCallback, &maDestroyCallback, + nullptr ); + } +} + +// In Solaris 8 the status window does not unmap if the frame unmapps, so +// unmap it the hard way + +void +SalI18N_InputContext::Unmap() +{ + UnsetICFocus(); + maClientData.pFrame = nullptr; +} + +void +SalI18N_InputContext::Map( SalFrame *pFrame ) +{ + if( !mbUseable ) + return; + + if( !pFrame ) + return; + + if ( maContext == nullptr ) + { + SalI18N_InputMethod *pInputMethod; + pInputMethod = vcl_sal::getSalDisplay(GetGenericUnixSalData())->GetInputMethod(); + + maContext = XCreateIC( pInputMethod->GetMethod(), + XNVaNestedList, mpAttributes, + nullptr ); + } + if( maClientData.pFrame != pFrame ) + SetICFocus( pFrame ); +} + +// Handle DestroyCallbacks +// in fact this is a callback called from the XNDestroyCallback + +void +SalI18N_InputContext::HandleDestroyIM() +{ + maContext = nullptr; // don't change + mbUseable = False; +} + +// make sure, the input method gets all the X-Events it needs, this is only +// called once on each frame, it relies on a valid maContext + +void +SalI18N_InputContext::ExtendEventMask( ::Window aFocusWindow ) +{ + unsigned long nIMEventMask; + XWindowAttributes aWindowAttributes; + + if ( mbUseable ) + { + Display *pDisplay = XDisplayOfIM( XIMOfIC(maContext) ); + + XGetWindowAttributes( pDisplay, aFocusWindow, + &aWindowAttributes ); + XGetICValues ( maContext, + XNFilterEvents, &nIMEventMask, + nullptr); + nIMEventMask |= aWindowAttributes.your_event_mask; + XSelectInput ( pDisplay, aFocusWindow, nIMEventMask ); + } +} + +// tune the styles provided by the input method with the supported one + +unsigned int +SalI18N_InputContext::GetWeightingOfIMStyle( XIMStyle nStyle ) +{ + struct StyleWeightingT { + const XIMStyle nStyle; + const unsigned int nWeight; + }; + + StyleWeightingT const *pWeightPtr; + static const StyleWeightingT pWeight[] = { + { XIMPreeditCallbacks, 0x10000000 }, + { XIMPreeditPosition, 0x02000000 }, + { XIMPreeditArea, 0x01000000 }, + { XIMPreeditNothing, 0x00100000 }, + { XIMPreeditNone, 0x00010000 }, + { XIMStatusCallbacks, 0x1000 }, + { XIMStatusArea, 0x0100 }, + { XIMStatusNothing, 0x0010 }, + { XIMStatusNone, 0x0001 }, + { 0, 0x0 } + }; + + int nWeight = 0; + for ( pWeightPtr = pWeight; pWeightPtr->nStyle != 0; pWeightPtr++ ) + { + if ( (pWeightPtr->nStyle & nStyle) != 0 ) + nWeight += pWeightPtr->nWeight; + } + return nWeight; +} + +bool +SalI18N_InputContext::IsSupportedIMStyle( XIMStyle nStyle ) const +{ + return (nStyle & mnSupportedPreeditStyle) + && (nStyle & g_nSupportedStatusStyle); +} + +bool +SalI18N_InputContext::SupportInputMethodStyle( XIMStyles const *pIMStyles ) +{ + mnPreeditStyle = 0; + mnStatusStyle = 0; + + if ( pIMStyles != nullptr ) + { + int nBestScore = 0; + int nActualScore = 0; + + // check whether the XIM supports one of the desired styles + // only a single preedit and a single status style must occur + // in an input method style. Hideki said so, so i trust him + for ( int nStyle = 0; nStyle < pIMStyles->count_styles; nStyle++ ) + { + XIMStyle nProvidedStyle = pIMStyles->supported_styles[ nStyle ]; + if ( IsSupportedIMStyle(nProvidedStyle) ) + { + nActualScore = GetWeightingOfIMStyle( nProvidedStyle ); + if ( nActualScore >= nBestScore ) + { + nBestScore = nActualScore; + mnPreeditStyle = nProvidedStyle & mnSupportedPreeditStyle; + mnStatusStyle = nProvidedStyle & g_nSupportedStatusStyle; + } + } + } + } + + return (mnPreeditStyle != 0) && (mnStatusStyle != 0) ; +} + +// handle extended and normal key input + +void +SalI18N_InputContext::CommitKeyEvent(sal_Unicode const * pText, std::size_t nLength) +{ + if (nLength == 1 && IsControlCode(pText[0])) + return; + + if( maClientData.pFrame ) + { + SalExtTextInputEvent aTextEvent; + + aTextEvent.mpTextAttr = nullptr; + aTextEvent.mnCursorPos = nLength; + aTextEvent.maText = OUString(pText, nLength); + aTextEvent.mnCursorFlags = 0; + + maClientData.pFrame->CallCallback(SalEvent::ExtTextInput, static_cast<void*>(&aTextEvent)); + maClientData.pFrame->CallCallback(SalEvent::EndExtTextInput, nullptr); + } +#if OSL_DEBUG_LEVEL > 1 + else + SAL_WARN("vcl.app", "CommitKeyEvent without frame."); +#endif +} + +int +SalI18N_InputContext::UpdateSpotLocation() +{ + if (maContext == nullptr || maClientData.pFrame == nullptr) + return -1; + + SalExtTextInputPosEvent aPosEvent; + maClientData.pFrame->CallCallback(SalEvent::ExtTextInputPos, static_cast<void*>(&aPosEvent)); + + XPoint aSpot; + aSpot.x = aPosEvent.mnX + aPosEvent.mnWidth; + aSpot.y = aPosEvent.mnY + aPosEvent.mnHeight; + + XVaNestedList preedit_attr = XVaCreateNestedList(0, XNSpotLocation, &aSpot, nullptr); + XSetICValues(maContext, XNPreeditAttributes, preedit_attr, nullptr); + XFree(preedit_attr); + + return 0; +} + +// set and unset the focus for the Input Context +// the context may be NULL despite it is usable if the framewindow is +// in unmapped state + +void +SalI18N_InputContext::SetICFocus( SalFrame* pFocusFrame ) +{ + if ( !(mbUseable && (maContext != nullptr)) ) + return; + + maClientData.pFrame = pFocusFrame; + + const SystemEnvData* pEnv = pFocusFrame->GetSystemData(); + ::Window aClientWindow = pEnv->aShellWindow; + ::Window aFocusWindow = pEnv->GetWindowHandle(pFocusFrame); + + XSetICValues( maContext, + XNFocusWindow, aFocusWindow, + XNClientWindow, aClientWindow, + nullptr ); + + if( maClientData.aInputEv.mpTextAttr ) + { + sendEmptyCommit(pFocusFrame); + // begin preedit again + vcl_sal::getSalDisplay(GetGenericUnixSalData())->SendInternalEvent( pFocusFrame, &maClientData.aInputEv, SalEvent::ExtTextInput ); + } + + XSetICFocus( maContext ); +} + +void +SalI18N_InputContext::UnsetICFocus() +{ + + if ( mbUseable && (maContext != nullptr) ) + { + // cancel an eventual event posted to begin preedit again + vcl_sal::getSalDisplay(GetGenericUnixSalData())->CancelInternalEvent( maClientData.pFrame, &maClientData.aInputEv, SalEvent::ExtTextInput ); + maClientData.pFrame = nullptr; + XUnsetICFocus( maContext ); + } +} + +// multi byte input method only + +void +SalI18N_InputContext::EndExtTextInput() +{ + if ( !mbUseable || (maContext == nullptr) || !maClientData.pFrame ) + return; + + vcl::DeletionListener aDel( maClientData.pFrame ); + // delete preedit in sal (commit an empty string) + sendEmptyCommit( maClientData.pFrame ); + if( ! aDel.isDeleted() ) + { + // mark previous preedit state again (will e.g. be sent at focus gain) + maClientData.aInputEv.mpTextAttr = maClientData.aInputFlags.data(); + if( static_cast<X11SalFrame*>(maClientData.pFrame)->hasFocus() ) + { + // begin preedit again + vcl_sal::getSalDisplay(GetGenericUnixSalData())->SendInternalEvent( maClientData.pFrame, &maClientData.aInputEv, SalEvent::ExtTextInput ); + } + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/generic/app/i18n_im.cxx b/vcl/unx/generic/app/i18n_im.cxx new file mode 100644 index 000000000..6a655ca39 --- /dev/null +++ b/vcl/unx/generic/app/i18n_im.cxx @@ -0,0 +1,410 @@ +/* -*- 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 <stdio.h> +#include <string.h> +#include <iostream> + +#ifdef LINUX +# ifndef __USE_XOPEN +# define __USE_XOPEN +# endif +#endif + +#include <X11/Xlib.h> + +#include <unx/i18n_im.hxx> + +#include <osl/thread.h> +#include <osl/process.h> +#include <sal/log.hxx> + +#include <unx/i18n_cb.hxx> + +using namespace vcl; + +// kinput2 IME needs special key handling since key release events are filtered in +// preeditmode and XmbResetIC does not work + +namespace { + +class XKeyEventOp : public XKeyEvent +{ + private: + void init(); + + public: + XKeyEventOp(); + + XKeyEventOp& operator= (const XKeyEvent &rEvent); + void erase (); + bool match (const XKeyEvent &rEvent) const; +}; + +} + +void +XKeyEventOp::init() +{ + type = 0; /* serial = 0; */ + send_event = 0; display = nullptr; + window = 0; root = 0; + subwindow = 0; /* time = 0; */ + /* x = 0; y = 0; */ + /* x_root = 0; y_root = 0; */ + state = 0; keycode = 0; + same_screen = 0; +} + +XKeyEventOp::XKeyEventOp() +{ + init(); +} + +XKeyEventOp& +XKeyEventOp::operator= (const XKeyEvent &rEvent) +{ + type = rEvent.type; /* serial = rEvent.serial; */ + send_event = rEvent.send_event; display = rEvent.display; + window = rEvent.window; root = rEvent.root; + subwindow = rEvent.subwindow;/* time = rEvent.time; */ + /* x = rEvent.x, y = rEvent.y; */ + /* x_root = rEvent.x_root, y_root = rEvent.y_root; */ + state = rEvent.state; keycode = rEvent.keycode; + same_screen = rEvent.same_screen; + + return *this; +} + +void +XKeyEventOp::erase () +{ + init(); +} + +bool +XKeyEventOp::match (const XKeyEvent &rEvent) const +{ + return ( (type == KeyPress && rEvent.type == KeyRelease) + || (type == KeyRelease && rEvent.type == KeyPress )) + /* && serial == rEvent.serial */ + && send_event == rEvent.send_event + && display == rEvent.display + && window == rEvent.window + && root == rEvent.root + && subwindow == rEvent.subwindow + /* && time == rEvent.time + && x == rEvent.x + && y == rEvent.y + && x_root == rEvent.x_root + && y_root == rEvent.y_root */ + && state == rEvent.state + && keycode == rEvent.keycode + && same_screen == rEvent.same_screen; +} + +// locale handling + +// Locale handling of the operating system layer + +static char* +SetSystemLocale( const char* p_inlocale ) +{ + char *p_outlocale = setlocale(LC_ALL, p_inlocale); + + SAL_WARN_IF(p_outlocale == nullptr, "vcl.app", + "I18N: Operating system doesn't support locale \"" + << p_inlocale << "\"."); + + return p_outlocale; +} + +#ifdef __sun +static void +SetSystemEnvironment( const OUString& rLocale ) +{ + OUString LC_ALL_Var("LC_ALL"); + osl_setEnvironment(LC_ALL_Var.pData, rLocale.pData); + + OUString LANG_Var("LANG"); + osl_setEnvironment(LANG_Var.pData, rLocale.pData); +} +#endif + +static Bool +IsPosixLocale( const char* p_locale ) +{ + if ( p_locale == nullptr ) + return False; + if ( (p_locale[ 0 ] == 'C') && (p_locale[ 1 ] == '\0') ) + return True; + if ( strncmp(p_locale, "POSIX", sizeof("POSIX")) == 0 ) + return True; + + return False; +} + +// Locale handling of the X Window System layer + +static Bool +IsXWindowCompatibleLocale( const char* p_locale ) +{ + if ( p_locale == nullptr ) + return False; + + if ( !XSupportsLocale() ) + { + SAL_WARN("vcl.app", + "I18N: X Window System doesn't support locale \"" + << p_locale << "\"."); + return False; + } + return True; +} + +// Set the operating system locale prior to trying to open an +// XIM InputMethod. +// Handle the cases where the current locale is either not supported by the +// operating system (LANG=gaga) or by the XWindow system (LANG=aa_ER@saaho) +// by providing a fallback. +// Upgrade "C" or "POSIX" to "en_US" locale to allow umlauts and accents +// see i8988, i9188, i8930, i16318 +// on Solaris the environment needs to be set equivalent to the locale (#i37047#) + +void +SalI18N_InputMethod::SetLocale() +{ + // check whether we want an Input Method engine, if we don't we + // do not need to set the locale + if ( !mbUseable ) + return; + + char *locale = SetSystemLocale( "" ); + if ( (!IsXWindowCompatibleLocale(locale)) || IsPosixLocale(locale) ) + { + osl_setThreadTextEncoding (RTL_TEXTENCODING_ISO_8859_1); + locale = SetSystemLocale( "en_US" ); +#ifdef __sun + SetSystemEnvironment( "en_US" ); +#endif + if (! IsXWindowCompatibleLocale(locale)) + { + locale = SetSystemLocale( "C" ); +#ifdef __sun + SetSystemEnvironment( "C" ); +#endif + if (! IsXWindowCompatibleLocale(locale)) + mbUseable = False; + } + } + + // must not fail if mbUseable since XSupportsLocale() asserts success + if ( mbUseable && XSetLocaleModifiers("") == nullptr ) + { + SAL_WARN("vcl.app", + "I18N: Can't set X modifiers for locale \"" + << locale << "\"."); + mbUseable = False; + } +} + +Bool +SalI18N_InputMethod::PosixLocale() +{ + if (maMethod) + return IsPosixLocale (XLocaleOfIM (maMethod)); + return False; +} + +// Constructor / Destructor / Initialisation + +SalI18N_InputMethod::SalI18N_InputMethod( ) + : mbUseable( bUseInputMethodDefault ) + , maMethod( nullptr ) + , mpStyles( nullptr ) +{ + maDestroyCallback.callback = nullptr; + maDestroyCallback.client_data = nullptr; + const char *pUseInputMethod = getenv( "SAL_USEINPUTMETHOD" ); + if ( pUseInputMethod != nullptr ) + mbUseable = pUseInputMethod[0] != '\0' ; +} + +SalI18N_InputMethod::~SalI18N_InputMethod() +{ + if ( mpStyles != nullptr ) + XFree( mpStyles ); + if ( maMethod != nullptr ) + XCloseIM ( maMethod ); +} + +// XXX +// debug routine: lets have a look at the provided method styles + +#if OSL_DEBUG_LEVEL > 1 + +extern "C" char* +GetMethodName( XIMStyle nStyle, char *pBuf, int nBufSize) +{ + struct StyleName { + const XIMStyle nStyle; + const char *pName; + const int nNameLen; + }; + + StyleName *pDescPtr; + static const StyleName pDescription[] = { + { XIMPreeditArea, "PreeditArea ", sizeof("PreeditArea ") }, + { XIMPreeditCallbacks, "PreeditCallbacks ",sizeof("PreeditCallbacks ")}, + { XIMPreeditPosition, "PreeditPosition ", sizeof("PreeditPosition ") }, + { XIMPreeditNothing, "PreeditNothing ", sizeof("PreeditNothing ") }, + { XIMPreeditNone, "PreeditNone ", sizeof("PreeditNone ") }, + { XIMStatusArea, "StatusArea ", sizeof("StatusArea ") }, + { XIMStatusCallbacks, "StatusCallbacks ", sizeof("StatusCallbacks ") }, + { XIMStatusNothing, "StatusNothing ", sizeof("StatusNothing ") }, + { XIMStatusNone, "StatusNone ", sizeof("StatusNone ") }, + { 0, "NULL", 0 } + }; + + if ( nBufSize > 0 ) + pBuf[0] = '\0'; + + char *pBufPtr = pBuf; + for ( pDescPtr = const_cast<StyleName*>(pDescription); pDescPtr->nStyle != 0; pDescPtr++ ) + { + int nSize = pDescPtr->nNameLen - 1; + if ( (nStyle & pDescPtr->nStyle) && (nBufSize > nSize) ) + { + strncpy( pBufPtr, pDescPtr->pName, nSize + 1); + pBufPtr += nSize; + nBufSize -= nSize; + } + } + + return pBuf; +} + +extern "C" void +PrintInputStyle( XIMStyles *pStyle ) +{ + char pBuf[ 128 ]; + int nBuf = sizeof( pBuf ); + + if ( pStyle == NULL ) + SAL_INFO("vcl.app", "no input method styles."); + else + for ( int nStyle = 0; nStyle < pStyle->count_styles; nStyle++ ) + { + SAL_INFO("vcl.app", "style #" + << nStyle + << " = " + << GetMethodName(pStyle->supported_styles[nStyle], pBuf, nBuf)); + } +} + +#endif + +// this is the real constructing routine, since locale setting has to be done +// prior to xopendisplay, the xopenim call has to be delayed + +void +SalI18N_InputMethod::CreateMethod ( Display *pDisplay ) +{ + if ( mbUseable ) + { + maMethod = XOpenIM(pDisplay, nullptr, nullptr, nullptr); + + if ((maMethod == nullptr) && (getenv("XMODIFIERS") != nullptr)) + { + OUString envVar("XMODIFIERS"); + osl_clearEnvironment(envVar.pData); + XSetLocaleModifiers(""); + maMethod = XOpenIM(pDisplay, nullptr, nullptr, nullptr); + } + + if ( maMethod != nullptr ) + { + if ( XGetIMValues(maMethod, XNQueryInputStyle, &mpStyles, nullptr) + != nullptr) + mbUseable = False; +#if OSL_DEBUG_LEVEL > 1 + SAL_INFO("vcl.app", "Creating Mono-Lingual InputMethod."); + PrintInputStyle( mpStyles ); +#endif + } + else + { + mbUseable = False; + } + } + +#if OSL_DEBUG_LEVEL > 1 + SAL_WARN_IF(!mbUseable, "vcl.app", "input method creation failed."); +#endif + + maDestroyCallback.callback = IM_IMDestroyCallback; + maDestroyCallback.client_data = reinterpret_cast<XPointer>(this); + if (mbUseable && maMethod != nullptr) + XSetIMValues(maMethod, XNDestroyCallback, &maDestroyCallback, nullptr); +} + +// give IM the opportunity to look at the event, and possibly hide it + +bool +SalI18N_InputMethod::FilterEvent( XEvent *pEvent, ::Window window ) +{ + if (!mbUseable) + return False; + + bool bFilterEvent = XFilterEvent (pEvent, window); + + if (pEvent->type != KeyPress && pEvent->type != KeyRelease) + return bFilterEvent; + + /* + * fix broken key release handling of some IMs + */ + XKeyEvent* pKeyEvent = &(pEvent->xkey); + static XKeyEventOp s_aLastKeyPress; + + if (bFilterEvent) + { + if (pKeyEvent->type == KeyRelease) + bFilterEvent = !s_aLastKeyPress.match (*pKeyEvent); + s_aLastKeyPress.erase(); + } + else /* (!bFilterEvent) */ + { + if (pKeyEvent->type == KeyPress) + s_aLastKeyPress = *pKeyEvent; + else + s_aLastKeyPress.erase(); + } + + return bFilterEvent; +} + +void +SalI18N_InputMethod::HandleDestroyIM() +{ + mbUseable = False; + maMethod = nullptr; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/generic/app/i18n_keysym.cxx b/vcl/unx/generic/app/i18n_keysym.cxx new file mode 100644 index 000000000..a77632a3e --- /dev/null +++ b/vcl/unx/generic/app/i18n_keysym.cxx @@ -0,0 +1,358 @@ +/* -*- 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 <X11/X.h> +#include <sal/types.h> + +#include <unx/i18n_keysym.hxx> + +// convert keysyms to unicode +// for all keysyms with byte1 and byte2 equal zero, and of course only for +// keysyms that have a unicode counterpart + +namespace { + +struct keymap_t { + const int first; const int last; + const sal_Unicode *map; +}; + +} + +// Latin-1 Byte 3 = 0x00 +const sal_Unicode keymap00_map[] = { + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x00a0, 0x00a1, 0x00a2, 0x00a3, 0x00a4, 0x00a5, 0x00a6, 0x00a7, + 0x00a8, 0x00a9, 0x00aa, 0x00ab, 0x00ac, 0x00ad, 0x00ae, 0x00af, + 0x00b0, 0x00b1, 0x00b2, 0x00b3, 0x00b4, 0x00b5, 0x00b6, 0x00b7, + 0x00b8, 0x00b9, 0x00ba, 0x00bb, 0x00bc, 0x00bd, 0x00be, 0x00bf, + 0x00c0, 0x00c1, 0x00c2, 0x00c3, 0x00c4, 0x00c5, 0x00c6, 0x00c7, + 0x00c8, 0x00c9, 0x00ca, 0x00cb, 0x00cc, 0x00cd, 0x00ce, 0x00cf, + 0x00d0, 0x00d1, 0x00d2, 0x00d3, 0x00d4, 0x00d5, 0x00d6, 0x00d7, + 0x00d8, 0x00d9, 0x00da, 0x00db, 0x00dc, 0x00dd, 0x00de, 0x00df, + 0x00e0, 0x00e1, 0x00e2, 0x00e3, 0x00e4, 0x00e5, 0x00e6, 0x00e7, + 0x00e8, 0x00e9, 0x00ea, 0x00eb, 0x00ec, 0x00ed, 0x00ee, 0x00ef, + 0x00f0, 0x00f1, 0x00f2, 0x00f3, 0x00f4, 0x00f5, 0x00f6, 0x00f7, + 0x00f8, 0x00f9, 0x00fa, 0x00fb, 0x00fc, 0x00fd, 0x00fe, 0x00ff }; +const keymap_t keymap00 = { 32, 255, keymap00_map }; + +// Latin-2 Byte 3 = 0x01 +const sal_Unicode keymap01_map[] = { + 0x0104, 0x02d8, 0x0141, 0x0000, 0x013d, 0x015a, 0x0000, 0x0000, + 0x0160, 0x015e, 0x0164, 0x0179, 0x0000, 0x017d, 0x017b, 0x0000, + 0x0105, 0x02db, 0x0142, 0x0000, 0x013e, 0x015b, 0x02c7, 0x0000, + 0x0161, 0x015f, 0x0165, 0x017a, 0x02dd, 0x017e, 0x017c, 0x0154, + 0x0000, 0x0000, 0x0102, 0x0000, 0x0139, 0x0106, 0x0000, 0x010c, + 0x0000, 0x0118, 0x0000, 0x011a, 0x0000, 0x0000, 0x010e, 0x0110, + 0x0143, 0x0147, 0x0000, 0x0000, 0x0150, 0x0000, 0x0000, 0x0158, + 0x016e, 0x0000, 0x0170, 0x0000, 0x0000, 0x0162, 0x0000, 0x0155, + 0x0000, 0x0000, 0x0103, 0x0000, 0x013a, 0x0107, 0x0000, 0x010d, + 0x0000, 0x0119, 0x0000, 0x011b, 0x0000, 0x0000, 0x010f, 0x0111, + 0x0144, 0x0148, 0x0000, 0x0000, 0x0151, 0x0000, 0x0000, 0x0159, + 0x016f, 0x0000, 0x0171, 0x0000, 0x0000, 0x0163, 0x02d9 }; +const keymap_t keymap01 = { 161, 255, keymap01_map }; + +// Latin-3 Byte 3 = 0x02 +const sal_Unicode keymap02_map[] = { + 0x0126, 0x0000, 0x0000, 0x0000, 0x0000, 0x0124, 0x0000, 0x0000, + 0x0130, 0x0000, 0x011e, 0x0134, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0127, 0x0000, 0x0000, 0x0000, 0x0000, 0x0125, 0x0000, 0x0000, + 0x0131, 0x0000, 0x011f, 0x0135, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x010a, 0x0108, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0120, 0x0000, 0x0000, 0x011c, + 0x0000, 0x0000, 0x0000, 0x0000, 0x016c, 0x015c, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x010b, 0x0109, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0121, 0x0000, 0x0000, 0x011d, + 0x0000, 0x0000, 0x0000, 0x0000, 0x016d, 0x015d }; +const keymap_t keymap02 = { 161, 254, keymap02_map }; + +// Latin-4 Byte 3 = 0x03 +const sal_Unicode keymap03_map[] = { + 0x0138, 0x0156, 0x0000, 0x0128, 0x013b, 0x0000, 0x0000, 0x0000, + 0x0112, 0x0122, 0x0166, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0157, 0x0000, 0x0129, 0x013c, 0x0000, 0x0000, 0x0000, + 0x0113, 0x0123, 0x0167, 0x014a, 0x0000, 0x014b, 0x0100, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x012e, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0116, 0x0000, 0x0000, 0x012a, 0x0000, 0x0145, + 0x014c, 0x0136, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0172, + 0x0000, 0x0000, 0x0000, 0x0168, 0x016a, 0x0000, 0x0101, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x012f, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0117, 0x0000, 0x0000, 0x012b, 0x0000, 0x0146, + 0x014d, 0x0137, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0173, + 0x0000, 0x0000, 0x0000, 0x0169, 0x016b }; +const keymap_t keymap03 = { 162, 254, keymap03_map }; + +// Kana Byte 3 = 0x04 +const sal_Unicode keymap04_map[] = { + 0x203e, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x3002, 0x300c, 0x300d, 0x3001, 0x30fb, + 0x30f2, 0x30a1, 0x30a3, 0x30a5, 0x30a7, 0x30a9, 0x30e3, 0x30e5, + 0x30e7, 0x30c3, 0x30fc, 0x30a2, 0x30a4, 0x30a6, 0x30a8, 0x30aa, + 0x30ab, 0x30ad, 0x30af, 0x30b1, 0x30b3, 0x30b5, 0x30b7, 0x30b9, + 0x30bb, 0x30bd, 0x30bf, 0x30c1, 0x30c4, 0x30c6, 0x30c8, 0x30ca, + 0x30cb, 0x30cc, 0x30cd, 0x30ce, 0x30cf, 0x30d2, 0x30d5, 0x30d8, + 0x30db, 0x30de, 0x30df, 0x30e0, 0x30e1, 0x30e2, 0x30e4, 0x30e6, + 0x30e8, 0x30e9, 0x30ea, 0x30eb, 0x30ec, 0x30ed, 0x30ef, 0x30f3, + 0x309b, 0x309c }; +const keymap_t keymap04 = { 126, 223, keymap04_map }; + +// Arabic Byte 3 = 0x05 +const sal_Unicode keymap05_map[] = { + 0x060c, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x061b, + 0x0000, 0x0000, 0x0000, 0x061f, 0x0000, 0x0621, 0x0622, 0x0623, + 0x0624, 0x0625, 0x0626, 0x0627, 0x0628, 0x0629, 0x062a, 0x062b, + 0x062c, 0x062d, 0x062e, 0x062f, 0x0630, 0x0631, 0x0632, 0x0633, + 0x0634, 0x0635, 0x0636, 0x0637, 0x0638, 0x0639, 0x063a, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0640, 0x0641, 0x0642, 0x0643, + 0x0644, 0x0645, 0x0646, 0x0647, 0x0648, 0x0649, 0x064a, 0x064b, + 0x064c, 0x064d, 0x064e, 0x064f, 0x0650, 0x0651, 0x0652 }; +const keymap_t keymap05 = { 172, 242, keymap05_map }; + +// Cyrillic Byte 3 = 0x06 +const sal_Unicode keymap06_map[] = { + 0x0452, 0x0453, 0x0451, 0x0454, 0x0455, 0x0456, 0x0457, 0x0458, + 0x0459, 0x045a, 0x045b, 0x045c, 0x0000, 0x045e, 0x045f, 0x2116, + 0x0402, 0x0403, 0x0401, 0x0404, 0x0405, 0x0406, 0x0407, 0x0408, + 0x0409, 0x040a, 0x040b, 0x040c, 0x0000, 0x040e, 0x040f, 0x044e, + 0x0430, 0x0431, 0x0446, 0x0434, 0x0435, 0x0444, 0x0433, 0x0445, + 0x0438, 0x0439, 0x043a, 0x043b, 0x043c, 0x043d, 0x043e, 0x043f, + 0x044f, 0x0440, 0x0441, 0x0442, 0x0443, 0x0436, 0x0432, 0x044c, + 0x044b, 0x0437, 0x0448, 0x044d, 0x0449, 0x0447, 0x044a, 0x042e, + 0x0410, 0x0411, 0x0426, 0x0414, 0x0415, 0x0424, 0x0413, 0x0425, + 0x0418, 0x0419, 0x041a, 0x041b, 0x041c, 0x041d, 0x041e, 0x041f, + 0x042f, 0x0420, 0x0421, 0x0422, 0x0423, 0x0416, 0x0412, 0x042c, + 0x042b, 0x0417, 0x0428, 0x042d, 0x0429, 0x0427, 0x042a }; +const keymap_t keymap06 = { 161, 255, keymap06_map }; + +// Greek Byte 3 = 0x07 +const sal_Unicode keymap07_map[] = { + 0x0386, 0x0388, 0x0389, 0x038a, 0x03aa, 0x0000, 0x038c, 0x038e, + 0x03ab, 0x0000, 0x038f, 0x0000, 0x0000, 0x0385, 0x2015, 0x0000, + 0x03ac, 0x03ad, 0x03ae, 0x03af, 0x03ca, 0x0390, 0x03cc, 0x03cd, + 0x03cb, 0x03b0, 0x03ce, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0391, 0x0392, 0x0393, 0x0394, 0x0395, 0x0396, 0x0397, 0x0398, + 0x0399, 0x039a, 0x039b, 0x039c, 0x039d, 0x039e, 0x039f, 0x03a0, + 0x03a1, 0x03a3, 0x0000, 0x03a4, 0x03a5, 0x03a6, 0x03a7, 0x03a8, + 0x03a9, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x03b1, 0x03b2, 0x03b3, 0x03b4, 0x03b5, 0x03b6, 0x03b7, 0x03b8, + 0x03b9, 0x03ba, 0x03bb, 0x03bc, 0x03bd, 0x03be, 0x03bf, 0x03c0, + 0x03c1, 0x03c3, 0x03c2, 0x03c4, 0x03c5, 0x03c6, 0x03c7, 0x03c8, + 0x03c9 }; +const keymap_t keymap07 = { 161, 249, keymap07_map }; + +// Technical Byte 3 = 0x08 +const sal_Unicode keymap08_map[] = { + 0x23b7, 0x250c, 0x2500, 0x2320, 0x2321, 0x2502, 0x23a1, 0x23a3, + 0x23a4, 0x23a6, 0x239b, 0x239d, 0x239e, 0x23a0, 0x23a8, 0x23ac, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x2264, 0x2260, 0x2265, 0x222b, 0x2234, + 0x221d, 0x221e, 0x0000, 0x0000, 0x2207, 0x0000, 0x0000, 0x223c, + 0x2243, 0x0000, 0x0000, 0x0000, 0x21d4, 0x21d2, 0x2261, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x221a, 0x0000, 0x0000, + 0x0000, 0x2282, 0x2283, 0x2229, 0x222a, 0x2227, 0x2228, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x2202, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0192, 0x0000, 0x0000, + 0x0000, 0x0000, 0x2190, 0x2191, 0x2192, 0x2193 }; +const keymap_t keymap08 = { 161, 254, keymap08_map }; + +// Special Byte 3 = 0x09 +const sal_Unicode keymap09_map[] = { + 0x25c6, 0x2592, 0x2409, 0x240c, 0x240d, 0x240a, 0x0000, 0x0000, + 0x2424, 0x240b, 0x2518, 0x2510, 0x250c, 0x2514, 0x253c, 0x23ba, + 0x23bb, 0x2500, 0x23bc, 0x23bd, 0x251c, 0x2524, 0x2534, 0x252c, + 0x2502 }; +const keymap_t keymap09 = { 224, 248, keymap09_map }; + +// Publishing Byte 3 = 0x0a = 10 +const sal_Unicode keymap10_map[] = { + 0x2003, 0x2002, 0x2004, 0x2005, 0x2007, 0x2008, 0x2009, 0x200a, + 0x2014, 0x2013, 0x0000, 0x0000, 0x0000, 0x2026, 0x2025, 0x2153, + 0x2154, 0x2155, 0x2156, 0x2157, 0x2158, 0x2159, 0x215a, 0x2105, + 0x0000, 0x0000, 0x2012, 0x2329, 0x0000, 0x232a, 0x0000, 0x0000, + 0x0000, 0x0000, 0x215b, 0x215c, 0x215d, 0x215e, 0x0000, 0x0000, + 0x2122, 0x2613, 0x0000, 0x25c1, 0x25b7, 0x25cb, 0x25af, 0x2018, + 0x2019, 0x201c, 0x201d, 0x211e, 0x0000, 0x2032, 0x2033, 0x0000, + 0x271d, 0x0000, 0x25ac, 0x25c0, 0x25b6, 0x25cf, 0x25ae, 0x25e6, + 0x25ab, 0x25ad, 0x25b3, 0x25bd, 0x2606, 0x2022, 0x25aa, 0x25b2, + 0x25bc, 0x261c, 0x261e, 0x2663, 0x2666, 0x2665, 0x0000, 0x2720, + 0x2020, 0x2021, 0x2713, 0x2717, 0x266f, 0x266d, 0x2642, 0x2640, + 0x260e, 0x2315, 0x2117, 0x2038, 0x201a, 0x201e }; +const keymap_t keymap10 = { 161, 254, keymap10_map }; + +// APL Byte 3 = 0x0b = 11 +const sal_Unicode keymap11_map[] = { + 0x003c, 0x0000, 0x0000, 0x003e, 0x0000, 0x2228, 0x2227, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x00af, 0x0000, 0x22a5, + 0x2229, 0x230a, 0x0000, 0x005f, 0x0000, 0x0000, 0x0000, 0x2218, + 0x0000, 0x2395, 0x0000, 0x22a4, 0x25cb, 0x0000, 0x0000, 0x0000, + 0x2308, 0x0000, 0x0000, 0x222a, 0x0000, 0x2283, 0x0000, 0x2282, + 0x0000, 0x22a2, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x22a3 }; +const keymap_t keymap11 = { 163, 252, keymap11_map }; + +// Hebrew Byte 3 = 0x0c = 12 +const sal_Unicode keymap12_map[] = { + 0x2017, 0x05d0, 0x05d1, 0x05d2, 0x05d3, 0x05d4, 0x05d5, 0x05d6, + 0x05d7, 0x05d8, 0x05d9, 0x05da, 0x05db, 0x05dc, 0x05dd, 0x05de, + 0x05df, 0x05e0, 0x05e1, 0x05e2, 0x05e3, 0x05e4, 0x05e5, 0x05e6, + 0x05e7, 0x05e8, 0x05e9, 0x05ea }; +const keymap_t keymap12 = { 223, 250, keymap12_map }; + +// Thai Byte 3 = 0x0d = 13 +const sal_Unicode keymap13_map[] = { + 0x0e01, 0x0e02, 0x0e03, 0x0e04, 0x0e05, 0x0e06, 0x0e07, 0x0e08, + 0x0e09, 0x0e0a, 0x0e0b, 0x0e0c, 0x0e0d, 0x0e0e, 0x0e0f, 0x0e10, + 0x0e11, 0x0e12, 0x0e13, 0x0e14, 0x0e15, 0x0e16, 0x0e17, 0x0e18, + 0x0e19, 0x0e1a, 0x0e1b, 0x0e1c, 0x0e1d, 0x0e1e, 0x0e1f, 0x0e20, + 0x0e21, 0x0e22, 0x0e23, 0x0e24, 0x0e25, 0x0e26, 0x0e27, 0x0e28, + 0x0e29, 0x0e2a, 0x0e2b, 0x0e2c, 0x0e2d, 0x0e2e, 0x0e2f, 0x0e30, + 0x0e31, 0x0e32, 0x0e33, 0x0e34, 0x0e35, 0x0e36, 0x0e37, 0x0e38, + 0x0e39, 0x0e3a, 0x0000, 0x0000, 0x0000, 0x0000, 0x0e3f, 0x0e40, + 0x0e41, 0x0e42, 0x0e43, 0x0e44, 0x0e45, 0x0e46, 0x0e47, 0x0e48, + 0x0e49, 0x0e4a, 0x0e4b, 0x0e4c, 0x0e4d, 0x0000, 0x0000, 0x0e50, + 0x0e51, 0x0e52, 0x0e53, 0x0e54, 0x0e55, 0x0e56, 0x0e57, 0x0e58, + 0x0e59 }; +const keymap_t keymap13 = { 161, 249, keymap13_map }; + +// Korean Byte 3 = 0x0e = 14 +const sal_Unicode keymap14_map[] = { + 0x3131, 0x3132, 0x3133, 0x3134, 0x3135, 0x3136, 0x3137, 0x3138, + 0x3139, 0x313a, 0x313b, 0x313c, 0x313d, 0x313e, 0x313f, 0x3140, + 0x3141, 0x3142, 0x3143, 0x3144, 0x3145, 0x3146, 0x3147, 0x3148, + 0x3149, 0x314a, 0x314b, 0x314c, 0x314d, 0x314e, 0x314f, 0x3150, + 0x3151, 0x3152, 0x3153, 0x3154, 0x3155, 0x3156, 0x3157, 0x3158, + 0x3159, 0x315a, 0x315b, 0x315c, 0x315d, 0x315e, 0x315f, 0x3160, + 0x3161, 0x3162, 0x3163, 0x11a8, 0x11a9, 0x11aa, 0x11ab, 0x11ac, + 0x11ad, 0x11ae, 0x11af, 0x11b0, 0x11b1, 0x11b2, 0x11b3, 0x11b4, + 0x11b5, 0x11b6, 0x11b7, 0x11b8, 0x11b9, 0x11ba, 0x11bb, 0x11bc, + 0x11bd, 0x11be, 0x11bf, 0x11c0, 0x11c1, 0x11c2, 0x316d, 0x3171, + 0x3178, 0x317f, 0x3181, 0x3184, 0x3186, 0x318d, 0x318e, 0x11eb, + 0x11f0, 0x11f9, 0x0000, 0x0000, 0x0000, 0x0000, 0x20a9 }; +const keymap_t keymap14 = { 161, 255, keymap14_map }; + +// missing: +// Latin-8 Byte 3 = 0x12 = 18 + +// Latin-9 Byte 3 = 0x13 = 19 +const sal_Unicode keymap19_map[] = { + 0x0152, 0x0153, 0x0178 }; +const keymap_t keymap19 = { 188, 190, keymap19_map }; + +// missing: +// Armenian Byte 3 = 0x14 = 20 +// Georgian Byte 3 = 0x15 = 21 +// Azeri Byte 3 = 0x16 = 22 +// Vietnamese Byte 3 = 0x1e = 30 + +// Currency Byte 3 = 0x20 = 32 +const sal_Unicode keymap32_map[] = { + 0x20a0, 0x20a1, 0x20a2, 0x20a3, 0x20a4, 0x20a5, 0x20a6, 0x20a7, + 0x20a8, 0x0000, 0x20aa, 0x20ab, 0x20ac }; +const keymap_t keymap32 = { 160, 172, keymap32_map }; + +// Keyboard (Keypad mappings) Byte 3 = 0xff = 255 +const sal_Unicode keymap255_map[] = { + 0x0020, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x0000, 0x0000, 0x0000, 0x003d }; +const keymap_t keymap255 = { 128, 189, keymap255_map }; + +#define INITIAL_KEYMAPS 33 +const keymap_t* const p_keymap[INITIAL_KEYMAPS] = { + &keymap00, &keymap01, &keymap02, &keymap03, /* 00 -- 03 */ + &keymap04, &keymap05, &keymap06, &keymap07, /* 04 -- 07 */ + &keymap08, &keymap09, &keymap10, &keymap11, /* 08 -- 11 */ + &keymap12, &keymap13, &keymap14, nullptr, /* 12 -- 15 */ + nullptr, nullptr, nullptr, &keymap19, /* 16 -- 19 */ + nullptr, nullptr, nullptr, nullptr, /* 20 -- 23 */ + nullptr, nullptr, nullptr, nullptr, /* 24 -- 27 */ + nullptr, nullptr, nullptr, nullptr, /* 28 -- 31 */ + &keymap32 /* 32 */ +}; + +sal_Unicode +KeysymToUnicode (KeySym nKeySym) +{ + // keysym is already unicode + if ((nKeySym & 0xff000000) == 0x01000000) + { + // strip off group indicator and iso10646 plane + // FIXME can't handle chars from surrogate area. + if (! (nKeySym & 0x00ff0000) ) + return static_cast<sal_Unicode>(nKeySym & 0x0000ffff); + } + // legacy keysyms, switch to appropriate codeset + else + { + unsigned char n_byte1 = (nKeySym & 0xff000000) >> 24; + unsigned char n_byte2 = (nKeySym & 0x00ff0000) >> 16; + unsigned char n_byte3 = (nKeySym & 0x0000ff00) >> 8; + unsigned char n_byte4 = (nKeySym & 0x000000ff); + + if (n_byte1 != 0) + return 0; + if (n_byte2 != 0) + return 0; + + keymap_t const* p_map = nullptr; + if (n_byte3 < INITIAL_KEYMAPS) + p_map = p_keymap[n_byte3]; + else if (n_byte3 == 255) + p_map = &keymap255; + + if ((p_map != nullptr) && (n_byte4 >= p_map->first) && (n_byte4 <= p_map->last) ) + return p_map->map[n_byte4 - p_map->first]; + } + + return 0; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/generic/app/i18n_xkb.cxx b/vcl/unx/generic/app/i18n_xkb.cxx new file mode 100644 index 000000000..0fc4d7933 --- /dev/null +++ b/vcl/unx/generic/app/i18n_xkb.cxx @@ -0,0 +1,107 @@ +/* -*- 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 <stdlib.h> +#include <stdio.h> +#include <iostream> + +#include <sal/log.hxx> + +#include <X11/Xlib.h> +#include <X11/XKBlib.h> + +#include <unx/i18n_xkb.hxx> + +SalI18N_KeyboardExtension::SalI18N_KeyboardExtension( Display* pDisplay ) + : mbUseExtension(true) + , mnEventBase(0) +{ + // allow user to set the default keyboard group idx or to disable the usage + // of x keyboard extension at all: + // setenv SAL_XKEYBOARDGROUP disables keyboard extension + // setenv SAL_XKEYBOARDGROUP 2 sets the keyboard group index to 2 + // keyboard group index must be in [1,4], may be specified in hex or decimal + static char *pUseKeyboardExtension = getenv( "SAL_XKEYBOARDGROUP" ); + if ( pUseKeyboardExtension != nullptr ) + { + mbUseExtension = pUseKeyboardExtension[0] != '\0' ; + } + + // query XServer support for XKB Extension, + // do not call XQueryExtension() / XInitExtension() due to possible version + // clashes ! + if ( mbUseExtension ) + { + int nMajorExtOpcode; + int nExtMajorVersion = XkbMajorVersion; + int nExtMinorVersion = XkbMinorVersion; + int nErrorBase = 0; + + mbUseExtension = XkbQueryExtension( pDisplay, + &nMajorExtOpcode, &mnEventBase, &nErrorBase, + &nExtMajorVersion, &nExtMinorVersion ) != 0; + } + + // query notification for changes of the keyboard group + if ( mbUseExtension ) + { + constexpr auto XkbGroupMask = XkbGroupStateMask | XkbGroupBaseMask + | XkbGroupLatchMask | XkbGroupLockMask; + + mbUseExtension = XkbSelectEventDetails( pDisplay, + XkbUseCoreKbd, XkbStateNotify, XkbGroupMask, XkbGroupMask ); + } + + // query initial keyboard group + if ( mbUseExtension ) + { + XkbStateRec aStateRecord; + XkbGetState( pDisplay, XkbUseCoreKbd, &aStateRecord ); + } +} + +void +SalI18N_KeyboardExtension::Dispatch( XEvent* pEvent ) +{ + // must the event be handled? + if ( !mbUseExtension + || (pEvent->type != mnEventBase) ) + return; + + // only handle state notify events for now, and only interested + // in group details + sal_uInt32 nXKBType = reinterpret_cast<XkbAnyEvent*>(pEvent)->xkb_type; + switch ( nXKBType ) + { + case XkbStateNotify: + break; + + default: +#if OSL_DEBUG_LEVEL > 1 + SAL_WARN("vcl.app", "Got unrequested XkbAnyEvent " + << std::hex << std::showbase + << static_cast<unsigned int>(nXKBType) + << "/" << std::dec + << static_cast<int>(nXKBType)); +#endif + break; + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/generic/app/keysymnames.cxx b/vcl/unx/generic/app/keysymnames.cxx new file mode 100644 index 000000000..16ffaa4b9 --- /dev/null +++ b/vcl/unx/generic/app/keysymnames.cxx @@ -0,0 +1,509 @@ +/* -*- 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 <o3tl/string_view.hxx> +#include <unx/saldisp.hxx> +#include <X11/keysym.h> +#include <sal/macros.h> + +#if !defined (SunXK_Undo) +#define SunXK_Undo 0x0000FF65 // XK_Undo +#define SunXK_Again 0x0000FF66 // XK_Redo +#define SunXK_Find 0x0000FF68 // XK_Find +#define SunXK_Stop 0x0000FF69 // XK_Cancel +#define SunXK_Props 0x1005FF70 +#define SunXK_Front 0x1005FF71 +#define SunXK_Copy 0x1005FF72 +#define SunXK_Open 0x1005FF73 +#define SunXK_Paste 0x1005FF74 +#define SunXK_Cut 0x1005FF75 +#endif + +#include <string.h> +#include <rtl/ustring.hxx> + +namespace vcl_sal { + + namespace { + + struct KeysymNameReplacement + { + KeySym aSymbol; + const char* pName; + }; + + struct KeyboardReplacements + { + const char* pLangName; + const KeysymNameReplacement* pReplacements; + int nReplacements; + }; + + } + + // CAUTION CAUTION CAUTION + // every string value in the replacements tables must be in UTF8 + // be careful with your editor ! + + const struct KeysymNameReplacement aImplReplacements_English[] = + { + { XK_Control_L, "Ctrl" }, + { XK_Control_R, "Ctrl" }, + { XK_Escape, "Esc" }, + { XK_space, "Space" }, + { XK_Page_Up, "PgUp"}, + { XK_Page_Down, "PgDn"}, + { XK_grave, "`"} + }; + + const struct KeysymNameReplacement aImplReplacements_Turkish[] = + { + { XK_Control_L, "Ctrl" }, + { XK_Control_R, "Ctrl" }, + { XK_Right, "Sa\304\237" }, + { XK_Left, "Sol" }, + { XK_Up, "Yukar\304\261" }, + { XK_Down, "A\305\237a\304\237\304\261" }, + { XK_space, "Bo\305\237luk" } + }; + + const struct KeysymNameReplacement aImplReplacements_Russian[] = + { + { XK_Right, "\320\222\320\277\321\200\320\260\320\262\320\276" }, + { XK_Left, "\320\222\320\273\320\265\320\262\320\276" }, + { XK_Up, "\320\222\320\262\320\265\321\200\321\205" }, + { XK_Down, "\320\222\320\275\320\270\320\267" }, + { XK_space, "\320\237\321\200\320\276\320\261\320\265\320\273" } + }; + + const struct KeysymNameReplacement aImplReplacements_German[] = + { + { XK_Control_L, "Strg" }, + { XK_Control_R, "Strg" }, + { XK_Shift_L, "Umschalt" }, + { XK_Shift_R, "Umschalt" }, + { XK_Alt_L, "Alt" }, + { XK_Alt_R, "Alt Gr" }, + { XK_Page_Up, "Bild auf" }, + { XK_Page_Down, "Bild ab" }, + { XK_End, "Ende" }, + { XK_Home, "Pos 1" }, + { XK_Insert, "Einfg" }, + { XK_Delete, "Entf" }, + { XK_Escape, "Esc" }, + { XK_Right, "Rechts" }, + { XK_Left, "Links" }, + { XK_Up, "Oben" }, + { XK_Down, "Unten" }, + { XK_BackSpace, "R\303\274ckschritt" }, + { XK_Return, "Eingabe" }, + { XK_slash, "Schr\303\244gstrich" }, + { XK_space, "Leertaste" }, + { SunXK_Stop, "Stop" }, + { SunXK_Again, "Wiederholen" }, + { SunXK_Props, "Eigenschaften" }, + { SunXK_Undo, "Zur\303\274cknehmen" }, + { SunXK_Front, "Vordergrund" }, + { SunXK_Copy, "Kopieren" }, + { SunXK_Open, "\303\226ffnen" }, + { SunXK_Paste, "Einsetzen" }, + { SunXK_Find, "Suchen" }, + { SunXK_Cut, "Ausschneiden" }, + }; + + const struct KeysymNameReplacement aImplReplacements_French[] = + { + { XK_Shift_L, "Maj" }, + { XK_Shift_R, "Maj" }, + { XK_Page_Up, "Pg. Pr\303\251c" }, + { XK_Page_Down, "Pg. Suiv" }, + { XK_End, "Fin" }, + { XK_Home, "Origine" }, + { XK_Insert, "Ins\303\251rer" }, + { XK_Delete, "Suppr" }, + { XK_Escape, "Esc" }, + { XK_Right, "Droite" }, + { XK_Left, "Gauche" }, + { XK_Up, "Haut" }, + { XK_Down, "Bas" }, + { XK_BackSpace, "Ret. Arr" }, + { XK_Return, "Retour" }, + { XK_space, "Espace" }, + { XK_KP_Enter, "Entr\303\251e" }, + { SunXK_Stop, "Stop" }, + { SunXK_Again, "Encore" }, + { SunXK_Props, "Props" }, + { SunXK_Undo, "Annuler" }, + { SunXK_Front, "Devant" }, + { SunXK_Copy, "Copy" }, + { SunXK_Open, "Ouvrir" }, + { SunXK_Paste, "Coller" }, + { SunXK_Find, "Cher." }, + { SunXK_Cut, "Couper" }, + }; + + const struct KeysymNameReplacement aImplReplacements_Italian[] = + { + { XK_Shift_L, "Maiusc" }, + { XK_Shift_R, "Maiusc" }, + { XK_Page_Up, "PgSu" }, + { XK_Page_Down, "PgGiu" }, + { XK_End, "Fine" }, + { XK_Insert, "Ins" }, + { XK_Delete, "Canc" }, + { XK_Escape, "Esc" }, + { XK_Right, "A destra" }, + { XK_Left, "A sinistra" }, + { XK_Up, "Sposta verso l'alto" }, + { XK_Down, "Sposta verso il basso" }, + { XK_BackSpace, "Backspace" }, + { XK_Return, "Invio" }, + { XK_space, "Spazio" }, + { SunXK_Stop, "Stop" }, + { SunXK_Again, "Ancora" }, + { SunXK_Props, "Propriet\303\240" }, + { SunXK_Undo, "Annulla" }, + { SunXK_Front, "Davanti" }, + { SunXK_Copy, "Copia" }, + { SunXK_Open, "Apri" }, + { SunXK_Paste, "Incolla" }, + { SunXK_Find, "Trova" }, + { SunXK_Cut, "Taglia" }, + }; + + const struct KeysymNameReplacement aImplReplacements_Dutch[] = + { + { XK_Page_Up, "PageUp" }, + { XK_Page_Down, "PageDown" }, + { XK_Escape, "Esc" }, + { XK_Right, "Rechts" }, + { XK_Left, "Links" }, + { XK_Up, "Boven" }, + { XK_Down, "Onder" }, + { XK_BackSpace, "Backspace" }, + { XK_Return, "Return" }, + { XK_space, "Spatiebalk" }, + { SunXK_Stop, "Stop" }, + { SunXK_Again, "Again" }, + { SunXK_Props, "Props" }, + { SunXK_Undo, "Undo" }, + { SunXK_Front, "Front" }, + { SunXK_Copy, "Copy" }, + { SunXK_Open, "Open" }, + { SunXK_Paste, "Paste" }, + { SunXK_Find, "Find" }, + { SunXK_Cut, "Cut" }, + }; + + const struct KeysymNameReplacement aImplReplacements_Norwegian[] = + { + { XK_Shift_L, "Skift" }, + { XK_Shift_R, "Skift" }, + { XK_Page_Up, "PageUp" }, + { XK_Page_Down, "PageDown" }, + { XK_Escape, "Esc" }, + { XK_Right, "H\303\270yre" }, + { XK_Left, "Venstre" }, + { XK_Up, "Opp" }, + { XK_Down, "Ned" }, + { XK_BackSpace, "Tilbake" }, + { XK_Return, "Enter" }, + { SunXK_Stop, "Avbryt" }, + { SunXK_Again, "Gjenta" }, + { SunXK_Props, "Egenskaper" }, + { SunXK_Undo, "Angre" }, + { SunXK_Front, "Front" }, + { SunXK_Copy, "Kopi" }, + { SunXK_Open, "\303\205pne" }, + { SunXK_Paste, "Lim" }, + { SunXK_Find, "S\303\270k" }, + { SunXK_Cut, "Klipp" }, + }; + + const struct KeysymNameReplacement aImplReplacements_Swedish[] = + { + { XK_Shift_L, "Skift" }, + { XK_Shift_R, "Skift" }, + { XK_Page_Up, "PageUp" }, + { XK_Page_Down, "PageDown" }, + { XK_Escape, "Esc" }, + { XK_Right, "H\303\266ger" }, + { XK_Left, "V\303\244nster" }, + { XK_Up, "Up" }, + { XK_Down, "Ned" }, + { XK_BackSpace, "Backsteg" }, + { XK_Return, "Retur" }, + { XK_space, "Blank" }, + { SunXK_Stop, "Avbryt" }, + { SunXK_Again, "Upprepa" }, + { SunXK_Props, "Egenskaper" }, + { SunXK_Undo, "\303\205ngra" }, + { SunXK_Front, "Fram" }, + { SunXK_Copy, "Kopiera" }, + { SunXK_Open, "\303\226ppna" }, + { SunXK_Paste, "Klistra in" }, + { SunXK_Find, "S\303\266k" }, + { SunXK_Cut, "Klipp ut" }, + }; + + const struct KeysymNameReplacement aImplReplacements_Portuguese[] = + { + { XK_Page_Up, "PageUp" }, + { XK_Page_Down, "PageDown" }, + { XK_Escape, "Esc" }, + { XK_Right, "Direita" }, + { XK_Left, "Esquerda" }, + { XK_Up, "Acima" }, + { XK_Down, "Abaixo" }, + { XK_BackSpace, "Backspace" }, + { XK_Return, "Enter" }, + { XK_slash, "Barra" }, + { SunXK_Stop, "Stop" }, + { SunXK_Again, "Again" }, + { SunXK_Props, "Props" }, + { SunXK_Undo, "Undo" }, + { SunXK_Front, "Front" }, + { SunXK_Copy, "Copy" }, + { SunXK_Open, "Open" }, + { SunXK_Paste, "Paste" }, + { SunXK_Find, "Find" }, + { SunXK_Cut, "Cut" }, + }; + + const struct KeysymNameReplacement aImplReplacements_Slovenian[] = + { + { XK_Control_L, "Krmilka" }, + { XK_Control_R, "Krmilka" }, + { XK_Shift_L, "Dvigalka" }, + { XK_Shift_R, "Dvigalka" }, + { XK_Alt_L, "Izmenjalka" }, + { XK_Alt_R, "Desna izmenjalka" }, + { XK_Page_Up, "Prej\305\241nja stranf" }, + { XK_Page_Down, "Naslednja stran" }, + { XK_End, "Konec" }, + { XK_Home, "Za\304\215etek" }, + { XK_Insert, "Vstavljalka" }, + { XK_Delete, "Brisalka" }, + { XK_Escape, "Ube\305\276nica" }, + { XK_Right, "Desno" }, + { XK_Left, "Levo" }, + { XK_Up, "Navzgor" }, + { XK_Down, "Navzdol" }, + { XK_BackSpace, "Vra\304\215alka" }, + { XK_Return, "Vna\305\241alka" }, + { XK_slash, "Po\305\241evnica" }, + { XK_space, "Preslednica" }, + { SunXK_Stop, "Ustavi" }, + { SunXK_Again, "Ponovi" }, + { SunXK_Props, "Lastnosti" }, + { SunXK_Undo, "Razveljavi" }, + { SunXK_Front, "Ospredje" }, + { SunXK_Copy, "Kopiraj" }, + { SunXK_Open, "Odpri" }, + { SunXK_Paste, "Prilepi" }, + { SunXK_Find, "Najdi" }, + { SunXK_Cut, "Izre\305\276i" }, + }; + + const struct KeysymNameReplacement aImplReplacements_Spanish[] = + { + { XK_Shift_L, "May\303\272s" }, + { XK_Shift_R, "May\303\272s" }, + { XK_Page_Up, "ReP\303\241g" }, + { XK_Page_Down, "AvP\303\241g" }, + { XK_End, "Fin" }, + { XK_Home, "Inicio" }, + { XK_Delete, "Supr" }, + { XK_Escape, "Esc" }, + { XK_Right, "Derecha" }, + { XK_Left, "Izquierda" }, + { XK_Up, "Arriba" }, + { XK_Down, "Abajo" }, + { XK_BackSpace, "Ret" }, + { XK_Return, "Entrada" }, + { XK_space, "Espacio" }, + { XK_KP_Enter, "Intro" }, + { SunXK_Stop, "Detener" }, + { SunXK_Again, "Repetir" }, + { SunXK_Props, "Props" }, + { SunXK_Undo, "Anular" }, + { SunXK_Front, "Delante" }, + { SunXK_Copy, "Copiar" }, + { SunXK_Open, "Abrir" }, + { SunXK_Paste, "Pegar" }, + { SunXK_Find, "Buscar" }, + { SunXK_Cut, "Cortar" }, + }; + + const struct KeysymNameReplacement aImplReplacements_Estonian[] = + { + { XK_Page_Up, "PgUp" }, + { XK_Page_Down, "PgDown" }, + { XK_End, "End" }, + { XK_Home, "Home" }, + { XK_Insert, "Ins" }, + { XK_Delete, "Del" }, + { XK_Escape, "Esc" }, + { XK_Right, "Nool paremale" }, + { XK_Left, "Nool vasakule" }, + { XK_Up, "Nool \303\274les" }, + { XK_Down, "Nool alla" }, + { XK_BackSpace, "Tagasil\303\274ke" }, + { XK_Return, "Enter" }, + { XK_slash, "Kaldkriips" }, + { XK_space, "T\303\274hik" }, + { XK_asterisk, "T\303\244rn" }, + { SunXK_Stop, "Peata" }, + { SunXK_Again, "Korda" }, + { SunXK_Props, "Omadused" }, + { SunXK_Undo, "V\303\265ta tagasi" }, + { SunXK_Front, "Esiplaanile" }, + { SunXK_Copy, "Kopeeri" }, + { SunXK_Open, "Ava" }, + { SunXK_Paste, "Aseta" }, + { SunXK_Find, "Otsi" }, + { SunXK_Cut, "L\303\265ika" }, + }; + + const struct KeysymNameReplacement aImplReplacements_Catalan[] = + { + { XK_Shift_L, "Maj" }, + { XK_Shift_R, "Maj" }, + { XK_Page_Up, "Re P\303\240g" }, + { XK_Page_Down, "Av P\303\240g" }, + { XK_End, "Fi" }, + { XK_Home, "Inici" }, + { XK_Delete, "Supr" }, + { XK_Escape, "Esc" }, + { XK_Right, "Dreta" }, + { XK_Left, "Esquerra" }, + { XK_Up, "Amunt" }, + { XK_Down, "Avall" }, + { XK_BackSpace, "Retroc\303\251s" }, + { XK_Return, "Retorn" }, + { XK_space, "Espai" }, + { XK_KP_Enter, "Retorn" }, + { SunXK_Stop, "Atura" }, + { SunXK_Again, "Repeteix" }, + { SunXK_Props, "Props" }, + { SunXK_Undo, "Desf\303\251s" }, + { SunXK_Front, "Davant" }, + { SunXK_Copy, "C\303\262pia" }, + { SunXK_Open, "Obre" }, + { SunXK_Paste, "Enganxa" }, + { SunXK_Find, "Cerca" }, + { SunXK_Cut, "Retalla" }, + }; + + const struct KeysymNameReplacement aImplReplacements_Lithuanian[] = + { + { XK_Control_L, "Vald" }, + { XK_Control_R, "Vald" }, + { XK_Shift_L, "Lyg2" }, + { XK_Shift_R, "Lyg2" }, + { XK_Alt_L, "Alt" }, + { XK_Alt_R, "Lyg3" }, + { XK_Page_Up, "Psl\342\206\221" }, + { XK_Page_Down, "Psl\342\206\223" }, + { XK_End, "Pab" }, + { XK_Home, "Prad" }, + { XK_Insert, "\304\256terpti" }, + { XK_Delete, "\305\240al" }, + { XK_Escape, "Gr" }, + { XK_Right, "De\305\241in\304\227n" }, + { XK_Left, "Kair\304\227n" }, + { XK_Up, "Auk\305\241tyn" }, + { XK_Down, "\305\275emyn" }, + { XK_BackSpace, "Naikinti" }, + { XK_Return, "\304\256vesti" }, + { XK_asterisk, "\305\275vaig\305\276dut\304\227" }, + { XK_slash, "De\305\241ininis br\305\253k\305\241nys" }, + { XK_space, "Tarpas" }, + { SunXK_Stop, "Stabdyti" }, + { SunXK_Again, "Kartoti" }, + { SunXK_Props, "Savyb\304\227s" }, + { SunXK_Undo, "At\305\241aukti" }, + { SunXK_Front, "Priekinis planas" }, + { SunXK_Copy, "Kopijuoti" }, + { SunXK_Open, "Atverti" }, + { SunXK_Paste, "\304\256d\304\227ti" }, + { SunXK_Find, "Ie\305\241koti" }, + { SunXK_Cut, "I\305\241kirpti" }, + }; + + const struct KeysymNameReplacement aImplReplacements_Hungarian[] = + { + { XK_Right, "Jobbra" }, + { XK_Left, "Balra" }, + { XK_Up, "Fel" }, + { XK_Down, "Le" }, + { XK_Return, "Enter" }, + { XK_space, "Sz\303\263k\303\266z" }, + { XK_asterisk, "Csillag" }, + { XK_slash, "Oszt\303\241sjel" }, + }; + + const struct KeyboardReplacements aKeyboards[] = + { + { "ca", aImplReplacements_Catalan, SAL_N_ELEMENTS(aImplReplacements_Catalan) }, + { "de", aImplReplacements_German, SAL_N_ELEMENTS(aImplReplacements_German) }, + { "sl", aImplReplacements_Slovenian, SAL_N_ELEMENTS(aImplReplacements_Slovenian) }, + { "es", aImplReplacements_Spanish, SAL_N_ELEMENTS(aImplReplacements_Spanish) }, + { "et", aImplReplacements_Estonian, SAL_N_ELEMENTS(aImplReplacements_Estonian) }, + { "fr", aImplReplacements_French, SAL_N_ELEMENTS(aImplReplacements_French) }, + { "hu", aImplReplacements_Hungarian, SAL_N_ELEMENTS(aImplReplacements_Hungarian) }, + { "it", aImplReplacements_Italian, SAL_N_ELEMENTS(aImplReplacements_Italian) }, + { "lt", aImplReplacements_Lithuanian, SAL_N_ELEMENTS(aImplReplacements_Lithuanian) }, + { "nl", aImplReplacements_Dutch, SAL_N_ELEMENTS(aImplReplacements_Dutch) }, + { "no", aImplReplacements_Norwegian, SAL_N_ELEMENTS(aImplReplacements_Norwegian) }, + { "pt", aImplReplacements_Portuguese, SAL_N_ELEMENTS(aImplReplacements_Portuguese) }, + { "ru", aImplReplacements_Russian, SAL_N_ELEMENTS(aImplReplacements_Russian) }, + { "sv", aImplReplacements_Swedish, SAL_N_ELEMENTS(aImplReplacements_Swedish) }, + { "tr", aImplReplacements_Turkish, SAL_N_ELEMENTS(aImplReplacements_Turkish) }, + }; + + // translate keycodes, used within the displayed menu shortcuts + OUString getKeysymReplacementName( std::u16string_view pLang, KeySym nSymbol ) + { + for(const auto & rKeyboard : aKeyboards) + { + if( o3tl::equalsAscii( pLang, rKeyboard.pLangName ) ) + { + const struct KeysymNameReplacement* pRepl = rKeyboard.pReplacements; + for( int m = rKeyboard.nReplacements ; m ; ) + { + if( nSymbol == pRepl[--m].aSymbol ) + return OUString( pRepl[m].pName, strlen(pRepl[m].pName), RTL_TEXTENCODING_UTF8 ); + } + } + } + + // try english fallbacks + const struct KeysymNameReplacement* pRepl = aImplReplacements_English; + for( int m = SAL_N_ELEMENTS(aImplReplacements_English); m ; ) + { + if( nSymbol == pRepl[--m].aSymbol ) + return OUString( pRepl[m].pName, strlen(pRepl[m].pName), RTL_TEXTENCODING_UTF8 ); + } + + return OUString(); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/generic/app/randrwrapper.cxx b/vcl/unx/generic/app/randrwrapper.cxx new file mode 100644 index 000000000..431c70ae3 --- /dev/null +++ b/vcl/unx/generic/app/randrwrapper.cxx @@ -0,0 +1,181 @@ +/* -*- 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 . + */ + +#ifdef USE_RANDR + +#include <X11/Xlib.h> +#include <X11/extensions/Xrandr.h> + +#include <sal/log.hxx> + +namespace +{ + +class RandRWrapper +{ + bool m_bValid; + + explicit RandRWrapper(Display*); +public: + static RandRWrapper& get(Display*); + static void releaseWrapper(); + + Bool XRRQueryExtension(Display* i_pDisp, int* o_event_base, int* o_error_base ) + { + Bool bRet = False; + if( m_bValid ) + bRet = ::XRRQueryExtension( i_pDisp, o_event_base, o_error_base ); + return bRet; + } + XRRScreenConfiguration* XRRGetScreenInfo( Display* i_pDisp, Drawable i_aDrawable ) + { + return m_bValid ? ::XRRGetScreenInfo( i_pDisp, i_aDrawable ) : nullptr; + } + void XRRFreeScreenConfigInfo( XRRScreenConfiguration* i_pConfig ) + { + if( m_bValid ) + ::XRRFreeScreenConfigInfo( i_pConfig ); + } + void XRRSelectInput( Display* i_pDisp, ::Window i_window, int i_nMask ) + { + if( m_bValid ) + ::XRRSelectInput( i_pDisp, i_window, i_nMask ); + } + int XRRUpdateConfiguration( XEvent* i_pEvent ) + { + return m_bValid ? ::XRRUpdateConfiguration( i_pEvent ) : 0; + } + XRRScreenSize* XRRConfigSizes( XRRScreenConfiguration* i_pConfig, int* o_nSizes ) + { + return m_bValid ? ::XRRConfigSizes( i_pConfig, o_nSizes ) : nullptr; + } + SizeID XRRConfigCurrentConfiguration( XRRScreenConfiguration* i_pConfig, Rotation* o_pRot ) + { + return m_bValid ? ::XRRConfigCurrentConfiguration( i_pConfig, o_pRot ) : 0; + } + int XRRRootToScreen( Display *dpy, ::Window root ) + { + return m_bValid ? ::XRRRootToScreen( dpy, root ) : -1; + } +}; + +RandRWrapper::RandRWrapper( Display* pDisplay ) : + m_bValid( true ) +{ + int nEventBase = 0, nErrorBase = 0; + if( !XRRQueryExtension( pDisplay, &nEventBase, &nErrorBase ) ) + m_bValid = false; +} + +RandRWrapper* pWrapper = nullptr; + +RandRWrapper& RandRWrapper::get( Display* i_pDisplay ) +{ + if( ! pWrapper ) + pWrapper = new RandRWrapper( i_pDisplay ); + return *pWrapper; +} + +void RandRWrapper::releaseWrapper() +{ + delete pWrapper; + pWrapper = nullptr; +} + +} // namespace + +#endif + +#include <unx/saldisp.hxx> +#if OSL_DEBUG_LEVEL > 1 +#include <cstdio> +#endif + +void SalDisplay::InitRandR( ::Window aRoot ) const +{ + #ifdef USE_RANDR + RandRWrapper::get( GetDisplay() ).XRRSelectInput( GetDisplay(), aRoot, RRScreenChangeNotifyMask ); + #else + (void)this; + (void)aRoot; + #endif +} + +void SalDisplay::DeInitRandR() +{ + #ifdef USE_RANDR + RandRWrapper::releaseWrapper(); +#if OSL_DEBUG_LEVEL > 1 + SAL_INFO("vcl.app", "SalDisplay::DeInitRandR()."); +#endif + #endif +} + +void SalDisplay::processRandREvent( XEvent* pEvent ) +{ +#ifdef USE_RANDR + XConfigureEvent* pCnfEvent=reinterpret_cast<XConfigureEvent*>(pEvent); + if( !pWrapper || pWrapper->XRRRootToScreen(GetDisplay(),pCnfEvent->window) == -1 ) + return; + + int nRet = pWrapper->XRRUpdateConfiguration( pEvent ); + if( nRet != 1 || pEvent->type == ConfigureNotify) // this should then be a XRRScreenChangeNotifyEvent + return; + + // update screens + bool bNotify = false; + for(ScreenData & rScreen : m_aScreens) + { + if( rScreen.m_bInit ) + { + XRRScreenConfiguration *pConfig = nullptr; + XRRScreenSize *pSizes = nullptr; + int nSizes = 0; + Rotation nRot = 0; + SizeID nId = 0; + + pConfig = pWrapper->XRRGetScreenInfo( GetDisplay(), rScreen.m_aRoot ); + nId = pWrapper->XRRConfigCurrentConfiguration( pConfig, &nRot ); + pSizes = pWrapper->XRRConfigSizes( pConfig, &nSizes ); + XRRScreenSize *pTargetSize = pSizes + nId; + + bNotify = bNotify || + rScreen.m_aSize.Width() != pTargetSize->width || + rScreen.m_aSize.Height() != pTargetSize->height; + + rScreen.m_aSize = Size( pTargetSize->width, pTargetSize->height ); + + pWrapper->XRRFreeScreenConfigInfo( pConfig ); + +#if OSL_DEBUG_LEVEL > 1 + SAL_INFO("vcl.app", "screen " << nId + << " changed to size " << (int)pTargetSize->width + << "x" << (int)pTargetSize->height); +#endif + } + } + if( bNotify ) + emitDisplayChanged(); +#else + (void)this; + (void)pEvent; +#endif +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/generic/app/saldata.cxx b/vcl/unx/generic/app/saldata.cxx new file mode 100644 index 000000000..488e9937d --- /dev/null +++ b/vcl/unx/generic/app/saldata.cxx @@ -0,0 +1,778 @@ +/* -*- 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 <unistd.h> +#include <fcntl.h> + +#include <cstdio> +#include <cstdlib> +#include <errno.h> +#ifdef SUN +#include <sys/systeminfo.h> +#endif +#ifdef AIX +#include <strings.h> +#endif +#ifdef FREEBSD +#include <sys/types.h> +#include <sys/time.h> +#endif + +#include <osl/process.h> + +#include <unx/saldisp.hxx> +#include <unx/saldata.hxx> +#include <unx/salunxtime.h> +#include <unx/sm.hxx> +#include <unx/i18n_im.hxx> + +#include <X11/Xlib.h> +#include <X11/Xproto.h> + +#include <salinst.hxx> +#include <saltimer.hxx> + +#include <osl/diagnose.h> +#include <osl/signal.h> +#include <osl/thread.h> +#include <sal/log.hxx> + +#include <vcl/svapp.hxx> + +X11SalData* GetX11SalData() +{ + return static_cast<X11SalData*>(ImplGetSVData()->mpSalData); +} + +extern "C" { + +static int XErrorHdl( Display *pDisplay, XErrorEvent *pEvent ) +{ + GetX11SalData()->XError( pDisplay, pEvent ); + return 0; +} + +static int XIOErrorHdl( Display * ) +{ + if ( Application::IsMainThread() ) + { + /* #106197# hack: until a real shutdown procedure exists + * _exit ASAP + */ + if( ImplGetSVData()->maAppData.mbAppQuit ) + _exit(1); + + // really bad hack + if( ! SessionManagerClient::checkDocumentsSaved() ) + /* oslSignalAction eToDo = */ osl_raiseSignal (OSL_SIGNAL_USER_X11SUBSYSTEMERROR, nullptr); + } + + std::fprintf( stderr, "X IO Error\n" ); + std::fflush( stdout ); + std::fflush( stderr ); + + /* #106197# the same reasons to use _exit instead of exit in salmain + * do apply here. Since there is nothing to be done after an XIO + * error we have to _exit immediately. + */ + _exit(1); + return 0; +} + +} + +const struct timeval noyield_ = { 0, 0 }; +const struct timeval yield_ = { 0, 10000 }; + +static const char* XRequest[] = { + // see /usr/lib/X11/XErrorDB, /usr/openwin/lib/XErrorDB ... + nullptr, + "X_CreateWindow", + "X_ChangeWindowAttributes", + "X_GetWindowAttributes", + "X_DestroyWindow", + "X_DestroySubwindows", + "X_ChangeSaveSet", + "X_ReparentWindow", + "X_MapWindow", + "X_MapSubwindows", + "X_UnmapWindow", + "X_UnmapSubwindows", + "X_ConfigureWindow", + "X_CirculateWindow", + "X_GetGeometry", + "X_QueryTree", + "X_InternAtom", + "X_GetAtomName", + "X_ChangeProperty", + "X_DeleteProperty", + "X_GetProperty", + "X_ListProperties", + "X_SetSelectionOwner", + "X_GetSelectionOwner", + "X_ConvertSelection", + "X_SendEvent", + "X_GrabPointer", + "X_UngrabPointer", + "X_GrabButton", + "X_UngrabButton", + "X_ChangeActivePointerGrab", + "X_GrabKeyboard", + "X_UngrabKeyboard", + "X_GrabKey", + "X_UngrabKey", + "X_AllowEvents", + "X_GrabServer", + "X_UngrabServer", + "X_QueryPointer", + "X_GetMotionEvents", + "X_TranslateCoords", + "X_WarpPointer", + "X_SetInputFocus", + "X_GetInputFocus", + "X_QueryKeymap", + "X_OpenFont", + "X_CloseFont", + "X_QueryFont", + "X_QueryTextExtents", + "X_ListFonts", + "X_ListFontsWithInfo", + "X_SetFontPath", + "X_GetFontPath", + "X_CreatePixmap", + "X_FreePixmap", + "X_CreateGC", + "X_ChangeGC", + "X_CopyGC", + "X_SetDashes", + "X_SetClipRectangles", + "X_FreeGC", + "X_ClearArea", + "X_CopyArea", + "X_CopyPlane", + "X_PolyPoint", + "X_PolyLine", + "X_PolySegment", + "X_PolyRectangle", + "X_PolyArc", + "X_FillPoly", + "X_PolyFillRectangle", + "X_PolyFillArc", + "X_PutImage", + "X_GetImage", + "X_PolyText8", + "X_PolyText16", + "X_ImageText8", + "X_ImageText16", + "X_CreateColormap", + "X_FreeColormap", + "X_CopyColormapAndFree", + "X_InstallColormap", + "X_UninstallColormap", + "X_ListInstalledColormaps", + "X_AllocColor", + "X_AllocNamedColor", + "X_AllocColorCells", + "X_AllocColorPlanes", + "X_FreeColors", + "X_StoreColors", + "X_StoreNamedColor", + "X_QueryColors", + "X_LookupColor", + "X_CreateCursor", + "X_CreateGlyphCursor", + "X_FreeCursor", + "X_RecolorCursor", + "X_QueryBestSize", + "X_QueryExtension", + "X_ListExtensions", + "X_ChangeKeyboardMapping", + "X_GetKeyboardMapping", + "X_ChangeKeyboardControl", + "X_GetKeyboardControl", + "X_Bell", + "X_ChangePointerControl", + "X_GetPointerControl", + "X_SetScreenSaver", + "X_GetScreenSaver", + "X_ChangeHosts", + "X_ListHosts", + "X_SetAccessControl", + "X_SetCloseDownMode", + "X_KillClient", + "X_RotateProperties", + "X_ForceScreenSaver", + "X_SetPointerMapping", + "X_GetPointerMapping", + "X_SetModifierMapping", + "X_GetModifierMapping", + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + "X_NoOperation" +}; + +X11SalData::X11SalData() + : GenericUnixSalData() +{ + pXLib_ = nullptr; + + m_aOrigXIOErrorHandler = XSetIOErrorHandler ( XIOErrorHdl ); + PushXErrorLevel( !!getenv( "SAL_IGNOREXERRORS" ) ); +} + +X11SalData::~X11SalData() +{ + DeleteDisplay(); + PopXErrorLevel(); + XSetIOErrorHandler (m_aOrigXIOErrorHandler); +} + +void X11SalData::Dispose() +{ + delete GetDisplay(); + SetSalData( nullptr ); +} + +void X11SalData::DeleteDisplay() +{ + delete GetDisplay(); + SetDisplay( nullptr ); + pXLib_.reset(); +} + +void X11SalData::Init() +{ + pXLib_.reset(new SalXLib()); + pXLib_->Init(); +} + +void X11SalData::ErrorTrapPush() +{ + PushXErrorLevel( true ); +} + +bool X11SalData::ErrorTrapPop( bool bIgnoreError ) +{ + bool err = false; + if( !bIgnoreError ) + err = HasXErrorOccurred(); + ResetXErrorOccurred(); + PopXErrorLevel(); + return err; +} + +void X11SalData::PushXErrorLevel( bool bIgnore ) +{ + m_aXErrorHandlerStack.emplace_back( ); + XErrorStackEntry& rEnt = m_aXErrorHandlerStack.back(); + rEnt.m_bWas = false; + rEnt.m_bIgnore = bIgnore; + rEnt.m_aHandler = XSetErrorHandler( XErrorHdl ); +} + +void X11SalData::PopXErrorLevel() +{ + if( !m_aXErrorHandlerStack.empty() ) + { + XSetErrorHandler( m_aXErrorHandlerStack.back().m_aHandler ); + m_aXErrorHandlerStack.pop_back(); + } +} + +SalXLib::SalXLib() +{ + m_aTimeout.tv_sec = 0; + m_aTimeout.tv_usec = 0; + m_nTimeoutMS = 0; + + nFDs_ = 0; + FD_ZERO( &aReadFDS_ ); + FD_ZERO( &aExceptionFDS_ ); + + m_pInputMethod = nullptr; + m_pDisplay = nullptr; + + m_pTimeoutFDS[0] = m_pTimeoutFDS[1] = -1; + if (pipe (m_pTimeoutFDS) == -1) + return; + + // initialize 'wakeup' pipe. + int flags; + + // set close-on-exec descriptor flag. + if ((flags = fcntl (m_pTimeoutFDS[0], F_GETFD)) != -1) + { + flags |= FD_CLOEXEC; + (void)fcntl(m_pTimeoutFDS[0], F_SETFD, flags); + } + if ((flags = fcntl (m_pTimeoutFDS[1], F_GETFD)) != -1) + { + flags |= FD_CLOEXEC; + (void)fcntl(m_pTimeoutFDS[1], F_SETFD, flags); + } + + // set non-blocking I/O flag. + if ((flags = fcntl (m_pTimeoutFDS[0], F_GETFL)) != -1) + { + flags |= O_NONBLOCK; + (void)fcntl(m_pTimeoutFDS[0], F_SETFL, flags); + } + if ((flags = fcntl (m_pTimeoutFDS[1], F_GETFL)) != -1) + { + flags |= O_NONBLOCK; + (void)fcntl(m_pTimeoutFDS[1], F_SETFL, flags); + } + + // insert [0] into read descriptor set. + FD_SET( m_pTimeoutFDS[0], &aReadFDS_ ); + nFDs_ = m_pTimeoutFDS[0] + 1; +} + +SalXLib::~SalXLib() +{ + // close 'wakeup' pipe. + close (m_pTimeoutFDS[0]); + close (m_pTimeoutFDS[1]); + + m_pInputMethod.reset(); +} + +static Display *OpenX11Display(OString& rDisplay) +{ + /* + * open connection to X11 Display + * try in this order: + * o -display command line parameter, + * o $DISPLAY environment variable + * o default display + */ + + Display *pDisp = nullptr; + + // is there a -display command line parameter? + + sal_uInt32 nParams = osl_getCommandArgCount(); + OUString aParam; + for (sal_uInt32 i=0; i<nParams; i++) + { + osl_getCommandArg(i, &aParam.pData); + if ( aParam == "-display" ) + { + osl_getCommandArg(i+1, &aParam.pData); + rDisplay = OUStringToOString( + aParam, osl_getThreadTextEncoding()); + + if ((pDisp = XOpenDisplay(rDisplay.getStr()))!=nullptr) + { + /* + * if a -display switch was used, we need + * to set the environment accordingly since + * the clipboard build another connection + * to the xserver using $DISPLAY + */ + OUString envVar("DISPLAY"); + osl_setEnvironment(envVar.pData, aParam.pData); + } + break; + } + } + + if (!pDisp && rDisplay.isEmpty()) + { + // Open $DISPLAY or default... + char *pDisplay = getenv("DISPLAY"); + if (pDisplay != nullptr) + rDisplay = OString(pDisplay); + pDisp = XOpenDisplay(pDisplay); + } + + return pDisp; +} + +void SalXLib::Init() +{ + m_pInputMethod.reset( new SalI18N_InputMethod ); + m_pInputMethod->SetLocale(); + XrmInitialize(); + + OString aDisplay; + m_pDisplay = OpenX11Display(aDisplay); + + if ( m_pDisplay ) + return; + + OUString aProgramFileURL; + osl_getExecutableFile( &aProgramFileURL.pData ); + OUString aProgramSystemPath; + osl_getSystemPathFromFileURL (aProgramFileURL.pData, &aProgramSystemPath.pData); + OString aProgramName = OUStringToOString( + aProgramSystemPath, + osl_getThreadTextEncoding() ); + std::fprintf( stderr, "%s X11 error: Can't open display: %s\n", + aProgramName.getStr(), aDisplay.getStr()); + std::fprintf( stderr, " Set DISPLAY environment variable, use -display option\n"); + std::fprintf( stderr, " or check permissions of your X-Server\n"); + std::fprintf( stderr, " (See \"man X\" resp. \"man xhost\" for details)\n"); + std::fflush( stderr ); + exit(0); + +} + +extern "C" { +static void EmitFontpathWarning() +{ + static Bool bOnce = False; + if ( !bOnce ) + { + bOnce = True; + std::fprintf( stderr, "Please verify your fontpath settings\n" + "\t(See \"man xset\" for details" + " or ask your system administrator)\n" ); + } +} + +} /* extern "C" */ + +static void PrintXError( Display *pDisplay, XErrorEvent *pEvent ) +{ + char msg[ 120 ] = ""; + XGetErrorText( pDisplay, pEvent->error_code, msg, sizeof( msg ) ); + std::fprintf( stderr, "X-Error: %s\n", msg ); + if( pEvent->request_code < SAL_N_ELEMENTS( XRequest ) ) + { + const char* pName = XRequest[pEvent->request_code]; + if( !pName ) + pName = "BadRequest?"; + std::fprintf( stderr, "\tMajor opcode: %d (%s)\n", pEvent->request_code, pName ); + } + else + { + std::fprintf( stderr, "\tMajor opcode: %d\n", pEvent->request_code ); + // TODO: also display extension name? + std::fprintf( stderr, "\tMinor opcode: %d\n", pEvent->minor_code ); + } + + std::fprintf( stderr, "\tResource ID: 0x%lx\n", + pEvent->resourceid ); + std::fprintf( stderr, "\tSerial No: %ld (%ld)\n", + pEvent->serial, LastKnownRequestProcessed(pDisplay) ); + + if( !getenv( "SAL_SYNCHRONIZE" ) ) + { + std::fprintf( stderr, "These errors are reported asynchronously,\n"); + std::fprintf( stderr, "set environment variable SAL_SYNCHRONIZE to 1 to help debugging\n"); + } + + std::fflush( stdout ); + std::fflush( stderr ); +} + +void X11SalData::XError( Display *pDisplay, XErrorEvent *pEvent ) +{ + if( ! m_aXErrorHandlerStack.back().m_bIgnore ) + { + if ( (pEvent->error_code == BadAlloc) + && (pEvent->request_code == X_OpenFont) ) + { + static Bool bOnce = False; + if ( !bOnce ) + { + std::fprintf(stderr, "X-Error occurred in a request for X_OpenFont\n"); + EmitFontpathWarning(); + + bOnce = True ; + } + return; + } + /* ignore + * X_SetInputFocus: it's a hint only anyway + * X_GetProperty: this is part of the XGetWindowProperty call and will + * be handled by the return value of that function + */ + else if( pEvent->request_code == X_SetInputFocus || + pEvent->request_code == X_GetProperty + ) + return; + + if( pDisplay != vcl_sal::getSalDisplay(GetGenericUnixSalData())->GetDisplay() ) + return; + + PrintXError( pDisplay, pEvent ); + + oslSignalAction eToDo = osl_raiseSignal (OSL_SIGNAL_USER_X11SUBSYSTEMERROR, nullptr); + switch (eToDo) + { + case osl_Signal_ActIgnore : + return; + case osl_Signal_ActAbortApp : + abort(); + case osl_Signal_ActKillApp : + exit(0); + case osl_Signal_ActCallNextHdl : + break; + default : + break; + } + + } + + m_aXErrorHandlerStack.back().m_bWas = true; +} + +void X11SalData::Timeout() +{ + ImplSVData* pSVData = ImplGetSVData(); + if( pSVData->maSchedCtx.mpSalTimer ) + pSVData->maSchedCtx.mpSalTimer->CallCallback(); +} + +namespace { + +struct YieldEntry +{ + int fd; // file descriptor for reading + void* data; // data for predicate and callback + YieldFunc pending; // predicate (determines pending events) + YieldFunc queued; // read and queue up events + YieldFunc handle; // handle pending events + + int HasPendingEvent() const { return pending( fd, data ); } + int IsEventQueued() const { return queued( fd, data ); } + void HandleNextEvent() const { handle( fd, data ); } +}; + +} + +#define MAX_NUM_DESCRIPTORS 128 + +static YieldEntry yieldTable[ MAX_NUM_DESCRIPTORS ]; + +void SalXLib::Insert( int nFD, void* data, + YieldFunc pending, + YieldFunc queued, + YieldFunc handle ) +{ + SAL_WARN_IF( !nFD, "vcl", "can not insert stdin descriptor" ); + SAL_WARN_IF( yieldTable[nFD].fd, "vcl", "SalXLib::Insert fd twice" ); + + yieldTable[nFD].fd = nFD; + yieldTable[nFD].data = data; + yieldTable[nFD].pending = pending; + yieldTable[nFD].queued = queued; + yieldTable[nFD].handle = handle; + + FD_SET( nFD, &aReadFDS_ ); + FD_SET( nFD, &aExceptionFDS_ ); + + if( nFD >= nFDs_ ) + nFDs_ = nFD + 1; +} + +void SalXLib::Remove( int nFD ) +{ + FD_CLR( nFD, &aReadFDS_ ); + FD_CLR( nFD, &aExceptionFDS_ ); + + yieldTable[nFD].fd = 0; + + if ( nFD == nFDs_ ) + { + for ( nFD = nFDs_ - 1; + nFD >= 0 && !yieldTable[nFD].fd; + nFD-- ) ; + + nFDs_ = nFD + 1; + } +} + +bool SalXLib::CheckTimeout( bool bExecuteTimers ) +{ + bool bRet = false; + if( m_aTimeout.tv_sec ) // timer is started + { + timeval aTimeOfDay; + gettimeofday( &aTimeOfDay, nullptr ); + if( aTimeOfDay >= m_aTimeout ) + { + bRet = true; + if( bExecuteTimers ) + { + // timed out, update timeout + m_aTimeout = aTimeOfDay; + /* + * #107827# autorestart immediately, will be stopped (or set + * to different value in notify hdl if necessary; + * CheckTimeout should return false while + * timers are being dispatched. + */ + m_aTimeout += m_nTimeoutMS; + // notify + X11SalData::Timeout(); + } + } + } + return bRet; +} + +bool +SalXLib::Yield( bool bWait, bool bHandleAllCurrentEvents ) +{ + // check for timeouts here if you want to make screenshots + static char* p_prioritize_timer = getenv ("SAL_HIGHPRIORITY_REPAINT"); + bool bHandledEvent = false; + if (p_prioritize_timer != nullptr) + bHandledEvent = CheckTimeout(); + + const int nMaxEvents = bHandleAllCurrentEvents ? 100 : 1; + + // first, check for already queued events. + for ( int nFD = 0; nFD < nFDs_; nFD++ ) + { + YieldEntry* pEntry = &(yieldTable[nFD]); + if ( pEntry->fd ) + { + SAL_WARN_IF( nFD != pEntry->fd, "vcl", "wrong fd in Yield()" ); + for( int i = 0; i < nMaxEvents && pEntry->HasPendingEvent(); i++ ) + { + pEntry->HandleNextEvent(); + if( ! bHandleAllCurrentEvents ) + { + return true; + } + } + } + } + + // next, select with or without timeout according to bWait. + int nFDs = nFDs_; + fd_set ReadFDS = aReadFDS_; + fd_set ExceptionFDS = aExceptionFDS_; + int nFound = 0; + + timeval Timeout = noyield_; + timeval *pTimeout = &Timeout; + + + if (bWait) + { + pTimeout = nullptr; + if (m_aTimeout.tv_sec) // Timer is started. + { + // determine remaining timeout. + gettimeofday (&Timeout, nullptr); + Timeout = m_aTimeout - Timeout; + if (yield_ >= Timeout) + { + // guard against micro timeout. + Timeout = yield_; + } + pTimeout = &Timeout; + } + } + + { + // release YieldMutex (and re-acquire at block end) + SolarMutexReleaser aReleaser; + nFound = select( nFDs, &ReadFDS, nullptr, &ExceptionFDS, pTimeout ); + } + if( nFound < 0 ) // error + { +#ifdef DBG_UTIL + SAL_INFO("vcl.app", "SalXLib::Yield e=" << errno << " f=" << nFound); +#endif + if( EINTR == errno ) + { + errno = 0; + } + } + + // usually handle timeouts here (as in 5.2) + if (p_prioritize_timer == nullptr) + bHandledEvent = CheckTimeout() || bHandledEvent; + + // handle wakeup events. + if ((nFound > 0) && FD_ISSET(m_pTimeoutFDS[0], &ReadFDS)) + { + int buffer; + while (read (m_pTimeoutFDS[0], &buffer, sizeof(buffer)) > 0) + continue; + nFound -= 1; + } + + // handle other events. + if( nFound > 0 ) + { + // now we are in the protected section ! + // recall select if we have acquired fd's, ready for reading, + + struct timeval noTimeout = { 0, 0 }; + nFound = select( nFDs_, &ReadFDS, nullptr, + &ExceptionFDS, &noTimeout ); + + // someone-else has done the job for us + if (nFound == 0) + { + return false; + } + + for ( int nFD = 0; nFD < nFDs_; nFD++ ) + { + YieldEntry* pEntry = &(yieldTable[nFD]); + if ( pEntry->fd ) + { + if ( FD_ISSET( nFD, &ExceptionFDS ) ) { +#if OSL_DEBUG_LEVEL > 1 + SAL_WARN("vcl.app", "SalXLib::Yield exception."); +#endif + nFound--; + } + if ( FD_ISSET( nFD, &ReadFDS ) ) + { + for( int i = 0; pEntry->IsEventQueued() && i < nMaxEvents; i++ ) + { + pEntry->HandleNextEvent(); + bHandledEvent = true; + // if a recursive call has done the job + // so abort here + } + nFound--; + } + } + } + } + + return bHandledEvent; +} + +void SalXLib::Wakeup() +{ + OSL_VERIFY(write (m_pTimeoutFDS[1], "", 1) == 1); +} + +void SalXLib::TriggerUserEventProcessing() +{ + Wakeup(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/generic/app/saldisp.cxx b/vcl/unx/generic/app/saldisp.cxx new file mode 100644 index 000000000..490b4b771 --- /dev/null +++ b/vcl/unx/generic/app/saldisp.cxx @@ -0,0 +1,2865 @@ +/* -*- 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 <string.h> +#include <stdio.h> +#include <stdlib.h> +#include <math.h> +#include <unistd.h> + +#if defined(__sun) || defined(AIX) +#include <osl/module.h> +#endif + +#include <X11/Xlib.h> +#include <X11/Xutil.h> +#include <X11/XKBlib.h> + +#include <X11/cursorfont.h> +#include <unx/x11_cursors/salcursors.h> +#include <unx/x11_cursors/invert50.h> +#ifdef __sun +#define XK_KOREAN +#endif +#include <X11/keysym.h> +#include <X11/Xatom.h> + +#ifdef USE_XINERAMA_XORG +#include <X11/extensions/Xinerama.h> +#endif + +#include <i18nlangtag/languagetag.hxx> +#include <tools/debug.hxx> +#include <vcl/svapp.hxx> +#include <vcl/settings.hxx> + +#include <sal/log.hxx> +#include <sal/types.h> +#include <unx/i18n_im.hxx> +#include <unx/i18n_xkb.hxx> +#include <unx/saldisp.hxx> +#include <unx/saldata.hxx> +#include <salinst.hxx> +#include <unx/salframe.h> +#include <vcl/keycodes.hxx> +#include <unx/salbmp.h> +#include <osl/diagnose.h> +#include <unx/salobj.h> +#include <unx/sm.hxx> +#include <unx/wmadaptor.hxx> +#include <unx/x11/xrender_peer.hxx> +#include <unx/glyphcache.hxx> + +#include <poll.h> +#include <memory> +#include <vector> + +/* From <X11/Intrinsic.h> */ +typedef unsigned long Pixel; + +using namespace vcl_sal; + +#ifdef DBG_UTIL +static const char *Null( const char *p ) { return p ? p : ""; } +static const char *GetEnv( const char *p ) { return Null( getenv( p ) ); } +static const char *KeyStr( KeySym n ) { return Null( XKeysymToString( n ) ); } + +static const char *GetAtomName( Display *d, Atom a ) +{ return Null( XGetAtomName( d, a ) ); } + +static double Hypothenuse( tools::Long w, tools::Long h ) +{ return sqrt( static_cast<double>((w*w)+(h*h)) ); } +#endif + +static int ColorDiff( int r, int g, int b ) +{ return (r*r)+(g*g)+(b*b); } + +static int ColorDiff( Color c1, int r, int g, int b ) +{ return ColorDiff( static_cast<int>(c1.GetRed())-r, + static_cast<int>(c1.GetGreen())-g, + static_cast<int>(c1.GetBlue())-b ); } + +static int sal_Shift( Pixel nMask ) +{ + int i = 24; + if( nMask < 0x00010000 ) { nMask <<= 16; i -= 16; } + if( nMask < 0x01000000 ) { nMask <<= 8; i -= 8; } + if( nMask < 0x10000000 ) { nMask <<= 4; i -= 4; } + if( nMask < 0x40000000 ) { nMask <<= 2; i -= 2; } + if( nMask < 0x80000000 ) { i -= 1; } + return i; +} + +static int sal_significantBits( Pixel nMask ) +{ + int nRotate = sizeof(Pixel)*4; + int nBits = 0; + while( nRotate-- ) + { + if( nMask & 1 ) + nBits++; + nMask >>= 1; + } + return nBits; +} + +// check if the resolution is sane +static bool sal_ValidDPI(tools::Long nDPI) +{ + return (nDPI >= 50) && (nDPI <= 500); +} + +static bool sal_GetVisualInfo( Display *pDisplay, XID nVID, XVisualInfo &rVI ) +{ + int nInfos; + XVisualInfo aTemplate; + XVisualInfo*pInfo; + + aTemplate.visualid = nVID; + + pInfo = XGetVisualInfo( pDisplay, VisualIDMask, &aTemplate, &nInfos ); + if( !pInfo ) + return false; + + rVI = *pInfo; + XFree( pInfo ); + + SAL_WARN_IF( rVI.visualid != nVID, "vcl", + "sal_GetVisualInfo: could not get correct visual by visualId" ); + return true; +} + +extern "C" srv_vendor_t +sal_GetServerVendor( Display *p_display ) +{ + struct vendor_t { + srv_vendor_t e_vendor; // vendor as enum + const char* p_name; // vendor name as returned by VendorString() + unsigned int n_len; // number of chars to compare + }; + + static const vendor_t vendorlist[] = { + { vendor_sun, "Sun Microsystems, Inc.", 10 }, + }; + + // handle regular server vendors + char *p_name = ServerVendor( p_display ); + for (auto const & vendor : vendorlist) + { + if ( strncmp (p_name, vendor.p_name, vendor.n_len) == 0 ) + return vendor.e_vendor; + } + + // vendor not found in list + return vendor_unknown; +} + +bool SalDisplay::BestVisual( Display *pDisplay, + int nScreen, + XVisualInfo &rVI ) +{ + VisualID nDefVID = XVisualIDFromVisual( DefaultVisual( pDisplay, nScreen ) ); + VisualID nVID = 0; + char *pVID = getenv( "SAL_VISUAL" ); + if( pVID ) + sscanf( pVID, "%li", &nVID ); + + if( nVID && sal_GetVisualInfo( pDisplay, nVID, rVI ) ) + return rVI.visualid == nDefVID; + + XVisualInfo aVI; + aVI.screen = nScreen; + // get all visuals + int nVisuals; + XVisualInfo* pVInfos = XGetVisualInfo( pDisplay, VisualScreenMask, + &aVI, &nVisuals ); + // pVInfos should contain at least one visual, otherwise + // we're in trouble + std::vector<int> aWeights(nVisuals); + int i; + for( i = 0; i < nVisuals; i++ ) + { + bool bUsable = false; + int nTrueColor = 1; + + if ( pVInfos[i].screen != nScreen ) + { + bUsable = false; + } + else if( pVInfos[i].c_class == TrueColor ) + { + nTrueColor = 2048; + if( pVInfos[i].depth == 24 ) + bUsable = true; + } + else if( pVInfos[i].c_class == PseudoColor ) + { + bUsable = true; + } + aWeights[i] = bUsable ? nTrueColor*pVInfos[i].depth : -1024; + aWeights[i] -= pVInfos[ i ].visualid; + } + + int nBestVisual = 0; + int nBestWeight = -1024; + for( i = 0; i < nVisuals; i++ ) + { + if (aWeights[i] > nBestWeight) + { + nBestWeight = aWeights[i]; + nBestVisual = i; + } + } + + rVI = pVInfos[ nBestVisual ]; + + XFree( pVInfos ); + return rVI.visualid == nDefVID; +} + +SalDisplay::SalDisplay( Display *display ) : + pXLib_( nullptr ), + mpKbdExtension( nullptr ), + pDisp_( display ), + m_nXDefaultScreen( 0 ), + nMaxRequestSize_( 0 ), + meServerVendor( vendor_unknown ), + bNumLockFromXS_( false ), + nNumLockIndex_( 0 ), + nShiftKeySym_( 0 ), + nCtrlKeySym_( 0 ), + nMod1KeySym_( 0 ), + m_bXinerama( false ), + m_nLastUserEventTime( CurrentTime ) +{ +#if OSL_DEBUG_LEVEL > 1 + SAL_INFO("vcl.app", "SalDisplay::SalDisplay()."); +#endif + GenericUnixSalData *pData = GetGenericUnixSalData(); + + SAL_WARN_IF( pData->GetDisplay(), "vcl", "Second SalDisplay created !!!" ); + pData->SetDisplay( this ); + + m_nXDefaultScreen = SalX11Screen( DefaultScreen( pDisp_ ) ); +} + +SalDisplay::~SalDisplay() +{ +#if OSL_DEBUG_LEVEL > 1 + SAL_INFO("vcl.app", "SalDisplay::~SalDisplay()."); +#endif + if( pDisp_ ) + { + doDestruct(); +#if OSL_DEBUG_LEVEL > 1 + SAL_INFO("vcl.app", "display " << pDisp_ << " closed."); +#endif + pDisp_ = nullptr; + } + // don't do this in doDestruct since RandR extension adds hooks into Display + // that is XCloseDisplay still needs the RandR library if it was used + DeInitRandR(); +} + +void SalDisplay::doDestruct() +{ + GenericUnixSalData *pData = GetGenericUnixSalData(); + + m_pWMAdaptor.reset(); + X11SalBitmap::ImplDestroyCache(); + + if (ImplGetSVData()) + { + SalDisplay* pSalDisp = vcl_sal::getSalDisplay(pData); + Display* const pX11Disp = pSalDisp->GetDisplay(); + int nMaxScreens = pSalDisp->GetXScreenCount(); + XRenderPeer& rRenderPeer = XRenderPeer::GetInstance(); + + for (int i = 0; i < nMaxScreens; i++) + { + SalDisplay::RenderEntryMap& rMap = pSalDisp->GetRenderEntries(SalX11Screen(i)); + for (auto const& elem : rMap) + { + if (elem.second.m_aPixmap) + ::XFreePixmap(pX11Disp, elem.second.m_aPixmap); + if (elem.second.m_aPicture) + rRenderPeer.FreePicture(elem.second.m_aPicture); + } + rMap.clear(); + } + } + FreetypeManager::get().ClearFontCache(); + + if( IsDisplay() ) + { + delete mpKbdExtension; + mpKbdExtension = nullptr; + + for( size_t i = 0; i < m_aScreens.size(); i++ ) + { + ScreenData& rData = m_aScreens[i]; + if( rData.m_bInit ) + { + if( rData.m_aMonoGC != rData.m_aCopyGC ) + XFreeGC( pDisp_, rData.m_aMonoGC ); + XFreeGC( pDisp_, rData.m_aCopyGC ); + XFreeGC( pDisp_, rData.m_aAndInvertedGC ); + XFreeGC( pDisp_, rData.m_aAndGC ); + XFreeGC( pDisp_, rData.m_aOrGC ); + XFreeGC( pDisp_, rData.m_aStippleGC ); + XFreePixmap( pDisp_, rData.m_hInvert50 ); + XDestroyWindow( pDisp_, rData.m_aRefWindow ); + Colormap aColMap = rData.m_aColormap.GetXColormap(); + if( aColMap != None && aColMap != DefaultColormap( pDisp_, i ) ) + XFreeColormap( pDisp_, aColMap ); + } + } + + for( const Cursor & aCsr : aPointerCache_ ) + { + if( aCsr ) + XFreeCursor( pDisp_, aCsr ); + } + + if( pXLib_ ) + pXLib_->Remove( ConnectionNumber( pDisp_ ) ); + } + + if( pData->GetDisplay() == static_cast<const SalGenericDisplay *>( this ) ) + pData->SetDisplay( nullptr ); +} + +static int DisplayHasEvent( int fd, void * data ) +{ + auto pDisplay = static_cast<SalX11Display *>(data); + SAL_WARN_IF( ConnectionNumber( pDisplay->GetDisplay() ) != fd, "vcl", + "wrong fd in DisplayHasEvent" ); + if( ! pDisplay->IsDisplay() ) + return 0; + + bool result; + + SolarMutexGuard aGuard; + result = pDisplay->IsEvent(); + return int(result); +} +static int DisplayQueue( int fd, void * data ) +{ + auto pDisplay = static_cast<SalX11Display *>(data); + SAL_WARN_IF( ConnectionNumber( pDisplay->GetDisplay() ) != fd, "vcl", + "wrong fd in DisplayHasEvent" ); + int result; + + SolarMutexGuard aGuard; + result = XEventsQueued( pDisplay->GetDisplay(), + QueuedAfterReading ); + return result; +} +static int DisplayYield( int fd, void * data ) +{ + auto pDisplay = static_cast<SalX11Display *>(data); + SAL_WARN_IF( ConnectionNumber( pDisplay->GetDisplay() ) != fd, "vcl", + "wrong fd in DisplayHasEvent" ); + + SolarMutexGuard aGuard; + pDisplay->Yield(); + return 1; +} + +SalX11Display::SalX11Display( Display *display ) + : SalDisplay( display ) +{ + Init(); + + pXLib_ = GetX11SalData()->GetLib(); + pXLib_->Insert( ConnectionNumber( pDisp_ ), + this, + reinterpret_cast<YieldFunc>(DisplayHasEvent), + reinterpret_cast<YieldFunc>(DisplayQueue), + reinterpret_cast<YieldFunc>(DisplayYield) ); +} + +SalX11Display::~SalX11Display() +{ +#if OSL_DEBUG_LEVEL > 1 + SAL_INFO("vcl.app", "SalX11Display::~SalX11Display()."); +#endif + if( pDisp_ ) + { + doDestruct(); + XCloseDisplay( pDisp_ ); + pDisp_ = nullptr; + } +} + +void SalX11Display::TriggerUserEventProcessing() +{ + if( pXLib_ ) + pXLib_->TriggerUserEventProcessing(); +} + +SalDisplay::ScreenData * +SalDisplay::initScreen( SalX11Screen nXScreen ) const +{ + if( nXScreen.getXScreen() >= m_aScreens.size() ) + nXScreen = m_nXDefaultScreen; + ScreenData* pSD = const_cast<ScreenData *>(&m_aScreens[nXScreen.getXScreen()]); + if( pSD->m_bInit ) + return nullptr; + pSD->m_bInit = true; + + XVisualInfo aVI; + Colormap aColMap; + + if( SalDisplay::BestVisual( pDisp_, nXScreen.getXScreen(), aVI ) ) // DefaultVisual + aColMap = DefaultColormap( pDisp_, nXScreen.getXScreen() ); + else + aColMap = XCreateColormap( pDisp_, + RootWindow( pDisp_, nXScreen.getXScreen() ), + aVI.visual, + AllocNone ); + + Screen* pScreen = ScreenOfDisplay( pDisp_, nXScreen.getXScreen() ); + + pSD->m_aSize = Size( WidthOfScreen( pScreen ), HeightOfScreen( pScreen ) ); + pSD->m_aRoot = RootWindow( pDisp_, nXScreen.getXScreen() ); + pSD->m_aVisual = SalVisual( &aVI ); + pSD->m_aColormap = SalColormap( this, aColMap, nXScreen ); + + // we're interested in configure notification of root windows + InitRandR( pSD->m_aRoot ); + + // - - - - - - - - - - Reference Window/Default Drawable - - + XSetWindowAttributes aXWAttributes; + aXWAttributes.border_pixel = 0; + aXWAttributes.background_pixel = 0; + aXWAttributes.colormap = aColMap; + pSD->m_aRefWindow = XCreateWindow( pDisp_, + pSD->m_aRoot, + 0,0, 16,16, 0, + pSD->m_aVisual.GetDepth(), + InputOutput, + pSD->m_aVisual.GetVisual(), + CWBorderPixel|CWBackPixel|CWColormap, + &aXWAttributes ); + + // set client leader (session id gets set when session is started) + if( pSD->m_aRefWindow ) + { + // client leader must have WM_CLIENT_LEADER pointing to itself + XChangeProperty( pDisp_, + pSD->m_aRefWindow, + XInternAtom( pDisp_, "WM_CLIENT_LEADER", False ), + XA_WINDOW, + 32, + PropModeReplace, + reinterpret_cast<unsigned char*>(&pSD->m_aRefWindow), + 1 + ); + + OString aExec(OUStringToOString(SessionManagerClient::getExecName(), osl_getThreadTextEncoding())); + const char* argv[1]; + argv[0] = aExec.getStr(); + XSetCommand( pDisp_, pSD->m_aRefWindow, const_cast<char**>(argv), 1 ); + XSelectInput( pDisp_, pSD->m_aRefWindow, PropertyChangeMask ); + + // - - - - - - - - - - GCs - - - - - - - - - - - - - - - - - + XGCValues values; + values.graphics_exposures = False; + values.fill_style = FillOpaqueStippled; + values.background = (1<<pSD->m_aVisual.GetDepth())-1; + values.foreground = 0; + + pSD->m_aCopyGC = XCreateGC( pDisp_, + pSD->m_aRefWindow, + GCGraphicsExposures + | GCForeground + | GCBackground, + &values ); + pSD->m_aAndInvertedGC= XCreateGC( pDisp_, + pSD->m_aRefWindow, + GCGraphicsExposures + | GCForeground + | GCBackground, + &values ); + pSD->m_aAndGC = XCreateGC( pDisp_, + pSD->m_aRefWindow, + GCGraphicsExposures + | GCForeground + | GCBackground, + &values ); + pSD->m_aOrGC = XCreateGC( pDisp_, + pSD->m_aRefWindow, + GCGraphicsExposures + | GCForeground + | GCBackground, + &values ); + pSD->m_aStippleGC = XCreateGC( pDisp_, + pSD->m_aRefWindow, + GCGraphicsExposures + | GCFillStyle + | GCForeground + | GCBackground, + &values ); + + XSetFunction( pDisp_, pSD->m_aAndInvertedGC, GXandInverted ); + XSetFunction( pDisp_, pSD->m_aAndGC, GXand ); + // PowerPC Solaris 2.5 (XSun 3500) Bug: GXor = GXnop + XSetFunction( pDisp_, pSD->m_aOrGC, GXxor ); + + if( 1 == pSD->m_aVisual.GetDepth() ) + { + XSetFunction( pDisp_, pSD->m_aCopyGC, GXcopyInverted ); + pSD->m_aMonoGC = pSD->m_aCopyGC; + } + else + { + Pixmap hPixmap = XCreatePixmap( pDisp_, pSD->m_aRefWindow, 1, 1, 1 ); + pSD->m_aMonoGC = XCreateGC( pDisp_, + hPixmap, + GCGraphicsExposures, + &values ); + XFreePixmap( pDisp_, hPixmap ); + } + pSD->m_hInvert50 = XCreateBitmapFromData( pDisp_, + pSD->m_aRefWindow, + reinterpret_cast<const char*>(invert50_bits), + invert50_width, + invert50_height ); + } + return pSD; +} + +void SalDisplay::Init() +{ + for( Cursor & aCsr : aPointerCache_ ) + aCsr = None; + + m_bXinerama = false; + + int nDisplayScreens = ScreenCount( pDisp_ ); + m_aScreens = std::vector<ScreenData>(nDisplayScreens); + + bool bExactResolution = false; + /* #i15507# + * Xft resolution should take precedence since + * it is what modern desktops use. + */ + const char* pValStr = XGetDefault( pDisp_, "Xft", "dpi" ); + if( pValStr != nullptr ) + { + const OString aValStr( pValStr ); + const tools::Long nDPI = static_cast<tools::Long>(aValStr.toDouble()); + // guard against insane resolution + if( sal_ValidDPI(nDPI) ) + { + aResolution_ = Pair( nDPI, nDPI ); + bExactResolution = true; + } + } + if( !bExactResolution ) + { + /* if Xft.dpi is not set, try and find the DPI from the + * reported screen sizes and resolution. If there are multiple + * screens, just fall back to the default 96x96 + */ + tools::Long xDPI = 96; + tools::Long yDPI = 96; + if (m_aScreens.size() == 1) { + xDPI = static_cast<tools::Long>(round(DisplayWidth(pDisp_, 0)*25.4/DisplayWidthMM(pDisp_, 0))); + yDPI = static_cast<tools::Long>(round(DisplayHeight(pDisp_, 0)*25.4/DisplayHeightMM(pDisp_, 0))); + // if either is invalid set it equal to the other + if (!sal_ValidDPI(xDPI) && sal_ValidDPI(yDPI)) + xDPI = yDPI; + if (!sal_ValidDPI(yDPI) && sal_ValidDPI(xDPI)) + yDPI = xDPI; + // if both are invalid, reset them to the default + if (!sal_ValidDPI(xDPI) && !sal_ValidDPI(yDPI)) + xDPI = yDPI = 96; + } + aResolution_ = Pair( xDPI, yDPI ); + } + + nMaxRequestSize_ = XExtendedMaxRequestSize( pDisp_ ) * 4; + if( !nMaxRequestSize_ ) + nMaxRequestSize_ = XMaxRequestSize( pDisp_ ) * 4; + + meServerVendor = sal_GetServerVendor(pDisp_); + X11SalBitmap::ImplCreateCache(); + + // - - - - - - - - - - Synchronize - - - - - - - - - - - - - + if( getenv( "SAL_SYNCHRONIZE" ) ) + XSynchronize( pDisp_, True ); + + // - - - - - - - - - - Keyboardmapping - - - - - - - - - - - + ModifierMapping(); + + // - - - - - - - - - - Window Manager - - - - - - - - - - - + m_pWMAdaptor = ::vcl_sal::WMAdaptor::createWMAdaptor( this ); + + InitXinerama(); + +#ifdef DBG_UTIL + PrintInfo(); +#endif +} + +void SalX11Display::SetupInput() +{ + GetGenericUnixSalData()->ErrorTrapPush(); + SalI18N_KeyboardExtension *pKbdExtension = new SalI18N_KeyboardExtension( pDisp_ ); + XSync( pDisp_, False ); + + bool bError = GetGenericUnixSalData()->ErrorTrapPop( false ); + GetGenericUnixSalData()->ErrorTrapPush(); + pKbdExtension->UseExtension( ! bError ); + GetGenericUnixSalData()->ErrorTrapPop(); + + SetKbdExtension( pKbdExtension ); +} + +// Sound +void SalDisplay::Beep() const +{ + XBell( pDisp_, 100 ); +} + +// Keyboard + +namespace { + +bool InitXkb(Display* dpy) +{ + int nOpcode, nEvent, nError; + int nXkbMajor = XkbMajorVersion; + int nXkbMinor = XkbMinorVersion; + + if (!XkbLibraryVersion(&nXkbMajor, &nXkbMinor)) + return false; + + return XkbQueryExtension( + dpy, &nOpcode, &nEvent, &nError, &nXkbMajor, &nXkbMinor); +} + +unsigned int GetKeySymMask(Display* dpy, KeySym nKeySym) +{ + int nMask = 0; + XModifierKeymap* pXmkMap = XGetModifierMapping(dpy); + KeyCode nKeyCode = XKeysymToKeycode(dpy, nKeySym); + if (nKeyCode == NoSymbol) + return 0; + + for (int i = 0; i < 8; ++i) + { + KeyCode nThisKeyCode = pXmkMap->modifiermap[pXmkMap->max_keypermod*i]; + if (nThisKeyCode == nKeyCode) + nMask = 1 << i; + } + XFreeModifiermap(pXmkMap); + return nMask; +} + +} + +void SalDisplay::SimulateKeyPress( sal_uInt16 nKeyCode ) +{ + if (nKeyCode != KEY_CAPSLOCK) + return; + + Display* dpy = GetDisplay(); + if (!InitXkb(dpy)) + return; + + unsigned int nMask = GetKeySymMask(dpy, XK_Caps_Lock); + XkbStateRec xkbState; + XkbGetState(dpy, XkbUseCoreKbd, &xkbState); + unsigned int nCapsLockState = xkbState.locked_mods & nMask; + if (nCapsLockState) + XkbLockModifiers (dpy, XkbUseCoreKbd, nMask, 0); + else + XkbLockModifiers (dpy, XkbUseCoreKbd, nMask, nMask); +} + +KeyIndicatorState SalDisplay::GetIndicatorState() const +{ + unsigned int _state = 0; + KeyIndicatorState nState = KeyIndicatorState::NONE; + XkbGetIndicatorState(pDisp_, XkbUseCoreKbd, &_state); + + if (_state & 0x00000001) + nState |= KeyIndicatorState::CAPSLOCK; + if (_state & 0x00000002) + nState |= KeyIndicatorState::NUMLOCK; + if (_state & 0x00000004) + nState |= KeyIndicatorState::SCROLLLOCK; + + return nState; +} + +OUString SalDisplay::GetKeyNameFromKeySym( KeySym nKeySym ) const +{ + OUString aLang = Application::GetSettings().GetUILanguageTag().getLanguage(); + OUString aRet; + + // return an empty string for keysyms that are not bound to + // any key code + KeyCode aKeyCode = XKeysymToKeycode( GetDisplay(), nKeySym ); + static_assert(NoSymbol == 0, "X11 inconsistency"); + if( aKeyCode != NoSymbol ) + { + if( !nKeySym ) + aRet = "???"; + else + { + aRet = ::vcl_sal::getKeysymReplacementName( aLang, nKeySym ); + if( aRet.isEmpty() ) + { + const char *pString = XKeysymToString( nKeySym ); + if (pString) + { + int n = strlen( pString ); + if( n > 2 && pString[n-2] == '_' ) + aRet = OUString( pString, n-2, RTL_TEXTENCODING_ISO_8859_1 ); + else + aRet = OUString( pString, n, RTL_TEXTENCODING_ISO_8859_1 ); + } + else + aRet = "???"; + } + } + } + return aRet; +} + +static KeySym sal_XModifier2Keysym( Display *pDisplay, + XModifierKeymap const *pXModMap, + int n ) +{ + return XkbKeycodeToKeysym( pDisplay, + pXModMap->modifiermap[n*pXModMap->max_keypermod], + 0,0 ); +} + +void SalDisplay::ModifierMapping() +{ + XModifierKeymap *pXModMap = XGetModifierMapping( pDisp_ ); + + bNumLockFromXS_ = True; + nShiftKeySym_ = sal_XModifier2Keysym( pDisp_, pXModMap, ShiftMapIndex ); + nCtrlKeySym_ = sal_XModifier2Keysym( pDisp_, pXModMap, ControlMapIndex ); + nMod1KeySym_ = sal_XModifier2Keysym( pDisp_, pXModMap, Mod1MapIndex ); + // on Sun and SCO servers XLookupString does not account for NumLock + if( GetServerVendor() == vendor_sun ) + { + KeyCode aNumLock = XKeysymToKeycode( pDisp_, XK_Num_Lock ); + + if( aNumLock ) + for( int i = ShiftMapIndex; i <= Mod5MapIndex; i++ ) + { + if( pXModMap->modifiermap[i*pXModMap->max_keypermod] == aNumLock ) + { + bNumLockFromXS_ = False; + nNumLockIndex_ = i; + break; + } + } + } + + XFreeModifiermap( pXModMap ); +} + +OUString SalDisplay::GetKeyName( sal_uInt16 nKeyCode ) const +{ + OUString aStrMap; + OUString aCustomKeyName; + + if( nKeyCode & KEY_MOD1 ) + aStrMap += GetKeyNameFromKeySym( nCtrlKeySym_ ); + + if( nKeyCode & KEY_MOD2 ) + { + if( !aStrMap.isEmpty() ) + aStrMap += "+"; + aStrMap += GetKeyNameFromKeySym( nMod1KeySym_ ); + } + + if( nKeyCode & KEY_SHIFT ) + { + if( !aStrMap.isEmpty() ) + aStrMap += "+"; + aStrMap += GetKeyNameFromKeySym( nShiftKeySym_ ); + } + nKeyCode &= 0x0FFF; + + KeySym nKeySym = 0; + + if( KEY_0 <= nKeyCode && nKeyCode <= KEY_9 ) + nKeySym = XK_0 + (nKeyCode - KEY_0); + else if( KEY_A <= nKeyCode && nKeyCode <= KEY_Z ) + nKeySym = XK_A + (nKeyCode - KEY_A); + else if( KEY_F1 <= nKeyCode && nKeyCode <= KEY_F26 ) // does this key exist? + nKeySym = XK_F1 + (nKeyCode - KEY_F1); + else switch( nKeyCode ) + { + case KEY_DOWN: + nKeySym = XK_Down; + break; + case KEY_UP: + nKeySym = XK_Up; + break; + case KEY_LEFT: + nKeySym = XK_Left; + break; + case KEY_RIGHT: + nKeySym = XK_Right; + break; + case KEY_HOME: + nKeySym = XK_Home; + break; + case KEY_END: + nKeySym = XK_End; + break; + case KEY_PAGEUP: + nKeySym = XK_Page_Up; + break; + case KEY_PAGEDOWN: + nKeySym = XK_Page_Down; + break; + case KEY_RETURN: + nKeySym = XK_Return; + break; + case KEY_ESCAPE: + nKeySym = XK_Escape; + break; + case KEY_TAB: + nKeySym = XK_Tab; + break; + case KEY_BACKSPACE: + nKeySym = XK_BackSpace; + break; + case KEY_SPACE: + nKeySym = XK_space; + break; + case KEY_INSERT: + nKeySym = XK_Insert; + break; + case KEY_DELETE: + nKeySym = XK_Delete; + break; + + #if !defined (SunXK_Undo) + // we don't intend to use SunXK_Undo, but if it has not been + // defined already, then we _do_ need the following: + #define SunXK_Props 0x1005FF70 + #define SunXK_Front 0x1005FF71 + #define SunXK_Copy 0x1005FF72 + #define SunXK_Open 0x1005FF73 + #define SunXK_Paste 0x1005FF74 + #define SunXK_Cut 0x1005FF75 + #endif + // the following are for XF86 systems + #define XF86XK_Copy 0x1008FF57 + #define XF86XK_Cut 0x1008FF58 + #define XF86XK_Open 0x1008FF6B + #define XF86XK_Paste 0x1008FF6D + // which leaves Apollo and OSF systems in the lurch + + case KEY_REPEAT: + nKeySym = XK_Redo; + break; + case KEY_PROPERTIES: + nKeySym = SunXK_Props; + break; + case KEY_UNDO: + nKeySym = XK_Undo; + break; + case KEY_FRONT: + nKeySym = SunXK_Front; + break; + case KEY_COPY: + nKeySym = GetServerVendor() == vendor_sun ? SunXK_Copy : XF86XK_Copy; + break; + case KEY_OPEN: + nKeySym = GetServerVendor() == vendor_sun ? SunXK_Open : XF86XK_Open; + break; + case KEY_PASTE: + nKeySym = GetServerVendor() == vendor_sun ? SunXK_Paste : XF86XK_Paste; + break; + case KEY_FIND: + nKeySym = XK_Find; + break; + case KEY_CUT: + nKeySym = GetServerVendor() == vendor_sun ? SunXK_Cut : XF86XK_Cut; + /* The original code here had: + nKeySym = GetServerVendor() == vendor_sun ? SunXK_Cut : XK_L10; + if anyone can remember which non-vendor_sun system used this + XK_L10 keysym, and why this hack only applied to KEY_CUT, + then please re-hack this code to put it back + */ + break; + case KEY_ADD: + aCustomKeyName = "+"; + break; + case KEY_SUBTRACT: + aCustomKeyName = "-"; + break; + case KEY_MULTIPLY: + nKeySym = XK_asterisk; + break; + case KEY_DIVIDE: + nKeySym = XK_slash; + break; + case KEY_POINT: + aCustomKeyName = "."; + break; + case KEY_COMMA: + nKeySym = XK_comma; + break; + case KEY_LESS: + nKeySym = XK_less; + break; + case KEY_GREATER: + nKeySym = XK_greater; + break; + case KEY_EQUAL: + nKeySym = XK_equal; + break; + case KEY_HELP: + nKeySym = XK_Help; + break; + case KEY_HANGUL_HANJA: + nKeySym = XK_Hangul_Hanja; + break; + case KEY_TILDE: + nKeySym = XK_asciitilde; + break; + case KEY_QUOTELEFT: + nKeySym = XK_grave; + break; + case KEY_BRACKETLEFT: + aCustomKeyName = "["; + break; + case KEY_BRACKETRIGHT: + aCustomKeyName = "]"; + break; + case KEY_SEMICOLON: + aCustomKeyName = ";"; + break; + case KEY_QUOTERIGHT: + aCustomKeyName = "'"; + break; + default: + nKeySym = 0; + break; + } + + if( nKeySym ) + { + OUString aKeyName = GetKeyNameFromKeySym( nKeySym ); + if( !aKeyName.isEmpty() ) + { + if( !aStrMap.isEmpty() ) + aStrMap += "+"; + aStrMap += aKeyName; + } + else + aStrMap.clear(); + } + else if (!aCustomKeyName.isEmpty()) + { + // For semicolon, bracket left and bracket right, it's better to use + // their keys than their names. (fdo#32891) + if (!aStrMap.isEmpty()) + aStrMap += "+"; + aStrMap += aCustomKeyName; + } + else + aStrMap.clear(); + + return aStrMap; +} + +#ifndef IsISOKey +#define IsISOKey( n ) (0x0000FE00==((n)&0xFFFFFF00)) +#endif + +sal_uInt16 SalDisplay::GetKeyCode( KeySym keysym, char*pcPrintable ) const +{ + sal_uInt16 nKey = 0; + + if( XK_a <= keysym && XK_z >= keysym ) + nKey = static_cast<sal_uInt16>(KEY_A + (keysym - XK_a)); + else if( XK_A <= keysym && XK_Z >= keysym ) + nKey = static_cast<sal_uInt16>(KEY_A + (keysym - XK_A)); + else if( XK_0 <= keysym && XK_9 >= keysym ) + nKey = static_cast<sal_uInt16>(KEY_0 + (keysym - XK_0)); + else if( IsModifierKey( keysym ) ) + ; + else if( IsKeypadKey( keysym ) ) + { + if( (keysym >= XK_KP_0) && (keysym <= XK_KP_9) ) + { + nKey = static_cast<sal_uInt16>(KEY_0 + (keysym - XK_KP_0)); + *pcPrintable = '0' + nKey - KEY_0; + } + else if( IsPFKey( keysym ) ) + nKey = static_cast<sal_uInt16>(KEY_F1 + (keysym - XK_KP_F1)); + else switch( keysym ) + { + case XK_KP_Space: + nKey = KEY_SPACE; + *pcPrintable = ' '; + break; + case XK_KP_Tab: + nKey = KEY_TAB; + break; + case XK_KP_Enter: + nKey = KEY_RETURN; + break; + case XK_KP_Begin: + case XK_KP_Home: + nKey = KEY_HOME; + break; + case XK_KP_Left: + nKey = KEY_LEFT; + break; + case XK_KP_Up: + nKey = KEY_UP; + break; + case XK_KP_Right: + nKey = KEY_RIGHT; + break; + case XK_KP_Down: + nKey = KEY_DOWN; + break; + case XK_KP_Page_Up: // XK_KP_Page_Up + nKey = KEY_PAGEUP; + break; + case XK_KP_Page_Down: // XK_KP_Page_Down + nKey = KEY_PAGEDOWN; + break; + case XK_KP_End: + nKey = KEY_END; + break; + case XK_KP_Insert: + nKey = KEY_INSERT; + break; + case XK_KP_Delete: + nKey = KEY_DELETE; + break; + case XK_KP_Equal: + nKey = KEY_EQUAL; + *pcPrintable = '='; + break; + case XK_KP_Multiply: + nKey = KEY_MULTIPLY; + *pcPrintable = '*'; + break; + case XK_KP_Add: + nKey = KEY_ADD; + *pcPrintable = '+'; + break; + case XK_KP_Separator: + nKey = KEY_DECIMAL; + *pcPrintable = ','; + break; + case XK_KP_Subtract: + nKey = KEY_SUBTRACT; + *pcPrintable = '-'; + break; + case XK_KP_Decimal: + nKey = KEY_DECIMAL; + *pcPrintable = '.'; + break; + case XK_KP_Divide: + nKey = KEY_DIVIDE; + *pcPrintable = '/'; + break; + } + } + else if( IsFunctionKey( keysym ) ) + { + if( bNumLockFromXS_ ) + { + if( keysym >= XK_F1 && keysym <= XK_F26 ) + nKey = static_cast<sal_uInt16>(KEY_F1 + keysym - XK_F1); + } + else switch( keysym ) + { + // - - - - - Sun X-Server keyboard without Cursorblock ??? - - - + case XK_R7: // XK_F27: + nKey = KEY_HOME; + break; + case XK_R8: // XK_F28: + nKey = KEY_UP; + break; + case XK_R9: // XK_F29: + nKey = KEY_PAGEUP; + break; + case XK_R10: // XK_F30: + nKey = KEY_LEFT; + break; + case XK_R11: // XK_F31: + nKey = 0; // KEY_F31 + break; + case XK_R12: // XK_F32: + nKey = KEY_RIGHT; + break; + case XK_R13: // XK_F33: + nKey = KEY_END; + break; + case XK_R14: // XK_F34: + nKey = KEY_DOWN; + break; + case XK_R15: // XK_F35: + nKey = KEY_PAGEDOWN; + break; + // - - - - - Sun X-Server keyboard ??? - - - - - - - - - - - - + case XK_L1: // XK_F11: + nKey = KEY_F11; // on a sun keyboard this actually is usually SunXK_Stop = 0x0000FF69 (XK_Cancel), + // but VCL doesn't have a key definition for that + break; + case XK_L2: // XK_F12: + if ( GetServerVendor() == vendor_sun ) + nKey = KEY_REPEAT; + else + nKey = KEY_F12; + break; + case XK_L3: // XK_F13: + nKey = KEY_PROPERTIES; // KEY_F13 + break; + case XK_L4: // XK_F14: + nKey = KEY_UNDO; // KEY_F14 + break; + case XK_L5: // XK_F15: + nKey = KEY_F15; // KEY_FRONT + break; + case XK_L6: // XK_F16: + nKey = KEY_COPY; // KEY_F16 + break; + case XK_L7: // XK_F17: + nKey = KEY_F17; // KEY_OPEN + break; + case XK_L8: // XK_F18: + nKey = KEY_PASTE; // KEY_F18 + break; + case XK_L9: // XK_F19: + nKey = KEY_F19; // KEY_FIND + break; + case XK_L10: // XK_F20: + nKey = KEY_CUT; // KEY_F20 + break; + default: + if( keysym >= XK_F1 && keysym <= XK_F26 ) + nKey = static_cast<sal_uInt16>(KEY_F1 + keysym - XK_F1); + break; + } + } + else if( IsCursorKey( keysym ) ) + { + switch( keysym ) + { + case XK_Begin: + case XK_Home: + nKey = KEY_HOME; + break; + case XK_Left: + nKey = KEY_LEFT; + break; + case XK_Up: + nKey = KEY_UP; + break; + case XK_Right: + nKey = KEY_RIGHT; + break; + case XK_Down: + nKey = KEY_DOWN; + break; + case XK_Page_Up: // XK_Page_Up + nKey = KEY_PAGEUP; + break; + case XK_Page_Down: // XK_Page_Down + nKey = KEY_PAGEDOWN; + break; + case XK_End: + nKey = KEY_END; + break; + } + } + else if( IsMiscFunctionKey( keysym ) ) + { + switch( keysym ) + { + case XK_Insert: + nKey = KEY_INSERT; + break; + case XK_Redo: + nKey = KEY_REPEAT; + break; + case XK_Undo: + nKey = KEY_UNDO; + break; + case XK_Find: + nKey = KEY_FIND; + break; + case XK_Help: + nKey = KEY_HELP; + break; + case XK_Menu: + nKey = KEY_CONTEXTMENU; + break; + } + } + else if( IsISOKey( keysym ) ) // XK_ISO_ + { + switch( keysym ) + { + case 0xFE20: // XK_ISO_Left_Tab: + nKey = KEY_TAB; + break; + } + } + else switch( keysym ) + { + case XK_Return: + nKey = KEY_RETURN; + break; + case XK_BackSpace: + nKey = KEY_BACKSPACE; + break; + case XK_Delete: + nKey = KEY_DELETE; + break; + case XK_space: + nKey = KEY_SPACE; + break; + case XK_Tab: + nKey = KEY_TAB; + break; + case XK_Escape: + nKey = KEY_ESCAPE; + break; + case XK_plus: + nKey = KEY_ADD; + break; + case XK_minus: + nKey = KEY_SUBTRACT; + break; + case XK_asterisk: + nKey = KEY_MULTIPLY; + break; + case XK_slash: + nKey = KEY_DIVIDE; + break; + case XK_period: + nKey = KEY_POINT; + *pcPrintable = '.'; + break; + case XK_comma: + nKey = KEY_COMMA; + break; + case XK_less: + nKey = KEY_LESS; + break; + case XK_greater: + nKey = KEY_GREATER; + break; + case XK_equal: + nKey = KEY_EQUAL; + break; + case XK_Hangul_Hanja: + nKey = KEY_HANGUL_HANJA; + break; + case XK_asciitilde: + nKey = KEY_TILDE; + *pcPrintable = '~'; + break; + case XK_grave: + nKey = KEY_QUOTELEFT; + *pcPrintable = '`'; + break; + case XK_bracketleft: + nKey = KEY_BRACKETLEFT; + *pcPrintable = '['; + break; + case XK_bracketright: + nKey = KEY_BRACKETRIGHT; + *pcPrintable = ']'; + break; + case XK_semicolon: + nKey = KEY_SEMICOLON; + *pcPrintable = ';'; + break; + case XK_quoteright: + nKey = KEY_QUOTERIGHT; + *pcPrintable = '\''; + break; + // - - - - - - - - - - - - - Apollo - - - - - - - - - - - - - 0x1000 + case 0x1000FF02: // apXK_Copy + nKey = KEY_COPY; + break; + case 0x1000FF03: // apXK_Cut + nKey = KEY_CUT; + break; + case 0x1000FF04: // apXK_Paste + nKey = KEY_PASTE; + break; + case 0x1000FF14: // apXK_Repeat + nKey = KEY_REPEAT; + break; + // Exit, Save + // - - - - - - - - - - - - - - D E C - - - - - - - - - - - - - 0x1000 + case 0x1000FF00: + nKey = KEY_DELETE; + break; + // - - - - - - - - - - - - - - H P - - - - - - - - - - - - - 0x1000 + case 0x1000FF73: // hpXK_DeleteChar + nKey = KEY_DELETE; + break; + case 0x1000FF74: // hpXK_BackTab + case 0x1000FF75: // hpXK_KP_BackTab + nKey = KEY_TAB; + break; + // - - - - - - - - - - - - - - I B M - - - - - - - - - - - - - + // - - - - - - - - - - - - - - O S F - - - - - - - - - - - - - 0x1004 + case 0x1004FF02: // osfXK_Copy + nKey = KEY_COPY; + break; + case 0x1004FF03: // osfXK_Cut + nKey = KEY_CUT; + break; + case 0x1004FF04: // osfXK_Paste + nKey = KEY_PASTE; + break; + case 0x1004FF07: // osfXK_BackTab + nKey = KEY_TAB; + break; + case 0x1004FF08: // osfXK_BackSpace + nKey = KEY_BACKSPACE; + break; + case 0x1004FF1B: // osfXK_Escape + nKey = KEY_ESCAPE; + break; + // Up, Down, Left, Right, PageUp, PageDown + // - - - - - - - - - - - - - - S C O - - - - - - - - - - - - - + // - - - - - - - - - - - - - - S G I - - - - - - - - - - - - - 0x1007 + // - - - - - - - - - - - - - - S N I - - - - - - - - - - - - - + // - - - - - - - - - - - - - - S U N - - - - - - - - - - - - - 0x1005 + case 0x1005FF10: // SunXK_F36 + nKey = KEY_F11; + break; + case 0x1005FF11: // SunXK_F37 + nKey = KEY_F12; + break; + case 0x1005FF70: // SunXK_Props + nKey = KEY_PROPERTIES; + break; + case 0x1005FF71: // SunXK_Front + nKey = KEY_FRONT; + break; + case 0x1005FF72: // SunXK_Copy + nKey = KEY_COPY; + break; + case 0x1005FF73: // SunXK_Open + nKey = KEY_OPEN; + break; + case 0x1005FF74: // SunXK_Paste + nKey = KEY_PASTE; + break; + case 0x1005FF75: // SunXK_Cut + nKey = KEY_CUT; + break; + } + return nKey; +} + +KeySym SalDisplay::GetKeySym( XKeyEvent *pEvent, + char *pPrintable, + int *pLen, + KeySym *pUnmodifiedKeySym, + Status *pStatusReturn, + XIC aInputContext ) const +{ + KeySym nKeySym = 0; + memset( pPrintable, 0, *pLen ); + *pStatusReturn = 0; + + SalI18N_InputMethod* const pInputMethod = + pXLib_ ? pXLib_->GetInputMethod() : nullptr; + + // first get the printable of the possibly modified KeySym + if ( (aInputContext == nullptr) + || (pEvent->type == KeyRelease) + || (pInputMethod != nullptr && pInputMethod->PosixLocale()) ) + { + // XmbLookupString must not be called for KeyRelease events + // Cannot enter space in c locale problem #89616# #88978# btraq #4478197 + *pLen = XLookupString( pEvent, pPrintable, 1, &nKeySym, nullptr ); + } + else + { + *pLen = XmbLookupString( aInputContext, + pEvent, pPrintable, *pLen - 1, &nKeySym, pStatusReturn ); + + // Lookup the string again, now with appropriate size + if ( *pStatusReturn == XBufferOverflow ) + { + pPrintable[ 0 ] = '\0'; + return 0; + } + + switch ( *pStatusReturn ) + { + case XBufferOverflow: + /* unhandled error */ + break; + case XLookupNone: + /* unhandled error */ + break; + case XLookupKeySym: + /* this is a strange one: on exceed sometimes + * no printable is returned for the first char entered, + * just to retry lookup solves the problem. The problem + * is not yet fully understood, so restrict 2nd lookup + * to 7bit ascii chars */ + if ( (XK_space <= nKeySym) && (XK_asciitilde >= nKeySym) ) + { + *pLen = 1; + pPrintable[ 0 ] = static_cast<char>(nKeySym); + } + break; + case XLookupBoth: + case XLookupChars: + + /* nothing to, char already in pPrintable */ + break; + } + } + + if( !bNumLockFromXS_ + && (IsCursorKey(nKeySym) + || IsFunctionKey(nKeySym) + || IsKeypadKey(nKeySym) + || XK_Delete == nKeySym ) ) + { + // For some X-servers special care is needed for Keypad keys. + // For example Solaris XServer: + // 2, 4, 6, 8 are classified as Cursorkeys (Up, Down, Left, Right) + // 1, 3, 5, 9 are classified as Functionkeys (F27,F29,F33,F35) + // 0 as Keypadkey, and the decimal point key not at all (KP_Insert) + KeySym nNewKeySym = XLookupKeysym( pEvent, nNumLockIndex_ ); + if( nNewKeySym != NoSymbol ) + nKeySym = nNewKeySym; + } + + // Now get the unmodified KeySym for KeyCode retrieval + // try to strip off modifiers, e.g. Ctrl-$ becomes Ctrl-Shift-4 + *pUnmodifiedKeySym = XkbKeycodeToKeysym( GetDisplay(), pEvent->keycode, 0, 0); + + return nKeySym; +} + +// Pointer +static unsigned char nullmask_bits[] = { 0x00, 0x00, 0x00, 0x00 }; +static unsigned char nullcurs_bits[] = { 0x00, 0x00, 0x00, 0x00 }; + +#define MAKE_BITMAP( name ) \ + XCreateBitmapFromData( pDisp_, \ + DefaultRootWindow( pDisp_ ), \ + reinterpret_cast<const char*>(name##_bits), \ + name##_width, \ + name##_height ) + +#define MAKE_CURSOR( name ) \ + aCursBitmap = MAKE_BITMAP( name##curs ); \ + aMaskBitmap = MAKE_BITMAP( name##mask ); \ + nXHot = name##curs_x_hot; \ + nYHot = name##curs_y_hot + +Cursor SalDisplay::GetPointer( PointerStyle ePointerStyle ) +{ + Cursor &aCur = aPointerCache_[ePointerStyle]; + + if( aCur != None ) + return aCur; + + Pixmap aCursBitmap = None, aMaskBitmap = None; + unsigned int nXHot = 0, nYHot = 0; + + switch( ePointerStyle ) + { + case PointerStyle::Null: + MAKE_CURSOR( null ); + break; + case PointerStyle::Arrow: + aCur = XCreateFontCursor( pDisp_, XC_left_ptr ); + SAL_WARN_IF( aCur == None, "vcl", "GetPointer: Could not define cursor" ); + break; + case PointerStyle::Wait: + aCur = XCreateFontCursor( pDisp_, XC_watch ); + break; + case PointerStyle::Text: // Mouse Pointer is a "I" Beam + aCur = XCreateFontCursor( pDisp_, XC_xterm ); + SAL_WARN_IF( aCur == None, "vcl", "GetPointer: Could not define cursor" ); + break; + case PointerStyle::Help: + aCur = XCreateFontCursor( pDisp_, XC_question_arrow ); + SAL_WARN_IF( aCur == None, "vcl", "GetPointer: Could not define cursor" ); + break; + case PointerStyle::Cross: // Mouse Pointer is a cross + aCur = XCreateFontCursor( pDisp_, XC_crosshair ); + SAL_WARN_IF( aCur == None, "vcl", "GetPointer: Could not define cursor" ); + break; + case PointerStyle::NSize: + aCur = XCreateFontCursor( pDisp_, XC_sb_v_double_arrow ); + SAL_WARN_IF( aCur == None, "vcl", "GetPointer: Could not define cursor" ); + break; + case PointerStyle::SSize: + aCur = XCreateFontCursor( pDisp_, XC_sb_v_double_arrow ); + SAL_WARN_IF( aCur == None, "vcl", "GetPointer: Could not define cursor" ); + break; + case PointerStyle::WSize: + aCur = XCreateFontCursor( pDisp_, XC_sb_h_double_arrow ); + SAL_WARN_IF( aCur == None, "vcl", "GetPointer: Could not define cursor" ); + break; + case PointerStyle::ESize: + aCur = XCreateFontCursor( pDisp_, XC_sb_h_double_arrow ); + SAL_WARN_IF( aCur == None, "vcl", "GetPointer: Could not define cursor" ); + break; + case PointerStyle::WindowNSize: + aCur = XCreateFontCursor( pDisp_, XC_top_side ); + SAL_WARN_IF( aCur == None, "vcl", "GetPointer: Could not define cursor" ); + break; + case PointerStyle::WindowSSize: + aCur = XCreateFontCursor( pDisp_, XC_bottom_side ); + SAL_WARN_IF( aCur == None, "vcl", "GetPointer: Could not define cursor" ); + break; + case PointerStyle::WindowWSize: + aCur = XCreateFontCursor( pDisp_, XC_left_side ); + SAL_WARN_IF( aCur == None, "vcl", "GetPointer: Could not define cursor" ); + break; + case PointerStyle::WindowESize: + aCur = XCreateFontCursor( pDisp_, XC_right_side ); + SAL_WARN_IF( aCur == None, "vcl", "GetPointer: Could not define cursor" ); + break; + case PointerStyle::NWSize: + aCur = XCreateFontCursor( pDisp_, XC_top_left_corner ); + break; + case PointerStyle::NESize: + aCur = XCreateFontCursor( pDisp_, XC_top_right_corner ); + break; + case PointerStyle::SWSize: + aCur = XCreateFontCursor( pDisp_, XC_bottom_left_corner ); + break; + case PointerStyle::SESize: + aCur = XCreateFontCursor( pDisp_, XC_bottom_right_corner ); + break; + case PointerStyle::WindowNWSize: + aCur = XCreateFontCursor( pDisp_, XC_top_left_corner ); + SAL_WARN_IF( aCur == None, "vcl", "GetPointer: Could not define cursor" ); + break; + case PointerStyle::WindowNESize: + aCur = XCreateFontCursor( pDisp_, XC_top_right_corner ); + SAL_WARN_IF( aCur == None, "vcl", "GetPointer: Could not define cursor" ); + break; + case PointerStyle::WindowSWSize: + aCur = XCreateFontCursor( pDisp_, XC_bottom_left_corner ); + SAL_WARN_IF( aCur == None, "vcl", "GetPointer: Could not define cursor" ); + break; + case PointerStyle::WindowSESize: + aCur = XCreateFontCursor( pDisp_, XC_bottom_right_corner ); + SAL_WARN_IF( aCur == None, "vcl", "GetPointer: Could not define cursor" ); + break; + case PointerStyle::HSplit: + aCur = XCreateFontCursor( pDisp_, XC_sb_h_double_arrow ); + break; + case PointerStyle::VSplit: + aCur = XCreateFontCursor( pDisp_, XC_sb_v_double_arrow ); + break; + case PointerStyle::HSizeBar: + aCur = XCreateFontCursor( pDisp_, XC_sb_h_double_arrow ); // ??? + SAL_WARN_IF( aCur == None, "vcl", "GetPointer: Could not define cursor" ); + break; + case PointerStyle::VSizeBar: + aCur = XCreateFontCursor( pDisp_, XC_sb_v_double_arrow ); // ??? + SAL_WARN_IF( aCur == None, "vcl", "GetPointer: Could not define cursor" ); + break; + case PointerStyle::RefHand: + aCur = XCreateFontCursor( pDisp_, XC_hand1 ); + SAL_WARN_IF( aCur == None, "vcl", "GetPointer: Could not define cursor" ); + break; + case PointerStyle::Hand: + aCur = XCreateFontCursor( pDisp_, XC_hand2 ); + break; + case PointerStyle::Magnify: + MAKE_CURSOR( magnify_ ); + break; + case PointerStyle::Fill: + MAKE_CURSOR( fill_ ); + break; + case PointerStyle::Move: + aCur = XCreateFontCursor( pDisp_, XC_fleur ); + break; + case PointerStyle::MoveData: + MAKE_CURSOR( movedata_ ); + break; + case PointerStyle::CopyData: + MAKE_CURSOR( copydata_ ); + break; + case PointerStyle::MoveFile: + MAKE_CURSOR( movefile_ ); + break; + case PointerStyle::CopyFile: + MAKE_CURSOR( copyfile_ ); + break; + case PointerStyle::MoveFiles: + MAKE_CURSOR( movefiles_ ); + break; + case PointerStyle::CopyFiles: + MAKE_CURSOR( copyfiles_ ); + break; + case PointerStyle::NotAllowed: + MAKE_CURSOR( nodrop_ ); + break; + case PointerStyle::Rotate: + MAKE_CURSOR( rotate_ ); + break; + case PointerStyle::HShear: + MAKE_CURSOR( hshear_ ); + break; + case PointerStyle::VShear: + MAKE_CURSOR( vshear_ ); + break; + case PointerStyle::DrawLine: + MAKE_CURSOR( drawline_ ); + break; + case PointerStyle::DrawRect: + MAKE_CURSOR( drawrect_ ); + break; + case PointerStyle::DrawPolygon: + MAKE_CURSOR( drawpolygon_ ); + break; + case PointerStyle::DrawBezier: + MAKE_CURSOR( drawbezier_ ); + break; + case PointerStyle::DrawArc: + MAKE_CURSOR( drawarc_ ); + break; + case PointerStyle::DrawPie: + MAKE_CURSOR( drawpie_ ); + break; + case PointerStyle::DrawCircleCut: + MAKE_CURSOR( drawcirclecut_ ); + break; + case PointerStyle::DrawEllipse: + MAKE_CURSOR( drawellipse_ ); + break; + case PointerStyle::DrawConnect: + MAKE_CURSOR( drawconnect_ ); + break; + case PointerStyle::DrawText: + MAKE_CURSOR( drawtext_ ); + break; + case PointerStyle::Mirror: + MAKE_CURSOR( mirror_ ); + break; + case PointerStyle::Crook: + MAKE_CURSOR( crook_ ); + break; + case PointerStyle::Crop: + MAKE_CURSOR( crop_ ); + break; + case PointerStyle::MovePoint: + MAKE_CURSOR( movepoint_ ); + break; + case PointerStyle::MoveBezierWeight: + MAKE_CURSOR( movebezierweight_ ); + break; + case PointerStyle::DrawFreehand: + MAKE_CURSOR( drawfreehand_ ); + break; + case PointerStyle::DrawCaption: + MAKE_CURSOR( drawcaption_ ); + break; + case PointerStyle::Pen: // Mouse Pointer is a pencil + aCur = XCreateFontCursor( pDisp_, XC_pencil ); + SAL_WARN_IF( aCur == None, "vcl", "GetPointer: Could not define cursor" ); + break; + case PointerStyle::LinkData: + MAKE_CURSOR( linkdata_ ); + break; + case PointerStyle::MoveDataLink: + MAKE_CURSOR( movedlnk_ ); + break; + case PointerStyle::CopyDataLink: + MAKE_CURSOR( copydlnk_ ); + break; + case PointerStyle::LinkFile: + MAKE_CURSOR( linkfile_ ); + break; + case PointerStyle::MoveFileLink: + MAKE_CURSOR( moveflnk_ ); + break; + case PointerStyle::CopyFileLink: + MAKE_CURSOR( copyflnk_ ); + break; + case PointerStyle::Chart: + MAKE_CURSOR( chart_ ); + break; + case PointerStyle::Detective: + MAKE_CURSOR( detective_ ); + break; + case PointerStyle::PivotCol: + MAKE_CURSOR( pivotcol_ ); + break; + case PointerStyle::PivotRow: + MAKE_CURSOR( pivotrow_ ); + break; + case PointerStyle::PivotField: + MAKE_CURSOR( pivotfld_ ); + break; + case PointerStyle::PivotDelete: + MAKE_CURSOR( pivotdel_ ); + break; + case PointerStyle::Chain: + MAKE_CURSOR( chain_ ); + break; + case PointerStyle::ChainNotAllowed: + MAKE_CURSOR( chainnot_ ); + break; + case PointerStyle::AutoScrollN: + MAKE_CURSOR(asn_ ); + break; + case PointerStyle::AutoScrollS: + MAKE_CURSOR( ass_ ); + break; + case PointerStyle::AutoScrollW: + MAKE_CURSOR( asw_ ); + break; + case PointerStyle::AutoScrollE: + MAKE_CURSOR( ase_ ); + break; + case PointerStyle::AutoScrollNW: + MAKE_CURSOR( asnw_ ); + break; + case PointerStyle::AutoScrollNE: + MAKE_CURSOR( asne_ ); + break; + case PointerStyle::AutoScrollSW: + MAKE_CURSOR( assw_ ); + break; + case PointerStyle::AutoScrollSE: + MAKE_CURSOR( asse_ ); + break; + case PointerStyle::AutoScrollNS: + MAKE_CURSOR( asns_ ); + break; + case PointerStyle::AutoScrollWE: + MAKE_CURSOR( aswe_ ); + break; + case PointerStyle::AutoScrollNSWE: + MAKE_CURSOR( asnswe_ ); + break; + case PointerStyle::TextVertical: + MAKE_CURSOR( vertcurs_ ); + break; + + // #i32329# Enhanced table selection + case PointerStyle::TabSelectS: + MAKE_CURSOR( tblsels_ ); + break; + case PointerStyle::TabSelectE: + MAKE_CURSOR( tblsele_ ); + break; + case PointerStyle::TabSelectSE: + MAKE_CURSOR( tblselse_ ); + break; + case PointerStyle::TabSelectW: + MAKE_CURSOR( tblselw_ ); + break; + case PointerStyle::TabSelectSW: + MAKE_CURSOR( tblselsw_ ); + break; + + case PointerStyle::HideWhitespace: + MAKE_CURSOR( hidewhitespace_ ); + break; + case PointerStyle::ShowWhitespace: + MAKE_CURSOR( showwhitespace_ ); + break; + case PointerStyle::FatCross: + MAKE_CURSOR( fatcross_ ); + break; + + default: + OSL_FAIL("pointer not implemented"); + aCur = XCreateFontCursor( pDisp_, XC_arrow ); + break; + } + + if( None == aCur ) + { + XColor aBlack, aWhite, aDummy; + Colormap hColormap = GetColormap(m_nXDefaultScreen).GetXColormap(); + + XAllocNamedColor( pDisp_, hColormap, "black", &aBlack, &aDummy ); + XAllocNamedColor( pDisp_, hColormap, "white", &aWhite, &aDummy ); + + aCur = XCreatePixmapCursor( pDisp_, + aCursBitmap, aMaskBitmap, + &aBlack, &aWhite, + nXHot, nYHot ); + + XFreePixmap( pDisp_, aCursBitmap ); + XFreePixmap( pDisp_, aMaskBitmap ); + } + + return aCur; +} + +int SalDisplay::CaptureMouse( SalFrame *pCapture ) +{ + static const char* pEnv = getenv( "SAL_NO_MOUSEGRABS" ); + + if( !pCapture ) + { + m_pCapture = nullptr; + if( !pEnv || !*pEnv ) + XUngrabPointer( GetDisplay(), CurrentTime ); + XFlush( GetDisplay() ); + return 0; + } + + m_pCapture = nullptr; + + // FIXME: get rid of X11SalFrame + const SystemEnvData* pEnvData = pCapture->GetSystemData(); + if( !pEnv || !*pEnv ) + { + int ret = XGrabPointer( GetDisplay(), + static_cast<::Window>(pEnvData->GetWindowHandle(pCapture)), + False, + PointerMotionMask| ButtonPressMask|ButtonReleaseMask, + GrabModeAsync, + GrabModeAsync, + None, + static_cast<X11SalFrame*>(pCapture)->GetCursor(), + CurrentTime ); + + if( ret != GrabSuccess ) + { + SAL_WARN("vcl", "SalDisplay::CaptureMouse could not grab pointer: " << ret); + return -1; + } + } + + m_pCapture = pCapture; + return 1; +} + +// Events + +bool SalX11Display::IsEvent() +{ + if( HasUserEvents() || XEventsQueued( pDisp_, QueuedAlready ) ) + return true; + + XFlush( pDisp_ ); + return false; +} + +void SalX11Display::Yield() +{ + if( DispatchInternalEvent() ) + return; + + XEvent aEvent; + DBG_ASSERT(GetSalInstance()->GetYieldMutex()->IsCurrentThread(), + "will crash soon since solar mutex not locked in SalDisplay::Yield" ); + + XNextEvent( pDisp_, &aEvent ); + + // coverity[overrun-buffer-val : FALSE] - coverity has problems with uno::Sequence + Dispatch( &aEvent ); + +#ifdef DBG_UTIL + if( GetX11SalData()->HasXErrorOccurred() ) + { + XFlush( pDisp_ ); + DbgPrintDisplayEvent("SalDisplay::Yield (WasXError)", &aEvent); + } +#endif + GetX11SalData()->ResetXErrorOccurred(); +} + +void SalX11Display::Dispatch( XEvent *pEvent ) +{ + SalI18N_InputMethod* const pInputMethod = + pXLib_ ? pXLib_->GetInputMethod() : nullptr; + + if( pInputMethod ) + { + ::Window aFrameWindow = None; + if( pEvent->type == KeyPress || pEvent->type == KeyRelease ) + { + const ::Window aWindow = pEvent->xkey.window; + for( auto pSalFrame : m_aFrames ) + { + const X11SalFrame* pFrame = static_cast< const X11SalFrame* >( pSalFrame ); + const ::Window aCurFrameWindow = pFrame->GetWindow(); + if( aCurFrameWindow == aWindow || pFrame->GetShellWindow() == aWindow ) + { + aFrameWindow = aCurFrameWindow; + break; + } + } + } + if( pInputMethod->FilterEvent( pEvent, aFrameWindow ) ) + return; + } + + SalInstance* pInstance = GetSalInstance(); + pInstance->CallEventCallback( pEvent, sizeof( XEvent ) ); + + switch( pEvent->type ) + { + case MotionNotify: + while( XCheckWindowEvent( pEvent->xany.display, + pEvent->xany.window, + ButtonMotionMask, + pEvent ) ) + ; + m_nLastUserEventTime = pEvent->xmotion.time; + break; + case PropertyNotify: + if( pEvent->xproperty.atom == getWMAdaptor()->getAtom( WMAdaptor::VCL_SYSTEM_SETTINGS ) ) + { + for(const ScreenData & rScreen : m_aScreens) + { + if( pEvent->xproperty.window == rScreen.m_aRefWindow ) + { + for (auto pSalFrame : m_aFrames ) + pSalFrame->CallCallback( SalEvent::SettingsChanged, nullptr ); + return; + } + } + } + break; + case MappingNotify: + if( MappingModifier == pEvent->xmapping.request ) + { + XRefreshKeyboardMapping( &pEvent->xmapping ); + ModifierMapping(); + } + break; + case ButtonPress: + case ButtonRelease: + m_nLastUserEventTime = pEvent->xbutton.time; + break; + case KeyPress: + case KeyRelease: + m_nLastUserEventTime = pEvent->xkey.time; + break; + default: + + if ( GetKbdExtension()->UseExtension() + && GetKbdExtension()->GetEventBase() == pEvent->type ) + { + GetKbdExtension()->Dispatch( pEvent ); + return; + } + break; + } + + for (auto pSalFrame : m_aFrames ) + { + X11SalFrame* pFrame = static_cast<X11SalFrame*>( pSalFrame ); + + ::Window aDispatchWindow = pEvent->xany.window; + if( pFrame->GetWindow() == aDispatchWindow + || pFrame->GetShellWindow() == aDispatchWindow + || pFrame->GetForeignParent() == aDispatchWindow + ) + { + pFrame->Dispatch( pEvent ); + return; + } + if( pEvent->type == ConfigureNotify && pEvent->xconfigure.window == pFrame->GetStackingWindow() ) + { + pFrame->Dispatch( pEvent ); + return; + } + } + + // dispatch to salobjects + X11SalObject::Dispatch( pEvent ); + + // is this perhaps a root window that changed size ? + processRandREvent( pEvent ); +} + +#ifdef DBG_UTIL +void SalDisplay::DbgPrintDisplayEvent(const char *pComment, const XEvent *pEvent) const +{ + static const char* const EventNames[] = + { + nullptr, + nullptr, + "KeyPress", + "KeyRelease", + "ButtonPress", + "ButtonRelease", + "MotionNotify", + "EnterNotify", + "LeaveNotify", + "FocusIn", + "FocusOut", + "KeymapNotify", + "Expose", + "GraphicsExpose", + "NoExpose", + "VisibilityNotify", + "CreateNotify", + "DestroyNotify", + "UnmapNotify", + "MapNotify", + "MapRequest", + "ReparentNotify", + "ConfigureNotify", + "ConfigureRequest", + "GravityNotify", + "ResizeRequest", + "CirculateNotify", + "CirculateRequest", + "PropertyNotify", + "SelectionClear", + "SelectionRequest", + "SelectionNotify", + "ColormapNotify", + "ClientMessage", + "MappingNotify" + }; + + if( pEvent->type <= MappingNotify ) + { + SAL_INFO("vcl.app", "[" << pComment << "] " + << EventNames[pEvent->type] + << " s=" << pEvent->xany.send_event + << " w=" << pEvent->xany.window); + + switch( pEvent->type ) + { + case KeyPress: + case KeyRelease: + SAL_INFO("vcl.app", "\t\ts=" << pEvent->xkey.state + << " c=" << pEvent->xkey.keycode); + break; + + case ButtonPress: + case ButtonRelease: + SAL_INFO("vcl.app", "\t\ts=" << pEvent->xbutton.state + << " b=" << pEvent->xbutton.button + << " x=" << pEvent->xbutton.x + << " y=" << pEvent->xbutton.y + << " rx=" << pEvent->xbutton.x_root + << " ry=" << pEvent->xbutton.y_root); + break; + + case MotionNotify: + SAL_INFO("vcl.app", "\t\ts=" << pEvent->xmotion.state + << " x=" << pEvent->xmotion.x + << " y=" << pEvent->xmotion.y); + break; + + case EnterNotify: + case LeaveNotify: + SAL_INFO("vcl.app", "\t\tm=" << pEvent->xcrossing.mode + << " f=" << pEvent->xcrossing.focus + << " x=" << pEvent->xcrossing.x + << " y=" << pEvent->xcrossing.y); + break; + + case FocusIn: + case FocusOut: + SAL_INFO("vcl.app", "\t\tm=" << pEvent->xfocus.mode + << " d=" << pEvent->xfocus.detail); + break; + + case Expose: + case GraphicsExpose: + SAL_INFO("vcl.app", "\t\tc=" << pEvent->xexpose.count + << " " << pEvent->xexpose.width + << "*" << pEvent->xexpose.height + << " " << pEvent->xexpose.x + << "+" << pEvent->xexpose.y ); + break; + + case VisibilityNotify: + SAL_INFO("vcl.app", "\t\ts=" << pEvent->xvisibility.state); + break; + + case CreateNotify: + case DestroyNotify: + break; + + case MapNotify: + case UnmapNotify: + break; + + case ReparentNotify: + SAL_INFO("vcl.app", "\t\tp=" << sal::static_int_cast< int >( + pEvent->xreparent.parent) + << " x=" << pEvent->xreparent.x + << " y=" << pEvent->xreparent.y ); + break; + + case ConfigureNotify: + SAL_INFO("vcl.app", "\t\tb=" << pEvent->xconfigure.border_width + << " " << pEvent->xconfigure.width + << "*" << pEvent->xconfigure.height + << " " << pEvent->xconfigure.x + << "+" << pEvent->xconfigure.y); + break; + + case PropertyNotify: + SAL_INFO("vcl.app", "\t\ta=" << GetAtomName( + pDisp_, pEvent->xproperty.atom) + << std::showbase << std::hex << std::uppercase + << " (" << sal::static_int_cast< unsigned int >( + pEvent->xproperty.atom) << ")."); + break; + + case ColormapNotify: + SAL_INFO("vcl.app", "\t\tc=" << pEvent->xcolormap.colormap + << " n=" << pEvent->xcolormap.c_new + << " s=" << pEvent->xcolormap.state); + break; + + case ClientMessage: + SAL_INFO("vcl.app", "\t\ta=" << GetAtomName( + pDisp_, pEvent->xclient.message_type) + << std::showbase << std::hex << std::uppercase + << " (" << sal::static_int_cast< unsigned int >( + pEvent->xclient.message_type) << ")" + << std::dec + << " f=" << pEvent->xclient.format + << std::hex + << " [" << pEvent->xclient.data.l[0] + << "," << pEvent->xclient.data.l[1] + << "," << pEvent->xclient.data.l[2] + << "," << pEvent->xclient.data.l[3] + << "," << pEvent->xclient.data.l[4] + << "]"); + break; + + case MappingNotify: + SAL_INFO("vcl.app", "\t\tr=" + << (MappingModifier == pEvent->xmapping.request ? + "MappingModifier" : + (MappingKeyboard == pEvent->xmapping.request ? + "MappingKeyboard" : "MappingPointer")) + << "d"); + + break; + } + } + else + SAL_INFO("vcl.app", "[" << pComment << "] " + << pEvent->type + << " s=" << pEvent->xany.send_event + << " w=" << pEvent->xany.window); +} + +void SalDisplay::PrintInfo() const +{ + if( IsDisplay() ) + { + SAL_INFO( "vcl", "Environment" ); + SAL_INFO( "vcl", "\t$DISPLAY \t\"" << GetEnv( "DISPLAY" ) << "\""); + SAL_INFO( "vcl", "\t$SAL_VISUAL \t\"" << GetEnv( "SAL_VISUAL" ) << "\""); + SAL_INFO( "vcl", "\t$SAL_IGNOREXERRORS\t\"" << GetEnv( "SAL_IGNOREXERRORS" ) << "\""); + SAL_INFO( "vcl", "\t$SAL_PROPERTIES \t\"" << GetEnv( "SAL_PROPERTIES" ) << "\""); + SAL_INFO( "vcl", "\t$SAL_SYNCHRONIZE \t\"" << GetEnv( "SAL_SYNCHRONIZE" ) << "\""); + + char sHostname[ 120 ]; + gethostname (sHostname, 120 ); + SAL_INFO( "vcl", "Client" ); + SAL_INFO( "vcl", "\tHost \t\"" << sHostname << "\""); + + SAL_INFO( "vcl", "Display" ); + SAL_INFO( "vcl", "\tHost \t\"" << DisplayString(pDisp_) << "\""); + SAL_INFO( "vcl", "\tVendor (Release) \t\"" << ServerVendor(pDisp_) << " (" << VendorRelease(pDisp_) << ")\""); + SAL_INFO( "vcl", "\tProtocol \t" << ProtocolVersion(pDisp_) << "." << ProtocolRevision(pDisp_) ); + SAL_INFO( "vcl", "\tScreen (count,def)\t" << m_nXDefaultScreen.getXScreen() << " (" << ScreenCount(pDisp_) << "," << DefaultScreen(pDisp_) << ")"); + SAL_INFO( "vcl", "\tshift ctrl alt \t" << KeyStr( nShiftKeySym_ ) << " (0x" << std::hex << sal::static_int_cast< unsigned int >(nShiftKeySym_) << ") " + << KeyStr( nCtrlKeySym_ ) << " (0x" << sal::static_int_cast< unsigned int >(nCtrlKeySym_) << ") " + << KeyStr( nMod1KeySym_ ) << " (0x" << sal::static_int_cast< unsigned int >(nMod1KeySym_) << ")"); + if( XExtendedMaxRequestSize(pDisp_) != 0 ) + SAL_INFO( "vcl", "\tXMaxRequestSize \t" << XMaxRequestSize(pDisp_) * 4 << " " << XExtendedMaxRequestSize(pDisp_) * 4 << " [bytes]"); + SAL_INFO( "vcl", "\tWMName \t" << getWMAdaptor()->getWindowManagerName() ); + } + SAL_INFO( "vcl", "Screen" ); + SAL_INFO( "vcl", "\tResolution/Size \t" << aResolution_.A() << "*" << aResolution_.B() + << " " << m_aScreens[m_nXDefaultScreen.getXScreen()].m_aSize.Width() << "*" << m_aScreens[m_nXDefaultScreen.getXScreen()].m_aSize.Height() + << " " << (Hypothenuse( DisplayWidthMM ( pDisp_, m_nXDefaultScreen.getXScreen() ), + DisplayHeightMM( pDisp_, m_nXDefaultScreen.getXScreen() ) ) / 25.4 ) << "\"" ); + SAL_INFO( "vcl", "\tBlack&White \t" << GetColormap(m_nXDefaultScreen).GetBlackPixel() << " " + << GetColormap(m_nXDefaultScreen).GetWhitePixel() ); + SAL_INFO( "vcl", "\tRGB \t0x" << std::hex << GetVisual(m_nXDefaultScreen).red_mask + << " 0x" << GetVisual(m_nXDefaultScreen).green_mask + << " 0x" << GetVisual(m_nXDefaultScreen).blue_mask); +} +#endif + +void SalDisplay::addXineramaScreenUnique( int i, tools::Long i_nX, tools::Long i_nY, tools::Long i_nWidth, tools::Long i_nHeight ) +{ + // see if any frame buffers are at the same coordinates + // this can happen with weird configuration e.g. on + // XFree86 and Clone displays + const size_t nScreens = m_aXineramaScreens.size(); + for( size_t n = 0; n < nScreens; n++ ) + { + if( m_aXineramaScreens[n].Left() == i_nX && + m_aXineramaScreens[n].Top() == i_nY ) + { + if( m_aXineramaScreens[n].GetWidth() < i_nWidth || + m_aXineramaScreens[n].GetHeight() < i_nHeight ) + { + m_aXineramaScreenIndexMap[i] = n; + m_aXineramaScreens[n].SetSize( Size( i_nWidth, i_nHeight ) ); + } + return; + } + } + m_aXineramaScreenIndexMap[i] = m_aXineramaScreens.size(); + m_aXineramaScreens.emplace_back( Point( i_nX, i_nY ), Size( i_nWidth, i_nHeight ) ); +} + +void SalDisplay::InitXinerama() +{ + if( m_aScreens.size() > 1 ) + { + m_bXinerama = false; + return; // multiple screens mean no xinerama + } +#if defined(USE_XINERAMA_XORG) + if( !XineramaIsActive( pDisp_ ) ) + return; + + int nFramebuffers = 1; + XineramaScreenInfo* pScreens = XineramaQueryScreens( pDisp_, &nFramebuffers ); + if( !pScreens ) + return; + + if( nFramebuffers > 1 ) + { + m_aXineramaScreens = std::vector<tools::Rectangle>(); + m_aXineramaScreenIndexMap = std::vector<int>(nFramebuffers); + for( int i = 0; i < nFramebuffers; i++ ) + { + addXineramaScreenUnique( i, pScreens[i].x_org, + pScreens[i].y_org, + pScreens[i].width, + pScreens[i].height ); + } + m_bXinerama = m_aXineramaScreens.size() > 1; + } + XFree( pScreens ); +#endif +#if OSL_DEBUG_LEVEL > 1 + if( m_bXinerama ) + { + for (auto const& screen : m_aXineramaScreens) + SAL_INFO("vcl.app", "Xinerama screen: " + << screen.GetWidth() + << "x" << screen.GetHeight() + << "+" << screen.Left() + << "+" << screen.Top()); + } +#endif +} + +extern "C" +{ + static Bool timestamp_predicate( Display*, XEvent* i_pEvent, XPointer i_pArg ) + { + SalDisplay* pSalDisplay = reinterpret_cast<SalDisplay*>(i_pArg); + if( i_pEvent->type == PropertyNotify && + i_pEvent->xproperty.window == pSalDisplay->GetDrawable( pSalDisplay->GetDefaultXScreen() ) && + i_pEvent->xproperty.atom == pSalDisplay->getWMAdaptor()->getAtom( WMAdaptor::SAL_GETTIMEEVENT ) + ) + return True; + + return False; + } +} + +Time SalDisplay::GetEventTimeImpl( bool i_bAlwaysReget ) const +{ + if( m_nLastUserEventTime == CurrentTime || i_bAlwaysReget ) + { + // get current server time + unsigned char c = 0; + XEvent aEvent; + Atom nAtom = getWMAdaptor()->getAtom( WMAdaptor::SAL_GETTIMEEVENT ); + XChangeProperty( GetDisplay(), GetDrawable( GetDefaultXScreen() ), + nAtom, nAtom, 8, PropModeReplace, &c, 1 ); + XIfEvent( GetDisplay(), &aEvent, timestamp_predicate, reinterpret_cast<XPointer>(const_cast<SalDisplay *>(this))); + m_nLastUserEventTime = aEvent.xproperty.time; + } + return m_nLastUserEventTime; +} + +bool SalDisplay::XIfEventWithTimeout( XEvent* o_pEvent, XPointer i_pPredicateData, + X_if_predicate i_pPredicate ) const +{ + /* #i99360# ugly workaround an X11 library bug + this replaces the following call: + XIfEvent( GetDisplay(), o_pEvent, i_pPredicate, i_pPredicateData ); + */ + bool bRet = true; + + if( ! XCheckIfEvent( GetDisplay(), o_pEvent, i_pPredicate, i_pPredicateData ) ) + { + // wait for some event to arrive + struct pollfd aFD; + aFD.fd = ConnectionNumber(GetDisplay()); + aFD.events = POLLIN; + aFD.revents = 0; + tools::Long nTimeout = 1000; + (void)poll(&aFD, 1, nTimeout); + if( ! XCheckIfEvent( GetDisplay(), o_pEvent, i_pPredicate, i_pPredicateData ) ) + { + (void)poll(&aFD, 1, nTimeout); // try once more for a packet of events from the Xserver + if( ! XCheckIfEvent( GetDisplay(), o_pEvent, i_pPredicate, i_pPredicateData ) ) + { + bRet = false; + } + } + } + return bRet; +} + +SalVisual::SalVisual() + : eRGBMode_(SalRGB::RGB), nRedShift_(0), nGreenShift_(0), nBlueShift_(0) + , nRedBits_(0), nGreenBits_(0), nBlueBits_(0) +{ + visual = nullptr; +} + +SalVisual::SalVisual( const XVisualInfo* pXVI ) +{ + *static_cast<XVisualInfo*>(this) = *pXVI; + if( GetClass() != TrueColor ) + { + eRGBMode_ = SalRGB::RGB; + nRedShift_ = nGreenShift_ = nBlueShift_ = 0; + nRedBits_ = nGreenBits_ = nBlueBits_ = 0; + return; + } + + nRedShift_ = sal_Shift( red_mask ); + nGreenShift_ = sal_Shift( green_mask ); + nBlueShift_ = sal_Shift( blue_mask ); + + nRedBits_ = sal_significantBits( red_mask ); + nGreenBits_ = sal_significantBits( green_mask ); + nBlueBits_ = sal_significantBits( blue_mask ); + + if( GetDepth() == 24 ) + if( red_mask == 0xFF0000 ) + if( green_mask == 0xFF00 ) + if( blue_mask == 0xFF ) + eRGBMode_ = SalRGB::RGB; + else + eRGBMode_ = SalRGB::otherSalRGB; + else if( blue_mask == 0xFF00 ) + if( green_mask == 0xFF ) + eRGBMode_ = SalRGB::RBG; + else + eRGBMode_ = SalRGB::otherSalRGB; + else + eRGBMode_ = SalRGB::otherSalRGB; + else if( green_mask == 0xFF0000 ) + if( red_mask == 0xFF00 ) + if( blue_mask == 0xFF ) + eRGBMode_ = SalRGB::GRB; + else + eRGBMode_ = SalRGB::otherSalRGB; + else if( blue_mask == 0xFF00 ) + if( red_mask == 0xFF ) + eRGBMode_ = SalRGB::GBR; + else + eRGBMode_ = SalRGB::otherSalRGB; + else + eRGBMode_ = SalRGB::otherSalRGB; + else if( blue_mask == 0xFF0000 ) + if( red_mask == 0xFF00 ) + if( green_mask == 0xFF ) + eRGBMode_ = SalRGB::BRG; + else + eRGBMode_ = SalRGB::otherSalRGB; + else if( green_mask == 0xFF00 ) + if( red_mask == 0xFF ) + eRGBMode_ = SalRGB::BGR; + else + eRGBMode_ = SalRGB::otherSalRGB; + else + eRGBMode_ = SalRGB::otherSalRGB; + else + eRGBMode_ = SalRGB::otherSalRGB; + else + eRGBMode_ = SalRGB::otherSalRGB; +} + +// Converts the order of bytes of a Pixel into bytes of a Color +// This is not reversible for the 6 XXXA + +// Color is RGB (ABGR) a=0xFF000000, r=0xFF0000, g=0xFF00, b=0xFF + +#define SALCOLOR SalRGB::RGB +#define SALCOLORREVERSE SalRGB::BGR + +Color SalVisual::GetTCColor( Pixel nPixel ) const +{ + if( SALCOLOR == eRGBMode_ ) + return Color(ColorTransparency, nPixel); + + if( SALCOLORREVERSE == eRGBMode_ ) + return Color( (nPixel & 0x0000FF), + (nPixel & 0x00FF00) >> 8, + (nPixel & 0xFF0000) >> 16); + + Pixel r = nPixel & red_mask; + Pixel g = nPixel & green_mask; + Pixel b = nPixel & blue_mask; + + if( SalRGB::otherSalRGB != eRGBMode_ ) // 8+8+8=24 + return Color( r >> nRedShift_, + g >> nGreenShift_, + b >> nBlueShift_ ); + + if( nRedShift_ > 0 ) r >>= nRedShift_; else r <<= -nRedShift_; + if( nGreenShift_ > 0 ) g >>= nGreenShift_; else g <<= -nGreenShift_; + if( nBlueShift_ > 0 ) b >>= nBlueShift_; else b <<= -nBlueShift_; + + if( nRedBits_ != 8 ) + r |= (r & 0xff) >> (8-nRedBits_); + if( nGreenBits_ != 8 ) + g |= (g & 0xff) >> (8-nGreenBits_); + if( nBlueBits_ != 8 ) + b |= (b & 0xff) >> (8-nBlueBits_); + + return Color( r, g, b ); +} + +Pixel SalVisual::GetTCPixel( Color nColor ) const +{ + if( SALCOLOR == eRGBMode_ ) + return static_cast<Pixel>(sal_uInt32(nColor)); + + Pixel r = static_cast<Pixel>( nColor.GetRed() ); + Pixel g = static_cast<Pixel>( nColor.GetGreen() ); + Pixel b = static_cast<Pixel>( nColor.GetBlue() ); + + if( SALCOLORREVERSE == eRGBMode_ ) + return (b << 16) | (g << 8) | r; + + if( SalRGB::otherSalRGB != eRGBMode_ ) // 8+8+8=24 + return (r << nRedShift_) | (g << nGreenShift_) | (b << nBlueShift_); + + if( nRedShift_ > 0 ) r <<= nRedShift_; else r >>= -nRedShift_; + if( nGreenShift_ > 0 ) g <<= nGreenShift_; else g >>= -nGreenShift_; + if( nBlueShift_ > 0 ) b <<= nBlueShift_; else b >>= -nBlueShift_; + + return (r&red_mask) | (g&green_mask) | (b&blue_mask); +} + +SalColormap::SalColormap( const SalDisplay *pDisplay, Colormap hColormap, + SalX11Screen nXScreen ) + : m_pDisplay( pDisplay ), + m_hColormap( hColormap ) +{ + m_aVisual = m_pDisplay->GetVisual( nXScreen ); + + XColor aColor; + + GetXPixel( aColor, 0x00, 0x00, 0x00 ); + m_nBlackPixel = aColor.pixel; + + GetXPixel( aColor, 0xFF, 0xFF, 0xFF ); + m_nWhitePixel = aColor.pixel; + + m_nUsed = 1 << m_aVisual.GetDepth(); + + if( m_aVisual.GetClass() != PseudoColor ) + return; + + int r, g, b; + + // black, white, gray, ~gray = 4 + GetXPixels( aColor, 0xC0, 0xC0, 0xC0 ); + + // light colors: 3 * 2 = 6 + + GetXPixels( aColor, 0x00, 0x00, 0xFF ); + GetXPixels( aColor, 0x00, 0xFF, 0x00 ); + GetXPixels( aColor, 0x00, 0xFF, 0xFF ); + + // standard colors: 7 * 2 = 14 + GetXPixels( aColor, 0x00, 0x00, 0x80 ); + GetXPixels( aColor, 0x00, 0x80, 0x00 ); + GetXPixels( aColor, 0x00, 0x80, 0x80 ); + GetXPixels( aColor, 0x80, 0x00, 0x00 ); + GetXPixels( aColor, 0x80, 0x00, 0x80 ); + GetXPixels( aColor, 0x80, 0x80, 0x00 ); + GetXPixels( aColor, 0x80, 0x80, 0x80 ); + GetXPixels( aColor, 0x00, 0xB8, 0xFF ); // Blue 7 + + // cube: 6*6*6 - 8 = 208 + for( r = 0; r < 0x100; r += 0x33 ) // 0x33, 0x66, 0x99, 0xCC, 0xFF + for( g = 0; g < 0x100; g += 0x33 ) + for( b = 0; b < 0x100; b += 0x33 ) + GetXPixels( aColor, r, g, b ); + + // gray: 16 - 6 = 10 + for( g = 0x11; g < 0xFF; g += 0x11 ) + GetXPixels( aColor, g, g, g ); + + // green: 16 - 6 = 10 + for( g = 0x11; g < 0xFF; g += 0x11 ) + GetXPixels( aColor, 0, g, 0 ); + + // red: 16 - 6 = 10 + for( r = 0x11; r < 0xFF; r += 0x11 ) + GetXPixels( aColor, r, 0, 0 ); + + // blue: 16 - 6 = 10 + for( b = 0x11; b < 0xFF; b += 0x11 ) + GetXPixels( aColor, 0, 0, b ); + +} + +// MonoChrome +SalColormap::SalColormap() + : m_pDisplay( vcl_sal::getSalDisplay(GetGenericUnixSalData()) ), + m_hColormap( None ), + m_nWhitePixel( 1 ), + m_nBlackPixel( 0 ), + m_nUsed( 2 ) +{ + m_aPalette = std::vector<Color>(m_nUsed); + + m_aPalette[m_nBlackPixel] = COL_BLACK; + m_aPalette[m_nWhitePixel] = COL_WHITE; +} + +// TrueColor +SalColormap::SalColormap( sal_uInt16 nDepth ) + : m_pDisplay( vcl_sal::getSalDisplay(GetGenericUnixSalData()) ), + m_hColormap( None ), + m_nWhitePixel( (1 << nDepth) - 1 ), + m_nBlackPixel( 0x00000000 ), + m_nUsed( 1 << nDepth ) +{ + SalX11Screen nXScreen( vcl_sal::getSalDisplay(GetGenericUnixSalData())->GetDefaultXScreen() ); + const SalVisual *pVisual = &m_pDisplay->GetVisual( nXScreen ); + + if( pVisual->GetClass() == TrueColor && pVisual->GetDepth() == nDepth ) + m_aVisual = *pVisual; + else + { + XVisualInfo aVI; + + if( !XMatchVisualInfo( m_pDisplay->GetDisplay(), + m_pDisplay->GetDefaultXScreen().getXScreen(), + nDepth, + TrueColor, + &aVI ) ) + { + aVI.visual = new Visual; + aVI.visualid = VisualID(-1); + aVI.screen = -1; + aVI.depth = nDepth; + aVI.c_class = TrueColor; + if( 24 == nDepth ) // 888 + { + aVI.red_mask = 0xFF0000; + aVI.green_mask = 0x00FF00; + aVI.blue_mask = 0x0000FF; + } + else if( 8 == nDepth ) // 332 + { + aVI.red_mask = 0x0000E0; + aVI.green_mask = 0x00001C; + aVI.blue_mask = 0x000003; + } + else + { + aVI.red_mask = 0x000000; + aVI.green_mask = 0x000000; + aVI.blue_mask = 0x000000; + } + aVI.colormap_size = 0; + aVI.bits_per_rgb = 8; + + aVI.visual->ext_data = nullptr; + aVI.visual->visualid = aVI.visualid; + aVI.visual->c_class = aVI.c_class; + aVI.visual->red_mask = aVI.red_mask; + aVI.visual->green_mask = aVI.green_mask; + aVI.visual->blue_mask = aVI.blue_mask; + aVI.visual->bits_per_rgb = aVI.bits_per_rgb; + aVI.visual->map_entries = aVI.colormap_size; + + m_aVisual = SalVisual( &aVI ); + m_aVisualOwnership.owner = true; + } + else + m_aVisual = SalVisual( &aVI ); + } +} + +SalColormap::~SalColormap() +{ + if (m_aVisualOwnership.owner) + { + delete m_aVisual.visual; + } +} + +void SalColormap::GetPalette() +{ + Pixel i; + m_aPalette = std::vector<Color>(m_nUsed); + + std::unique_ptr<XColor[]> aColor(new XColor[m_nUsed]); + + for( i = 0; i < m_nUsed; i++ ) + { + aColor[i].red = aColor[i].green = aColor[i].blue = 0; + aColor[i].pixel = i; + } + + XQueryColors( m_pDisplay->GetDisplay(), m_hColormap, aColor.get(), m_nUsed ); + + for( i = 0; i < m_nUsed; i++ ) + { + m_aPalette[i] = Color( aColor[i].red >> 8, + aColor[i].green >> 8, + aColor[i].blue >> 8 ); + } +} + +static sal_uInt16 sal_Lookup( const std::vector<Color>& rPalette, + int r, int g, int b, + Pixel nUsed ) +{ + sal_uInt16 nPixel = 0; + int nBest = ColorDiff( rPalette[0], r, g, b ); + + for( Pixel i = 1; i < nUsed; i++ ) + { + int n = ColorDiff( rPalette[i], r, g, b ); + + if( n < nBest ) + { + if( !n ) + return i; + + nPixel = i; + nBest = n; + } + } + return nPixel; +} + +void SalColormap::GetLookupTable() +{ + m_aLookupTable = std::vector<sal_uInt16>(16*16*16); + + int i = 0; + for( int r = 0; r < 256; r += 17 ) + for( int g = 0; g < 256; g += 17 ) + for( int b = 0; b < 256; b += 17 ) + m_aLookupTable[i++] = sal_Lookup( m_aPalette, r, g, b, m_nUsed ); +} + +Color SalColormap::GetColor( Pixel nPixel ) const +{ + if( m_nBlackPixel == nPixel ) return COL_BLACK; + if( m_nWhitePixel == nPixel ) return COL_WHITE; + + if( m_aVisual.GetVisual() ) + { + if( m_aVisual.GetClass() == TrueColor ) + return m_aVisual.GetTCColor( nPixel ); + + if( m_aPalette.empty() + && m_hColormap + && m_aVisual.GetDepth() <= 12 + && m_aVisual.GetClass() == PseudoColor ) + const_cast<SalColormap*>(this)->GetPalette(); + } + + if( !m_aPalette.empty() && nPixel < m_nUsed ) + return m_aPalette[nPixel]; + + if( !m_hColormap ) + { + SAL_WARN("vcl", "SalColormap::GetColor() !m_hColormap"); + return Color(ColorTransparency, nPixel); + } + + // DirectColor, StaticColor, StaticGray, GrayScale + XColor aColor; + + aColor.pixel = nPixel; + + XQueryColor( m_pDisplay->GetDisplay(), m_hColormap, &aColor ); + + return Color( aColor.red>>8, aColor.green>>8, aColor.blue>>8 ); +} + +inline bool SalColormap::GetXPixel( XColor &rColor, + int r, + int g, + int b ) const +{ + rColor.red = r * 257; + rColor.green = g * 257; + rColor.blue = b * 257; + return XAllocColor( GetXDisplay(), m_hColormap, &rColor ); +} + +bool SalColormap::GetXPixels( XColor &rColor, + int r, + int g, + int b ) const +{ + if( !GetXPixel( rColor, r, g, b ) ) + return false; + if( rColor.pixel & 1 ) + return true; + return GetXPixel( rColor, r^0xFF, g^0xFF, b^0xFF ); +} + +Pixel SalColormap::GetPixel( Color nColor ) const +{ + if( SALCOLOR_NONE == nColor ) return 0; + if( COL_BLACK == nColor ) return m_nBlackPixel; + if( COL_WHITE == nColor ) return m_nWhitePixel; + + if( m_aVisual.GetClass() == TrueColor ) + return m_aVisual.GetTCPixel( nColor ); + + if( m_aLookupTable.empty() ) + { + if( m_aPalette.empty() + && m_hColormap + && m_aVisual.GetDepth() <= 12 + && m_aVisual.GetClass() == PseudoColor ) // what else ??? + const_cast<SalColormap*>(this)->GetPalette(); + + if( !m_aPalette.empty() ) + for( Pixel i = 0; i < m_nUsed; i++ ) + if( m_aPalette[i] == nColor ) + return i; + + if( m_hColormap ) + { + // DirectColor, StaticColor, StaticGray, GrayScale (PseudoColor) + XColor aColor; + + if( GetXPixel( aColor, + nColor.GetRed(), + nColor.GetGreen(), + nColor.GetBlue() ) ) + { + if( !m_aPalette.empty() && m_aPalette[aColor.pixel] == Color(0) ) + { + const_cast<SalColormap*>(this)->m_aPalette[aColor.pixel] = nColor; + + if( !(aColor.pixel & 1) && m_aPalette[aColor.pixel+1] == Color(0) ) + { + XColor aInversColor; + + Color nInversColor(ColorTransparency, sal_uInt32(nColor) ^ 0xFFFFFF); + + GetXPixel( aInversColor, + nInversColor.GetRed(), + nInversColor.GetGreen(), + nInversColor.GetBlue() ); + + if( m_aPalette[aInversColor.pixel] == Color(0) ) + const_cast<SalColormap*>(this)->m_aPalette[aInversColor.pixel] = nInversColor; +#ifdef DBG_UTIL + else + SAL_INFO("vcl.app", "SalColormap::GetPixel() " + << std::showbase << std::setfill('0') + << std::setw(6) << std::hex + << static_cast< unsigned long >( + sal_uInt32(nColor)) + << "=" + << std::dec + << aColor.pixel << " " + << std::showbase << std::setfill('0') + << std::setw(6) << std::hex + << static_cast< unsigned long >( + sal_uInt32(nInversColor)) + << "=" + << std::dec + << aInversColor.pixel); +#endif + } + } + + return aColor.pixel; + } + +#ifdef DBG_UTIL + SAL_INFO("vcl.app", "SalColormap::GetPixel() !XAllocColor " + << std::hex + << static_cast< unsigned long >(sal_uInt32(nColor))); +#endif + } + + if( m_aPalette.empty() ) + { +#ifdef DBG_UTIL + SAL_INFO("vcl.app", "SalColormap::GetPixel() Palette empty " + << std::hex + << static_cast< unsigned long >(sal_uInt32(nColor))); +#endif + return sal_uInt32(nColor); + } + + const_cast<SalColormap*>(this)->GetLookupTable(); + } + + // color matching via palette + sal_uInt16 r = nColor.GetRed(); + sal_uInt16 g = nColor.GetGreen(); + sal_uInt16 b = nColor.GetBlue(); + return m_aLookupTable[ (((r+8)/17) << 8) + + (((g+8)/17) << 4) + + ((b+8)/17) ]; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/generic/app/salinst.cxx b/vcl/unx/generic/app/salinst.cxx new file mode 100644 index 000000000..502f4c7f7 --- /dev/null +++ b/vcl/unx/generic/app/salinst.cxx @@ -0,0 +1,254 @@ +/* -*- 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 <stdlib.h> + +#include <config_features.h> +#include <vcl/skia/SkiaHelper.hxx> +#include <config_skia.h> +#if HAVE_FEATURE_SKIA +#include <skia/x11/gdiimpl.hxx> +#include <skia/salbmp.hxx> +#endif + +#include <unx/saldata.hxx> +#include <unx/saldisp.hxx> +#include <unx/salinst.h> +#include <unx/geninst.h> +#include <unx/genpspgraphics.h> +#include <unx/salframe.h> +#include <unx/sm.hxx> +#include <unx/i18n_im.hxx> +#include <unx/salbmp.h> + +#include <vcl/inputtypes.hxx> + +#include <salwtype.hxx> + +// plugin factory function +extern "C" +{ + VCLPLUG_GEN_PUBLIC SalInstance* create_SalInstance() + { + /* #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(); + + X11SalInstance* pInstance = new X11SalInstance( std::make_unique<SalYieldMutex>() ); + + // initialize SalData + X11SalData *pSalData = new X11SalData(); + + pSalData->Init(); + pInstance->SetLib( pSalData->GetLib() ); + + return pInstance; + } +} + +X11SalInstance::X11SalInstance(std::unique_ptr<SalYieldMutex> pMutex) + : SalGenericInstance(std::move(pMutex)) + , mpXLib(nullptr) +{ + ImplSVData* pSVData = ImplGetSVData(); + pSVData->maAppData.mxToolkitName = OUString("x11"); + m_bSupportsOpenGL = true; +#if HAVE_FEATURE_SKIA + X11SkiaSalGraphicsImpl::prepareSkia(); +#if SKIA_USE_BITMAP32 + if (SkiaHelper::isVCLSkiaEnabled()) + m_bSupportsBitmap32 = true; +#endif +#endif +} + +X11SalInstance::~X11SalInstance() +{ + // close session management + SessionManagerClient::close(); + + // dispose SalDisplay list from SalData + // would be done in a static destructor else which is + // a little late + GetGenericUnixSalData()->Dispose(); + +#if HAVE_FEATURE_SKIA + SkiaHelper::cleanup(); +#endif +} + +SalX11Display* X11SalInstance::CreateDisplay() const +{ + return new SalX11Display( mpXLib->GetDisplay() ); +} + +// AnyInput from sv/mow/source/app/svapp.cxx + +namespace { + +struct PredicateReturn +{ + VclInputFlags nType; + bool bRet; +}; + +} + +extern "C" { +static Bool ImplPredicateEvent( Display *, XEvent *pEvent, char *pData ) +{ + PredicateReturn *pPre = reinterpret_cast<PredicateReturn *>(pData); + + if ( pPre->bRet ) + return False; + + VclInputFlags nType; + + switch( pEvent->type ) + { + case ButtonPress: + case ButtonRelease: + case MotionNotify: + case EnterNotify: + case LeaveNotify: + nType = VclInputFlags::MOUSE; + break; + + case KeyPress: + //case KeyRelease: + nType = VclInputFlags::KEYBOARD; + break; + case Expose: + case GraphicsExpose: + case NoExpose: + nType = VclInputFlags::PAINT; + break; + default: + nType = VclInputFlags::NONE; + } + + if ( (nType & pPre->nType) || ( nType == VclInputFlags::NONE && (pPre->nType & VclInputFlags::OTHER) ) ) + pPre->bRet = true; + + return False; +} +} + +bool X11SalInstance::AnyInput(VclInputFlags nType) +{ + GenericUnixSalData *pData = GetGenericUnixSalData(); + Display *pDisplay = vcl_sal::getSalDisplay(pData)->GetDisplay(); + bool bRet = false; + + if( (nType & VclInputFlags::TIMER) && (mpXLib && mpXLib->CheckTimeout(false)) ) + bRet = true; + + if( !bRet && XPending(pDisplay) ) + { + PredicateReturn aInput; + XEvent aEvent; + + aInput.bRet = false; + aInput.nType = nType; + + XCheckIfEvent(pDisplay, &aEvent, ImplPredicateEvent, + reinterpret_cast<char *>(&aInput) ); + + bRet = aInput.bRet; + } +#if OSL_DEBUG_LEVEL > 1 + SAL_INFO("vcl.app", "AnyInput " + << std::showbase << std::hex + << static_cast<unsigned int>(nType) + << " = " << (bRet ? "true" : "false")); +#endif + return bRet; +} + +bool X11SalInstance::DoYield(bool bWait, bool bHandleAllCurrentEvents) +{ + return mpXLib->Yield( bWait, bHandleAllCurrentEvents ); +} + +OUString X11SalInstance::GetConnectionIdentifier() +{ + static const char* pDisplay = getenv( "DISPLAY" ); + return pDisplay ? OUString::createFromAscii(pDisplay) : OUString(); +} + +SalFrame *X11SalInstance::CreateFrame( SalFrame *pParent, SalFrameStyleFlags nSalFrameStyle ) +{ + SalFrame *pFrame = new X11SalFrame( pParent, nSalFrameStyle ); + + return pFrame; +} + +SalFrame* X11SalInstance::CreateChildFrame( SystemParentData* pParentData, SalFrameStyleFlags nStyle ) +{ + SalFrame* pFrame = new X11SalFrame( nullptr, nStyle, pParentData ); + + return pFrame; +} + +void X11SalInstance::DestroyFrame( SalFrame* pFrame ) +{ + delete pFrame; +} + +void X11SalInstance::AfterAppInit() +{ + assert( mpXLib->GetDisplay() ); + assert( mpXLib->GetInputMethod() ); + + SalX11Display *pSalDisplay = CreateDisplay(); + mpXLib->GetInputMethod()->CreateMethod( mpXLib->GetDisplay() ); + pSalDisplay->SetupInput(); +} + +void X11SalInstance::AddToRecentDocumentList(const OUString&, const OUString&, const OUString&) {} + +void X11SalInstance::PostPrintersChanged() +{ + SalDisplay* pDisp = vcl_sal::getSalDisplay(GetGenericUnixSalData()); + for (auto pSalFrame : pDisp->getFrames() ) + pDisp->PostEvent( pSalFrame, nullptr, SalEvent::PrinterChanged ); +} + +std::unique_ptr<GenPspGraphics> X11SalInstance::CreatePrintGraphics() +{ + return std::make_unique<GenPspGraphics>(); +} + +std::shared_ptr<SalBitmap> X11SalInstance::CreateSalBitmap() +{ +#if HAVE_FEATURE_SKIA + if (SkiaHelper::isVCLSkiaEnabled()) + return std::make_shared<SkiaSalBitmap>(); + else +#endif + return std::make_shared<X11SalBitmap>(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/generic/app/saltimer.cxx b/vcl/unx/generic/app/saltimer.cxx new file mode 100644 index 000000000..dc7a61dfe --- /dev/null +++ b/vcl/unx/generic/app/saltimer.cxx @@ -0,0 +1,68 @@ +/* -*- 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 <sys/time.h> + +#include <unx/salunxtime.h> +#include <unx/saldisp.hxx> +#include <unx/saltimer.h> +#include <unx/salinst.h> + +void SalXLib::StopTimer() +{ + m_aTimeout.tv_sec = 0; + m_aTimeout.tv_usec = 0; + m_nTimeoutMS = 0; +} + +void SalXLib::StartTimer( sal_uInt64 nMS ) +{ + timeval Timeout (m_aTimeout); // previous timeout. + gettimeofday (&m_aTimeout, nullptr); + + m_nTimeoutMS = nMS; + m_aTimeout += m_nTimeoutMS; + + if ((Timeout > m_aTimeout) || (Timeout.tv_sec == 0)) + { + // Wakeup from previous timeout (or stopped timer). + Wakeup(); + } +} + +SalTimer* X11SalInstance::CreateSalTimer() +{ + return new X11SalTimer( mpXLib ); +} + +X11SalTimer::~X11SalTimer() +{ +} + +void X11SalTimer::Stop() +{ + mpXLib->StopTimer(); +} + +void X11SalTimer::Start( sal_uInt64 nMS ) +{ + mpXLib->StartTimer( nMS ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/generic/app/sm.cxx b/vcl/unx/generic/app/sm.cxx new file mode 100644 index 000000000..b6a4c4ad4 --- /dev/null +++ b/vcl/unx/generic/app/sm.cxx @@ -0,0 +1,856 @@ +/* -*- 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 <memory> +#include <sal/config.h> + +#include <cassert> + +#include <string.h> +#include <unistd.h> +#include <poll.h> +#include <fcntl.h> + +#include <rtl/strbuf.hxx> +#include <sal/log.hxx> + +#include <rtl/process.h> +#include <osl/security.h> + +#include <X11/Xlib.h> +#include <X11/Xatom.h> + +#include <unx/sm.hxx> +#include <unx/saldisp.hxx> +#include <unx/salinst.h> + +#include <vcl/svapp.hxx> +#include <vcl/window.hxx> + +#include <salframe.hxx> +#include <salsession.hxx> + +namespace { + +class IceSalSession : public SalSession +{ +public: + IceSalSession() {} + +private: + virtual ~IceSalSession() override {} + + virtual void queryInteraction() override; + virtual void interactionDone() override; + virtual void saveDone() override; + virtual bool cancelShutdown() override; +}; + +} + +std::unique_ptr<SalSession> X11SalInstance::CreateSalSession() +{ + SAL_INFO("vcl.sm", "X11SalInstance::CreateSalSession"); + + std::unique_ptr<SalSession> p(new IceSalSession); + SessionManagerClient::open(p.get()); + return p; +} + +void IceSalSession::queryInteraction() +{ + SAL_INFO("vcl.sm", "IceSalSession::queryInteraction"); + + if( ! SessionManagerClient::queryInteraction() ) + { + SAL_INFO("vcl.sm.debug", " call SalSessionInteractionEvent"); + SalSessionInteractionEvent aEvent( false ); + CallCallback( &aEvent ); + } +} + +void IceSalSession::interactionDone() +{ + SAL_INFO("vcl.sm", "IceSalSession::interactionDone"); + + SessionManagerClient::interactionDone( false ); +} + +void IceSalSession::saveDone() +{ + SAL_INFO("vcl.sm", "IceSalSession::saveDone"); + + SessionManagerClient::saveDone(); +} + +bool IceSalSession::cancelShutdown() +{ + SAL_INFO("vcl.sm", "IceSalSession::cancelShutdown"); + + SessionManagerClient::interactionDone( true ); + return false; +} + +extern "C" { + +static void ICEWatchProc( + IceConn ice_conn, IcePointer client_data, Bool opening, + IcePointer * watch_data); + +static void ICEConnectionWorker(void * data); + +} + +class ICEConnectionObserver +{ + friend void ICEWatchProc(IceConn, IcePointer, Bool, IcePointer *); + + friend void ICEConnectionWorker(void *); + + struct pollfd* m_pFilehandles; + int m_nConnections; + IceConn* m_pConnections; + int m_nWakeupFiles[2]; + oslThread m_ICEThread; + IceIOErrorHandler m_origIOErrorHandler; + IceErrorHandler m_origErrorHandler; + + void wakeup(); + +public: + osl::Mutex m_ICEMutex; + + ICEConnectionObserver() + : m_pFilehandles(nullptr) + , m_nConnections(0) + , m_pConnections(nullptr) + , m_ICEThread(nullptr) + , m_origIOErrorHandler(nullptr) + , m_origErrorHandler(nullptr) + { + SAL_INFO("vcl.sm", "ICEConnectionObserver::ICEConnectionObserver"); + + m_nWakeupFiles[0] = m_nWakeupFiles[1] = 0; + } + + void activate(); + void deactivate(); + void terminate(oslThread iceThread); +}; + +SalSession * SessionManagerClient::m_pSession = nullptr; +std::unique_ptr< ICEConnectionObserver > +SessionManagerClient::m_xICEConnectionObserver; +SmcConn SessionManagerClient::m_pSmcConnection = nullptr; +OString SessionManagerClient::m_aClientID = ""; +OString SessionManagerClient::m_aTimeID = ""; +OString SessionManagerClient::m_aClientTimeID = ""; +bool SessionManagerClient::m_bDocSaveDone = false; // HACK + +extern "C" { + +static void IgnoreIceErrors( + SAL_UNUSED_PARAMETER IceConn, SAL_UNUSED_PARAMETER Bool, + SAL_UNUSED_PARAMETER int, SAL_UNUSED_PARAMETER unsigned long, + SAL_UNUSED_PARAMETER int, SAL_UNUSED_PARAMETER int, + SAL_UNUSED_PARAMETER IcePointer) +{} + +static void IgnoreIceIOErrors(SAL_UNUSED_PARAMETER IceConn) {} + +} + +static SmProp* pSmProps = nullptr; +static SmProp** ppSmProps = nullptr; +static char ** ppSmDel = nullptr; + +static int nSmProps = 0; +static int nSmDel = 0; +static unsigned char *pSmRestartHint = nullptr; + + +enum { eCloneCommand, eProgram, eRestartCommand, eUserId, eRestartStyleHint }; +enum { eDiscardCommand }; + + +static void BuildSmPropertyList() +{ + SAL_INFO("vcl.sm", "BuildSmPropertyList"); + + if( ! pSmProps ) + { + nSmProps = 5; + nSmDel = 1; + pSmProps = new SmProp[ nSmProps ]; + ppSmProps = new SmProp*[ nSmProps ]; + ppSmDel = new char*[ nSmDel ]; + } + + OString aExec(OUStringToOString(SessionManagerClient::getExecName(), osl_getThreadTextEncoding())); + + pSmProps[ eCloneCommand ].name = const_cast<char*>(SmCloneCommand); + pSmProps[ eCloneCommand ].type = const_cast<char*>(SmLISTofARRAY8); + pSmProps[ eCloneCommand ].num_vals = 1; + pSmProps[ eCloneCommand ].vals = new SmPropValue; + pSmProps[ eCloneCommand ].vals->length = aExec.getLength()+1; + pSmProps[ eCloneCommand ].vals->value = strdup( aExec.getStr() ); + + pSmProps[ eProgram ].name = const_cast<char*>(SmProgram); + pSmProps[ eProgram ].type = const_cast<char*>(SmARRAY8); + pSmProps[ eProgram ].num_vals = 1; + pSmProps[ eProgram ].vals = new SmPropValue; + pSmProps[ eProgram ].vals->length = aExec.getLength()+1; + pSmProps[ eProgram ].vals->value = strdup( aExec.getStr() ); + + pSmProps[ eRestartCommand ].name = const_cast<char*>(SmRestartCommand); + pSmProps[ eRestartCommand ].type = const_cast<char*>(SmLISTofARRAY8); + pSmProps[ eRestartCommand ].num_vals = 3; + pSmProps[ eRestartCommand ].vals = new SmPropValue[3]; + pSmProps[ eRestartCommand ].vals[0].length = aExec.getLength()+1; + pSmProps[ eRestartCommand ].vals[0].value = strdup( aExec.getStr() ); + OString aRestartOption = "--session=" + SessionManagerClient::getSessionID(); + pSmProps[ eRestartCommand ].vals[1].length = aRestartOption.getLength()+1; + pSmProps[ eRestartCommand ].vals[1].value = strdup(aRestartOption.getStr()); + OString aRestartOptionNoLogo("--nologo"); + pSmProps[ eRestartCommand ].vals[2].length = aRestartOptionNoLogo.getLength()+1; + pSmProps[ eRestartCommand ].vals[2].value = strdup(aRestartOptionNoLogo.getStr()); + + OUString aUserName; + OString aUser; + oslSecurity aSec = osl_getCurrentSecurity(); + if( aSec ) + { + osl_getUserName( aSec, &aUserName.pData ); + aUser = OUStringToOString( aUserName, osl_getThreadTextEncoding() ); + osl_freeSecurityHandle( aSec ); + } + + pSmProps[ eUserId ].name = const_cast<char*>(SmUserID); + pSmProps[ eUserId ].type = const_cast<char*>(SmARRAY8); + pSmProps[ eUserId ].num_vals = 1; + pSmProps[ eUserId ].vals = new SmPropValue; + pSmProps[ eUserId ].vals->value = strdup( aUser.getStr() ); + pSmProps[ eUserId ].vals->length = rtl_str_getLength( static_cast<char *>(pSmProps[ 3 ].vals->value) )+1; + + pSmProps[ eRestartStyleHint ].name = const_cast<char*>(SmRestartStyleHint); + pSmProps[ eRestartStyleHint ].type = const_cast<char*>(SmCARD8); + pSmProps[ eRestartStyleHint ].num_vals = 1; + pSmProps[ eRestartStyleHint ].vals = new SmPropValue; + pSmProps[ eRestartStyleHint ].vals->value = malloc(1); + pSmRestartHint = static_cast<unsigned char *>(pSmProps[ 4 ].vals->value); + *pSmRestartHint = SmRestartIfRunning; + pSmProps[ eRestartStyleHint ].vals->length = 1; + + for( int i = 0; i < nSmProps; i++ ) + ppSmProps[ i ] = &pSmProps[i]; + + ppSmDel[eDiscardCommand] = const_cast<char*>(SmDiscardCommand); +} + +bool SessionManagerClient::checkDocumentsSaved() +{ + SAL_INFO("vcl.sm", "SessionManagerClient::checkDocumentsSaved"); + + SAL_INFO("vcl.sm.debug", " m_bcheckDocumentsSaved = " << (m_bDocSaveDone ? "true" : "false" )); + return m_bDocSaveDone; +} + +IMPL_STATIC_LINK( SessionManagerClient, SaveYourselfHdl, void*, pStateVal, void ) +{ + SAL_INFO("vcl.sm", "SessionManagerClient, SaveYourselfHdl"); + + // Decode argument smuggled in as void*: + sal_uIntPtr nStateVal = reinterpret_cast< sal_uIntPtr >(pStateVal); + bool shutdown = nStateVal != 0; + + static bool bFirstShutdown=true; + + SAL_INFO("vcl.sm.debug", " shutdown = " << (shutdown ? "true" : "false" ) << + ", bFirstShutdown = " << (bFirstShutdown ? "true" : "false" )); + if (shutdown && bFirstShutdown) //first shutdown request + { + bFirstShutdown = false; + /* + If we have no actual frames open, e.g. we launched a quickstarter, + and then shutdown all our frames leaving just a quickstarter running, + then we don't want to launch an empty toplevel frame on the next + start. (The job of scheduling the restart of the quick-starter is a + task of the quick-starter) + */ + *pSmRestartHint = SmRestartNever; + for (auto pSalFrame : vcl_sal::getSalDisplay(GetGenericUnixSalData())->getFrames() ) + { + vcl::Window *pWindow = pSalFrame->GetWindow(); + if (pWindow && pWindow->IsVisible()) + { + *pSmRestartHint = SmRestartIfRunning; + SAL_INFO("vcl.sm.debug", " pSmRestartHint = SmRestartIfRunning"); + break; + } + } + } + + if( m_pSession ) + { + SalSessionSaveRequestEvent aEvent( shutdown ); + m_pSession->CallCallback( &aEvent ); + } + else + saveDone(); +} + +IMPL_STATIC_LINK_NOARG( SessionManagerClient, InteractionHdl, void*, void ) +{ + SAL_INFO("vcl.sm", "SessionManagerClient, InteractionHdl"); + + if( m_pSession ) + { + SalSessionInteractionEvent aEvent( true ); + m_pSession->CallCallback( &aEvent ); + } +} + +IMPL_STATIC_LINK_NOARG( SessionManagerClient, ShutDownCancelHdl, void*, void ) +{ + SAL_INFO("vcl.sm", "SessionManagerClient, ShutDownCancelHdl"); + + if( m_pSession ) + { + SalSessionShutdownCancelEvent aEvent; + m_pSession->CallCallback( &aEvent ); + } +} + +void SessionManagerClient::SaveYourselfProc( + SmcConn, + SmPointer, + int save_type, + Bool shutdown, + int interact_style, + Bool + ) +{ + SAL_INFO("vcl.sm", "SessionManagerClient::SaveYourselfProc"); + + TimeValue now; + osl_getSystemTime(&now); + + SAL_INFO("vcl.sm", " save_type = " << ((save_type == SmSaveLocal ) ? "local" : + (save_type == SmSaveGlobal) ? "global" : "both") << + ", shutdown = " << (shutdown ? "true" : "false" ) << + ", interact_style = " << ((interact_style == SmInteractStyleNone) ? "SmInteractStyleNone" : + (interact_style == SmInteractStyleErrors) ? "SmInteractStyleErrors" : + "SmInteractStyleAny")); + char num[100]; + snprintf(num, sizeof(num), "_%" SAL_PRIuUINT32 "_%" SAL_PRIuUINT32, now.Seconds, (now.Nanosec / 1001)); + m_aTimeID = OString(num); + + BuildSmPropertyList(); + + SmcSetProperties( m_pSmcConnection, 1, &ppSmProps[ eProgram ] ); + SmcSetProperties( m_pSmcConnection, 1, &ppSmProps[ eUserId ] ); + + + m_bDocSaveDone = false; + /* #i49875# some session managers send a "die" message if the + * saveDone does not come early enough for their convenience + * this can occasionally happen on startup, especially the first + * startup. So shortcut the "not shutting down" case since the + * upper layers are currently not interested in that event anyway. + */ + if( ! shutdown ) + { + SessionManagerClient::saveDone(); + return; + } + // Smuggle argument in as void*: + sal_uIntPtr nStateVal = shutdown; + Application::PostUserEvent( LINK( nullptr, SessionManagerClient, SaveYourselfHdl ), reinterpret_cast< void * >(nStateVal) ); +} + +IMPL_STATIC_LINK_NOARG( SessionManagerClient, ShutDownHdl, void*, void ) +{ + SAL_INFO("vcl.sm", "SessionManagerClient, ShutDownHdl"); + + if( m_pSession ) + { + SalSessionQuitEvent aEvent; + m_pSession->CallCallback( &aEvent ); + } + + SalFrame *pAnyFrame = vcl_sal::getSalDisplay(GetGenericUnixSalData())->anyFrame(); + SAL_INFO("vcl.sm.debug", " rFrames.empty() = " << (pAnyFrame ? "true" : "false")); + if( pAnyFrame ) + pAnyFrame->CallCallback( SalEvent::Shutdown, nullptr ); +} + +void SessionManagerClient::DieProc( + SmcConn connection, + SmPointer + ) +{ + SAL_INFO("vcl.sm", "SessionManagerClient::DieProc"); + + if( connection == m_pSmcConnection ) + { + SAL_INFO("vcl.sm.debug", " connection == m_pSmcConnection" ); + Application::PostUserEvent( LINK( nullptr, SessionManagerClient, ShutDownHdl ) ); + } +} + +void SessionManagerClient::SaveCompleteProc( + SmcConn, + SmPointer + ) +{ + SAL_INFO("vcl.sm", "SessionManagerClient::SaveCompleteProc"); +} + +void SessionManagerClient::ShutdownCanceledProc( + SmcConn connection, + SmPointer ) +{ + SAL_INFO("vcl.sm", "SessionManagerClient::ShutdownCanceledProc" ); + + SAL_INFO("vcl.sm.debug", " connection == m_pSmcConnection = " << (( connection == m_pSmcConnection ) ? "true" : "false")); + if( connection == m_pSmcConnection ) + Application::PostUserEvent( LINK( nullptr, SessionManagerClient, ShutDownCancelHdl ) ); +} + +void SessionManagerClient::InteractProc( + SmcConn connection, + SmPointer ) +{ + SAL_INFO("vcl.sm", "SessionManagerClient::InteractProc" ); + + SAL_INFO("vcl.sm.debug", " connection == m_pSmcConnection = " << (( connection == m_pSmcConnection ) ? "true" : "false")); + if( connection == m_pSmcConnection ) + Application::PostUserEvent( LINK( nullptr, SessionManagerClient, InteractionHdl ) ); +} + +void SessionManagerClient::saveDone() +{ + SAL_INFO("vcl.sm", "SessionManagerClient::saveDone"); + + if( !m_pSmcConnection ) + return; + + assert(m_xICEConnectionObserver); + osl::MutexGuard g(m_xICEConnectionObserver->m_ICEMutex); + //SmcSetProperties( m_pSmcConnection, 1, &ppSmProps[ eCloneCommand ] ); + // this message-handling is now equal to kate and plasma desktop + SmcSetProperties( m_pSmcConnection, 1, &ppSmProps[ eRestartCommand ] ); + SmcDeleteProperties( m_pSmcConnection, 1, &ppSmDel[ eDiscardCommand ] ); + SmcSetProperties( m_pSmcConnection, 1, &ppSmProps[ eRestartStyleHint ] ); + + SmcSaveYourselfDone( m_pSmcConnection, True ); + SAL_INFO("vcl.sm.debug", " sent SmRestartHint = " << (*pSmRestartHint) ); + m_bDocSaveDone = true; +} + +void SessionManagerClient::open(SalSession * pSession) +{ + SAL_INFO("vcl.sm", "SessionManagerClient::open"); + + assert(!m_pSession && !m_xICEConnectionObserver && !m_pSmcConnection); + // must only be called once + m_pSession = pSession; + // This is the way Xt does it, so we can too: + if( getenv( "SESSION_MANAGER" ) ) + { + SAL_INFO("vcl.sm.debug", " getenv( SESSION_MANAGER ) = true"); + m_xICEConnectionObserver.reset(new ICEConnectionObserver); + m_xICEConnectionObserver->activate(); + + { + osl::MutexGuard g(m_xICEConnectionObserver->m_ICEMutex); + + static SmcCallbacks aCallbacks; // does this need to be static? + aCallbacks.save_yourself.callback = SaveYourselfProc; + aCallbacks.save_yourself.client_data = nullptr; + aCallbacks.die.callback = DieProc; + aCallbacks.die.client_data = nullptr; + aCallbacks.save_complete.callback = SaveCompleteProc; + aCallbacks.save_complete.client_data = nullptr; + aCallbacks.shutdown_cancelled.callback = ShutdownCanceledProc; + aCallbacks.shutdown_cancelled.client_data = nullptr; + OString aPrevId(getPreviousSessionID()); + char* pClientID = nullptr; + char aErrBuf[1024]; + m_pSmcConnection = SmcOpenConnection( nullptr, + nullptr, + SmProtoMajor, + SmProtoMinor, + SmcSaveYourselfProcMask | + SmcDieProcMask | + SmcSaveCompleteProcMask | + SmcShutdownCancelledProcMask , + &aCallbacks, + aPrevId.isEmpty() ? nullptr : const_cast<char*>(aPrevId.getStr()), + &pClientID, + sizeof( aErrBuf ), + aErrBuf ); + if( !m_pSmcConnection ) + SAL_INFO("vcl.sm.debug", " SmcOpenConnection failed: " << aErrBuf); + else + SAL_INFO("vcl.sm.debug", " SmcOpenConnection succeeded, client ID is " << pClientID ); + m_aClientID = OString(pClientID); + free( pClientID ); + pClientID = nullptr; + } + + SalDisplay* pDisp = vcl_sal::getSalDisplay(GetGenericUnixSalData()); + if( pDisp->GetDrawable(pDisp->GetDefaultXScreen()) && !m_aClientID.isEmpty() ) + { + SAL_INFO("vcl.sm.debug", " SmcOpenConnection open: pDisp->GetDrawable = true"); + XChangeProperty( pDisp->GetDisplay(), + pDisp->GetDrawable( pDisp->GetDefaultXScreen() ), + XInternAtom( pDisp->GetDisplay(), "SM_CLIENT_ID", False ), + XA_STRING, + 8, + PropModeReplace, + reinterpret_cast<unsigned char const *>(m_aClientID.getStr()), + m_aClientID.getLength() + ); + } + } + else + { + SAL_INFO("vcl.sm.debug", " getenv( SESSION_MANAGER ) = false"); + } +} + +const OString& SessionManagerClient::getSessionID() +{ + SAL_INFO("vcl.sm", "SessionManagerClient::getSessionID"); + + m_aClientTimeID = m_aClientID + m_aTimeID; + + SAL_INFO("vcl.sm", " SessionID = " << m_aClientTimeID); + + return m_aClientTimeID; +} + +void SessionManagerClient::close() +{ + SAL_INFO("vcl.sm", "SessionManagerClient::close"); + + if( !m_pSmcConnection ) + return; + + SAL_INFO("vcl.sm.debug", " attempting SmcCloseConnection"); + assert(m_xICEConnectionObserver); + { + osl::MutexGuard g(m_xICEConnectionObserver->m_ICEMutex); + SmcCloseConnection( m_pSmcConnection, 0, nullptr ); + SAL_INFO("vcl.sm", " SmcCloseConnection closed"); + } + m_xICEConnectionObserver->deactivate(); + m_xICEConnectionObserver.reset(); + m_pSmcConnection = nullptr; +} + +bool SessionManagerClient::queryInteraction() +{ + SAL_INFO("vcl.sm", "SessionManagerClient::queryInteraction"); + + bool bRet = false; + if( m_pSmcConnection ) + { + assert(m_xICEConnectionObserver); + osl::MutexGuard g(m_xICEConnectionObserver->m_ICEMutex); + SAL_INFO("vcl.sm.debug", " SmcInteractRequest" ); + if( SmcInteractRequest( m_pSmcConnection, SmDialogNormal, InteractProc, nullptr ) ) + bRet = true; + } + return bRet; +} + +void SessionManagerClient::interactionDone( bool bCancelShutdown ) +{ + SAL_INFO("vcl.sm", "SessionManagerClient::interactionDone"); + + if( m_pSmcConnection ) + { + assert(m_xICEConnectionObserver); + osl::MutexGuard g(m_xICEConnectionObserver->m_ICEMutex); + SAL_INFO("vcl.sm.debug", " SmcInteractDone = " << (bCancelShutdown ? "true" : "false") ); + SmcInteractDone( m_pSmcConnection, bCancelShutdown ? True : False ); + } +} + +OUString SessionManagerClient::getExecName() +{ + SAL_INFO("vcl.sm", "SessionManagerClient::getExecName"); + + OUString aExec, aSysExec; + osl_getExecutableFile( &aExec.pData ); + osl_getSystemPathFromFileURL( aExec.pData, &aSysExec.pData ); + + if( aSysExec.endsWith(".bin") ) + aSysExec = aSysExec.copy( 0, aSysExec.getLength() - RTL_CONSTASCII_LENGTH(".bin") ); + + SAL_INFO("vcl.sm.debug", " aSysExec = " << aSysExec); + return aSysExec; +} + +OString SessionManagerClient::getPreviousSessionID() +{ + SAL_INFO("vcl.sm", "SessionManagerClient::getPreviousSessionID"); + + OString aPrevId; + + sal_uInt32 n = rtl_getAppCommandArgCount(); + for (sal_uInt32 i = 0; i != n; ++i) + { + OUString aArg; + rtl_getAppCommandArg( i, &aArg.pData ); + if(aArg.match("--session=")) + { + aPrevId = OUStringToOString( + aArg.subView(RTL_CONSTASCII_LENGTH("--session=")), + osl_getThreadTextEncoding()); + break; + } + } + + SAL_INFO("vcl.sm.debug", " previous ID = " << aPrevId); + return aPrevId; +} + +void ICEConnectionObserver::activate() +{ + SAL_INFO("vcl.sm", "ICEConnectionObserver::activate"); + + /* + * Default handlers call exit, we don't care that strongly if something + * happens to fail + */ + m_origIOErrorHandler = IceSetIOErrorHandler( IgnoreIceIOErrors ); + m_origErrorHandler = IceSetErrorHandler( IgnoreIceErrors ); + IceAddConnectionWatch( ICEWatchProc, this ); +} + +void ICEConnectionObserver::deactivate() +{ + SAL_INFO("vcl.sm", "ICEConnectionObserver::deactivate"); + + oslThread t; + { + osl::MutexGuard g(m_ICEMutex); + IceRemoveConnectionWatch( ICEWatchProc, this ); + IceSetErrorHandler( m_origErrorHandler ); + IceSetIOErrorHandler( m_origIOErrorHandler ); + m_nConnections = 0; + t = m_ICEThread; + m_ICEThread = nullptr; + } + if (t) + { + SAL_INFO("vcl.sm.debug", " terminate"); + terminate(t); + } +} + +void ICEConnectionObserver::wakeup() +{ + SAL_INFO("vcl.sm", "ICEConnectionObserver::wakeup"); + + char cChar = 'w'; + OSL_VERIFY(write(m_nWakeupFiles[1], &cChar, 1) == 1); +} + +void ICEConnectionObserver::terminate(oslThread iceThread) +{ + SAL_INFO("vcl.sm", "ICEConnectionObserver::terminate"); + + osl_terminateThread(iceThread); + wakeup(); + osl_joinWithThread(iceThread); + osl_destroyThread(iceThread); + close(m_nWakeupFiles[1]); + close(m_nWakeupFiles[0]); +} + +void ICEConnectionWorker(void * data) +{ + SAL_INFO("vcl.sm", "ICEConnectionWorker"); + + osl::Thread::setName("ICEConnectionWorker"); + ICEConnectionObserver * pThis = static_cast< ICEConnectionObserver * >( + data); + for (;;) + { + oslThread t; + { + osl::MutexGuard g(pThis->m_ICEMutex); + if (pThis->m_ICEThread == nullptr || pThis->m_nConnections == 0) + { + break; + } + t = pThis->m_ICEThread; + } + if (!osl_scheduleThread(t)) + { + break; + } + + int nConnectionsBefore; + struct pollfd* pLocalFD; + { + osl::MutexGuard g(pThis->m_ICEMutex); + nConnectionsBefore = pThis->m_nConnections; + int nBytes = sizeof( struct pollfd )*(nConnectionsBefore+1); + pLocalFD = static_cast<struct pollfd*>(std::malloc( nBytes )); + memcpy( pLocalFD, pThis->m_pFilehandles, nBytes ); + } + + int nRet = poll( pLocalFD,nConnectionsBefore+1,-1 ); + bool bWakeup = (pLocalFD[0].revents & POLLIN); + std::free( pLocalFD ); + + if( nRet < 1 ) + continue; + + // clear wakeup pipe + if( bWakeup ) + { + char buf[4]; + while( read( pThis->m_nWakeupFiles[0], buf, sizeof( buf ) ) > 0 ) + ; + SAL_INFO("vcl.sm.debug", " file handles active in wakeup: " << nRet); + if( nRet == 1 ) + continue; + } + + // check fd's after we obtained the lock + osl::MutexGuard g(pThis->m_ICEMutex); + if( pThis->m_nConnections > 0 && pThis->m_nConnections == nConnectionsBefore ) + { + nRet = poll( pThis->m_pFilehandles+1, pThis->m_nConnections, 0 ); + if( nRet > 0 ) + { + SAL_INFO("vcl.sm.debug", " IceProcessMessages"); + Bool bReply; + for( int i = 0; i < pThis->m_nConnections; i++ ) + if( pThis->m_pFilehandles[i+1].revents & POLLIN ) + IceProcessMessages( pThis->m_pConnections[i], nullptr, &bReply ); + } + } + } + + SAL_INFO("vcl.sm.debug", " shutting down ICE dispatch thread"); +} + +void ICEWatchProc( + IceConn ice_conn, IcePointer client_data, Bool opening, + SAL_UNUSED_PARAMETER IcePointer *) +{ + SAL_INFO("vcl.sm", "ICEWatchProc"); + + // Note: This is a callback function for ICE; this implicitly means that a + // call into ICE lib is calling this, so the m_ICEMutex MUST already be + // locked by the caller. + ICEConnectionObserver * pThis = static_cast< ICEConnectionObserver * >( + client_data); + if( opening ) + { + SAL_INFO("vcl.sm.debug", " opening"); + int fd = IceConnectionNumber( ice_conn ); + pThis->m_nConnections++; + pThis->m_pConnections = static_cast<IceConn*>(std::realloc( pThis->m_pConnections, sizeof( IceConn )*pThis->m_nConnections )); + pThis->m_pFilehandles = static_cast<struct pollfd*>(std::realloc( pThis->m_pFilehandles, sizeof( struct pollfd )*(pThis->m_nConnections+1) )); + pThis->m_pConnections[ pThis->m_nConnections-1 ] = ice_conn; + pThis->m_pFilehandles[ pThis->m_nConnections ].fd = fd; + pThis->m_pFilehandles[ pThis->m_nConnections ].events = POLLIN; + if( pThis->m_nConnections == 1 ) + { + SAL_INFO("vcl.sm.debug", " First connection"); + if (!pipe(pThis->m_nWakeupFiles)) + { + int flags; + pThis->m_pFilehandles[0].fd = pThis->m_nWakeupFiles[0]; + pThis->m_pFilehandles[0].events = POLLIN; + // set close-on-exec and nonblock descriptor flag. + if ((flags = fcntl(pThis->m_nWakeupFiles[0], F_GETFD)) != -1) + { + flags |= FD_CLOEXEC; + (void)fcntl(pThis->m_nWakeupFiles[0], F_SETFD, flags); + } + if ((flags = fcntl(pThis->m_nWakeupFiles[0], F_GETFL)) != -1) + { + flags |= O_NONBLOCK; + (void)fcntl(pThis->m_nWakeupFiles[0], F_SETFL, flags); + } + // set close-on-exec and nonblock descriptor flag. + if ((flags = fcntl(pThis->m_nWakeupFiles[1], F_GETFD)) != -1) + { + flags |= FD_CLOEXEC; + (void)fcntl(pThis->m_nWakeupFiles[1], F_SETFD, flags); + } + if ((flags = fcntl(pThis->m_nWakeupFiles[1], F_GETFL)) != -1) + { + flags |= O_NONBLOCK; + (void)fcntl(pThis->m_nWakeupFiles[1], F_SETFL, flags); + } + pThis->m_ICEThread = osl_createThread( + ICEConnectionWorker, pThis); + } + } + } + else // closing + { + SAL_INFO("vcl.sm.debug", " closing"); + for( int i = 0; i < pThis->m_nConnections; i++ ) + { + if( pThis->m_pConnections[i] == ice_conn ) + { + if( i < pThis->m_nConnections-1 ) + { + memmove( pThis->m_pConnections+i, pThis->m_pConnections+i+1, sizeof( IceConn )*(pThis->m_nConnections-i-1) ); + memmove( pThis->m_pFilehandles+i+1, pThis->m_pFilehandles+i+2, sizeof( struct pollfd )*(pThis->m_nConnections-i-1) ); + } + pThis->m_nConnections--; + pThis->m_pConnections = static_cast<IceConn*>(std::realloc( pThis->m_pConnections, sizeof( IceConn )*pThis->m_nConnections )); + pThis->m_pFilehandles = static_cast<struct pollfd*>(std::realloc( pThis->m_pFilehandles, sizeof( struct pollfd )*(pThis->m_nConnections+1) )); + break; + } + } + if( pThis->m_nConnections == 0 && pThis->m_ICEThread ) + { + SAL_INFO("vcl.sm.debug", " terminating ICEThread"); + oslThread t = pThis->m_ICEThread; + pThis->m_ICEThread = nullptr; + + // must release the mutex here + pThis->m_ICEMutex.release(); + + pThis->terminate(t); + + // acquire the mutex again, because the caller does not expect + // it to be released when calling into SM + pThis->m_ICEMutex.acquire(); + } + } + + SAL_INFO( "vcl.sm.debug", " ICE connection on " << IceConnectionNumber( ice_conn ) ); + SAL_INFO( "vcl.sm.debug", " Display connection is " << ConnectionNumber( vcl_sal::getSalDisplay(GetGenericUnixSalData())->GetDisplay() ) ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/generic/app/wmadaptor.cxx b/vcl/unx/generic/app/wmadaptor.cxx new file mode 100644 index 000000000..37384c7b5 --- /dev/null +++ b/vcl/unx/generic/app/wmadaptor.cxx @@ -0,0 +1,2215 @@ +/* -*- 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 <string.h> +#include <stdlib.h> +#include <unistd.h> +#include <string_view> + +#include <i18nlangtag/languagetag.hxx> +#include <rtl/locale.h> + +#include <osl/thread.h> +#include <osl/process.h> +#include <sal/macros.h> +#include <sal/log.hxx> +#include <comphelper/string.hxx> +#include <configsettings.hxx> +#include <o3tl/string_view.hxx> + +#include <unx/wmadaptor.hxx> +#include <unx/saldisp.hxx> +#include <unx/salframe.h> + +#include <X11/Xlib.h> +#include <X11/Xutil.h> +#include <X11/Xatom.h> + +namespace vcl_sal { + +class NetWMAdaptor : public WMAdaptor +{ + void setNetWMState( X11SalFrame* pFrame ) const; + void initAtoms(); + virtual bool isValid() const override; +public: + explicit NetWMAdaptor( SalDisplay* ); + + virtual void setWMName( X11SalFrame* pFrame, const OUString& rWMName ) const override; + virtual void maximizeFrame( X11SalFrame* pFrame, bool bHorizontal = true, bool bVertical = true ) const override; + virtual void setFrameTypeAndDecoration( X11SalFrame* pFrame, WMWindowType eType, int nDecorationFlags, X11SalFrame* pTransientFrame ) const override; + virtual void enableAlwaysOnTop( X11SalFrame* pFrame, bool bEnable ) const override; + virtual int handlePropertyNotify( X11SalFrame* pFrame, XPropertyEvent* pEvent ) const override; + virtual void showFullScreen( X11SalFrame* pFrame, bool bFullScreen ) const override; + virtual void frameIsMapping( X11SalFrame* pFrame ) const override; + virtual void setUserTime( X11SalFrame* i_pFrame, tools::Long i_nUserTime ) const override; +}; + +class GnomeWMAdaptor : public WMAdaptor +{ + bool m_bValid; + + void setGnomeWMState( X11SalFrame* pFrame ) const; + void initAtoms(); + virtual bool isValid() const override; +public: + explicit GnomeWMAdaptor( SalDisplay * ); + + virtual void maximizeFrame( X11SalFrame* pFrame, bool bHorizontal = true, bool bVertical = true ) const override; + virtual void enableAlwaysOnTop( X11SalFrame* pFrame, bool bEnable ) const override; + virtual int handlePropertyNotify( X11SalFrame* pFrame, XPropertyEvent* pEvent ) const override; +}; + +} + +using namespace vcl_sal; + +namespace { + +struct WMAdaptorProtocol +{ + const char* pProtocol; + int nProtocol; +}; + +} + +/* + * table must be sorted ascending in strings + * since it is use with bsearch + */ +const WMAdaptorProtocol aProtocolTab[] = +{ + { "_KDE_NET_WM_WINDOW_TYPE_OVERRIDE", WMAdaptor::KDE_NET_WM_WINDOW_TYPE_OVERRIDE }, + { "_NET_ACTIVE_WINDOW", WMAdaptor::NET_ACTIVE_WINDOW }, + { "_NET_CURRENT_DESKTOP", WMAdaptor::NET_CURRENT_DESKTOP }, + { "_NET_NUMBER_OF_DESKTOPS", WMAdaptor::NET_NUMBER_OF_DESKTOPS }, + { "_NET_WM_DESKTOP", WMAdaptor::NET_WM_DESKTOP }, + { "_NET_WM_ICON", WMAdaptor::NET_WM_ICON }, + { "_NET_WM_ICON_NAME", WMAdaptor::NET_WM_ICON_NAME }, + { "_NET_WM_PING", WMAdaptor::NET_WM_PING }, + { "_NET_WM_STATE", WMAdaptor::NET_WM_STATE }, + { "_NET_WM_STATE_ABOVE", WMAdaptor::NET_WM_STATE_STAYS_ON_TOP }, + { "_NET_WM_STATE_FULLSCREEN", WMAdaptor::NET_WM_STATE_FULLSCREEN }, + { "_NET_WM_STATE_MAXIMIZED_HORIZ", WMAdaptor::NET_WM_STATE_MAXIMIZED_HORZ }, // common bug in e.g. older kwin and sawfish implementations + { "_NET_WM_STATE_MAXIMIZED_HORZ", WMAdaptor::NET_WM_STATE_MAXIMIZED_HORZ }, + { "_NET_WM_STATE_MAXIMIZED_VERT", WMAdaptor::NET_WM_STATE_MAXIMIZED_VERT }, + { "_NET_WM_STATE_MODAL", WMAdaptor::NET_WM_STATE_MODAL }, + { "_NET_WM_STATE_SKIP_PAGER", WMAdaptor::NET_WM_STATE_SKIP_PAGER }, + { "_NET_WM_STATE_SKIP_TASKBAR", WMAdaptor::NET_WM_STATE_SKIP_TASKBAR }, + { "_NET_WM_STATE_STAYS_ON_TOP", WMAdaptor::NET_WM_STATE_STAYS_ON_TOP }, + { "_NET_WM_STATE_STICKY", WMAdaptor::NET_WM_STATE_STICKY }, + { "_NET_WM_STRUT", WMAdaptor::NET_WM_STRUT }, + { "_NET_WM_STRUT_PARTIAL", WMAdaptor::NET_WM_STRUT_PARTIAL }, + { "_NET_WM_WINDOW_TYPE", WMAdaptor::NET_WM_WINDOW_TYPE }, + { "_NET_WM_WINDOW_TYPE_DESKTOP", WMAdaptor::NET_WM_WINDOW_TYPE_DESKTOP }, + { "_NET_WM_WINDOW_TYPE_DIALOG", WMAdaptor::NET_WM_WINDOW_TYPE_DIALOG }, + { "_NET_WM_WINDOW_TYPE_DOCK", WMAdaptor::NET_WM_WINDOW_TYPE_DOCK }, + { "_NET_WM_WINDOW_TYPE_MENU", WMAdaptor::NET_WM_WINDOW_TYPE_MENU }, + { "_NET_WM_WINDOW_TYPE_NORMAL", WMAdaptor::NET_WM_WINDOW_TYPE_NORMAL }, + { "_NET_WM_WINDOW_TYPE_SPLASH", WMAdaptor::NET_WM_WINDOW_TYPE_SPLASH }, + { "_NET_WM_WINDOW_TYPE_SPLASHSCREEN", WMAdaptor::NET_WM_WINDOW_TYPE_SPLASH }, // bug in Metacity 2.4.1 + { "_NET_WM_WINDOW_TYPE_TOOLBAR", WMAdaptor::NET_WM_WINDOW_TYPE_TOOLBAR }, + { "_NET_WM_WINDOW_TYPE_UTILITY", WMAdaptor::NET_WM_WINDOW_TYPE_UTILITY }, + { "_NET_WORKAREA", WMAdaptor::NET_WORKAREA }, + { "_WIN_APP_STATE", WMAdaptor::WIN_APP_STATE }, + { "_WIN_CLIENT_LIST", WMAdaptor::WIN_CLIENT_LIST }, + { "_WIN_EXPANDED_SIZE", WMAdaptor::WIN_EXPANDED_SIZE }, + { "_WIN_HINTS", WMAdaptor::WIN_HINTS }, + { "_WIN_ICONS", WMAdaptor::WIN_ICONS }, + { "_WIN_LAYER", WMAdaptor::WIN_LAYER }, + { "_WIN_STATE", WMAdaptor::WIN_STATE }, + { "_WIN_WORKSPACE", WMAdaptor::WIN_WORKSPACE }, + { "_WIN_WORKSPACE_COUNT", WMAdaptor::WIN_WORKSPACE_COUNT } +}; + +/* + * table containing atoms to get anyway + */ + +const WMAdaptorProtocol aAtomTab[] = +{ + { "WM_STATE", WMAdaptor::WM_STATE }, + { "_MOTIF_WM_HINTS", WMAdaptor::MOTIF_WM_HINTS }, + { "WM_PROTOCOLS", WMAdaptor::WM_PROTOCOLS }, + { "WM_DELETE_WINDOW", WMAdaptor::WM_DELETE_WINDOW }, + { "WM_TAKE_FOCUS", WMAdaptor::WM_TAKE_FOCUS }, + { "WM_COMMAND", WMAdaptor::WM_COMMAND }, + { "WM_CLIENT_LEADER", WMAdaptor::WM_CLIENT_LEADER }, + { "WM_LOCALE_NAME", WMAdaptor::WM_LOCALE_NAME }, + { "WM_TRANSIENT_FOR", WMAdaptor::WM_TRANSIENT_FOR }, + { "SAL_QUITEVENT", WMAdaptor::SAL_QUITEVENT }, + { "SAL_USEREVENT", WMAdaptor::SAL_USEREVENT }, + { "SAL_EXTTEXTEVENT", WMAdaptor::SAL_EXTTEXTEVENT }, + { "SAL_GETTIMEEVENT", WMAdaptor::SAL_GETTIMEEVENT }, + { "VCL_SYSTEM_SETTINGS", WMAdaptor::VCL_SYSTEM_SETTINGS }, + { "_XSETTINGS_SETTINGS", WMAdaptor::XSETTINGS }, + { "_XEMBED", WMAdaptor::XEMBED }, + { "_XEMBED_INFO", WMAdaptor::XEMBED_INFO }, + { "_NET_WM_USER_TIME", WMAdaptor::NET_WM_USER_TIME }, + { "_NET_WM_PID", WMAdaptor::NET_WM_PID } +}; + +extern "C" { +static int compareProtocol( const void* pLeft, const void* pRight ) +{ + return strcmp( static_cast<const WMAdaptorProtocol*>(pLeft)->pProtocol, static_cast<const WMAdaptorProtocol*>(pRight)->pProtocol ); +} +} + +std::unique_ptr<WMAdaptor> WMAdaptor::createWMAdaptor( SalDisplay* pSalDisplay ) +{ + std::unique_ptr<WMAdaptor> pAdaptor; + + // try a NetWM + pAdaptor.reset(new NetWMAdaptor( pSalDisplay )); + if( ! pAdaptor->isValid() ) + { + pAdaptor.reset(); + } +#if OSL_DEBUG_LEVEL > 1 + else + SAL_INFO("vcl.app", "WM supports extended WM hints."); +#endif + + // try a GnomeWM + if( ! pAdaptor ) + { + pAdaptor.reset(new GnomeWMAdaptor( pSalDisplay )); + if( ! pAdaptor->isValid() ) + { + pAdaptor.reset(); + } +#if OSL_DEBUG_LEVEL > 1 + else + SAL_INFO("vcl.app", "WM supports GNOME WM hints."); +#endif + } + + if( ! pAdaptor ) + pAdaptor.reset(new WMAdaptor( pSalDisplay )); + +#if OSL_DEBUG_LEVEL > 1 + SAL_INFO("vcl.app", "Window Manager's name is \"" + << pAdaptor->getWindowManagerName() + << "\"."); +#endif + return pAdaptor; +} + +/* + * WMAdaptor constructor + */ + +WMAdaptor::WMAdaptor( SalDisplay* pDisplay ) : + m_pSalDisplay( pDisplay ), + m_bEnableAlwaysOnTopWorks( false ), + m_bLegacyPartialFullscreen( false ), + m_nWinGravity( StaticGravity ), + m_nInitWinGravity( StaticGravity ), + m_bWMshouldSwitchWorkspace( true ), + m_bWMshouldSwitchWorkspaceInit( false ) +{ + Atom aRealType = None; + int nFormat = 8; + unsigned long nItems = 0; + unsigned long nBytesLeft = 0; + unsigned char* pProperty = nullptr; + + // default desktops + m_nDesktops = 1; + m_aWMWorkAreas = ::std::vector< tools::Rectangle > + ( 1, tools::Rectangle( Point(), m_pSalDisplay->GetScreenSize( m_pSalDisplay->GetDefaultXScreen() ) ) ); + m_bEqualWorkAreas = true; + + memset( m_aWMAtoms, 0, sizeof( m_aWMAtoms ) ); + m_pDisplay = m_pSalDisplay->GetDisplay(); + + initAtoms(); + getNetWmName(); // try to discover e.g. Sawfish + + if( m_aWMName.isEmpty() ) + { + // check for ReflectionX wm (as it needs a workaround in Windows mode + Atom aRwmRunning = XInternAtom( m_pDisplay, "RWM_RUNNING", True ); + if( aRwmRunning != None && + XGetWindowProperty( m_pDisplay, + m_pSalDisplay->GetRootWindow( m_pSalDisplay->GetDefaultXScreen() ), + aRwmRunning, + 0, 32, + False, + aRwmRunning, + &aRealType, + &nFormat, + &nItems, + &nBytesLeft, + &pProperty ) == 0 ) + { + if( aRealType == aRwmRunning ) + m_aWMName = "ReflectionX"; + XFree( pProperty ); + } + else + { + aRwmRunning = XInternAtom( m_pDisplay, "_WRQ_WM_RUNNING", True ); + if( aRwmRunning != None && + XGetWindowProperty( m_pDisplay, + m_pSalDisplay->GetRootWindow( m_pSalDisplay->GetDefaultXScreen() ), + aRwmRunning, + 0, 32, + False, + XA_STRING, + &aRealType, + &nFormat, + &nItems, + &nBytesLeft, + &pProperty ) == 0 ) + { + if( aRealType == XA_STRING ) + m_aWMName = "ReflectionX Windows"; + XFree( pProperty ); + } + } + } + if( !m_aWMName.isEmpty() ) + return; + + Atom aTTAPlatform = XInternAtom( m_pDisplay, "TTA_CLIENT_PLATFORM", True ); + if( aTTAPlatform == None || + XGetWindowProperty( m_pDisplay, + m_pSalDisplay->GetRootWindow( m_pSalDisplay->GetDefaultXScreen() ), + aTTAPlatform, + 0, 32, + False, + XA_STRING, + &aRealType, + &nFormat, + &nItems, + &nBytesLeft, + &pProperty ) != 0 ) + return; + + if( aRealType == XA_STRING ) + { + m_aWMName = "Tarantella"; + // #i62319# pretend that AlwaysOnTop works since + // the alwaysontop workaround in salframe.cxx results + // in a raise/lower loop on a Windows tarantella client + // FIXME: this property contains an identification string that + // in theory should be good enough to recognize running on a + // Windows client; however this string does not seem to be + // documented as well as the property itself. + m_bEnableAlwaysOnTopWorks = true; + } + XFree( pProperty ); +} + +/* + * WMAdaptor destructor + */ + +WMAdaptor::~WMAdaptor() +{ +} + +/* + * NetWMAdaptor constructor + */ + +NetWMAdaptor::NetWMAdaptor( SalDisplay* pSalDisplay ) : + WMAdaptor( pSalDisplay ) +{ + // currently all _NET WMs do transient like expected + + Atom aRealType = None; + int nFormat = 8; + unsigned long nItems = 0; + unsigned long nBytesLeft = 0; + unsigned char* pProperty = nullptr; + + initAtoms(); + + // check for NetWM + bool bNetWM = getNetWmName(); + if( bNetWM + && XGetWindowProperty( m_pDisplay, + m_pSalDisplay->GetRootWindow( m_pSalDisplay->GetDefaultXScreen() ), + m_aWMAtoms[ NET_SUPPORTED ], + 0, 0, + False, + XA_ATOM, + &aRealType, + &nFormat, + &nItems, + &nBytesLeft, + &pProperty ) == 0 + && aRealType == XA_ATOM + && nFormat == 32 + ) + { + if( pProperty ) + { + XFree( pProperty ); + pProperty = nullptr; + } + // collect supported protocols + if( XGetWindowProperty( m_pDisplay, + m_pSalDisplay->GetRootWindow( m_pSalDisplay->GetDefaultXScreen() ), + m_aWMAtoms[ NET_SUPPORTED ], + 0, nBytesLeft/4, + False, + XA_ATOM, + &aRealType, + &nFormat, + &nItems, + &nBytesLeft, + &pProperty ) == 0 + && nItems + ) + { + Atom* pAtoms = reinterpret_cast<Atom*>(pProperty); + char** pAtomNames = static_cast<char**>(alloca( sizeof(char*)*nItems )); + if( XGetAtomNames( m_pDisplay, pAtoms, nItems, pAtomNames ) ) + { +#if OSL_DEBUG_LEVEL > 1 + SAL_INFO("vcl.app", "supported protocols:"); +#endif + for( unsigned long i = 0; i < nItems; i++ ) + { + // #i80971# protect against invalid atoms + if( pAtomNames[i] == nullptr ) + continue; + + WMAdaptorProtocol aSearch; + aSearch.pProtocol = pAtomNames[i]; + WMAdaptorProtocol* pMatch = static_cast<WMAdaptorProtocol*>( + bsearch( &aSearch, + aProtocolTab, + SAL_N_ELEMENTS( aProtocolTab ), + sizeof( struct WMAdaptorProtocol ), + compareProtocol )); + if( pMatch ) + { + m_aWMAtoms[ pMatch->nProtocol ] = pAtoms[ i ]; + if( pMatch->nProtocol == NET_WM_STATE_STAYS_ON_TOP ) + m_bEnableAlwaysOnTopWorks = true; + } +#if OSL_DEBUG_LEVEL > 1 + SAL_INFO("vcl.app", " " + << pAtomNames[i] + << (((pMatch)&&(pMatch->nProtocol != -1)) ? + "" : " (unsupported)")); +#endif + XFree( pAtomNames[i] ); + } + } + XFree( pProperty ); + pProperty = nullptr; + } + else if( pProperty ) + { + XFree( pProperty ); + pProperty = nullptr; + } + + // get number of desktops + if( m_aWMAtoms[ NET_NUMBER_OF_DESKTOPS ] + && XGetWindowProperty( m_pDisplay, + m_pSalDisplay->GetRootWindow( m_pSalDisplay->GetDefaultXScreen() ), + m_aWMAtoms[ NET_NUMBER_OF_DESKTOPS ], + 0, 1, + False, + XA_CARDINAL, + &aRealType, + &nFormat, + &nItems, + &nBytesLeft, + &pProperty ) == 0 + && pProperty + ) + { + m_nDesktops = *reinterpret_cast<long*>(pProperty); + XFree( pProperty ); + pProperty = nullptr; + // get work areas + if( m_aWMAtoms[ NET_WORKAREA ] + && XGetWindowProperty( m_pDisplay, + m_pSalDisplay->GetRootWindow( m_pSalDisplay->GetDefaultXScreen() ), + m_aWMAtoms[ NET_WORKAREA ], + 0, 4*m_nDesktops, + False, + XA_CARDINAL, + &aRealType, + &nFormat, + &nItems, + &nBytesLeft, + &pProperty + ) == 0 + && nItems == 4*static_cast<unsigned>(m_nDesktops) + ) + { + m_aWMWorkAreas = ::std::vector< tools::Rectangle > ( m_nDesktops ); + tools::Long* pValues = reinterpret_cast<long*>(pProperty); + for( int i = 0; i < m_nDesktops; i++ ) + { + Point aPoint( pValues[4*i], + pValues[4*i+1] ); + Size aSize( pValues[4*i+2], + pValues[4*i+3] ); + tools::Rectangle aWorkArea( aPoint, aSize ); + m_aWMWorkAreas[i] = aWorkArea; + if( aWorkArea != m_aWMWorkAreas[0] ) + m_bEqualWorkAreas = false; +#if OSL_DEBUG_LEVEL > 1 + SAL_INFO("vcl.app", "workarea " << i + << ": " << m_aWMWorkAreas[i].GetWidth() + << "x" << m_aWMWorkAreas[i].GetHeight() + << "+" << m_aWMWorkAreas[i].Left() + << "+" << m_aWMWorkAreas[i].Top()); +#endif + } + XFree( pProperty ); + } + else + { +#if OSL_DEBUG_LEVEL > 1 + SAL_INFO("vcl.app", nItems/4 << " workareas for " + << m_nDesktops << " desktops !"); +#endif + if( pProperty ) + { + XFree(pProperty); + pProperty = nullptr; + } + } + } + else if( pProperty ) + { + XFree( pProperty ); + pProperty = nullptr; + } + } + else if( pProperty ) + { + XFree( pProperty ); + pProperty = nullptr; + } +} + +/* + * GnomeWMAdaptor constructor + */ + +GnomeWMAdaptor::GnomeWMAdaptor( SalDisplay* pSalDisplay ) : + WMAdaptor( pSalDisplay ), + m_bValid( false ) +{ + // currently all Gnome WMs do transient like expected + + Atom aRealType = None; + int nFormat = 8; + unsigned long nItems = 0; + unsigned long nBytesLeft = 0; + unsigned char* pProperty = nullptr; + + initAtoms(); + + // check for GnomeWM + if( m_aWMAtoms[ WIN_SUPPORTING_WM_CHECK ] && m_aWMAtoms[ WIN_PROTOCOLS ] ) + { + if( XGetWindowProperty( m_pDisplay, + m_pSalDisplay->GetRootWindow( m_pSalDisplay->GetDefaultXScreen() ), + m_aWMAtoms[ WIN_SUPPORTING_WM_CHECK ], + 0, 1, + False, + XA_CARDINAL, + &aRealType, + &nFormat, + &nItems, + &nBytesLeft, + &pProperty ) == 0 + && aRealType == XA_CARDINAL + && nFormat == 32 + && nItems != 0 + ) + { + ::Window aWMChild = *reinterpret_cast< ::Window* >(pProperty); + XFree( pProperty ); + pProperty = nullptr; + GetGenericUnixSalData()->ErrorTrapPush(); + if( XGetWindowProperty( m_pDisplay, + aWMChild, + m_aWMAtoms[ WIN_SUPPORTING_WM_CHECK ], + 0, 1, + False, + XA_CARDINAL, + &aRealType, + &nFormat, + &nItems, + &nBytesLeft, + &pProperty ) == 0 + && aRealType == XA_CARDINAL + && nFormat == 32 + && nItems != 0 ) + { + if (! GetGenericUnixSalData()->ErrorTrapPop( false ) ) + { + GetGenericUnixSalData()->ErrorTrapPush(); + + ::Window aCheckWindow = *reinterpret_cast< ::Window* >(pProperty); + XFree( pProperty ); + pProperty = nullptr; + if( aCheckWindow == aWMChild ) + { + m_bValid = true; + /* + * get name of WM + * this is NOT part of the GNOME WM hints, but e.g. Sawfish + * already supports this part of the extended WM hints + */ + m_aWMAtoms[ UTF8_STRING ] = XInternAtom( m_pDisplay, "UTF8_STRING", False ); + getNetWmName(); + } + } + else + GetGenericUnixSalData()->ErrorTrapPush(); + } + GetGenericUnixSalData()->ErrorTrapPop(); + } + else if( pProperty ) + { + XFree( pProperty ); + pProperty = nullptr; + } + } + if( m_bValid + && XGetWindowProperty( m_pDisplay, + m_pSalDisplay->GetRootWindow( m_pSalDisplay->GetDefaultXScreen() ), + m_aWMAtoms[ WIN_PROTOCOLS ], + 0, 0, + False, + XA_ATOM, + &aRealType, + &nFormat, + &nItems, + &nBytesLeft, + &pProperty ) == 0 + && aRealType == XA_ATOM + && nFormat == 32 + ) + { + if( pProperty ) + { + XFree( pProperty ); + pProperty = nullptr; + } + // collect supported protocols + if( XGetWindowProperty( m_pDisplay, + m_pSalDisplay->GetRootWindow( m_pSalDisplay->GetDefaultXScreen() ), + m_aWMAtoms[ WIN_PROTOCOLS ], + 0, nBytesLeft/4, + False, + XA_ATOM, + &aRealType, + &nFormat, + &nItems, + &nBytesLeft, + &pProperty ) == 0 + && pProperty + ) + { + Atom* pAtoms = reinterpret_cast<Atom*>(pProperty); + char** pAtomNames = static_cast<char**>(alloca( sizeof(char*)*nItems )); + if( XGetAtomNames( m_pDisplay, pAtoms, nItems, pAtomNames ) ) + { +#if OSL_DEBUG_LEVEL > 1 + SAL_INFO("vcl.app", "supported protocols:"); +#endif + for( unsigned long i = 0; i < nItems; i++ ) + { + // #i80971# protect against invalid atoms + if( pAtomNames[i] == nullptr ) + continue; + + WMAdaptorProtocol aSearch; + aSearch.pProtocol = pAtomNames[i]; + WMAdaptorProtocol* pMatch = static_cast<WMAdaptorProtocol*>( + bsearch( &aSearch, + aProtocolTab, + SAL_N_ELEMENTS( aProtocolTab ), + sizeof( struct WMAdaptorProtocol ), + compareProtocol )); + if( pMatch ) + { + m_aWMAtoms[ pMatch->nProtocol ] = pAtoms[ i ]; + if( pMatch->nProtocol == WIN_LAYER ) + m_bEnableAlwaysOnTopWorks = true; + } + if( strncmp( "_ICEWM_TRAY", pAtomNames[i], 11 ) == 0 ) + { + m_aWMName = "IceWM"; + m_nWinGravity = NorthWestGravity; + m_nInitWinGravity = NorthWestGravity; + } +#if OSL_DEBUG_LEVEL > 1 + SAL_INFO("vcl.app", " " + << pAtomNames[i] + << (((pMatch) && (pMatch->nProtocol != -1)) ? + "" : " (unsupported)")); +#endif + XFree( pAtomNames[i] ); + } + } + XFree( pProperty ); + pProperty = nullptr; + } + else if( pProperty ) + { + XFree( pProperty ); + pProperty = nullptr; + } + + // get number of desktops + if( m_aWMAtoms[ WIN_WORKSPACE_COUNT ] + && XGetWindowProperty( m_pDisplay, + m_pSalDisplay->GetRootWindow( m_pSalDisplay->GetDefaultXScreen() ), + m_aWMAtoms[ WIN_WORKSPACE_COUNT ], + 0, 1, + False, + XA_CARDINAL, + &aRealType, + &nFormat, + &nItems, + &nBytesLeft, + &pProperty ) == 0 + && pProperty + ) + { + m_nDesktops = *reinterpret_cast<long*>(pProperty); + XFree( pProperty ); + pProperty = nullptr; + } + else if( pProperty ) + { + XFree( pProperty ); + pProperty = nullptr; + } + } + else if( pProperty ) + { + XFree( pProperty ); + pProperty = nullptr; + } +} + +/* + * getNetWmName() + */ +bool WMAdaptor::getNetWmName() +{ + Atom aRealType = None; + int nFormat = 8; + unsigned long nItems = 0; + unsigned long nBytesLeft = 0; + unsigned char* pProperty = nullptr; + bool bNetWM = false; + + if( m_aWMAtoms[ NET_SUPPORTING_WM_CHECK ] && m_aWMAtoms[ NET_WM_NAME ] ) + { + if( XGetWindowProperty( m_pDisplay, + m_pSalDisplay->GetRootWindow( m_pSalDisplay->GetDefaultXScreen() ), + m_aWMAtoms[ NET_SUPPORTING_WM_CHECK ], + 0, 1, + False, + XA_WINDOW, + &aRealType, + &nFormat, + &nItems, + &nBytesLeft, + &pProperty ) == 0 + && aRealType == XA_WINDOW + && nFormat == 32 + && nItems != 0 + ) + { + ::Window aWMChild = *reinterpret_cast< ::Window* >(pProperty); + XFree( pProperty ); + pProperty = nullptr; + GetGenericUnixSalData()->ErrorTrapPush(); + if( XGetWindowProperty( m_pDisplay, + aWMChild, + m_aWMAtoms[ NET_SUPPORTING_WM_CHECK ], + 0, 1, + False, + XA_WINDOW, + &aRealType, + &nFormat, + &nItems, + &nBytesLeft, + &pProperty ) == 0 + && aRealType == XA_WINDOW + && nFormat == 32 + && nItems != 0 ) + { + if ( ! GetGenericUnixSalData()->ErrorTrapPop( false ) ) + { + GetGenericUnixSalData()->ErrorTrapPush(); + ::Window aCheckWindow = *reinterpret_cast< ::Window* >(pProperty); + XFree( pProperty ); + pProperty = nullptr; + if( aCheckWindow == aWMChild ) + { + bNetWM = true; + // get name of WM + m_aWMAtoms[ UTF8_STRING ] = XInternAtom( m_pDisplay, "UTF8_STRING", False ); + if( XGetWindowProperty( m_pDisplay, + aWMChild, + m_aWMAtoms[ NET_WM_NAME ], + 0, 256, + False, + AnyPropertyType, /* m_aWMAtoms[ UTF8_STRING ],*/ + &aRealType, + &nFormat, + &nItems, + &nBytesLeft, + &pProperty ) == 0 + && nItems != 0 + ) + { + if (aRealType == m_aWMAtoms[ UTF8_STRING ]) + m_aWMName = OUString( reinterpret_cast<char*>(pProperty), nItems, RTL_TEXTENCODING_UTF8 ); + else if (aRealType == XA_STRING) + m_aWMName = OUString( reinterpret_cast<char*>(pProperty), nItems, RTL_TEXTENCODING_ISO_8859_1 ); + + XFree( pProperty ); + pProperty = nullptr; + } + else if( pProperty ) + { + XFree( pProperty ); + pProperty = nullptr; + } + + // if this is metacity, check for version to enable a legacy workaround + if( m_aWMName == "Metacity" ) + { + int nVersionMajor = 0, nVersionMinor = 0; + Atom nVersionAtom = XInternAtom( m_pDisplay, "_METACITY_VERSION", True ); + if( nVersionAtom ) + { + if( XGetWindowProperty( m_pDisplay, + aWMChild, + nVersionAtom, + 0, 256, + False, + m_aWMAtoms[ UTF8_STRING ], + &aRealType, + &nFormat, + &nItems, + &nBytesLeft, + &pProperty ) == 0 + && nItems != 0 + ) + { + OUString aMetaVersion( reinterpret_cast<char*>(pProperty), nItems, RTL_TEXTENCODING_UTF8 ); + sal_Int32 nIdx {0}; + nVersionMajor = o3tl::toInt32(o3tl::getToken(aMetaVersion, 0, '.', nIdx)); + nVersionMinor = o3tl::toInt32(o3tl::getToken(aMetaVersion, 0, '.', nIdx)); + } + if( pProperty ) + { + XFree( pProperty ); + pProperty = nullptr; + } + } + if( nVersionMajor < 2 || (nVersionMajor == 2 && nVersionMinor < 12) ) + m_bLegacyPartialFullscreen = true; + } + } + } + else + { + if( pProperty ) + { + XFree( pProperty ); + pProperty = nullptr; + } + GetGenericUnixSalData()->ErrorTrapPush(); + } + } + + GetGenericUnixSalData()->ErrorTrapPop(); + } + else if( pProperty ) + { + XFree( pProperty ); + pProperty = nullptr; + } + } + return bNetWM; +} + +bool WMAdaptor::getWMshouldSwitchWorkspace() const +{ + if( ! m_bWMshouldSwitchWorkspaceInit ) + { + WMAdaptor * pWMA = const_cast<WMAdaptor*>(this); + + pWMA->m_bWMshouldSwitchWorkspace = true; + vcl::SettingsConfigItem* pItem = vcl::SettingsConfigItem::get(); + OUString aSetting( pItem->getValue( "WM", + "ShouldSwitchWorkspace" ) ); + if( aSetting.isEmpty() ) + { + if( m_aWMName == "awesome" ) + { + pWMA->m_bWMshouldSwitchWorkspace = false; + } + } + else + pWMA->m_bWMshouldSwitchWorkspace = aSetting.toBoolean(); + pWMA->m_bWMshouldSwitchWorkspaceInit = true; + } + return m_bWMshouldSwitchWorkspace; +} + +/* + * WMAdaptor::isValid() + */ +bool WMAdaptor::isValid() const +{ + return true; +} + +/* + * NetWMAdaptor::isValid() + */ +bool NetWMAdaptor::isValid() const +{ + // some necessary sanity checks; there are WMs out there + // which implement some of the WM hints spec without + // real functionality + return + m_aWMAtoms[ NET_SUPPORTED ] + && m_aWMAtoms[ NET_SUPPORTING_WM_CHECK ] + && m_aWMAtoms[ NET_WM_NAME ] + && m_aWMAtoms[ NET_WM_WINDOW_TYPE_NORMAL ] + && m_aWMAtoms[ NET_WM_WINDOW_TYPE_DIALOG ] + ; +} + +/* + * GnomeWMAdaptor::isValid() + */ +bool GnomeWMAdaptor::isValid() const +{ + return m_bValid; +} + +/* + * WMAdaptor::initAtoms + */ + +void WMAdaptor::initAtoms() +{ + // get basic atoms + for(const WMAdaptorProtocol & i : aAtomTab) + m_aWMAtoms[ i.nProtocol ] = XInternAtom( m_pDisplay, i.pProtocol, False ); + m_aWMAtoms[ NET_SUPPORTING_WM_CHECK ] = XInternAtom( m_pDisplay, "_NET_SUPPORTING_WM_CHECK", True ); + m_aWMAtoms[ NET_WM_NAME ] = XInternAtom( m_pDisplay, "_NET_WM_NAME", True ); +} + +/* + * NetWMAdaptor::initAtoms + */ + +void NetWMAdaptor::initAtoms() +{ + WMAdaptor::initAtoms(); + + m_aWMAtoms[ NET_SUPPORTED ] = XInternAtom( m_pDisplay, "_NET_SUPPORTED", True ); +} + +/* + * GnomeWMAdaptor::initAtoms + */ + +void GnomeWMAdaptor::initAtoms() +{ + WMAdaptor::initAtoms(); + + m_aWMAtoms[ WIN_PROTOCOLS ] = XInternAtom( m_pDisplay, "_WIN_PROTOCOLS", True ); + m_aWMAtoms[ WIN_SUPPORTING_WM_CHECK ] = XInternAtom( m_pDisplay, "_WIN_SUPPORTING_WM_CHECK", True ); +} + +/* + * WMAdaptor::setWMName + * sets WM_NAME + * WM_ICON_NAME + */ + +void WMAdaptor::setWMName( X11SalFrame* pFrame, const OUString& rWMName ) const +{ + OString aTitle(OUStringToOString(rWMName, + osl_getThreadTextEncoding())); + + OString aWMLocale; + rtl_Locale* pLocale = nullptr; + osl_getProcessLocale( &pLocale ); + if( pLocale ) + { + OUString aLocaleString( + LanguageTag( *pLocale).getGlibcLocaleString( std::u16string_view())); + aWMLocale = OUStringToOString( aLocaleString, RTL_TEXTENCODING_ISO_8859_1 ); + } + else + { + static const char* pLang = getenv( "LANG" ); + aWMLocale = pLang ? pLang : "C"; + } + + char* pT = const_cast<char*>(aTitle.getStr()); + XTextProperty aProp = { nullptr, None, 0, 0 }; + XmbTextListToTextProperty( m_pDisplay, + &pT, + 1, + XStdICCTextStyle, + &aProp ); + + unsigned char const * pData = aProp.nitems ? aProp.value : reinterpret_cast<unsigned char const *>(aTitle.getStr()); + Atom nType = aProp.nitems ? aProp.encoding : XA_STRING; + int nFormat = aProp.nitems ? aProp.format : 8; + int nBytes = aProp.nitems ? aProp.nitems : aTitle.getLength(); + const SystemEnvData* pEnv = pFrame->GetSystemData(); + XChangeProperty( m_pDisplay, + static_cast<::Window>(pEnv->aShellWindow), + XA_WM_NAME, + nType, + nFormat, + PropModeReplace, + pData, + nBytes ); + XChangeProperty( m_pDisplay, + static_cast<::Window>(pEnv->aShellWindow), + XA_WM_ICON_NAME, + nType, + nFormat, + PropModeReplace, + pData, + nBytes ); + XChangeProperty( m_pDisplay, + static_cast<::Window>(pEnv->aShellWindow), + m_aWMAtoms[ WM_LOCALE_NAME ], + XA_STRING, + 8, + PropModeReplace, + reinterpret_cast<unsigned char const *>(aWMLocale.getStr()), + aWMLocale.getLength() ); + if (aProp.value != nullptr) + XFree( aProp.value ); +} + +/* + * NetWMAdaptor::setWMName + * sets WM_NAME + * _NET_WM_NAME + * WM_ICON_NAME + * _NET_WM_ICON_NAME + */ +void NetWMAdaptor::setWMName( X11SalFrame* pFrame, const OUString& rWMName ) const +{ + WMAdaptor::setWMName( pFrame, rWMName ); + + OString aTitle(OUStringToOString(rWMName, RTL_TEXTENCODING_UTF8)); + const SystemEnvData* pEnv = pFrame->GetSystemData(); + if( m_aWMAtoms[ NET_WM_NAME ] ) + XChangeProperty( m_pDisplay, + static_cast<::Window>(pEnv->aShellWindow), + m_aWMAtoms[ NET_WM_NAME ], + m_aWMAtoms[ UTF8_STRING ], + 8, + PropModeReplace, + reinterpret_cast<unsigned char const *>(aTitle.getStr()), + aTitle.getLength() ); + if( m_aWMAtoms[ NET_WM_ICON_NAME ] ) + XChangeProperty( m_pDisplay, + static_cast<::Window>(pEnv->aShellWindow), + m_aWMAtoms[ NET_WM_ICON_NAME ], + m_aWMAtoms[ UTF8_STRING ], + 8, + PropModeReplace, + reinterpret_cast<unsigned char const *>(aTitle.getStr()), + aTitle.getLength() ); +} + +/* + * NetWMAdaptor::setNetWMState + * sets _NET_WM_STATE + */ +void NetWMAdaptor::setNetWMState( X11SalFrame* pFrame ) const +{ + if( !(m_aWMAtoms[ NET_WM_STATE ]) ) + return; + + Atom aStateAtoms[ 10 ]; + int nStateAtoms = 0; + + // set NET_WM_STATE_MODAL + if( pFrame->mbMaximizedVert + && m_aWMAtoms[ NET_WM_STATE_MAXIMIZED_VERT ] ) + aStateAtoms[ nStateAtoms++ ] = m_aWMAtoms[ NET_WM_STATE_MAXIMIZED_VERT ]; + if( pFrame->mbMaximizedHorz + && m_aWMAtoms[ NET_WM_STATE_MAXIMIZED_HORZ ] ) + aStateAtoms[ nStateAtoms++ ] = m_aWMAtoms[ NET_WM_STATE_MAXIMIZED_HORZ ]; + if( pFrame->bAlwaysOnTop_ && m_aWMAtoms[ NET_WM_STATE_STAYS_ON_TOP ] ) + aStateAtoms[ nStateAtoms++ ] = m_aWMAtoms[ NET_WM_STATE_STAYS_ON_TOP ]; + if( pFrame->mbFullScreen && m_aWMAtoms[ NET_WM_STATE_FULLSCREEN ] ) + aStateAtoms[ nStateAtoms++ ] = m_aWMAtoms[ NET_WM_STATE_FULLSCREEN ]; + if( pFrame->meWindowType == WMWindowType::Utility && m_aWMAtoms[ NET_WM_STATE_SKIP_TASKBAR ] ) + aStateAtoms[ nStateAtoms++ ] = m_aWMAtoms[ NET_WM_STATE_SKIP_TASKBAR ]; + + if( nStateAtoms ) + { + XChangeProperty( m_pDisplay, + pFrame->GetShellWindow(), + m_aWMAtoms[ NET_WM_STATE ], + XA_ATOM, + 32, + PropModeReplace, + reinterpret_cast<unsigned char*>(aStateAtoms), + nStateAtoms + ); + } + else + XDeleteProperty( m_pDisplay, + pFrame->GetShellWindow(), + m_aWMAtoms[ NET_WM_STATE ] ); + if( !pFrame->mbMaximizedHorz + || !pFrame->mbMaximizedVert + || ( pFrame->nStyle_ & SalFrameStyleFlags::SIZEABLE ) ) + return; + + /* + * for maximizing use NorthWestGravity (including decoration) + */ + XSizeHints hints; + tools::Long supplied; + bool bHint = false; + if( XGetWMNormalHints( m_pDisplay, + pFrame->GetShellWindow(), + &hints, + &supplied ) ) + { + bHint = true; + hints.flags |= PWinGravity; + hints.win_gravity = NorthWestGravity; + XSetWMNormalHints( m_pDisplay, + pFrame->GetShellWindow(), + &hints ); + XSync( m_pDisplay, False ); + } + + // SetPosSize necessary to set width/height, min/max w/h + sal_Int32 nCurrent = 0; + /* + * get current desktop here if work areas have different size + * (does this happen on any platform ?) + */ + if( ! m_bEqualWorkAreas ) + { + nCurrent = getCurrentWorkArea(); + if( nCurrent < 0 ) + nCurrent = 0; + } + tools::Rectangle aPosSize = m_aWMWorkAreas[nCurrent]; + const SalFrameGeometry& rGeom( pFrame->GetUnmirroredGeometry() ); + aPosSize = tools::Rectangle( Point( aPosSize.Left() + rGeom.nLeftDecoration, + aPosSize.Top() + rGeom.nTopDecoration ), + Size( aPosSize.GetWidth() + - rGeom.nLeftDecoration + - rGeom.nRightDecoration, + aPosSize.GetHeight() + - rGeom.nTopDecoration + - rGeom.nBottomDecoration ) + ); + pFrame->SetPosSize( aPosSize ); + + /* + * reset gravity hint to static gravity + * (this should not move window according to ICCCM) + */ + if( bHint && pFrame->nShowState_ != X11ShowState::Unknown ) + { + hints.win_gravity = StaticGravity; + XSetWMNormalHints( m_pDisplay, + pFrame->GetShellWindow(), + &hints ); + } +} + +/* + * GnomeWMAdaptor::setNetWMState + * sets _WIN_STATE + */ +void GnomeWMAdaptor::setGnomeWMState( X11SalFrame* pFrame ) const +{ + if( !(m_aWMAtoms[ WIN_STATE ]) ) + return; + + sal_uInt32 nWinWMState = 0; + + if( pFrame->mbMaximizedVert ) + nWinWMState |= 1 << 2; + if( pFrame->mbMaximizedHorz ) + nWinWMState |= 1 << 3; + + XChangeProperty( m_pDisplay, + pFrame->GetShellWindow(), + m_aWMAtoms[ WIN_STATE ], + XA_CARDINAL, + 32, + PropModeReplace, + reinterpret_cast<unsigned char*>(&nWinWMState), + 1 + ); + if( !pFrame->mbMaximizedHorz + || !pFrame->mbMaximizedVert + || ( pFrame->nStyle_ & SalFrameStyleFlags::SIZEABLE ) ) + return; + + /* + * for maximizing use NorthWestGravity (including decoration) + */ + XSizeHints hints; + tools::Long supplied; + bool bHint = false; + if( XGetWMNormalHints( m_pDisplay, + pFrame->GetShellWindow(), + &hints, + &supplied ) ) + { + bHint = true; + hints.flags |= PWinGravity; + hints.win_gravity = NorthWestGravity; + XSetWMNormalHints( m_pDisplay, + pFrame->GetShellWindow(), + &hints ); + XSync( m_pDisplay, False ); + } + + // SetPosSize necessary to set width/height, min/max w/h + sal_Int32 nCurrent = 0; + /* + * get current desktop here if work areas have different size + * (does this happen on any platform ?) + */ + if( ! m_bEqualWorkAreas ) + { + nCurrent = getCurrentWorkArea(); + if( nCurrent < 0 ) + nCurrent = 0; + } + tools::Rectangle aPosSize = m_aWMWorkAreas[nCurrent]; + const SalFrameGeometry& rGeom( pFrame->GetUnmirroredGeometry() ); + aPosSize = tools::Rectangle( Point( aPosSize.Left() + rGeom.nLeftDecoration, + aPosSize.Top() + rGeom.nTopDecoration ), + Size( aPosSize.GetWidth() + - rGeom.nLeftDecoration + - rGeom.nRightDecoration, + aPosSize.GetHeight() + - rGeom.nTopDecoration + - rGeom.nBottomDecoration ) + ); + pFrame->SetPosSize( aPosSize ); + + /* + * reset gravity hint to static gravity + * (this should not move window according to ICCCM) + */ + if( bHint && pFrame->nShowState_ != X11ShowState::Unknown ) + { + hints.win_gravity = StaticGravity; + XSetWMNormalHints( m_pDisplay, + pFrame->GetShellWindow(), + &hints ); + } +} + +/* + * WMAdaptor::setFrameDecoration + * sets _MOTIF_WM_HINTS + * WM_TRANSIENT_FOR + */ + +void WMAdaptor::setFrameTypeAndDecoration( X11SalFrame* pFrame, WMWindowType eType, int nDecorationFlags, X11SalFrame* pReferenceFrame ) const +{ + pFrame->meWindowType = eType; + + if( ! pFrame->mbFullScreen ) + { + // set mwm hints + struct _mwmhints { + unsigned long flags, func, deco; + tools::Long input_mode; + unsigned long status; + } aHint; + + aHint.flags = 15; /* flags for functions, decoration, input mode and status */ + aHint.deco = 0; + aHint.func = 1 << 2; + aHint.status = 0; + aHint.input_mode = 0; + + // evaluate decoration flags + if( nDecorationFlags & decoration_All ) + { + aHint.deco = 1; + aHint.func = 1; + } + else + { + if( nDecorationFlags & decoration_Title ) + aHint.deco |= 1 << 3; + if( nDecorationFlags & decoration_Border ) + aHint.deco |= 1 << 1; + if( nDecorationFlags & decoration_Resize ) + { + aHint.deco |= 1 << 2; + aHint.func |= 1 << 1; + } + if( nDecorationFlags & decoration_MinimizeBtn ) + { + aHint.deco |= 1 << 5; + aHint.func |= 1 << 3; + } + if( nDecorationFlags & decoration_MaximizeBtn ) + { + aHint.deco |= 1 << 6; + aHint.func |= 1 << 4; + } + if( nDecorationFlags & decoration_CloseBtn ) + { + aHint.deco |= 1 << 4; + aHint.func |= 1 << 5; + } + } + + // set the hint + XChangeProperty( m_pDisplay, + pFrame->GetShellWindow(), + m_aWMAtoms[ MOTIF_WM_HINTS ], + m_aWMAtoms[ MOTIF_WM_HINTS ], + 32, + PropModeReplace, + reinterpret_cast<unsigned char*>(&aHint), + 5 ); + } + + // set transientFor hint + /* #91030# dtwm will not map a dialogue if the transient + * window is iconified. This is deemed undesirable because + * message boxes do not get mapped, so use the root as transient + * instead. + */ + if( pReferenceFrame ) + { + XSetTransientForHint( m_pDisplay, + pFrame->GetShellWindow(), + pReferenceFrame->bMapped_ ? + pReferenceFrame->GetShellWindow() : + m_pSalDisplay->GetRootWindow( pFrame->GetScreenNumber() ) + ); + if( ! pReferenceFrame->bMapped_ ) + pFrame->mbTransientForRoot = true; + } +} + +/* + * NetWMAdaptor::setFrameDecoration + * sets _MOTIF_WM_HINTS + * _NET_WM_WINDOW_TYPE + * _NET_WM_STATE + * WM_TRANSIENT_FOR + */ + +void NetWMAdaptor::setFrameTypeAndDecoration( X11SalFrame* pFrame, WMWindowType eType, int nDecorationFlags, X11SalFrame* pReferenceFrame ) const +{ + WMAdaptor::setFrameTypeAndDecoration( pFrame, eType, nDecorationFlags, pReferenceFrame ); + + setNetWMState( pFrame ); + + // set NET_WM_WINDOW_TYPE + if( m_aWMAtoms[ NET_WM_WINDOW_TYPE ] ) + { + Atom aWindowTypes[4]; + int nWindowTypes = 0; + switch( eType ) + { + case WMWindowType::Utility: + aWindowTypes[nWindowTypes++] = + m_aWMAtoms[ NET_WM_WINDOW_TYPE_UTILITY ] ? + m_aWMAtoms[ NET_WM_WINDOW_TYPE_UTILITY ] : + m_aWMAtoms[ NET_WM_WINDOW_TYPE_DIALOG ]; + break; + case WMWindowType::ModelessDialogue: + aWindowTypes[nWindowTypes++] = + m_aWMAtoms[ NET_WM_WINDOW_TYPE_DIALOG ]; + break; + case WMWindowType::Splash: + aWindowTypes[nWindowTypes++] = + m_aWMAtoms[ NET_WM_WINDOW_TYPE_SPLASH ] ? + m_aWMAtoms[ NET_WM_WINDOW_TYPE_SPLASH ] : + m_aWMAtoms[ NET_WM_WINDOW_TYPE_NORMAL ]; + break; + case WMWindowType::Toolbar: + if( m_aWMAtoms[ KDE_NET_WM_WINDOW_TYPE_OVERRIDE ] ) + aWindowTypes[nWindowTypes++] = m_aWMAtoms[ KDE_NET_WM_WINDOW_TYPE_OVERRIDE ]; + aWindowTypes[nWindowTypes++] = + m_aWMAtoms[ NET_WM_WINDOW_TYPE_TOOLBAR ] ? + m_aWMAtoms[ NET_WM_WINDOW_TYPE_TOOLBAR ] : + m_aWMAtoms[ NET_WM_WINDOW_TYPE_NORMAL]; + break; + case WMWindowType::Dock: + aWindowTypes[nWindowTypes++] = + m_aWMAtoms[ NET_WM_WINDOW_TYPE_DOCK ] ? + m_aWMAtoms[ NET_WM_WINDOW_TYPE_DOCK ] : + m_aWMAtoms[ NET_WM_WINDOW_TYPE_NORMAL]; + break; + default: + aWindowTypes[nWindowTypes++] = m_aWMAtoms[ NET_WM_WINDOW_TYPE_NORMAL ]; + break; + } + XChangeProperty( m_pDisplay, + pFrame->GetShellWindow(), + m_aWMAtoms[ NET_WM_WINDOW_TYPE ], + XA_ATOM, + 32, + PropModeReplace, + reinterpret_cast<unsigned char*>(aWindowTypes), + nWindowTypes ); + } + if( ( eType == WMWindowType::ModelessDialogue ) + && ! pReferenceFrame ) + { + XSetTransientForHint( m_pDisplay, + pFrame->GetShellWindow(), + m_pSalDisplay->GetRootWindow( pFrame->GetScreenNumber() ) ); + pFrame->mbTransientForRoot = true; + } +} + +/* + * WMAdaptor::maximizeFrame + */ + +void WMAdaptor::maximizeFrame( X11SalFrame* pFrame, bool bHorizontal, bool bVertical ) const +{ + pFrame->mbMaximizedVert = bVertical; + pFrame->mbMaximizedHorz = bHorizontal; + + const SalFrameGeometry& rGeom( pFrame->GetUnmirroredGeometry() ); + + // discard pending configure notifies for this frame + XSync( m_pDisplay, False ); + XEvent aDiscard; + while( XCheckTypedWindowEvent( m_pDisplay, + pFrame->GetShellWindow(), + ConfigureNotify, + &aDiscard ) ) + ; + while( XCheckTypedWindowEvent( m_pDisplay, + pFrame->GetWindow(), + ConfigureNotify, + &aDiscard ) ) + ; + + if( bHorizontal || bVertical ) + { + Size aScreenSize( m_pSalDisplay->GetScreenSize( pFrame->GetScreenNumber() ) ); + Point aTL( rGeom.nLeftDecoration, rGeom.nTopDecoration ); + if( m_pSalDisplay->IsXinerama() ) + { + Point aMed( aTL.X() + rGeom.nWidth/2, aTL.Y() + rGeom.nHeight/2 ); + const std::vector< tools::Rectangle >& rScreens = m_pSalDisplay->GetXineramaScreens(); + for(const auto & rScreen : rScreens) + if( rScreen.Contains( aMed ) ) + { + aTL += rScreen.TopLeft(); + aScreenSize = rScreen.GetSize(); + break; + } + } + tools::Rectangle aTarget( aTL, + Size( aScreenSize.Width() - rGeom.nLeftDecoration - rGeom.nTopDecoration, + aScreenSize.Height() - rGeom.nTopDecoration - rGeom.nBottomDecoration ) + ); + if( ! bHorizontal ) + { + aTarget.SetSize( + Size( + pFrame->maRestorePosSize.IsEmpty() ? + rGeom.nWidth : pFrame->maRestorePosSize.GetWidth(), + aTarget.GetHeight() + ) + ); + aTarget.SetLeft( + pFrame->maRestorePosSize.IsEmpty() ? + rGeom.nX : pFrame->maRestorePosSize.Left() ); + } + else if( ! bVertical ) + { + aTarget.SetSize( + Size( + aTarget.GetWidth(), + pFrame->maRestorePosSize.IsEmpty() ? + rGeom.nHeight : pFrame->maRestorePosSize.GetHeight() + ) + ); + aTarget.SetTop( + pFrame->maRestorePosSize.IsEmpty() ? + rGeom.nY : pFrame->maRestorePosSize.Top() ); + } + + tools::Rectangle aRestore( Point( rGeom.nX, rGeom.nY ), Size( rGeom.nWidth, rGeom.nHeight ) ); + if( pFrame->bMapped_ ) + { + XSetInputFocus( m_pDisplay, + pFrame->GetShellWindow(), + RevertToNone, + CurrentTime + ); + } + + if( pFrame->maRestorePosSize.IsEmpty() ) + pFrame->maRestorePosSize = aRestore; + + pFrame->SetPosSize( aTarget ); + pFrame->nWidth_ = aTarget.GetWidth(); + pFrame->nHeight_ = aTarget.GetHeight(); + XRaiseWindow( m_pDisplay, + pFrame->GetShellWindow() + ); + if( pFrame->GetStackingWindow() ) + XRaiseWindow( m_pDisplay, + pFrame->GetStackingWindow() + ); + + } + else + { + pFrame->SetPosSize( pFrame->maRestorePosSize ); + pFrame->maRestorePosSize = tools::Rectangle(); + pFrame->nWidth_ = rGeom.nWidth; + pFrame->nHeight_ = rGeom.nHeight; + } +} + +/* + * NetWMAdaptor::maximizeFrame + * changes _NET_WM_STATE by sending a client message + */ + +void NetWMAdaptor::maximizeFrame( X11SalFrame* pFrame, bool bHorizontal, bool bVertical ) const +{ + pFrame->mbMaximizedVert = bVertical; + pFrame->mbMaximizedHorz = bHorizontal; + + if( m_aWMAtoms[ NET_WM_STATE ] + && m_aWMAtoms[ NET_WM_STATE_MAXIMIZED_VERT ] + && m_aWMAtoms[ NET_WM_STATE_MAXIMIZED_HORZ ] + && ( pFrame->nStyle_ & ~SalFrameStyleFlags::DEFAULT ) + ) + { + if( pFrame->bMapped_ ) + { + // window already mapped, send WM a message + XEvent aEvent; + aEvent.type = ClientMessage; + aEvent.xclient.display = m_pDisplay; + aEvent.xclient.window = pFrame->GetShellWindow(); + aEvent.xclient.message_type = m_aWMAtoms[ NET_WM_STATE ]; + aEvent.xclient.format = 32; + aEvent.xclient.data.l[0] = bHorizontal ? 1 : 0; + aEvent.xclient.data.l[1] = m_aWMAtoms[ NET_WM_STATE_MAXIMIZED_HORZ ]; + aEvent.xclient.data.l[2] = bHorizontal == bVertical ? m_aWMAtoms[ NET_WM_STATE_MAXIMIZED_VERT ] : 0; + aEvent.xclient.data.l[3] = 0; + aEvent.xclient.data.l[4] = 0; + XSendEvent( m_pDisplay, + m_pSalDisplay->GetRootWindow( pFrame->GetScreenNumber() ), + False, + SubstructureNotifyMask | SubstructureRedirectMask, + &aEvent + ); + if( bHorizontal != bVertical ) + { + aEvent.xclient.data.l[0]= bVertical ? 1 : 0; + aEvent.xclient.data.l[1]= m_aWMAtoms[ NET_WM_STATE_MAXIMIZED_VERT ]; + aEvent.xclient.data.l[2]= 0; + XSendEvent( m_pDisplay, + m_pSalDisplay->GetRootWindow( pFrame->GetScreenNumber() ), + False, + SubstructureNotifyMask | SubstructureRedirectMask, + &aEvent + ); + } + } + else + { + // window not mapped yet, set _NET_WM_STATE directly + setNetWMState( pFrame ); + } + if( !bHorizontal && !bVertical ) + pFrame->maRestorePosSize = tools::Rectangle(); + else if( pFrame->maRestorePosSize.IsEmpty() ) + { + const SalFrameGeometry& rGeom( pFrame->GetUnmirroredGeometry() ); + pFrame->maRestorePosSize = + tools::Rectangle( Point( rGeom.nX, rGeom.nY ), Size( rGeom.nWidth, rGeom.nHeight ) ); + } + } + else + WMAdaptor::maximizeFrame( pFrame, bHorizontal, bVertical ); +} + +/* + * GnomeWMAdaptor::maximizeFrame + * changes _WIN_STATE by sending a client message + */ + +void GnomeWMAdaptor::maximizeFrame( X11SalFrame* pFrame, bool bHorizontal, bool bVertical ) const +{ + pFrame->mbMaximizedVert = bVertical; + pFrame->mbMaximizedHorz = bHorizontal; + + if( m_aWMAtoms[ WIN_STATE ] + && ( pFrame->nStyle_ & ~SalFrameStyleFlags::DEFAULT ) + ) + { + if( pFrame->bMapped_ ) + { + // window already mapped, send WM a message + XEvent aEvent; + aEvent.type = ClientMessage; + aEvent.xclient.display = m_pDisplay; + aEvent.xclient.window = pFrame->GetShellWindow(); + aEvent.xclient.message_type = m_aWMAtoms[ WIN_STATE ]; + aEvent.xclient.format = 32; + aEvent.xclient.data.l[0] = (1<<2)|(1<<3); + aEvent.xclient.data.l[1] = + (bVertical ? (1<<2) : 0) + | (bHorizontal ? (1<<3) : 0); + aEvent.xclient.data.l[2] = 0; + aEvent.xclient.data.l[3] = 0; + aEvent.xclient.data.l[4] = 0; + XSendEvent( m_pDisplay, + m_pSalDisplay->GetRootWindow( pFrame->GetScreenNumber() ), + False, + SubstructureNotifyMask, + &aEvent + ); + } + else + // window not mapped yet, set _WIN_STATE directly + setGnomeWMState( pFrame ); + + if( !bHorizontal && !bVertical ) + pFrame->maRestorePosSize = tools::Rectangle(); + else if( pFrame->maRestorePosSize.IsEmpty() ) + { + const SalFrameGeometry& rGeom( pFrame->GetUnmirroredGeometry() ); + pFrame->maRestorePosSize = + tools::Rectangle( Point( rGeom.nX, rGeom.nY ), Size( rGeom.nWidth, rGeom.nHeight ) ); + } + } + else + WMAdaptor::maximizeFrame( pFrame, bHorizontal, bVertical ); +} + +/* + * WMAdaptor::enableAlwaysOnTop + */ +void WMAdaptor::enableAlwaysOnTop( X11SalFrame*, bool /*bEnable*/ ) const +{ +} + +/* + * NetWMAdaptor::enableAlwaysOnTop + */ +void NetWMAdaptor::enableAlwaysOnTop( X11SalFrame* pFrame, bool bEnable ) const +{ + pFrame->bAlwaysOnTop_ = bEnable; + if( !(m_aWMAtoms[ NET_WM_STATE_STAYS_ON_TOP ]) ) + return; + + if( pFrame->bMapped_ ) + { + // window already mapped, send WM a message + XEvent aEvent; + aEvent.type = ClientMessage; + aEvent.xclient.display = m_pDisplay; + aEvent.xclient.window = pFrame->GetShellWindow(); + aEvent.xclient.message_type = m_aWMAtoms[ NET_WM_STATE ]; + aEvent.xclient.format = 32; + aEvent.xclient.data.l[0] = bEnable ? 1 : 0; + aEvent.xclient.data.l[1] = m_aWMAtoms[ NET_WM_STATE_STAYS_ON_TOP ]; + aEvent.xclient.data.l[2] = 0; + aEvent.xclient.data.l[3] = 0; + aEvent.xclient.data.l[4] = 0; + XSendEvent( m_pDisplay, + m_pSalDisplay->GetRootWindow( pFrame->GetScreenNumber() ), + False, + SubstructureNotifyMask | SubstructureRedirectMask, + &aEvent + ); + } + else + setNetWMState( pFrame ); +} + +/* + * GnomeWMAdaptor::enableAlwaysOnTop + */ +void GnomeWMAdaptor::enableAlwaysOnTop( X11SalFrame* pFrame, bool bEnable ) const +{ + pFrame->bAlwaysOnTop_ = bEnable; + if( !(m_aWMAtoms[ WIN_LAYER ]) ) + return; + + if( pFrame->bMapped_ ) + { + // window already mapped, send WM a message + XEvent aEvent; + aEvent.type = ClientMessage; + aEvent.xclient.display = m_pDisplay; + aEvent.xclient.window = pFrame->GetShellWindow(); + aEvent.xclient.message_type = m_aWMAtoms[ WIN_LAYER ]; + aEvent.xclient.format = 32; + aEvent.xclient.data.l[0] = bEnable ? 6 : 4; + aEvent.xclient.data.l[1] = 0; + aEvent.xclient.data.l[2] = 0; + aEvent.xclient.data.l[3] = 0; + aEvent.xclient.data.l[4] = 0; + XSendEvent( m_pDisplay, + m_pSalDisplay->GetRootWindow( pFrame->GetScreenNumber() ), + False, + SubstructureNotifyMask | SubstructureRedirectMask, + &aEvent + ); + } + else + { + sal_uInt32 nNewLayer = bEnable ? 6 : 4; + XChangeProperty( m_pDisplay, + pFrame->GetShellWindow(), + m_aWMAtoms[ WIN_LAYER ], + XA_CARDINAL, + 32, + PropModeReplace, + reinterpret_cast<unsigned char*>(&nNewLayer), + 1 + ); + } +} + +/* + * WMAdaptor::changeReferenceFrame + */ +void WMAdaptor::changeReferenceFrame( X11SalFrame* pFrame, X11SalFrame const * pReferenceFrame ) const +{ + if( ( pFrame->nStyle_ & SalFrameStyleFlags::PLUG ) + || pFrame->IsOverrideRedirect() + || pFrame->IsFloatGrabWindow() + ) + return; + + ::Window aTransient = pFrame->pDisplay_->GetRootWindow( pFrame->GetScreenNumber() ); + pFrame->mbTransientForRoot = true; + if( pReferenceFrame ) + { + aTransient = pReferenceFrame->GetShellWindow(); + pFrame->mbTransientForRoot = false; + } + XSetTransientForHint( m_pDisplay, + pFrame->GetShellWindow(), + aTransient ); +} + +/* + * WMAdaptor::handlePropertyNotify + */ +int WMAdaptor::handlePropertyNotify( X11SalFrame*, XPropertyEvent* ) const +{ + return 0; +} + +/* + * NetWMAdaptor::handlePropertyNotify + */ +int NetWMAdaptor::handlePropertyNotify( X11SalFrame* pFrame, XPropertyEvent* pEvent ) const +{ + int nHandled = 1; + if( pEvent->atom == m_aWMAtoms[ NET_WM_STATE ] ) + { + pFrame->mbMaximizedHorz = pFrame->mbMaximizedVert = false; + + if( pEvent->state == PropertyNewValue ) + { + Atom nType, *pStates; + int nFormat; + unsigned long nItems, nBytesLeft; + unsigned char* pData = nullptr; + tools::Long nOffset = 0; + do + { + XGetWindowProperty( m_pDisplay, + pEvent->window, + m_aWMAtoms[ NET_WM_STATE ], + nOffset, 64, + False, + XA_ATOM, + &nType, + &nFormat, + &nItems, &nBytesLeft, + &pData ); + if( pData ) + { + if( nType == XA_ATOM && nFormat == 32 && nItems > 0 ) + { + pStates = reinterpret_cast<Atom*>(pData); + for( unsigned long i = 0; i < nItems; i++ ) + { + if( pStates[i] == m_aWMAtoms[ NET_WM_STATE_MAXIMIZED_VERT ] && m_aWMAtoms[ NET_WM_STATE_MAXIMIZED_VERT ] ) + pFrame->mbMaximizedVert = true; + else if( pStates[i] == m_aWMAtoms[ NET_WM_STATE_MAXIMIZED_HORZ ] && m_aWMAtoms[ NET_WM_STATE_MAXIMIZED_HORZ ] ) + pFrame->mbMaximizedHorz = true; + } + } + XFree( pData ); + pData = nullptr; + nOffset += nItems * nFormat / 32; + } + else + break; + } while( nBytesLeft > 0 ); + } + + if( ! (pFrame->mbMaximizedHorz || pFrame->mbMaximizedVert ) ) + pFrame->maRestorePosSize = tools::Rectangle(); + else + { + const SalFrameGeometry& rGeom = pFrame->GetUnmirroredGeometry(); + // the current geometry may already be changed by the corresponding + // ConfigureNotify, but this cannot be helped + pFrame->maRestorePosSize = + tools::Rectangle( Point( rGeom.nX, rGeom.nY ), + Size( rGeom.nWidth, rGeom.nHeight ) ); + } + } + else if( pEvent->atom == m_aWMAtoms[ NET_WM_DESKTOP ] ) + { + pFrame->m_nWorkArea = getWindowWorkArea( pFrame->GetShellWindow() ); + } + else + nHandled = 0; + + return nHandled; +} + +/* + * GnomeWMAdaptor::handlePropertyNotify + */ +int GnomeWMAdaptor::handlePropertyNotify( X11SalFrame* pFrame, XPropertyEvent* pEvent ) const +{ + int nHandled = 1; + if( pEvent->atom == m_aWMAtoms[ WIN_STATE ] ) + { + pFrame->mbMaximizedHorz = pFrame->mbMaximizedVert = false; + + if( pEvent->state == PropertyNewValue ) + { + Atom nType; + int nFormat = 0; + unsigned long nItems = 0; + unsigned long nBytesLeft = 0; + unsigned char* pData = nullptr; + XGetWindowProperty( m_pDisplay, + pEvent->window, + m_aWMAtoms[ WIN_STATE ], + 0, 1, + False, + XA_CARDINAL, + &nType, + &nFormat, + &nItems, &nBytesLeft, + &pData ); + if( pData ) + { + if( nType == XA_CARDINAL && nFormat == 32 && nItems == 1 ) + { + sal_uInt32 nWinState = *reinterpret_cast<sal_uInt32*>(pData); + if( nWinState & (1<<2) ) + pFrame->mbMaximizedVert = true; + if( nWinState & (1<<3) ) + pFrame->mbMaximizedHorz = true; + } + XFree( pData ); + } + } + + if( ! (pFrame->mbMaximizedHorz || pFrame->mbMaximizedVert ) ) + pFrame->maRestorePosSize = tools::Rectangle(); + else + { + const SalFrameGeometry& rGeom = pFrame->GetUnmirroredGeometry(); + // the current geometry may already be changed by the corresponding + // ConfigureNotify, but this cannot be helped + pFrame->maRestorePosSize = + tools::Rectangle( Point( rGeom.nX, rGeom.nY ), + Size( rGeom.nWidth, rGeom.nHeight ) ); + } + } + else if( pEvent->atom == m_aWMAtoms[ NET_WM_DESKTOP ] ) + { + pFrame->m_nWorkArea = getWindowWorkArea( pFrame->GetShellWindow() ); + } + else + nHandled = 0; + + return nHandled; +} + +/* + * WMAdaptor::showFullScreen + */ +void WMAdaptor::showFullScreen( X11SalFrame* pFrame, bool bFullScreen ) const +{ + pFrame->mbFullScreen = bFullScreen; + maximizeFrame( pFrame, bFullScreen, bFullScreen ); +} + +/* + * NetWMAdaptor::showFullScreen + */ +void NetWMAdaptor::showFullScreen( X11SalFrame* pFrame, bool bFullScreen ) const +{ + if( m_aWMAtoms[ NET_WM_STATE_FULLSCREEN ] ) + { + pFrame->mbFullScreen = bFullScreen; + if( bFullScreen ) + { + if( m_aWMAtoms[ MOTIF_WM_HINTS ] ) + { + XDeleteProperty( m_pDisplay, + pFrame->GetShellWindow(), + m_aWMAtoms[ MOTIF_WM_HINTS ] ); + } + } + if( pFrame->bMapped_ ) + { + // window already mapped, send WM a message + XEvent aEvent; + aEvent.type = ClientMessage; + aEvent.xclient.display = m_pDisplay; + aEvent.xclient.window = pFrame->GetShellWindow(); + aEvent.xclient.message_type = m_aWMAtoms[ NET_WM_STATE ]; + aEvent.xclient.format = 32; + aEvent.xclient.data.l[0] = bFullScreen ? 1 : 0; + aEvent.xclient.data.l[1] = m_aWMAtoms[ NET_WM_STATE_FULLSCREEN ]; + aEvent.xclient.data.l[2] = 0; + aEvent.xclient.data.l[3] = 0; + aEvent.xclient.data.l[4] = 0; + XSendEvent( m_pDisplay, + m_pSalDisplay->GetRootWindow( pFrame->GetScreenNumber() ), + False, + SubstructureNotifyMask | SubstructureRedirectMask, + &aEvent + ); + } + else + { + // window not mapped yet, set _NET_WM_STATE directly + setNetWMState( pFrame ); + } + // #i42750# guess size before resize event shows up + if( bFullScreen ) + { + if( m_pSalDisplay->IsXinerama() ) + { + ::Window aRoot, aChild; + int root_x = 0, root_y = 0, lx, ly; + unsigned int mask; + XQueryPointer( m_pDisplay, + m_pSalDisplay->GetRootWindow( pFrame->GetScreenNumber() ), + &aRoot, &aChild, + &root_x, &root_y, &lx, &ly, &mask ); + const std::vector< tools::Rectangle >& rScreens = m_pSalDisplay->GetXineramaScreens(); + Point aMousePoint( root_x, root_y ); + for(const auto & rScreen : rScreens) + { + if( rScreen.Contains( aMousePoint ) ) + { + pFrame->maGeometry.nX = rScreen.Left(); + pFrame->maGeometry.nY = rScreen.Top(); + pFrame->maGeometry.nWidth = rScreen.GetWidth(); + pFrame->maGeometry.nHeight = rScreen.GetHeight(); + break; + } + } + } + else + { + Size aSize = m_pSalDisplay->GetScreenSize( pFrame->GetScreenNumber() ); + pFrame->maGeometry.nX = 0; + pFrame->maGeometry.nY = 0; + pFrame->maGeometry.nWidth = aSize.Width(); + pFrame->maGeometry.nHeight = aSize.Height(); + } + pFrame->CallCallback( SalEvent::MoveResize, nullptr ); + } + } + else WMAdaptor::showFullScreen( pFrame, bFullScreen ); +} + +/* + * WMAdaptor::getCurrentWorkArea + */ +// FIXME: multiscreen case +int WMAdaptor::getCurrentWorkArea() const +{ + int nCurrent = -1; + if( m_aWMAtoms[ NET_CURRENT_DESKTOP ] ) + { + Atom aRealType = None; + int nFormat = 8; + unsigned long nItems = 0; + unsigned long nBytesLeft = 0; + unsigned char* pProperty = nullptr; + if( XGetWindowProperty( m_pDisplay, + m_pSalDisplay->GetRootWindow( m_pSalDisplay->GetDefaultXScreen() ), + m_aWMAtoms[ NET_CURRENT_DESKTOP ], + 0, 1, + False, + XA_CARDINAL, + &aRealType, + &nFormat, + &nItems, + &nBytesLeft, + &pProperty ) == 0 + && pProperty + ) + { + nCurrent = int(*reinterpret_cast<sal_Int32*>(pProperty)); + XFree( pProperty ); + } + else if( pProperty ) + { + XFree( pProperty ); + pProperty = nullptr; + } + } + return nCurrent; +} + +/* + * WMAdaptor::getWindowWorkArea + */ +int WMAdaptor::getWindowWorkArea( ::Window aWindow ) const +{ + int nCurrent = -1; + if( m_aWMAtoms[ NET_WM_DESKTOP ] ) + { + Atom aRealType = None; + int nFormat = 8; + unsigned long nItems = 0; + unsigned long nBytesLeft = 0; + unsigned char* pProperty = nullptr; + if( XGetWindowProperty( m_pDisplay, + aWindow, + m_aWMAtoms[ NET_WM_DESKTOP ], + 0, 1, + False, + XA_CARDINAL, + &aRealType, + &nFormat, + &nItems, + &nBytesLeft, + &pProperty ) == 0 + && pProperty + ) + { + nCurrent = int(*reinterpret_cast<sal_Int32*>(pProperty)); + XFree( pProperty ); + } + else if( pProperty ) + { + XFree( pProperty ); + pProperty = nullptr; + } + } + return nCurrent; +} + +/* + * WMAdaptor::getCurrentWorkArea + */ +// fixme: multi screen case +void WMAdaptor::switchToWorkArea( int nWorkArea ) const +{ + if( ! getWMshouldSwitchWorkspace() ) + return; + + if( !m_aWMAtoms[ NET_CURRENT_DESKTOP ] ) + return; + + XEvent aEvent; + aEvent.type = ClientMessage; + aEvent.xclient.display = m_pDisplay; + aEvent.xclient.window = m_pSalDisplay->GetRootWindow( m_pSalDisplay->GetDefaultXScreen() ); + aEvent.xclient.message_type = m_aWMAtoms[ NET_CURRENT_DESKTOP ]; + aEvent.xclient.format = 32; + aEvent.xclient.data.l[0] = nWorkArea; + aEvent.xclient.data.l[1] = 0; + aEvent.xclient.data.l[2] = 0; + aEvent.xclient.data.l[3] = 0; + aEvent.xclient.data.l[4] = 0; + XSendEvent( m_pDisplay, + m_pSalDisplay->GetRootWindow( m_pSalDisplay->GetDefaultXScreen() ), + False, + SubstructureNotifyMask | SubstructureRedirectMask, + &aEvent + ); + +} + +/* + * WMAdaptor::frameIsMapping + */ +void WMAdaptor::frameIsMapping( X11SalFrame* ) const +{ +} + +/* + * NetWMAdaptor::frameIsMapping + */ +void NetWMAdaptor::frameIsMapping( X11SalFrame* pFrame ) const +{ + setNetWMState( pFrame ); +} + +/* + * WMAdaptor::setUserTime + */ +void WMAdaptor::setUserTime( X11SalFrame*, tools::Long ) const +{ +} + +/* + * NetWMAdaptor::setUserTime + */ +void NetWMAdaptor::setUserTime( X11SalFrame* i_pFrame, tools::Long i_nUserTime ) const +{ + if( m_aWMAtoms[NET_WM_USER_TIME] ) + { + XChangeProperty( m_pDisplay, + i_pFrame->GetShellWindow(), + m_aWMAtoms[NET_WM_USER_TIME], + XA_CARDINAL, + 32, + PropModeReplace, + reinterpret_cast<unsigned char*>(&i_nUserTime), + 1 + ); + } +} + +/* + * WMAdaptor::setPID + */ +void WMAdaptor::setPID( X11SalFrame const * i_pFrame ) const +{ + if( !(m_aWMAtoms[NET_WM_PID]) ) + return; + + tools::Long nPID = static_cast<tools::Long>(getpid()); + XChangeProperty( m_pDisplay, + i_pFrame->GetShellWindow(), + m_aWMAtoms[NET_WM_PID], + XA_CARDINAL, + 32, + PropModeReplace, + reinterpret_cast<unsigned char*>(&nPID), + 1 + ); +} + +/* +* WMAdaptor::setClientMachine +*/ +void WMAdaptor::setClientMachine( X11SalFrame const * i_pFrame ) const +{ + OString aWmClient( OUStringToOString( GetGenericUnixSalData()->GetHostname(), RTL_TEXTENCODING_ASCII_US ) ); + XTextProperty aClientProp = { reinterpret_cast<unsigned char *>(const_cast<char *>(aWmClient.getStr())), XA_STRING, 8, sal::static_int_cast<unsigned long>( aWmClient.getLength() ) }; + XSetWMClientMachine( m_pDisplay, i_pFrame->GetShellWindow(), &aClientProp ); +} + +void WMAdaptor::answerPing( X11SalFrame const * i_pFrame, XClientMessageEvent const * i_pEvent ) const +{ + if( !m_aWMAtoms[NET_WM_PING] || + i_pEvent->message_type != m_aWMAtoms[ WM_PROTOCOLS ] || + static_cast<Atom>(i_pEvent->data.l[0]) != m_aWMAtoms[ NET_WM_PING ] ) + return; + + XEvent aEvent; + aEvent.xclient = *i_pEvent; + aEvent.xclient.window = m_pSalDisplay->GetRootWindow( i_pFrame->GetScreenNumber() ); + XSendEvent( m_pDisplay, + m_pSalDisplay->GetRootWindow( i_pFrame->GetScreenNumber() ), + False, + SubstructureNotifyMask | SubstructureRedirectMask, + &aEvent + ); + XFlush( m_pDisplay ); +} + +void WMAdaptor::activateWindow( X11SalFrame const *pFrame, Time nTimestamp ) +{ + if (!pFrame->bMapped_) + return; + + XEvent aEvent; + + aEvent.xclient.type = ClientMessage; + aEvent.xclient.window = pFrame->GetShellWindow(); + aEvent.xclient.message_type = m_aWMAtoms[ NET_ACTIVE_WINDOW ]; + aEvent.xclient.format = 32; + aEvent.xclient.data.l[0] = 1; + aEvent.xclient.data.l[1] = nTimestamp; + aEvent.xclient.data.l[2] = None; + aEvent.xclient.data.l[3] = 0; + aEvent.xclient.data.l[4] = 0; + + XSendEvent( m_pDisplay, + m_pSalDisplay->GetRootWindow( pFrame->GetScreenNumber() ), + False, + SubstructureNotifyMask | SubstructureRedirectMask, + &aEvent ); + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/generic/desktopdetect/desktopdetector.cxx b/vcl/unx/generic/desktopdetect/desktopdetector.cxx new file mode 100644 index 000000000..bad134fbd --- /dev/null +++ b/vcl/unx/generic/desktopdetect/desktopdetector.cxx @@ -0,0 +1,253 @@ +/* -*- 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 <X11/Xlib.h> + +#include <unx/desktops.hxx> + +#include <rtl/bootstrap.hxx> +#include <rtl/process.h> +#include <osl/thread.h> + +#include <vclpluginapi.h> + +#include <string.h> +#include <comphelper/string.hxx> + +static bool is_gnome_desktop( Display* pDisplay ) +{ + + // warning: these checks are coincidental, GNOME does not + // explicitly advertise itself + if ( getenv( "GNOME_DESKTOP_SESSION_ID" ) ) + return true; + + bool ret = false; + + Atom nAtom1 = XInternAtom( pDisplay, "GNOME_SM_PROXY", True ); + Atom nAtom2 = XInternAtom( pDisplay, "NAUTILUS_DESKTOP_WINDOW_ID", True ); + if( nAtom1 || nAtom2 ) + { + int nProperties = 0; + Atom* pProperties = XListProperties( pDisplay, DefaultRootWindow( pDisplay ), &nProperties ); + if( pProperties && nProperties ) + { + for( int i = 0; i < nProperties; i++ ) + if( pProperties[ i ] == nAtom1 || + pProperties[ i ] == nAtom2 ) + { + ret = true; + break; + } + XFree( pProperties ); + } + } + if (ret) + return true; + + Atom nUTFAtom = XInternAtom( pDisplay, "UTF8_STRING", True ); + Atom nNetWMNameAtom = XInternAtom( pDisplay, "_NET_WM_NAME", True ); + if( nUTFAtom && nNetWMNameAtom ) + { + // another, more expensive check: search for a gnome-panel + ::Window aRoot, aParent, *pChildren = nullptr; + unsigned int nChildren = 0; + XQueryTree( pDisplay, DefaultRootWindow( pDisplay ), + &aRoot, &aParent, &pChildren, &nChildren ); + if( pChildren && nChildren ) + { + for( unsigned int i = 0; i < nChildren && ! ret; i++ ) + { + Atom nType = None; + int nFormat = 0; + unsigned long nItems = 0, nBytes = 0; + unsigned char* pProp = nullptr; + XGetWindowProperty( pDisplay, + pChildren[i], + nNetWMNameAtom, + 0, 8, + False, + nUTFAtom, + &nType, + &nFormat, + &nItems, + &nBytes, + &pProp ); + if( pProp && nType == nUTFAtom ) + { + OString aWMName( reinterpret_cast<char*>(pProp) ); + if ( + (aWMName.equalsIgnoreAsciiCase("gnome-shell")) || + (aWMName.equalsIgnoreAsciiCase("gnome-panel")) + ) + { + ret = true; + } + } + if( pProp ) + XFree( pProp ); + } + XFree( pChildren ); + } + } + + return ret; +} + +static bool is_plasma5_desktop() +{ + static const char* pFullVersion = getenv("KDE_FULL_SESSION"); + static const char* pSessionVersion = getenv("KDE_SESSION_VERSION"); + return pFullVersion && pSessionVersion && (0 == strcmp(pSessionVersion, "5")); +} + +extern "C" +{ + +DESKTOP_DETECTOR_PUBLIC DesktopType get_desktop_environment() +{ + static const char *pOverride = getenv( "OOO_FORCE_DESKTOP" ); + + if ( pOverride && *pOverride ) + { + OString aOver( pOverride ); + + if ( aOver.equalsIgnoreAsciiCase( "lxqt" ) ) + return DESKTOP_LXQT; + if (aOver.equalsIgnoreAsciiCase("plasma5") || aOver.equalsIgnoreAsciiCase("plasma")) + return DESKTOP_PLASMA5; + if ( aOver.equalsIgnoreAsciiCase( "gnome" ) ) + return DESKTOP_GNOME; + if ( aOver.equalsIgnoreAsciiCase( "gnome-wayland" ) ) + return DESKTOP_GNOME; + if ( aOver.equalsIgnoreAsciiCase( "unity" ) ) + return DESKTOP_UNITY; + if ( aOver.equalsIgnoreAsciiCase( "xfce" ) ) + return DESKTOP_XFCE; + if ( aOver.equalsIgnoreAsciiCase( "mate" ) ) + return DESKTOP_MATE; + if ( aOver.equalsIgnoreAsciiCase( "none" ) ) + return DESKTOP_UNKNOWN; + } + + OUString plugin; + rtl::Bootstrap::get("SAL_USE_VCLPLUGIN", plugin); + + if (plugin == "svp") + return DESKTOP_NONE; + + const char *pDesktop = getenv( "XDG_CURRENT_DESKTOP" ); + if ( pDesktop ) + { + OString aCurrentDesktop( pDesktop, strlen( pDesktop ) ); + + //it may be separated by colon ( e.g. unity:unity7:ubuntu ) + std::vector<OUString> aSplitCurrentDesktop = comphelper::string::split( + OStringToOUString( aCurrentDesktop, RTL_TEXTENCODING_UTF8), ':'); + for (const auto& rCurrentDesktopStr : aSplitCurrentDesktop) + { + if ( rCurrentDesktopStr.equalsIgnoreAsciiCase( "unity" ) ) + return DESKTOP_UNITY; + else if ( rCurrentDesktopStr.equalsIgnoreAsciiCase( "gnome" ) ) + return DESKTOP_GNOME; + else if ( rCurrentDesktopStr.equalsIgnoreAsciiCase( "lxqt" ) ) + return DESKTOP_LXQT; + } + } + + const char *pSession = getenv( "DESKTOP_SESSION" ); + OString aDesktopSession; + if ( pSession ) + aDesktopSession = OString( pSession, strlen( pSession ) ); + + // fast environment variable checks + if ( aDesktopSession.equalsIgnoreAsciiCase( "gnome" ) ) + return DESKTOP_GNOME; + else if ( aDesktopSession.equalsIgnoreAsciiCase( "gnome-wayland" ) ) + return DESKTOP_GNOME; + else if ( aDesktopSession.equalsIgnoreAsciiCase( "mate" ) ) + return DESKTOP_MATE; + else if ( aDesktopSession.equalsIgnoreAsciiCase( "xfce" ) ) + return DESKTOP_XFCE; + else if ( aDesktopSession.equalsIgnoreAsciiCase( "lxqt" ) ) + return DESKTOP_LXQT; + + if (is_plasma5_desktop()) + return DESKTOP_PLASMA5; + + // tdf#121275 if we still can't tell, and WAYLAND_DISPLAY + // is set, default to gtk3 + const char* pWaylandStr = getenv("WAYLAND_DISPLAY"); + if (pWaylandStr && *pWaylandStr) + return DESKTOP_GNOME; + + // these guys can be slower, with X property fetches, + // round-trips etc. and so are done later. + + // get display to connect to + const char* pDisplayStr = getenv( "DISPLAY" ); + + int nParams = rtl_getAppCommandArgCount(); + OUString aParam; + OString aBParm; + for( int i = 0; i < nParams; i++ ) + { + rtl_getAppCommandArg( i, &aParam.pData ); + if( i < nParams-1 && (aParam == "-display" || aParam == "--display" ) ) + { + rtl_getAppCommandArg( i+1, &aParam.pData ); + aBParm = OUStringToOString( aParam, osl_getThreadTextEncoding() ); + pDisplayStr = aBParm.getStr(); + break; + } + } + + // no server at all + if( ! pDisplayStr || !*pDisplayStr ) + return DESKTOP_NONE; + + + /* #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(); + + Display* pDisplay = XOpenDisplay( pDisplayStr ); + if( pDisplay == nullptr ) + return DESKTOP_NONE; + + DesktopType ret; + if ( is_gnome_desktop( pDisplay ) ) + ret = DESKTOP_GNOME; + else + ret = DESKTOP_UNKNOWN; + + XCloseDisplay( pDisplay ); + + return ret; +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/generic/dtrans/X11_clipboard.cxx b/vcl/unx/generic/dtrans/X11_clipboard.cxx new file mode 100644 index 000000000..9b50eff8d --- /dev/null +++ b/vcl/unx/generic/dtrans/X11_clipboard.cxx @@ -0,0 +1,231 @@ +/* -*- 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 <X11/Xatom.h> +#include "X11_clipboard.hxx" +#include "X11_transferable.hxx" +#include <com/sun/star/datatransfer/clipboard/RenderingCapabilities.hpp> +#include <cppuhelper/supportsservice.hxx> +#include <rtl/ref.hxx> +#include <sal/log.hxx> + +#if OSL_DEBUG_LEVEL > 1 +#include <stdio.h> +#endif + +using namespace com::sun::star::datatransfer; +using namespace com::sun::star::datatransfer::clipboard; +using namespace com::sun::star::lang; +using namespace com::sun::star::uno; +using namespace com::sun::star::awt; +using namespace cppu; +using namespace osl; +using namespace x11; + +X11Clipboard::X11Clipboard( SelectionManager& rManager, Atom aSelection ) : + ::cppu::WeakComponentImplHelper< + css::datatransfer::clipboard::XSystemClipboard, + css::lang::XServiceInfo + >( rManager.getMutex() ), + + m_xSelectionManager( &rManager ), + m_aSelection( aSelection ) +{ +#if OSL_DEBUG_LEVEL > 1 + SAL_INFO("vcl.unx.dtrans", "creating instance of X11Clipboard (this=" + << this << ")."); +#endif +} + +css::uno::Reference<css::datatransfer::clipboard::XClipboard> +X11Clipboard::create( SelectionManager& rManager, Atom aSelection ) +{ + rtl::Reference<X11Clipboard> cb(new X11Clipboard(rManager, aSelection)); + if( aSelection != None ) + { + rManager.registerHandler(aSelection, *cb); + } + else + { + rManager.registerHandler(XA_PRIMARY, *cb); + rManager.registerHandler(rManager.getAtom("CLIPBOARD"), *cb); + } + return cb; +} + +X11Clipboard::~X11Clipboard() +{ + MutexGuard aGuard( *Mutex::getGlobalMutex() ); + +#if OSL_DEBUG_LEVEL > 1 + SAL_INFO("vcl.unx.dtrans", "shutting down instance of X11Clipboard (this=" + << this + << ", Selection=\"" + << m_xSelectionManager->getString( m_aSelection ) + << "\")."); +#endif + + if( m_aSelection != None ) + m_xSelectionManager->deregisterHandler( m_aSelection ); + else + { + m_xSelectionManager->deregisterHandler( XA_PRIMARY ); + m_xSelectionManager->deregisterHandler( m_xSelectionManager->getAtom( "CLIPBOARD" ) ); + } +} + +void X11Clipboard::fireChangedContentsEvent() +{ + ClearableMutexGuard aGuard( m_xSelectionManager->getMutex() ); +#if OSL_DEBUG_LEVEL > 1 + SAL_INFO("vcl.unx.dtrans", "X11Clipboard::fireChangedContentsEvent for " + << m_xSelectionManager->getString( m_aSelection ) + << " (" << m_aListeners.size() << " listeners)."); +#endif + ::std::vector< Reference< XClipboardListener > > listeners( m_aListeners ); + aGuard.clear(); + + ClipboardEvent aEvent( static_cast<OWeakObject*>(this), m_aContents); + for (auto const& listener : listeners) + { + if( listener.is() ) + listener->changedContents(aEvent); + } +} + +void X11Clipboard::clearContents() +{ + ClearableMutexGuard aGuard(m_xSelectionManager->getMutex()); + // protect against deletion during outside call + Reference< XClipboard > xThis( static_cast<XClipboard*>(this)); + // copy member references on stack so they can be called + // without having the mutex + Reference< XClipboardOwner > xOwner( m_aOwner ); + Reference< XTransferable > xKeepAlive( m_aContents ); + // clear members + m_aOwner.clear(); + m_aContents.clear(); + + // release the mutex + aGuard.clear(); + + // inform previous owner of lost ownership + if ( xOwner.is() ) + xOwner->lostOwnership(xThis, m_aContents); +} + +Reference< XTransferable > SAL_CALL X11Clipboard::getContents() +{ + MutexGuard aGuard(m_xSelectionManager->getMutex()); + + if( ! m_aContents.is() ) + m_aContents = new X11Transferable( SelectionManager::get(), m_aSelection ); + return m_aContents; +} + +void SAL_CALL X11Clipboard::setContents( + const Reference< XTransferable >& xTrans, + const Reference< XClipboardOwner >& xClipboardOwner ) +{ + // remember old values for callbacks before setting the new ones. + ClearableMutexGuard aGuard(m_xSelectionManager->getMutex()); + + Reference< XClipboardOwner > oldOwner( m_aOwner ); + m_aOwner = xClipboardOwner; + + Reference< XTransferable > oldContents( m_aContents ); + m_aContents = xTrans; + + aGuard.clear(); + + // for now request ownership for both selections + if( m_aSelection != None ) + m_xSelectionManager->requestOwnership( m_aSelection ); + else + { + m_xSelectionManager->requestOwnership( XA_PRIMARY ); + m_xSelectionManager->requestOwnership( m_xSelectionManager->getAtom( "CLIPBOARD" ) ); + } + + // notify old owner on loss of ownership + if( oldOwner.is() ) + oldOwner->lostOwnership(static_cast < XClipboard * > (this), oldContents); + + // notify all listeners on content changes + fireChangedContentsEvent(); +} + +OUString SAL_CALL X11Clipboard::getName() +{ + return m_xSelectionManager->getString( m_aSelection ); +} + +sal_Int8 SAL_CALL X11Clipboard::getRenderingCapabilities() +{ + return RenderingCapabilities::Delayed; +} + +void SAL_CALL X11Clipboard::addClipboardListener( const Reference< XClipboardListener >& listener ) +{ + MutexGuard aGuard( m_xSelectionManager->getMutex() ); + m_aListeners.push_back( listener ); +} + +void SAL_CALL X11Clipboard::removeClipboardListener( const Reference< XClipboardListener >& listener ) +{ + MutexGuard aGuard( m_xSelectionManager->getMutex() ); + m_aListeners.erase( std::remove(m_aListeners.begin(), m_aListeners.end(), listener), m_aListeners.end() ); +} + +Reference< XTransferable > X11Clipboard::getTransferable() +{ + return getContents(); +} + +void X11Clipboard::clearTransferable() +{ + clearContents(); +} + +void X11Clipboard::fireContentsChanged() +{ + fireChangedContentsEvent(); +} + +Reference< XInterface > X11Clipboard::getReference() noexcept +{ + return Reference< XInterface >( static_cast< OWeakObject* >(this) ); +} + +OUString SAL_CALL X11Clipboard::getImplementationName( ) +{ + return X11_CLIPBOARD_IMPLEMENTATION_NAME; +} + +sal_Bool SAL_CALL X11Clipboard::supportsService( const OUString& ServiceName ) +{ + return cppu::supportsService(this, ServiceName); +} + +Sequence< OUString > SAL_CALL X11Clipboard::getSupportedServiceNames( ) +{ + return X11Clipboard_getSupportedServiceNames(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/generic/dtrans/X11_clipboard.hxx b/vcl/unx/generic/dtrans/X11_clipboard.hxx new file mode 100644 index 000000000..6a2ab732a --- /dev/null +++ b/vcl/unx/generic/dtrans/X11_clipboard.hxx @@ -0,0 +1,111 @@ +/* -*- 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 . + */ + +#pragma once + +#include "X11_selection.hxx" + +#include <com/sun/star/datatransfer/clipboard/XSystemClipboard.hpp> +#include <cppuhelper/compbase.hxx> + +inline constexpr OUStringLiteral X11_CLIPBOARD_IMPLEMENTATION_NAME = u"com.sun.star.datatransfer.X11ClipboardSupport"; + +namespace x11 { + + class X11Clipboard : + public ::cppu::WeakComponentImplHelper < + css::datatransfer::clipboard::XSystemClipboard, + css::lang::XServiceInfo + >, + public SelectionAdaptor + { + css::uno::Reference< css::datatransfer::XTransferable > m_aContents; + css::uno::Reference< css::datatransfer::clipboard::XClipboardOwner > m_aOwner; + + rtl::Reference<SelectionManager> m_xSelectionManager; + ::std::vector< css::uno::Reference< css::datatransfer::clipboard::XClipboardListener > > m_aListeners; + Atom m_aSelection; + + X11Clipboard( SelectionManager& rManager, Atom aSelection ); + + friend class SelectionManager; + + void fireChangedContentsEvent(); + void clearContents(); + + public: + + static css::uno::Reference<css::datatransfer::clipboard::XClipboard> + create( SelectionManager& rManager, Atom aSelection ); + + virtual ~X11Clipboard() override; + + /* + * XServiceInfo + */ + + virtual OUString SAL_CALL getImplementationName( ) override; + + virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override; + + virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames( ) override; + + /* + * XClipboard + */ + + virtual css::uno::Reference< css::datatransfer::XTransferable > SAL_CALL getContents() override; + + virtual void SAL_CALL setContents( + const css::uno::Reference< css::datatransfer::XTransferable >& xTrans, + const css::uno::Reference< css::datatransfer::clipboard::XClipboardOwner >& xClipboardOwner ) override; + + virtual OUString SAL_CALL getName() override; + + /* + * XClipboardEx + */ + + virtual sal_Int8 SAL_CALL getRenderingCapabilities() override; + + /* + * XClipboardNotifier + */ + virtual void SAL_CALL addClipboardListener( + const css::uno::Reference< css::datatransfer::clipboard::XClipboardListener >& listener ) override; + + virtual void SAL_CALL removeClipboardListener( + const css::uno::Reference< css::datatransfer::clipboard::XClipboardListener >& listener ) override; + + /* + * SelectionAdaptor + */ + virtual css::uno::Reference< css::datatransfer::XTransferable > getTransferable() override; + virtual void clearTransferable() override; + virtual void fireContentsChanged() override; + virtual css::uno::Reference< css::uno::XInterface > getReference() noexcept override; + }; + + css::uno::Sequence< OUString > X11Clipboard_getSupportedServiceNames(); + css::uno::Reference< css::uno::XInterface > SAL_CALL X11Clipboard_createInstance( + const css::uno::Reference< css::lang::XMultiServiceFactory > & xMultiServiceFactory); + +} // namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/generic/dtrans/X11_dndcontext.cxx b/vcl/unx/generic/dtrans/X11_dndcontext.cxx new file mode 100644 index 000000000..638c47387 --- /dev/null +++ b/vcl/unx/generic/dtrans/X11_dndcontext.cxx @@ -0,0 +1,118 @@ +/* -*- 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 "X11_dndcontext.hxx" +#include "X11_selection.hxx" + +using namespace cppu; +using namespace x11; + +/* + * DropTargetDropContext + */ + +DropTargetDropContext::DropTargetDropContext( + ::Window aDropWindow, + SelectionManager& rManager ) : + m_aDropWindow( aDropWindow ), + m_xManager( &rManager ) +{ +} + +DropTargetDropContext::~DropTargetDropContext() +{ +} + +void DropTargetDropContext::acceptDrop( sal_Int8 dragOperation ) +{ + m_xManager->accept( dragOperation, m_aDropWindow ); +} + +void DropTargetDropContext::rejectDrop() +{ + m_xManager->reject( m_aDropWindow ); +} + +void DropTargetDropContext::dropComplete( sal_Bool success ) +{ + m_xManager->dropComplete( success, m_aDropWindow ); +} + +/* + * DropTargetDragContext + */ + +DropTargetDragContext::DropTargetDragContext( + ::Window aDropWindow, + SelectionManager& rManager ) : + m_aDropWindow( aDropWindow ), + m_xManager( &rManager ) +{ +} + +DropTargetDragContext::~DropTargetDragContext() +{ +} + +void DropTargetDragContext::acceptDrag( sal_Int8 dragOperation ) +{ + m_xManager->accept( dragOperation, m_aDropWindow ); +} + +void DropTargetDragContext::rejectDrag() +{ + m_xManager->reject( m_aDropWindow ); +} + +/* + * DragSourceContext + */ + +DragSourceContext::DragSourceContext( + ::Window aDropWindow, + SelectionManager& rManager ) : + m_aDropWindow( aDropWindow ), + m_xManager( &rManager ) +{ +} + +DragSourceContext::~DragSourceContext() +{ +} + +sal_Int32 DragSourceContext::getCurrentCursor() +{ + return m_xManager->getCurrentCursor(); +} + +void DragSourceContext::setCursor( sal_Int32 cursorId ) +{ + m_xManager->setCursor( cursorId, m_aDropWindow ); +} + +void DragSourceContext::setImage( sal_Int32 ) +{ +} + +void DragSourceContext::transferablesFlavorsChanged() +{ + m_xManager->transferablesFlavorsChanged(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/generic/dtrans/X11_dndcontext.hxx b/vcl/unx/generic/dtrans/X11_dndcontext.hxx new file mode 100644 index 000000000..71283ea64 --- /dev/null +++ b/vcl/unx/generic/dtrans/X11_dndcontext.hxx @@ -0,0 +1,80 @@ +/* -*- 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 . + */ + +#pragma once + +#include <com/sun/star/datatransfer/dnd/XDragSourceContext.hpp> +#include <com/sun/star/datatransfer/dnd/XDropTargetDropContext.hpp> +#include <com/sun/star/datatransfer/dnd/XDropTargetDragContext.hpp> +#include <cppuhelper/implbase.hxx> +#include <rtl/ref.hxx> + +#include <X11/X.h> + +namespace x11 { + + class SelectionManager; + + class DropTargetDropContext : + public ::cppu::WeakImplHelper<css::datatransfer::dnd::XDropTargetDropContext> + { + ::Window m_aDropWindow; + rtl::Reference<SelectionManager> m_xManager; + public: + DropTargetDropContext( ::Window, SelectionManager& ); + virtual ~DropTargetDropContext() override; + + // XDropTargetDropContext + virtual void SAL_CALL acceptDrop( sal_Int8 dragOperation ) override; + virtual void SAL_CALL rejectDrop() override; + virtual void SAL_CALL dropComplete( sal_Bool success ) override; + }; + + class DropTargetDragContext : + public ::cppu::WeakImplHelper<css::datatransfer::dnd::XDropTargetDragContext> + { + ::Window m_aDropWindow; + rtl::Reference<SelectionManager> m_xManager; + public: + DropTargetDragContext( ::Window, SelectionManager& ); + virtual ~DropTargetDragContext() override; + + // XDropTargetDragContext + virtual void SAL_CALL acceptDrag( sal_Int8 dragOperation ) override; + virtual void SAL_CALL rejectDrag() override; + }; + + class DragSourceContext : + public ::cppu::WeakImplHelper<css::datatransfer::dnd::XDragSourceContext> + { + ::Window m_aDropWindow; + rtl::Reference<SelectionManager> m_xManager; + public: + DragSourceContext( ::Window, SelectionManager& ); + virtual ~DragSourceContext() override; + + // XDragSourceContext + virtual sal_Int32 SAL_CALL getCurrentCursor() override; + virtual void SAL_CALL setCursor( sal_Int32 cursorId ) override; + virtual void SAL_CALL setImage( sal_Int32 imageId ) override; + virtual void SAL_CALL transferablesFlavorsChanged() override; + }; +} // namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/generic/dtrans/X11_droptarget.cxx b/vcl/unx/generic/dtrans/X11_droptarget.cxx new file mode 100644 index 000000000..7ce291902 --- /dev/null +++ b/vcl/unx/generic/dtrans/X11_droptarget.cxx @@ -0,0 +1,177 @@ +/* -*- 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 <cppuhelper/supportsservice.hxx> +#include "X11_selection.hxx" + +using namespace x11; +using namespace com::sun::star::uno; +using namespace com::sun::star::lang; +using namespace com::sun::star::awt; +using namespace com::sun::star::datatransfer; +using namespace com::sun::star::datatransfer::dnd; + +DropTarget::DropTarget() : + ::cppu::WeakComponentImplHelper< + XDropTarget, + XInitialization, + XServiceInfo + >( m_aMutex ), + m_bActive( false ), + m_nDefaultActions( 0 ), + m_aTargetWindow( None ) +{ +} + +DropTarget::~DropTarget() +{ + if( m_xSelectionManager.is() ) + m_xSelectionManager->deregisterDropTarget( m_aTargetWindow ); +} + +void DropTarget::initialize( const Sequence< Any >& arguments ) +{ + if( arguments.getLength() <= 1 ) + return; + + OUString aDisplayName; + Reference< XDisplayConnection > xConn; + arguments.getConstArray()[0] >>= xConn; + if( xConn.is() ) + { + Any aIdentifier; + aIdentifier >>= aDisplayName; + } + + m_xSelectionManager = &SelectionManager::get( aDisplayName ); + m_xSelectionManager->initialize( arguments ); + + if( m_xSelectionManager->getDisplay() ) // #136582# sanity check + { + sal_IntPtr aWindow = None; + arguments.getConstArray()[1] >>= aWindow; + m_xSelectionManager->registerDropTarget( aWindow, this ); + m_aTargetWindow = aWindow; + m_bActive = true; + } +} + +void DropTarget::addDropTargetListener( const Reference< XDropTargetListener >& xListener ) +{ + ::osl::Guard< ::osl::Mutex > aGuard( m_aMutex ); + + m_aListeners.push_back( xListener ); +} + +void DropTarget::removeDropTargetListener( const Reference< XDropTargetListener >& xListener ) +{ + ::osl::Guard< ::osl::Mutex > aGuard( m_aMutex ); + + m_aListeners.erase( std::remove(m_aListeners.begin(), m_aListeners.end(), xListener), m_aListeners.end() ); +} + +sal_Bool DropTarget::isActive() +{ + return m_bActive; +} + +void DropTarget::setActive( sal_Bool active ) +{ + ::osl::Guard< ::osl::Mutex > aGuard( m_aMutex ); + + m_bActive = active; +} + +sal_Int8 DropTarget::getDefaultActions() +{ + return m_nDefaultActions; +} + +void DropTarget::setDefaultActions( sal_Int8 actions ) +{ + ::osl::Guard< ::osl::Mutex > aGuard( m_aMutex ); + + m_nDefaultActions = actions; +} + +void DropTarget::drop( const DropTargetDropEvent& dtde ) noexcept +{ + osl::ClearableGuard< ::osl::Mutex > aGuard( m_aMutex ); + std::vector< Reference< XDropTargetListener > > aListeners( m_aListeners ); + aGuard.clear(); + + for (auto const& listener : aListeners) + { + listener->drop(dtde); + } +} + +void DropTarget::dragEnter( const DropTargetDragEnterEvent& dtde ) noexcept +{ + osl::ClearableGuard< ::osl::Mutex > aGuard( m_aMutex ); + std::vector< Reference< XDropTargetListener > > aListeners( m_aListeners ); + aGuard.clear(); + + for (auto const& listener : aListeners) + { + listener->dragEnter(dtde); + } +} + +void DropTarget::dragExit( const DropTargetEvent& dte ) noexcept +{ + osl::ClearableGuard< ::osl::Mutex > aGuard( m_aMutex ); + std::vector< Reference< XDropTargetListener > > aListeners( m_aListeners ); + aGuard.clear(); + + for (auto const& listener : aListeners) + { + listener->dragExit(dte); + } +} + +void DropTarget::dragOver( const DropTargetDragEvent& dtde ) noexcept +{ + osl::ClearableGuard< ::osl::Mutex > aGuard( m_aMutex ); + std::vector< Reference< XDropTargetListener > > aListeners( m_aListeners ); + aGuard.clear(); + + for (auto const& listener : aListeners) + { + listener->dragOver(dtde); + } +} + +// XServiceInfo +OUString DropTarget::getImplementationName() +{ + return "com.sun.star.datatransfer.dnd.XdndDropTarget"; +} + +sal_Bool DropTarget::supportsService( const OUString& ServiceName ) +{ + return cppu::supportsService(this, ServiceName); +} + +Sequence< OUString > DropTarget::getSupportedServiceNames() +{ + return Xdnd_dropTarget_getSupportedServiceNames(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/generic/dtrans/X11_selection.cxx b/vcl/unx/generic/dtrans/X11_selection.cxx new file mode 100644 index 000000000..d35b496bf --- /dev/null +++ b/vcl/unx/generic/dtrans/X11_selection.cxx @@ -0,0 +1,4156 @@ +/* -*- 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 <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_TEXTENCODING_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( "TARGETS" ); + m_nTIMESTAMPAtom = getAtom( "TIMESTAMP" ); + m_nTEXTAtom = getAtom( "TEXT" ); + m_nINCRAtom = getAtom( "INCR" ); + m_nCOMPOUNDAtom = getAtom( "COMPOUND_TEXT" ); + m_nMULTIPLEAtom = getAtom( "MULTIPLE" ); + m_nImageBmpAtom = getAtom( "image/bmp" ); + + // Atoms for Xdnd protocol + m_nXdndAware = getAtom( "XdndAware" ); + m_nXdndEnter = getAtom( "XdndEnter" ); + m_nXdndLeave = getAtom( "XdndLeave" ); + m_nXdndPosition = getAtom( "XdndPosition" ); + m_nXdndStatus = getAtom( "XdndStatus" ); + m_nXdndDrop = getAtom( "XdndDrop" ); + m_nXdndFinished = getAtom( "XdndFinished" ); + m_nXdndSelection = getAtom( "XdndSelection" ); + m_nXdndTypeList = getAtom( "XdndTypeList" ); + m_nXdndProxy = getAtom( "XdndProxy" ); + m_nXdndActionCopy = getAtom( "XdndActionCopy" ); + m_nXdndActionMove = getAtom( "XdndActionMove" ); + m_nXdndActionLink = getAtom( "XdndActionLink" ); + m_nXdndActionAsk = getAtom( "XdndActionAsk" ); + + // 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( const OUString& rDisplayName ) +{ + osl::MutexGuard aGuard( *osl::Mutex::getGlobalMutex() ); + + OUString aDisplayName( rDisplayName ); + if( aDisplayName.isEmpty() ) + 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( "text/plain;charset=utf-8", 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(); + osl::Thread::wait(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" ) + { + // lets 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 = aTemp; + + std::vector< Atom > aNativeTemp( nNewFlavors ); + for( i = 0; i < nNewFlavors-1; i++ ) + aNativeTemp[ i + 1 ] = aNativeTypes[ i ]; + aNativeTemp[0] = None; + aNativeTypes = 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 = 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 = 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] ) : + "<None>")); +#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:"; + + int nProps = 0; + Atom* pProps = XListProperties( m_pDisplay, rRequest.requestor, &nProps ); + if( pProps ) + { + for( int i = 0; i < nProps; i++ ) + oss << " \"" << getString( pProps[i]) << "\""; + XFree( pProps ); + } + SAL_INFO("vcl.unx.dtrans", oss.str()); + } +#endif + } + else + { + aGuard.clear(); + bEventSuccess = sendData( pAdaptor, rRequest.requestor, rRequest.target, rRequest.property, rRequest.selection ); + aGuard.reset(); + } + if( bEventSuccess ) + { + aNotify.xselection.target = rRequest.target; + aNotify.xselection.property = rRequest.property; + } + } + aGuard.clear(); + xTrans.clear(); + aGuard.reset(); + } + XSendEvent( m_pDisplay, rRequest.requestor, False, 0, &aNotify ); + + if( rRequest.selection == XA_PRIMARY && + m_bWaitingForPrimaryConversion && + m_xDragSourceListener.is() ) + { + DragSourceDropEvent dsde; + dsde.Source = static_cast< OWeakObject* >(this); + dsde.DragSourceContext = new DragSourceContext( m_aDropWindow, *this ); + dsde.DragSource = static_cast< XDragSource* >(this); + if( aNotify.xselection.property != None ) + { + dsde.DropAction = DNDConstants::ACTION_COPY; + dsde.DropSuccess = true; + } + else + { + dsde.DropAction = DNDConstants::ACTION_NONE; + dsde.DropSuccess = false; + } + css::uno::Reference< XDragSourceListener > xListener( m_xDragSourceListener ); + m_xDragSourceListener.clear(); + aGuard.clear(); + if( xListener.is() ) + xListener->dragDropEnd( dsde ); + } + + // we handled the event in any case by answering + return true; +} + +bool SelectionManager::handleReceivePropertyNotify( XPropertyEvent const & rNotify ) +{ + osl::MutexGuard aGuard( m_aMutex ); + // data we requested arrived +#if OSL_DEBUG_LEVEL > 1 + SAL_INFO("vcl.unx.dtrans", "handleReceivePropertyNotify for property " + << getString( rNotify.atom )); +#endif + bool bHandled = false; + + std::unordered_map< Atom, Selection* >::iterator it = + m_aSelections.find( rNotify.atom ); + if( it != m_aSelections.end() && + rNotify.state == PropertyNewValue && + ( it->second->m_eState == Selection::WaitingForResponse || + it->second->m_eState == Selection::WaitingForData || + it->second->m_eState == Selection::IncrementalTransfer + ) + ) + { + // MULTIPLE requests are only complete after selection notify + if( it->second->m_aRequestedType == m_nMULTIPLEAtom && + ( it->second->m_eState == Selection::WaitingForResponse || + it->second->m_eState == Selection::WaitingForData ) ) + return false; + + bHandled = true; + + Atom nType = None; + int nFormat = 0; + unsigned long nItems = 0, nBytes = 0; + unsigned char* pData = nullptr; + + // get type and length + XGetWindowProperty( m_pDisplay, + rNotify.window, + rNotify.atom, + 0, 0, + False, + AnyPropertyType, + &nType, &nFormat, + &nItems, &nBytes, + &pData ); +#if OSL_DEBUG_LEVEL > 1 + SAL_INFO("vcl.unx.dtrans", "found " + << nBytes + << " bytes data of type " + << getString( nType ) + << " and format " + << nFormat + << ", items = " + << nItems); +#endif + if( pData ) + { + XFree( pData ); + pData = nullptr; + } + + if( nType == m_nINCRAtom ) + { + // start data transfer + XDeleteProperty( m_pDisplay, rNotify.window, rNotify.atom ); + it->second->m_eState = Selection::IncrementalTransfer; + } + else if( nType != None ) + { + XGetWindowProperty( m_pDisplay, + rNotify.window, + rNotify.atom, + 0, nBytes/4 +1, + True, + nType, + &nType, &nFormat, + &nItems, &nBytes, + &pData ); + +#if OSL_DEBUG_LEVEL > 1 + SAL_INFO("vcl.unx.dtrans", "read " + << nItems + << " items data of type " + << getString( nType ) + << " and format " + << nFormat + << ", " + << nBytes + << " bytes left in property."); +#endif + + std::size_t nUnitSize = GetTrueFormatSize(nFormat); + + if( it->second->m_eState == Selection::WaitingForData || + it->second->m_eState == Selection::WaitingForResponse ) + { + // copy data + it->second->m_aData = Sequence< sal_Int8 >( reinterpret_cast<sal_Int8*>(pData), nItems*nUnitSize ); + it->second->m_eState = Selection::Inactive; + it->second->m_aDataArrived.set(); + } + else if( it->second->m_eState == Selection::IncrementalTransfer ) + { + if( nItems ) + { + // append data + Sequence< sal_Int8 > aData( it->second->m_aData.getLength() + nItems*nUnitSize ); + memcpy( aData.getArray(), it->second->m_aData.getArray(), it->second->m_aData.getLength() ); + memcpy( aData.getArray() + it->second->m_aData.getLength(), pData, nItems*nUnitSize ); + it->second->m_aData = aData; + } + else + { + it->second->m_eState = Selection::Inactive; + it->second->m_aDataArrived.set(); + } + } + if( pData ) + XFree( pData ); + } + else if( it->second->m_eState == Selection::IncrementalTransfer ) + { + it->second->m_eState = Selection::Inactive; + it->second->m_aDataArrived.set(); + } + } + return bHandled; +} + +bool SelectionManager::handleSendPropertyNotify( XPropertyEvent const & rNotify ) +{ + osl::MutexGuard aGuard( m_aMutex ); + + // ready for next part of an IncrementalTransfer +#if OSL_DEBUG_LEVEL > 1 + SAL_INFO("vcl.unx.dtrans", "handleSendPropertyNotify for property " + << getString( rNotify.atom ) + << " (" + << (rNotify.state == PropertyNewValue ? + "new value" : + (rNotify.state == PropertyDelete ? + "deleted" : + "unknown")) + << ")."); +#endif + + bool bHandled = false; + // feed incrementals + if( rNotify.state == PropertyDelete ) + { + auto it = m_aIncrementals.find( rNotify.window ); + if( it != m_aIncrementals.end() ) + { + bHandled = true; + int nCurrentTime = time( nullptr ); + // throw out aborted transfers + std::vector< Atom > aTimeouts; + for (auto const& incrementalTransfer : it->second) + { + if( (nCurrentTime - incrementalTransfer.second.m_nTransferStartTime) > (getSelectionTimeout()+2) ) + { + aTimeouts.push_back( incrementalTransfer.first ); +#if OSL_DEBUG_LEVEL > 1 + const IncrementalTransfer& rInc = incrementalTransfer.second; + SAL_INFO("vcl.unx.dtrans", + "timeout on INCR transfer for window " + << std::showbase << std::hex + << rInc.m_aRequestor + << ", property " + << getString( rInc.m_aProperty ) + << ", type " + << getString( rInc.m_aTarget )); +#endif + } + } + + for (auto const& timeout : aTimeouts) + { + // transfer broken, might even be a new client with the + // same window id + it->second.erase( timeout ); + } + aTimeouts.clear(); + + auto inc_it = it->second.find( rNotify.atom ); + if( inc_it != it->second.end() ) + { + IncrementalTransfer& rInc = inc_it->second; + + int nBytes = rInc.m_aData.getLength() - rInc.m_nBufferPos; + nBytes = std::min(nBytes, m_nIncrementalThreshold); + if( nBytes < 0 ) // sanity check + nBytes = 0; +#if OSL_DEBUG_LEVEL > 1 + SAL_INFO("vcl.unx.dtrans", "pushing " + << nBytes + << " bytes: \"" + << std::setw(std::min(nBytes, 32)) + << ((const unsigned char*) + rInc.m_aData.getConstArray()+rInc.m_nBufferPos) + << "\"..."); +#endif + std::size_t nUnitSize = GetTrueFormatSize(rInc.m_nFormat); + + XChangeProperty( m_pDisplay, + rInc.m_aRequestor, + rInc.m_aProperty, + rInc.m_aTarget, + rInc.m_nFormat, + PropModeReplace, + reinterpret_cast<const unsigned char*>(rInc.m_aData.getConstArray())+rInc.m_nBufferPos, + nBytes/nUnitSize ); + rInc.m_nBufferPos += nBytes; + rInc.m_nTransferStartTime = nCurrentTime; + + if( nBytes == 0 ) // transfer finished + { +#if OSL_DEBUG_LEVEL > 1 + SAL_INFO("vcl.unx.dtrans", "finished INCR transfer for " + << "window " + << std::showbase << std::hex + << rInc.m_aRequestor + << ", property " + << getString( rInc.m_aProperty ) + << ", type " + << getString( rInc.m_aTarget )); +#endif + it->second.erase( inc_it ); + } + + } + // eventually clean up the hash map + if( it->second.empty() ) + m_aIncrementals.erase( it ); + } + } + return bHandled; +} + +bool SelectionManager::handleSelectionNotify( XSelectionEvent const & rNotify ) +{ + osl::MutexGuard aGuard( m_aMutex ); + + bool bHandled = false; + + // notification about success/failure of one of our conversion requests +#if OSL_DEBUG_LEVEL > 1 + SAL_INFO("vcl.unx.dtrans", "handleSelectionNotify for selection " + << getString( rNotify.selection ) + << " and property " + << (rNotify.property ? getString( rNotify.property ) : "None") + << " (" + << std::showbase << std::hex + << rNotify.property + << ")."); + SAL_WARN_IF(rNotify.requestor != m_aWindow && + rNotify.requestor != m_aCurrentDropWindow, + "vcl.unx.dtrans", "selection notify for unknown window " + << std::showbase << std::hex + << rNotify.requestor); +#endif + std::unordered_map< Atom, Selection* >::iterator it = + m_aSelections.find( rNotify.selection ); + if ( + (rNotify.requestor == m_aWindow || rNotify.requestor == m_aCurrentDropWindow) && + it != m_aSelections.end() && + ( + (it->second->m_eState == Selection::WaitingForResponse) || + (it->second->m_eState == Selection::WaitingForData) + ) + ) + { + bHandled = true; + if( it->second->m_aRequestedType == m_nMULTIPLEAtom ) + { + Atom nType = None; + int nFormat = 0; + unsigned long nItems = 0, nBytes = 0; + unsigned char* pData = nullptr; + + // get type and length + XGetWindowProperty( m_pDisplay, + rNotify.requestor, + rNotify.property, + 0, 256, + False, + AnyPropertyType, + &nType, &nFormat, + &nItems, &nBytes, + &pData ); + if( nBytes ) // HUGE request !!! + { + if( pData ) + XFree( pData ); + XGetWindowProperty( m_pDisplay, + rNotify.requestor, + rNotify.property, + 0, 256+(nBytes+3)/4, + False, + AnyPropertyType, + &nType, &nFormat, + &nItems, &nBytes, + &pData ); + } + it->second->m_eState = Selection::Inactive; + std::size_t nUnitSize = GetTrueFormatSize(nFormat); + it->second->m_aData = Sequence< sal_Int8 >(reinterpret_cast<sal_Int8*>(pData), nItems * nUnitSize); + it->second->m_aDataArrived.set(); + if( pData ) + XFree( pData ); + } + // WaitingForData can actually happen; some + // applications (e.g. cmdtool on Solaris) first send + // a success and then cancel it. Weird ! + else if( rNotify.property == None ) + { + // conversion failed, stop transfer + it->second->m_eState = Selection::Inactive; + it->second->m_aData = Sequence< sal_Int8 >(); + it->second->m_aDataArrived.set(); + } + // get the bytes, by INCR if necessary + else + it->second->m_eState = Selection::WaitingForData; + } +#if OSL_DEBUG_LEVEL > 1 + else if( it != m_aSelections.end() ) + SAL_WARN("vcl.unx.dtrans", "selection in state " << it->second->m_eState); +#endif + return bHandled; +} + +bool SelectionManager::handleDropEvent( XClientMessageEvent const & rMessage ) +{ + osl::ResettableMutexGuard aGuard(m_aMutex); + + // handle drop related events + ::Window aSource = rMessage.data.l[0]; + ::Window aTarget = rMessage.window; + + bool bHandled = false; + + std::unordered_map< ::Window, DropTargetEntry >::iterator it = + m_aDropTargets.find( aTarget ); + +#if OSL_DEBUG_LEVEL > 1 + if( rMessage.message_type == m_nXdndEnter || + rMessage.message_type == m_nXdndLeave || + rMessage.message_type == m_nXdndDrop || + rMessage.message_type == m_nXdndPosition ) + { + std::ostringstream oss; + oss << "got drop event " + << getString( rMessage.message_type ) + << ", "; + + if( it == m_aDropTargets.end() ) + oss << "but no target found."; + else if( ! it->second.m_pTarget->m_bActive ) + oss << "but target is inactive."; + else if( m_aDropEnterEvent.data.l[0] != None && (::Window)m_aDropEnterEvent.data.l[0] != aSource ) + oss << "but source " + << std::showbase << std::hex + << aSource + << " is unknown (expected " + << m_aDropEnterEvent.data.l[0] + << " or 0)."; + else + oss << "processing."; + SAL_INFO("vcl.unx.dtrans", oss.str()); + } +#endif + + if( it != m_aDropTargets.end() && it->second.m_pTarget->m_bActive && + m_bDropWaitingForCompletion && m_aDropEnterEvent.data.l[0] ) + { + bHandled = true; + OSL_FAIL( "someone forgot to call dropComplete ?" ); + // some listener forgot to call dropComplete in the last operation + // let us end it now and accept the new enter event + aGuard.clear(); + dropComplete( false, m_aCurrentDropWindow ); + aGuard.reset(); + } + + if( it != m_aDropTargets.end() && + it->second.m_pTarget->m_bActive && + ( m_aDropEnterEvent.data.l[0] == None || ::Window(m_aDropEnterEvent.data.l[0]) == aSource ) + ) + { + if( rMessage.message_type == m_nXdndEnter ) + { + bHandled = true; + m_aDropEnterEvent = rMessage; + m_bDropEnterSent = false; + m_aCurrentDropWindow = aTarget; + m_nCurrentProtocolVersion = m_aDropEnterEvent.data.l[1] >> 24; +#if OSL_DEBUG_LEVEL > 1 + SAL_INFO("vcl.unx.dtrans", "received XdndEnter on " + << std::showbase << std::hex + << aTarget); +#endif + } + else if( + rMessage.message_type == m_nXdndPosition && + aSource == ::Window(m_aDropEnterEvent.data.l[0]) + ) + { + bHandled = true; + m_nDropTime = m_nCurrentProtocolVersion > 0 ? rMessage.data.l[3] : CurrentTime; + + ::Window aChild; + XTranslateCoordinates( m_pDisplay, + it->second.m_aRootWindow, + it->first, + rMessage.data.l[2] >> 16, + rMessage.data.l[2] & 0xffff, + &m_nLastX, &m_nLastY, + &aChild ); +#if OSL_DEBUG_LEVEL > 1 + SAL_INFO("vcl.unx.dtrans", "received XdndPosition on " + << std::showbase << std::hex + << aTarget + << " (" + << std::dec + << m_nLastX + << ", " + << m_nLastY + << ")."); +#endif + DropTargetDragEnterEvent aEvent; + aEvent.Source = static_cast< XDropTarget* >(it->second.m_pTarget); + aEvent.Context = new DropTargetDragContext( m_aCurrentDropWindow, *this ); + aEvent.LocationX = m_nLastX; + aEvent.LocationY = m_nLastY; + aEvent.SourceActions = m_nSourceActions; + if( m_nCurrentProtocolVersion < 2 ) + aEvent.DropAction = DNDConstants::ACTION_COPY; + else if( Atom(rMessage.data.l[4]) == m_nXdndActionCopy ) + aEvent.DropAction = DNDConstants::ACTION_COPY; + else if( Atom(rMessage.data.l[4]) == m_nXdndActionMove ) + aEvent.DropAction = DNDConstants::ACTION_MOVE; + else if( Atom(rMessage.data.l[4]) == m_nXdndActionLink ) + aEvent.DropAction = DNDConstants::ACTION_LINK; + else if( Atom(rMessage.data.l[4]) == m_nXdndActionAsk ) + // currently no interface to implement ask + aEvent.DropAction = ~0; + else + aEvent.DropAction = DNDConstants::ACTION_NONE; + + m_nLastDropAction = aEvent.DropAction; + if( ! m_bDropEnterSent ) + { + m_bDropEnterSent = true; + aEvent.SupportedDataFlavors = m_xDropTransferable->getTransferDataFlavors(); + aGuard.clear(); + it->second->dragEnter( aEvent ); + } + else + { + aGuard.clear(); + it->second->dragOver( aEvent ); + } + } + else if( + rMessage.message_type == m_nXdndLeave && + aSource == ::Window(m_aDropEnterEvent.data.l[0]) + ) + { + bHandled = true; +#if OSL_DEBUG_LEVEL > 1 + SAL_INFO("vcl.unx.dtrans", "received XdndLeave on " + << std::showbase << std::hex + << aTarget); +#endif + DropTargetEvent aEvent; + aEvent.Source = static_cast< XDropTarget* >(it->second.m_pTarget); + m_aDropEnterEvent.data.l[0] = None; + if( m_aCurrentDropWindow == aTarget ) + m_aCurrentDropWindow = None; + m_nCurrentProtocolVersion = nXdndProtocolRevision; + aGuard.clear(); + it->second->dragExit( aEvent ); + } + else if( + rMessage.message_type == m_nXdndDrop && + aSource == ::Window(m_aDropEnterEvent.data.l[0]) + ) + { + bHandled = true; + m_nDropTime = m_nCurrentProtocolVersion > 0 ? rMessage.data.l[2] : CurrentTime; +#if OSL_DEBUG_LEVEL > 1 + SAL_INFO("vcl.unx.dtrans", "received XdndDrop on " + << std::showbase << std::hex + << aTarget + << " (" + << m_nLastX + << ", " + << m_nLastY + << ")."); +#endif + if( m_bLastDropAccepted ) + { + DropTargetDropEvent aEvent; + aEvent.Source = static_cast< XDropTarget* >(it->second.m_pTarget); + aEvent.Context = new DropTargetDropContext( m_aCurrentDropWindow, *this ); + aEvent.LocationX = m_nLastX; + aEvent.LocationY = m_nLastY; + aEvent.DropAction = m_nLastDropAction; + // there is nothing corresponding to source supported actions + // every source can do link, copy and move + aEvent.SourceActions= m_nLastDropAction; + aEvent.Transferable = m_xDropTransferable; + + m_bDropWaitingForCompletion = true; + aGuard.clear(); + it->second->drop( aEvent ); + } + else + { +#if OSL_DEBUG_LEVEL > 1 + SAL_INFO("vcl.unx.dtrans", "XdndDrop canceled due to " + << "m_bLastDropAccepted = false." ); +#endif + DropTargetEvent aEvent; + aEvent.Source = static_cast< XDropTarget* >(it->second.m_pTarget); + aGuard.clear(); + it->second->dragExit( aEvent ); + // reset the drop status, notify source + dropComplete( false, m_aCurrentDropWindow ); + } + } + } + return bHandled; +} + +/* + * methods for XDropTargetDropContext + */ + +void SelectionManager::dropComplete( bool bSuccess, ::Window aDropWindow ) +{ + osl::ClearableMutexGuard aGuard(m_aMutex); + + if( aDropWindow == m_aCurrentDropWindow ) + { + if( m_xDragSourceListener.is() ) + { + DragSourceDropEvent dsde; + dsde.Source = static_cast< OWeakObject* >(this); + dsde.DragSourceContext = new DragSourceContext( m_aDropWindow, *this ); + dsde.DragSource = static_cast< XDragSource* >(this); + dsde.DropAction = getUserDragAction(); + dsde.DropSuccess = bSuccess; + css::uno::Reference< XDragSourceListener > xListener = m_xDragSourceListener; + m_xDragSourceListener.clear(); + + aGuard.clear(); + xListener->dragDropEnd( dsde ); + } + else if( m_aDropEnterEvent.data.l[0] && m_aCurrentDropWindow ) + { + XEvent aEvent; + aEvent.xclient.type = ClientMessage; + aEvent.xclient.display = m_pDisplay; + aEvent.xclient.window = m_aDropEnterEvent.data.l[0]; + aEvent.xclient.message_type = m_nXdndFinished; + aEvent.xclient.format = 32; + aEvent.xclient.data.l[0] = m_aCurrentDropWindow; + aEvent.xclient.data.l[1] = bSuccess ? 1 : 0; + aEvent.xclient.data.l[2] = 0; + aEvent.xclient.data.l[3] = 0; + aEvent.xclient.data.l[4] = 0; + if( bSuccess ) + { + if( m_nLastDropAction & DNDConstants::ACTION_MOVE ) + aEvent.xclient.data.l[2] = m_nXdndActionMove; + else if( m_nLastDropAction & DNDConstants::ACTION_COPY ) + aEvent.xclient.data.l[2] = m_nXdndActionCopy; + else if( m_nLastDropAction & DNDConstants::ACTION_LINK ) + aEvent.xclient.data.l[2] = m_nXdndActionLink; + } +#if OSL_DEBUG_LEVEL > 1 + SAL_INFO("vcl.unx.dtrans", "Sending XdndFinished to " + << std::showbase << std::hex + << m_aDropEnterEvent.data.l[0]); +#endif + XSendEvent( m_pDisplay, m_aDropEnterEvent.data.l[0], + False, NoEventMask, & aEvent ); + + m_aDropEnterEvent.data.l[0] = None; + m_aCurrentDropWindow = None; + m_nCurrentProtocolVersion = nXdndProtocolRevision; + } + m_bDropWaitingForCompletion = false; + } + else + OSL_FAIL( "dropComplete from invalid DropTargetDropContext" ); +} + +/* + * methods for XDropTargetDragContext + */ + +void SelectionManager::sendDragStatus( Atom nDropAction ) +{ + osl::ClearableMutexGuard aGuard(m_aMutex); + + if( m_xDragSourceListener.is() ) + { + sal_Int8 nNewDragAction; + if( nDropAction == m_nXdndActionMove ) + nNewDragAction = DNDConstants::ACTION_MOVE; + else if( nDropAction == m_nXdndActionCopy ) + nNewDragAction = DNDConstants::ACTION_COPY; + else if( nDropAction == m_nXdndActionLink ) + nNewDragAction = DNDConstants::ACTION_LINK; + else + nNewDragAction = DNDConstants::ACTION_NONE; + nNewDragAction &= m_nSourceActions; + + if( nNewDragAction != m_nTargetAcceptAction ) + { + setCursor( getDefaultCursor( nNewDragAction ), m_aDropWindow ); + m_nTargetAcceptAction = nNewDragAction; + } + + DragSourceDragEvent dsde; + dsde.Source = static_cast< OWeakObject* >(this); + dsde.DragSourceContext = new DragSourceContext( m_aDropWindow, *this ); + dsde.DragSource = static_cast< XDragSource* >(this); + dsde.DropAction = m_nSourceActions; + dsde.UserAction = getUserDragAction(); + + css::uno::Reference< XDragSourceListener > xListener( m_xDragSourceListener ); + // caution: do not change anything after this + aGuard.clear(); + if( xListener.is() ) + xListener->dragOver( dsde ); + } + else if( m_aDropEnterEvent.data.l[0] && m_aCurrentDropWindow ) + { + XEvent aEvent; + aEvent.xclient.type = ClientMessage; + aEvent.xclient.display = m_pDisplay; + aEvent.xclient.window = m_aDropEnterEvent.data.l[0]; + aEvent.xclient.message_type = m_nXdndStatus; + aEvent.xclient.format = 32; + aEvent.xclient.data.l[0] = m_aCurrentDropWindow; + aEvent.xclient.data.l[1] = 2; + if( nDropAction == m_nXdndActionMove || + nDropAction == m_nXdndActionLink || + nDropAction == m_nXdndActionCopy ) + aEvent.xclient.data.l[1] |= 1; + aEvent.xclient.data.l[2] = 0; + aEvent.xclient.data.l[3] = 0; + aEvent.xclient.data.l[4] = m_nCurrentProtocolVersion > 1 ? nDropAction : 0; + +#if OSL_DEBUG_LEVEL > 1 + SAL_INFO("vcl.unx.dtrans", "Sending XdndStatus to " + << std::showbase << std::hex + << m_aDropEnterEvent.data.l[0] + << " with action " + << getString( nDropAction )); +#endif + + XSendEvent( m_pDisplay, m_aDropEnterEvent.data.l[0], + False, NoEventMask, & aEvent ); + XFlush( m_pDisplay ); + } +} + +sal_Int8 SelectionManager::getUserDragAction() const +{ + return (m_nTargetAcceptAction != DNDConstants::ACTION_DEFAULT) ? m_nTargetAcceptAction : m_nUserDragAction; +} + +bool SelectionManager::updateDragAction( int modifierState ) +{ + bool bRet = false; + + sal_Int8 nNewDropAction = DNDConstants::ACTION_MOVE; + if( ( modifierState & ShiftMask ) && ! ( modifierState & ControlMask ) ) + nNewDropAction = DNDConstants::ACTION_MOVE; + else if( ( modifierState & ControlMask ) && ! ( modifierState & ShiftMask ) ) + nNewDropAction = DNDConstants::ACTION_COPY; + else if( ( modifierState & ShiftMask ) && ( modifierState & ControlMask ) ) + nNewDropAction = DNDConstants::ACTION_LINK; + if( m_nCurrentProtocolVersion < 0 && m_aDropWindow != None ) + nNewDropAction = DNDConstants::ACTION_COPY; + nNewDropAction &= m_nSourceActions; + + if( ! ( modifierState & ( ControlMask | ShiftMask ) ) ) + { + if( ! nNewDropAction ) + { + // default to an action so the user does not have to press + // keys explicitly + if( m_nSourceActions & DNDConstants::ACTION_MOVE ) + nNewDropAction = DNDConstants::ACTION_MOVE; + else if( m_nSourceActions & DNDConstants::ACTION_COPY ) + nNewDropAction = DNDConstants::ACTION_COPY; + else if( m_nSourceActions & DNDConstants::ACTION_LINK ) + nNewDropAction = DNDConstants::ACTION_LINK; + } + nNewDropAction |= DNDConstants::ACTION_DEFAULT; + } + + if( nNewDropAction != m_nUserDragAction || m_nTargetAcceptAction != DNDConstants::ACTION_DEFAULT ) + { +#if OSL_DEBUG_LEVEL > 1 + SAL_INFO("vcl.unx.dtrans", "updateDragAction: " + << std::hex + << (int)m_nUserDragAction + << " -> " + << (int)nNewDropAction); +#endif + bRet = true; + m_nUserDragAction = nNewDropAction; + + DragSourceDragEvent dsde; + dsde.Source = static_cast< OWeakObject* >(this); + dsde.DragSourceContext = new DragSourceContext( m_aDropWindow, *this ); + dsde.DragSource = static_cast< XDragSource* >(this); + dsde.DropAction = m_nUserDragAction; + dsde.UserAction = m_nUserDragAction; + m_nTargetAcceptAction = DNDConstants::ACTION_DEFAULT; // invalidate last accept + m_xDragSourceListener->dropActionChanged( dsde ); + } + return bRet; +} + +void SelectionManager::sendDropPosition( bool bForce, Time eventTime ) +{ + osl::ClearableMutexGuard aGuard(m_aMutex); + + if( m_bDropSent ) + return; + + std::unordered_map< ::Window, DropTargetEntry >::const_iterator it = + m_aDropTargets.find( m_aDropWindow ); + if( it != m_aDropTargets.end() ) + { + if( it->second.m_pTarget->m_bActive ) + { + int x, y; + ::Window aChild; + XTranslateCoordinates( m_pDisplay, it->second.m_aRootWindow, m_aDropWindow, m_nLastDragX, m_nLastDragY, &x, &y, &aChild ); + DropTargetDragEvent dtde; + dtde.Source = static_cast< OWeakObject* >(it->second.m_pTarget ); + dtde.Context = new DropTargetDragContext( m_aCurrentDropWindow, *this ); + dtde.LocationX = x; + dtde.LocationY = y; + dtde.DropAction = getUserDragAction(); + dtde.SourceActions = m_nSourceActions; + aGuard.clear(); + it->second->dragOver( dtde ); + } + } + else if( bForce || + + m_nLastDragX < m_nNoPosX || m_nLastDragX >= m_nNoPosX+m_nNoPosWidth || + m_nLastDragY < m_nNoPosY || m_nLastDragY >= m_nNoPosY+m_nNoPosHeight + ) + { + // send XdndPosition + XEvent aEvent; + aEvent.type = ClientMessage; + aEvent.xclient.display = m_pDisplay; + aEvent.xclient.format = 32; + aEvent.xclient.message_type = m_nXdndPosition; + aEvent.xclient.window = m_aDropWindow; + aEvent.xclient.data.l[0] = m_aWindow; + aEvent.xclient.data.l[1] = 0; + aEvent.xclient.data.l[2] = m_nLastDragX << 16 | (m_nLastDragY&0xffff); + aEvent.xclient.data.l[3] = eventTime; + + if( m_nUserDragAction & DNDConstants::ACTION_COPY ) + aEvent.xclient.data.l[4]=m_nXdndActionCopy; + else if( m_nUserDragAction & DNDConstants::ACTION_MOVE ) + aEvent.xclient.data.l[4]=m_nXdndActionMove; + else if( m_nUserDragAction & DNDConstants::ACTION_LINK ) + aEvent.xclient.data.l[4]=m_nXdndActionLink; + else + aEvent.xclient.data.l[4]=m_nXdndActionCopy; + XSendEvent( m_pDisplay, m_aDropProxy, False, NoEventMask, &aEvent ); + m_nNoPosX = m_nNoPosY = m_nNoPosWidth = m_nNoPosHeight = 0; + } +} + +bool SelectionManager::handleDragEvent( XEvent const & rMessage ) +{ + if( ! m_xDragSourceListener.is() ) + return false; + + osl::ResettableMutexGuard aGuard(m_aMutex); + + bool bHandled = false; + + // for shortcut + std::unordered_map< ::Window, DropTargetEntry >::const_iterator it = + m_aDropTargets.find( m_aDropWindow ); + +#if OSL_DEBUG_LEVEL > 1 + switch( rMessage.type ) + { + case ClientMessage: + SAL_INFO("vcl.unx.dtrans", "handleDragEvent: " + << getString( rMessage.xclient.message_type )); + break; + case MotionNotify: + break; + case EnterNotify: + SAL_INFO("vcl.unx.dtrans", "handleDragEvent: EnterNotify."); + break; + case LeaveNotify: + SAL_INFO("vcl.unx.dtrans", "handleDragEvent: LeaveNotify."); + break; + case ButtonPress: + SAL_INFO("vcl.unx.dtrans", "handleDragEvent: ButtonPress " + << rMessage.xbutton.button + << " (m_nDragButton = " + << m_nDragButton + << ")."); + break; + case ButtonRelease: + SAL_INFO("vcl.unx.dtrans", "handleDragEvent: ButtonRelease " + << rMessage.xbutton.button + << " (m_nDragButton = " + << m_nDragButton + << ")."); + break; + case KeyPress: + SAL_INFO("vcl.unx.dtrans", "handleDragEvent: KeyPress."); + break; + case KeyRelease: + SAL_INFO("vcl.unx.dtrans", "handleDragEvent: KeyRelease."); + break; + default: + SAL_INFO("vcl.unx.dtrans", "handleDragEvent: <unknown type " + << rMessage.type + << ">."); + break; + } +#endif + + // handle drag related events + if( rMessage.type == ClientMessage ) + { + if( rMessage.xclient.message_type == m_nXdndStatus && Atom(rMessage.xclient.data.l[0]) == m_aDropWindow ) + { + bHandled = true; + DragSourceDragEvent dsde; + dsde.Source = static_cast< OWeakObject* >(this); + dsde.DragSourceContext = new DragSourceContext( m_aDropWindow, *this ); + dsde.DragSource = static_cast< XDragSource* >( this ); + dsde.UserAction = getUserDragAction(); + dsde.DropAction = DNDConstants::ACTION_NONE; + m_bDropSuccess = (rMessage.xclient.data.l[1] & 1) != 0; +#if OSL_DEBUG_LEVEL > 1 + SAL_INFO("vcl.unx.dtrans", "status drop action: accept = " + << (m_bDropSuccess ? "true" : "false") + << ", " + << getString( rMessage.xclient.data.l[4] )); +#endif + if( rMessage.xclient.data.l[1] & 1 ) + { + if( m_nCurrentProtocolVersion > 1 ) + { + if( Atom(rMessage.xclient.data.l[4]) == m_nXdndActionCopy ) + dsde.DropAction = DNDConstants::ACTION_COPY; + else if( Atom(rMessage.xclient.data.l[4]) == m_nXdndActionMove ) + dsde.DropAction = DNDConstants::ACTION_MOVE; + else if( Atom(rMessage.xclient.data.l[4]) == m_nXdndActionLink ) + dsde.DropAction = DNDConstants::ACTION_LINK; + } + else + dsde.DropAction = DNDConstants::ACTION_COPY; + } + m_nTargetAcceptAction = dsde.DropAction; + + if( ! ( rMessage.xclient.data.l[1] & 2 ) ) + { + m_nNoPosX = rMessage.xclient.data.l[2] >> 16; + m_nNoPosY = rMessage.xclient.data.l[2] & 0xffff; + m_nNoPosWidth = rMessage.xclient.data.l[3] >> 16; + m_nNoPosHeight = rMessage.xclient.data.l[3] & 0xffff; + } + else + m_nNoPosX = m_nNoPosY = m_nNoPosWidth = m_nNoPosHeight = 0; + + setCursor( getDefaultCursor( dsde.DropAction ), m_aDropWindow ); + aGuard.clear(); + m_xDragSourceListener->dragOver( dsde ); + } + else if( rMessage.xclient.message_type == m_nXdndFinished && m_aDropWindow == Atom(rMessage.xclient.data.l[0]) ) + { + bHandled = true; + // notify the listener + DragSourceDropEvent dsde; + dsde.Source = static_cast< OWeakObject* >(this); + dsde.DragSourceContext = new DragSourceContext( m_aDropWindow, *this ); + dsde.DragSource = static_cast< XDragSource* >(this); + dsde.DropAction = m_nTargetAcceptAction; + dsde.DropSuccess = m_bDropSuccess; + css::uno::Reference< XDragSourceListener > xListener( m_xDragSourceListener ); + m_xDragSourceListener.clear(); + aGuard.clear(); + xListener->dragDropEnd( dsde ); + } + } + else if( rMessage.type == MotionNotify || + rMessage.type == EnterNotify || rMessage.type == LeaveNotify + ) + { + bHandled = true; + bool bForce = false; + int root_x = rMessage.type == MotionNotify ? rMessage.xmotion.x_root : rMessage.xcrossing.x_root; + int root_y = rMessage.type == MotionNotify ? rMessage.xmotion.y_root : rMessage.xcrossing.y_root; + ::Window root = rMessage.type == MotionNotify ? rMessage.xmotion.root : rMessage.xcrossing.root; + + aGuard.clear(); + if( rMessage.type == MotionNotify ) + { + bForce = updateDragAction( rMessage.xmotion.state ); + } + updateDragWindow( root_x, root_y, root ); + aGuard.reset(); + + if( m_nCurrentProtocolVersion >= 0 && m_aDropProxy != None ) + { + aGuard.clear(); + sendDropPosition( bForce, rMessage.type == MotionNotify ? rMessage.xmotion.time : rMessage.xcrossing.time ); + } + } + else if( rMessage.type == KeyPress || rMessage.type == KeyRelease ) + { + bHandled = true; + KeySym aKey = XkbKeycodeToKeysym( m_pDisplay, rMessage.xkey.keycode, 0, 0 ); + if( aKey == XK_Escape ) + { + // abort drag + if( it != m_aDropTargets.end() ) + { + DropTargetEvent dte; + dte.Source = static_cast< OWeakObject* >( it->second.m_pTarget ); + aGuard.clear(); + it->second.m_pTarget->dragExit( dte ); + aGuard.reset(); + } + else if( m_aDropProxy != None && m_nCurrentProtocolVersion >= 0 ) + { + // send XdndLeave + XEvent aEvent; + aEvent.type = ClientMessage; + aEvent.xclient.display = m_pDisplay; + aEvent.xclient.format = 32; + aEvent.xclient.message_type = m_nXdndLeave; + aEvent.xclient.window = m_aDropWindow; + aEvent.xclient.data.l[0] = m_aWindow; + memset( aEvent.xclient.data.l+1, 0, sizeof(long)*4); + m_aDropWindow = m_aDropProxy = None; + XSendEvent( m_pDisplay, m_aDropProxy, False, NoEventMask, &aEvent ); + } + // notify the listener + DragSourceDropEvent dsde; + dsde.Source = static_cast< OWeakObject* >(this); + dsde.DragSourceContext = new DragSourceContext( m_aDropWindow, *this ); + dsde.DragSource = static_cast< XDragSource* >(this); + dsde.DropAction = DNDConstants::ACTION_NONE; + dsde.DropSuccess = false; + css::uno::Reference< XDragSourceListener > xListener( m_xDragSourceListener ); + m_xDragSourceListener.clear(); + aGuard.clear(); + xListener->dragDropEnd( dsde ); + } + else + { + /* + * man page says: state is state immediate PRIOR to the + * event. It would seem that this is a somewhat arguable + * design decision. + */ + int nState = rMessage.xkey.state; + int nNewState = 0; + switch( aKey ) + { + case XK_Shift_R: + case XK_Shift_L: nNewState = ShiftMask;break; + case XK_Control_R: + case XK_Control_L: nNewState = ControlMask;break; + // just interested in shift and ctrl for dnd + } + if( rMessage.type == KeyPress ) + nState |= nNewState; + else + nState &= ~nNewState; + aGuard.clear(); + if( updateDragAction( nState ) ) + sendDropPosition( true, rMessage.xkey.time ); + } + } + else if( + ( rMessage.type == ButtonPress || rMessage.type == ButtonRelease ) && + rMessage.xbutton.button == m_nDragButton ) + { + bool bCancel = true; + if( m_aDropWindow != None ) + { + if( it != m_aDropTargets.end() ) + { + if( it->second.m_pTarget->m_bActive && m_nUserDragAction != DNDConstants::ACTION_NONE && m_bLastDropAccepted ) + { + bHandled = true; + int x, y; + ::Window aChild; + XTranslateCoordinates( m_pDisplay, rMessage.xbutton.root, m_aDropWindow, rMessage.xbutton.x_root, rMessage.xbutton.y_root, &x, &y, &aChild ); + DropTargetDropEvent dtde; + dtde.Source = static_cast< OWeakObject* >(it->second.m_pTarget ); + dtde.Context = new DropTargetDropContext( m_aCurrentDropWindow, *this ); + dtde.LocationX = x; + dtde.LocationY = y; + dtde.DropAction = m_nUserDragAction; + dtde.SourceActions = m_nSourceActions; + dtde.Transferable = m_xDragSourceTransferable; + m_bDropSent = true; + m_nDropTimeout = time( nullptr ); + m_bDropWaitingForCompletion = true; + aGuard.clear(); + it->second->drop( dtde ); + bCancel = false; + } + else bCancel = true; + } + else if( m_nCurrentProtocolVersion >= 0 ) + { + bHandled = true; + + XEvent aEvent; + aEvent.type = ClientMessage; + aEvent.xclient.display = m_pDisplay; + aEvent.xclient.format = 32; + aEvent.xclient.message_type = m_nXdndDrop; + aEvent.xclient.window = m_aDropWindow; + aEvent.xclient.data.l[0] = m_aWindow; + aEvent.xclient.data.l[1] = 0; + aEvent.xclient.data.l[2] = rMessage.xbutton.time; + aEvent.xclient.data.l[3] = 0; + aEvent.xclient.data.l[4] = 0; + + m_bDropSent = true; + m_nDropTimeout = time( nullptr ); + XSendEvent( m_pDisplay, m_aDropProxy, False, NoEventMask, &aEvent ); + bCancel = false; + } + else + { + // dropping on non XdndWindows: acquire ownership of + // PRIMARY and send a middle mouse button click down/up to + // target window + SelectionAdaptor* pAdaptor = getAdaptor( XA_PRIMARY ); + if( pAdaptor ) + { + bHandled = true; + + ::Window aDummy; + XEvent aEvent; + aEvent.type = ButtonPress; + aEvent.xbutton.display = m_pDisplay; + aEvent.xbutton.window = m_aDropWindow; + aEvent.xbutton.root = rMessage.xbutton.root; + aEvent.xbutton.subwindow = m_aDropWindow; + aEvent.xbutton.time = rMessage.xbutton.time+1; + aEvent.xbutton.x_root = rMessage.xbutton.x_root; + aEvent.xbutton.y_root = rMessage.xbutton.y_root; + aEvent.xbutton.state = rMessage.xbutton.state; + aEvent.xbutton.button = Button2; + aEvent.xbutton.same_screen = True; + XTranslateCoordinates( m_pDisplay, + rMessage.xbutton.root, m_aDropWindow, + rMessage.xbutton.x_root, rMessage.xbutton.y_root, + &aEvent.xbutton.x, &aEvent.xbutton.y, + &aDummy ); + XSendEvent( m_pDisplay, m_aDropWindow, False, ButtonPressMask, &aEvent ); + aEvent.xbutton.type = ButtonRelease; + aEvent.xbutton.time++; + aEvent.xbutton.state |= Button2Mask; + XSendEvent( m_pDisplay, m_aDropWindow, False, ButtonReleaseMask, &aEvent ); + + m_bDropSent = true; + m_nDropTimeout = time( nullptr ); + XSendEvent( m_pDisplay, m_aDropProxy, False, NoEventMask, &aEvent ); + m_bWaitingForPrimaryConversion = true; + m_bDropSent = true; + m_nDropTimeout = time( nullptr ); + // HACK :-) + aGuard.clear(); + static_cast< X11Clipboard* >( pAdaptor )->setContents( m_xDragSourceTransferable, css::uno::Reference< css::datatransfer::clipboard::XClipboardOwner >() ); + aGuard.reset(); + bCancel = false; + } + } + } + if( bCancel ) + { + // cancel drag + DragSourceDropEvent dsde; + dsde.Source = static_cast< OWeakObject* >(this); + dsde.DragSourceContext = new DragSourceContext( m_aDropWindow, *this ); + dsde.DragSource = static_cast< XDragSource* >(this); + dsde.DropAction = DNDConstants::ACTION_NONE; + dsde.DropSuccess = false; + css::uno::Reference< XDragSourceListener > xListener( m_xDragSourceListener ); + m_xDragSourceListener.clear(); + aGuard.clear(); + xListener->dragDropEnd( dsde ); + bHandled = true; + } + } + return bHandled; +} + +void SelectionManager::accept( sal_Int8 dragOperation, ::Window aDropWindow ) +{ + if( aDropWindow != m_aCurrentDropWindow ) + return; + +#if OSL_DEBUG_LEVEL > 1 + SAL_INFO("vcl.unx.dtrans", "accept: " << std::hex << dragOperation); +#endif + Atom nAction = None; + dragOperation &= (DNDConstants::ACTION_MOVE | DNDConstants::ACTION_COPY | DNDConstants::ACTION_LINK); + if( dragOperation & DNDConstants::ACTION_MOVE ) + nAction = m_nXdndActionMove; + else if( dragOperation & DNDConstants::ACTION_COPY ) + nAction = m_nXdndActionCopy; + else if( dragOperation & DNDConstants::ACTION_LINK ) + nAction = m_nXdndActionLink; + m_bLastDropAccepted = true; + sendDragStatus( nAction ); +} + +void SelectionManager::reject( ::Window aDropWindow ) +{ + if( aDropWindow != m_aCurrentDropWindow ) + return; + +#if OSL_DEBUG_LEVEL > 1 + SAL_INFO("vcl.unx.dtrans", "reject."); +#endif + m_bLastDropAccepted = false; + sendDragStatus( None ); + if( m_bDropSent && m_xDragSourceListener.is() ) + { + DragSourceDropEvent dsde; + dsde.Source = static_cast< OWeakObject* >(this); + dsde.DragSourceContext = new DragSourceContext( m_aDropWindow, *this ); + dsde.DragSource = static_cast< XDragSource* >(this); + dsde.DropAction = DNDConstants::ACTION_NONE; + dsde.DropSuccess = false; + m_xDragSourceListener->dragDropEnd( dsde ); + m_xDragSourceListener.clear(); + } +} + +/* + * XDragSource + */ + +sal_Bool SelectionManager::isDragImageSupported() +{ + return false; +} + +sal_Int32 SelectionManager::getDefaultCursor( sal_Int8 dragAction ) +{ + Cursor aCursor = m_aNoneCursor; + if( dragAction & DNDConstants::ACTION_MOVE ) + aCursor = m_aMoveCursor; + else if( dragAction & DNDConstants::ACTION_COPY ) + aCursor = m_aCopyCursor; + else if( dragAction & DNDConstants::ACTION_LINK ) + aCursor = m_aLinkCursor; + return aCursor; +} + +int SelectionManager::getXdndVersion( ::Window aWindow, ::Window& rProxy ) +{ + Atom* pProperties = nullptr; + int nProperties = 0; + Atom nType; + int nFormat; + unsigned long nItems, nBytes; + unsigned char* pBytes = nullptr; + + int nVersion = -1; + rProxy = None; + + /* + * XListProperties is used here to avoid unnecessary XGetWindowProperty calls + * and therefore reducing latency penalty + */ + pProperties = XListProperties( m_pDisplay, aWindow, &nProperties ); + // first look for proxy + int i; + for( i = 0; i < nProperties; i++ ) + { + if( pProperties[i] == m_nXdndProxy ) + { + XGetWindowProperty( m_pDisplay, aWindow, m_nXdndProxy, 0, 1, False, XA_WINDOW, + &nType, &nFormat, &nItems, &nBytes, &pBytes ); + if( pBytes ) + { + if( nType == XA_WINDOW ) + rProxy = *reinterpret_cast< ::Window* >(pBytes); + XFree( pBytes ); + pBytes = nullptr; + if( rProxy != None ) + { + // now check proxy whether it points to itself + XGetWindowProperty( m_pDisplay, rProxy, m_nXdndProxy, 0, 1, False, XA_WINDOW, + &nType, &nFormat, &nItems, &nBytes, &pBytes ); + if( pBytes ) + { + if( nType == XA_WINDOW && *reinterpret_cast< ::Window* >(pBytes) != rProxy ) + rProxy = None; + XFree( pBytes ); + pBytes = nullptr; + } + else + rProxy = None; + } + } + break; + } + } + if ( pProperties ) + XFree (pProperties); + + ::Window aAwareWindow = rProxy != None ? rProxy : aWindow; + + XGetWindowProperty( m_pDisplay, aAwareWindow, m_nXdndAware, 0, 1, False, XA_ATOM, + &nType, &nFormat, &nItems, &nBytes, &pBytes ); + if( pBytes ) + { + if( nType == XA_ATOM ) + nVersion = *reinterpret_cast<Atom*>(pBytes); + XFree( pBytes ); + } + + nVersion = std::min<int>(nVersion, nXdndProtocolRevision); + + return nVersion; +} + +void SelectionManager::updateDragWindow( int nX, int nY, ::Window aRoot ) +{ + osl::ResettableMutexGuard aGuard( m_aMutex ); + + css::uno::Reference< XDragSourceListener > xListener( m_xDragSourceListener ); + + m_nLastDragX = nX; + m_nLastDragY = nY; + + ::Window aParent = aRoot; + ::Window aChild; + ::Window aNewProxy = None, aNewCurrentWindow = None; + int nNewProtocolVersion = -1; + int nWinX, nWinY; + + // find the first XdndAware window or check if root window is + // XdndAware or has XdndProxy + do + { + XTranslateCoordinates( m_pDisplay, aRoot, aParent, nX, nY, &nWinX, &nWinY, &aChild ); + if( aChild != None ) + { + if( aChild == m_aCurrentDropWindow && aChild != aRoot && m_nCurrentProtocolVersion >= 0 ) + { + aParent = aChild; + break; + } + nNewProtocolVersion = getXdndVersion( aChild, aNewProxy ); + aParent = aChild; + } + } while( aChild != None && nNewProtocolVersion < 0 ); + + aNewCurrentWindow = aParent; + if( aNewCurrentWindow == aRoot ) + { + // no children, try root drop + nNewProtocolVersion = getXdndVersion( aNewCurrentWindow, aNewProxy ); + if( nNewProtocolVersion < 3 ) + { + aNewCurrentWindow = aNewProxy = None; + nNewProtocolVersion = nXdndProtocolRevision; + } + } + + DragSourceDragEvent dsde; + dsde.Source = static_cast< OWeakObject* >(this); + dsde.DragSourceContext = new DragSourceContext( m_aDropWindow, *this ); + dsde.DragSource = static_cast< XDragSource* >(this); + dsde.DropAction = nNewProtocolVersion >= 0 ? m_nUserDragAction : DNDConstants::ACTION_COPY; + dsde.UserAction = nNewProtocolVersion >= 0 ? m_nUserDragAction : DNDConstants::ACTION_COPY; + + std::unordered_map< ::Window, DropTargetEntry >::const_iterator it; + if( aNewCurrentWindow != m_aDropWindow ) + { +#if OSL_DEBUG_LEVEL > 1 + SAL_INFO("vcl.unx.dtrans", "drag left window " + << std::showbase << std::hex + << m_aDropWindow + << std::dec + << " (rev. " + << m_nCurrentProtocolVersion + << "), entered window " + << std::showbase << std::hex + << aNewCurrentWindow + << " (rev " + << std::dec + << nNewProtocolVersion + << ")."); +#endif + if( m_aDropWindow != None ) + { + it = m_aDropTargets.find( m_aDropWindow ); + if( it != m_aDropTargets.end() ) + // shortcut for own drop targets + { + DropTargetEvent dte; + dte.Source = static_cast< OWeakObject* >( it->second.m_pTarget ); + aGuard.clear(); + it->second.m_pTarget->dragExit( dte ); + aGuard.reset(); + } + else + { + // send old drop target a XdndLeave + XEvent aEvent; + aEvent.type = ClientMessage; + aEvent.xclient.display = m_pDisplay; + aEvent.xclient.format = 32; + aEvent.xclient.message_type = m_nXdndLeave; + aEvent.xclient.window = m_aDropWindow; + aEvent.xclient.data.l[0] = m_aWindow; + aEvent.xclient.data.l[1] = 0; + XSendEvent( m_pDisplay, m_aDropProxy, False, NoEventMask, &aEvent ); + } + if( xListener.is() ) + { + aGuard.clear(); + xListener->dragExit( dsde ); + aGuard.reset(); + } + } + + m_nCurrentProtocolVersion = nNewProtocolVersion; + m_aDropWindow = aNewCurrentWindow; + m_aDropProxy = aNewProxy != None ? aNewProxy : m_aDropWindow; + + it = m_aDropTargets.find( m_aDropWindow ); + if( it != m_aDropTargets.end() && ! it->second.m_pTarget->m_bActive ) + m_aDropProxy = None; + + if( m_aDropProxy != None && xListener.is() ) + { + aGuard.clear(); + xListener->dragEnter( dsde ); + aGuard.reset(); + } + // send XdndEnter + if( m_aDropProxy != None && m_nCurrentProtocolVersion >= 0 ) + { + it = m_aDropTargets.find( m_aDropWindow ); + if( it != m_aDropTargets.end() ) + { + XTranslateCoordinates( m_pDisplay, aRoot, m_aDropWindow, nX, nY, &nWinX, &nWinY, &aChild ); + DropTargetDragEnterEvent dtde; + dtde.Source = static_cast< OWeakObject* >( it->second.m_pTarget ); + dtde.Context = new DropTargetDragContext( m_aCurrentDropWindow, *this ); + dtde.LocationX = nWinX; + dtde.LocationY = nWinY; + dtde.DropAction = m_nUserDragAction; + dtde.SourceActions = m_nSourceActions; + dtde.SupportedDataFlavors = m_xDragSourceTransferable->getTransferDataFlavors(); + aGuard.clear(); + it->second.m_pTarget->dragEnter( dtde ); + aGuard.reset(); + } + else + { + XEvent aEvent; + aEvent.type = ClientMessage; + aEvent.xclient.display = m_pDisplay; + aEvent.xclient.format = 32; + aEvent.xclient.message_type = m_nXdndEnter; + aEvent.xclient.window = m_aDropWindow; + aEvent.xclient.data.l[0] = m_aWindow; + aEvent.xclient.data.l[1] = m_nCurrentProtocolVersion << 24; + memset( aEvent.xclient.data.l + 2, 0, sizeof( long )*3 ); + // fill in data types + ::std::list< Atom > aConversions; + getNativeTypeList( m_aDragFlavors, aConversions, m_nXdndSelection ); + if( aConversions.size() > 3 ) + aEvent.xclient.data.l[1] |= 1; + ::std::list< Atom >::const_iterator type_it = aConversions.begin(); + for( int i = 0; type_it != aConversions.end() && i < 3; i++, ++type_it ) + aEvent.xclient.data.l[i+2] = *type_it; + XSendEvent( m_pDisplay, m_aDropProxy, False, NoEventMask, &aEvent ); + } + } + m_nNoPosX = m_nNoPosY = m_nNoPosWidth = m_nNoPosHeight = 0; + } + else if( m_aDropProxy != None && xListener.is() ) + { + aGuard.clear(); + // drag over for XdndAware windows comes when receiving XdndStatus + xListener->dragOver( dsde ); + } +} + +void SelectionManager::startDrag( + const DragGestureEvent& trigger, + sal_Int8 sourceActions, + sal_Int32, + sal_Int32, + const css::uno::Reference< XTransferable >& transferable, + const css::uno::Reference< XDragSourceListener >& listener + ) +{ +#if OSL_DEBUG_LEVEL > 1 + SAL_INFO("vcl.unx.dtrans", "startDrag( sourceActions = " + << std::hex + << (int)sourceActions + << " )."); +#endif + DragSourceDropEvent aDragFailedEvent; + aDragFailedEvent.Source = static_cast< OWeakObject* >(this); + aDragFailedEvent.DragSource = static_cast< XDragSource* >(this); + aDragFailedEvent.DragSourceContext = new DragSourceContext( None, *this ); + aDragFailedEvent.DropAction = DNDConstants::ACTION_NONE; + aDragFailedEvent.DropSuccess = false; + + if( m_aDragRunning.check() ) + { + if( listener.is() ) + listener->dragDropEnd( aDragFailedEvent ); + +#if OSL_DEBUG_LEVEL > 1 + SAL_WARN("vcl.unx.dtrans", + "*** ERROR *** second drag and drop started."); + if( m_xDragSourceListener.is() ) + SAL_WARN("vcl.unx.dtrans", + "*** ERROR *** drag source listener already set."); + else + SAL_WARN("vcl.unx.dtrans", + "*** ERROR *** drag thread already running."); +#endif + return; + } + + SalFrame* pCaptureFrame = nullptr; + + { + osl::ClearableMutexGuard aGuard(m_aMutex); + + // first get the current pointer position and the window that + // the pointer is located in. since said window should be one + // of our DropTargets at the time of executeDrag we can use + // them for a start + ::Window aRoot, aParent, aChild; + int root_x(0), root_y(0), win_x(0), win_y(0); + unsigned int mask(0); + + bool bPointerFound = false; + for (auto const& dropTarget : m_aDropTargets) + { + if( XQueryPointer( m_pDisplay, dropTarget.second.m_aRootWindow, + &aRoot, &aParent, + &root_x, &root_y, + &win_x, &win_y, + &mask ) ) + { + aParent = dropTarget.second.m_aRootWindow; + aRoot = aParent; + bPointerFound = true; + break; + } + } + + // don't start DnD if there is none of our windows on the same screen as + // the pointer or if no mouse button is pressed + if( !bPointerFound || (mask & (Button1Mask|Button2Mask|Button3Mask)) == 0 ) + { + aGuard.clear(); + if( listener.is() ) + listener->dragDropEnd( aDragFailedEvent ); + return; + } + + // try to find which of our drop targets is the drag source + // if that drop target is deregistered we should stop executing + // the drag (actually this is a poor substitute for an "endDrag" + // method ). + m_aDragSourceWindow = None; + do + { + XTranslateCoordinates( m_pDisplay, aRoot, aParent, root_x, root_y, &win_x, &win_y, &aChild ); + if( aChild != None && m_aDropTargets.find( aChild ) != m_aDropTargets.end() ) + { + m_aDragSourceWindow = aChild; +#if OSL_DEBUG_LEVEL > 1 + SAL_INFO("vcl.unx.dtrans", "found drag source window " + << std::showbase << std::hex + << m_aDragSourceWindow); +#endif + break; + } + aParent = aChild; + } while( aChild != None ); + +#if OSL_DEBUG_LEVEL > 1 + SAL_INFO("vcl.unx.dtrans", "try to grab pointer ..."); +#endif + int nPointerGrabSuccess = + XGrabPointer( m_pDisplay, aRoot, True, + DRAG_EVENT_MASK, + GrabModeAsync, GrabModeAsync, + None, + None, + CurrentTime ); + /* if we could not grab the pointer here, there is a chance + that the pointer is grabbed by the other vcl display (the main loop) + so let's break that grab and reset it later + + remark: this whole code should really be molten into normal vcl so only + one display is used... + */ + if( nPointerGrabSuccess != GrabSuccess ) + { + comphelper::SolarMutex& rSolarMutex( Application::GetSolarMutex() ); + if( rSolarMutex.tryToAcquire() ) + { + pCaptureFrame = vcl_sal::getSalDisplay(GetGenericUnixSalData())->GetCaptureFrame(); + if( pCaptureFrame ) + { + vcl_sal::getSalDisplay(GetGenericUnixSalData())->CaptureMouse( nullptr ); + nPointerGrabSuccess = + XGrabPointer( m_pDisplay, aRoot, True, + DRAG_EVENT_MASK, + GrabModeAsync, GrabModeAsync, + None, + None, + CurrentTime ); + } + } + } +#if OSL_DEBUG_LEVEL > 1 + SAL_INFO("vcl.unx.dtrans", "... grabbed pointer: " + << nPointerGrabSuccess); + SAL_INFO("vcl.unx.dtrans", "try to grab keyboard ..."); +#endif + int nKeyboardGrabSuccess = + XGrabKeyboard( m_pDisplay, aRoot, True, + GrabModeAsync, GrabModeAsync, CurrentTime ); +#if OSL_DEBUG_LEVEL > 1 + SAL_INFO("vcl.unx.dtrans", "... grabbed keyboard: " + << nKeyboardGrabSuccess); +#endif + if( nPointerGrabSuccess != GrabSuccess || nKeyboardGrabSuccess != GrabSuccess ) + { + if( nPointerGrabSuccess == GrabSuccess ) + XUngrabPointer( m_pDisplay, CurrentTime ); + if( nKeyboardGrabSuccess == GrabSuccess ) + XUngrabKeyboard( m_pDisplay, CurrentTime ); + XFlush( m_pDisplay ); + aGuard.clear(); + if( listener.is() ) + listener->dragDropEnd( aDragFailedEvent ); + if( pCaptureFrame ) + { + comphelper::SolarMutex& rSolarMutex( Application::GetSolarMutex() ); + if( rSolarMutex.tryToAcquire() ) + vcl_sal::getSalDisplay(GetGenericUnixSalData())->CaptureMouse( pCaptureFrame ); +#if OSL_DEBUG_LEVEL > 0 + else + OSL_FAIL( "failed to acquire SolarMutex to reset capture frame" ); +#endif + } + return; + } + + m_xDragSourceTransferable = transferable; + m_xDragSourceListener = listener; + m_aDragFlavors = transferable->getTransferDataFlavors(); + m_aCurrentCursor = None; + + requestOwnership( m_nXdndSelection ); + + ::std::list< Atom > aConversions; + getNativeTypeList( m_aDragFlavors, aConversions, m_nXdndSelection ); + + Atom* pTypes = static_cast<Atom*>(alloca( sizeof(Atom)*aConversions.size() )); + int nTypes = 0; + for (auto const& conversion : aConversions) + pTypes[nTypes++] = conversion; + + XChangeProperty( m_pDisplay, m_aWindow, m_nXdndTypeList, XA_ATOM, 32, PropModeReplace, reinterpret_cast<unsigned char*>(pTypes), nTypes ); + + m_nSourceActions = sourceActions | DNDConstants::ACTION_DEFAULT; + m_nUserDragAction = DNDConstants::ACTION_MOVE & m_nSourceActions; + if( ! m_nUserDragAction ) + m_nUserDragAction = DNDConstants::ACTION_COPY & m_nSourceActions; + if( ! m_nUserDragAction ) + m_nUserDragAction = DNDConstants::ACTION_LINK & m_nSourceActions; + m_nTargetAcceptAction = DNDConstants::ACTION_DEFAULT; + m_bDropSent = false; + m_bDropSuccess = false; + m_bWaitingForPrimaryConversion = false; + m_nDragButton = Button1; // default to left button + css::awt::MouseEvent aEvent; + if( trigger.Event >>= aEvent ) + { + if( aEvent.Buttons & MouseButton::LEFT ) + m_nDragButton = Button1; + else if( aEvent.Buttons & MouseButton::RIGHT ) + m_nDragButton = Button3; + else if( aEvent.Buttons & MouseButton::MIDDLE ) + m_nDragButton = Button2; + } +#if OSL_DEBUG_LEVEL > 1 + SAL_INFO("vcl.unx.dtrans", "m_nUserDragAction = " + << std::hex + << (int)m_nUserDragAction); +#endif + updateDragWindow( root_x, root_y, aRoot ); + m_nUserDragAction = ~0; + updateDragAction( mask ); + } + + m_aDragRunning.set(); + m_aDragExecuteThread = osl_createSuspendedThread( call_SelectionManager_runDragExecute, this ); + if( m_aDragExecuteThread ) + osl_resumeThread( m_aDragExecuteThread ); + else + { +#if OSL_DEBUG_LEVEL > 1 + SAL_INFO("vcl.unx.dtrans", "osl_createSuspendedThread failed " + << "for drag execute."); +#endif + m_xDragSourceListener.clear(); + m_xDragSourceTransferable.clear(); + + m_bDropSent = false; + m_bDropSuccess = false; + m_bWaitingForPrimaryConversion = false; + m_aDropWindow = None; + m_aDropProxy = None; + m_nCurrentProtocolVersion = nXdndProtocolRevision; + m_nNoPosX = 0; + m_nNoPosY = 0; + m_nNoPosWidth = 0; + m_nNoPosHeight = 0; + m_aCurrentCursor = None; + + XUngrabPointer( m_pDisplay, CurrentTime ); + XUngrabKeyboard( m_pDisplay, CurrentTime ); + XFlush( m_pDisplay ); + + if( pCaptureFrame ) + { + comphelper::SolarMutex& rSolarMutex( Application::GetSolarMutex() ); + if( rSolarMutex.tryToAcquire() ) + vcl_sal::getSalDisplay(GetGenericUnixSalData())->CaptureMouse( pCaptureFrame ); +#if OSL_DEBUG_LEVEL > 0 + else + OSL_FAIL( "failed to acquire SolarMutex to reset capture frame" ); +#endif + } + + m_aDragRunning.reset(); + + if( listener.is() ) + listener->dragDropEnd( aDragFailedEvent ); + } +} + +void SelectionManager::runDragExecute( void* pThis ) +{ + SelectionManager* This = static_cast<SelectionManager*>(pThis); + This->dragDoDispatch(); +} + +void SelectionManager::dragDoDispatch() +{ + + // do drag + // m_xDragSourceListener will be cleared on finished drop +#if OSL_DEBUG_LEVEL > 1 + SAL_INFO("vcl.unx.dtrans", "begin executeDrag dispatching."); +#endif + oslThread aThread = m_aDragExecuteThread; + while( m_xDragSourceListener.is() && ( ! m_bDropSent || time(nullptr)-m_nDropTimeout < 5 ) && osl_scheduleThread( aThread ) ) + { + // let the thread in the run method do the dispatching + // just look occasionally here whether drop timed out or is completed + osl::Thread::wait(std::chrono::milliseconds(200)); + } +#if OSL_DEBUG_LEVEL > 1 + SAL_INFO("vcl.unx.dtrans", "end executeDrag dispatching."); +#endif + { + osl::ClearableMutexGuard aGuard(m_aMutex); + + css::uno::Reference< XDragSourceListener > xListener( m_xDragSourceListener ); + css::uno::Reference< XTransferable > xTransferable( m_xDragSourceTransferable ); + m_xDragSourceListener.clear(); + m_xDragSourceTransferable.clear(); + + DragSourceDropEvent dsde; + dsde.Source = static_cast< OWeakObject* >(this); + dsde.DragSourceContext = new DragSourceContext( m_aDropWindow, *this ); + dsde.DragSource = static_cast< XDragSource* >(this); + dsde.DropAction = DNDConstants::ACTION_NONE; + dsde.DropSuccess = false; + + // cleanup after drag + if( m_bWaitingForPrimaryConversion ) + { + SelectionAdaptor* pAdaptor = getAdaptor( XA_PRIMARY ); + if (pAdaptor) + pAdaptor->clearTransferable(); + } + + m_bDropSent = false; + m_bDropSuccess = false; + m_bWaitingForPrimaryConversion = false; + m_aDropWindow = None; + m_aDropProxy = None; + m_nCurrentProtocolVersion = nXdndProtocolRevision; + m_nNoPosX = 0; + m_nNoPosY = 0; + m_nNoPosWidth = 0; + m_nNoPosHeight = 0; + m_aCurrentCursor = None; + + XUngrabPointer( m_pDisplay, CurrentTime ); + XUngrabKeyboard( m_pDisplay, CurrentTime ); + XFlush( m_pDisplay ); + + m_aDragExecuteThread = nullptr; + m_aDragRunning.reset(); + + aGuard.clear(); + if( xListener.is() ) + { + xTransferable.clear(); + xListener->dragDropEnd( dsde ); + } + } + osl_destroyThread( aThread ); +} + +/* + * XDragSourceContext + */ + + +void SelectionManager::setCursor( sal_Int32 cursor, ::Window aDropWindow ) +{ + osl::MutexGuard aGuard( m_aMutex ); + if( aDropWindow == m_aDropWindow && Cursor(cursor) != m_aCurrentCursor ) + { + if( m_xDragSourceListener.is() && ! m_bDropSent ) + { + m_aCurrentCursor = cursor; + XChangeActivePointerGrab( m_pDisplay, DRAG_EVENT_MASK, cursor, CurrentTime ); + XFlush( m_pDisplay ); + } + } +} + +void SelectionManager::transferablesFlavorsChanged() +{ + osl::MutexGuard aGuard(m_aMutex); + + m_aDragFlavors = m_xDragSourceTransferable->getTransferDataFlavors(); + + std::list< Atom > aConversions; + + getNativeTypeList( m_aDragFlavors, aConversions, m_nXdndSelection ); + + Atom* pTypes = static_cast<Atom*>(alloca( sizeof(Atom)*aConversions.size() )); + int nTypes = 0; + for (auto const& conversion : aConversions) + pTypes[nTypes++] = conversion; + XChangeProperty( m_pDisplay, m_aWindow, m_nXdndTypeList, XA_ATOM, 32, PropModeReplace, reinterpret_cast<unsigned char*>(pTypes), nTypes ); + + if( m_aCurrentDropWindow == None || m_nCurrentProtocolVersion < 0 ) + return; + + // send synthetic leave and enter events + + XEvent aEvent; + + aEvent.type = ClientMessage; + aEvent.xclient.display = m_pDisplay; + aEvent.xclient.format = 32; + aEvent.xclient.window = m_aDropWindow; + aEvent.xclient.data.l[0] = m_aWindow; + + aEvent.xclient.message_type = m_nXdndLeave; + aEvent.xclient.data.l[1] = 0; + XSendEvent( m_pDisplay, m_aDropProxy, False, NoEventMask, &aEvent ); + + aEvent.xclient.message_type = m_nXdndEnter; + aEvent.xclient.data.l[1] = m_nCurrentProtocolVersion << 24; + memset( aEvent.xclient.data.l + 2, 0, sizeof( long )*3 ); + // fill in data types + if( nTypes > 3 ) + aEvent.xclient.data.l[1] |= 1; + for( int j = 0; j < nTypes && j < 3; j++ ) + aEvent.xclient.data.l[j+2] = pTypes[j]; + + XSendEvent( m_pDisplay, m_aDropProxy, False, NoEventMask, &aEvent ); + +} + +/* + * dispatch loop + */ + +bool SelectionManager::handleXEvent( XEvent& rEvent ) +{ + /* + * since we are XConnectionListener to a second X display + * to get client messages it is essential not to dispatch + * events twice that we get on both connections + * + * between dispatching ButtonPress and startDrag + * the user can already have released the mouse. The ButtonRelease + * will then be dispatched in VCLs queue and never turn up here. + * Which is not so good, since startDrag will XGrabPointer and + * XGrabKeyboard -> solid lock. + */ + if( rEvent.xany.display != m_pDisplay + && rEvent.type != ClientMessage + && rEvent.type != ButtonPress + && rEvent.type != ButtonRelease + ) + return false; + + bool bHandled = false; + switch (rEvent.type) + { + case SelectionClear: + { + osl::ClearableMutexGuard aGuard(m_aMutex); +#if OSL_DEBUG_LEVEL > 1 + SAL_INFO("vcl.unx.dtrans", "SelectionClear for selection " + << getString( rEvent.xselectionclear.selection )); +#endif + SelectionAdaptor* pAdaptor = getAdaptor( rEvent.xselectionclear.selection ); + std::unordered_map< Atom, Selection* >::iterator it( m_aSelections.find( rEvent.xselectionclear.selection ) ); + if( it != m_aSelections.end() ) + it->second->m_bOwner = false; + aGuard.clear(); + if ( pAdaptor ) + pAdaptor->clearTransferable(); + } + break; + + case SelectionRequest: + bHandled = handleSelectionRequest( rEvent.xselectionrequest ); + break; + case PropertyNotify: + if( rEvent.xproperty.window == m_aWindow || + rEvent.xproperty.window == m_aCurrentDropWindow + ) + bHandled = handleReceivePropertyNotify( rEvent.xproperty ); + else + bHandled = handleSendPropertyNotify( rEvent.xproperty ); + break; + case SelectionNotify: + bHandled = handleSelectionNotify( rEvent.xselection ); + break; + case ClientMessage: + // messages from drag target + if( rEvent.xclient.message_type == m_nXdndStatus || + rEvent.xclient.message_type == m_nXdndFinished ) + bHandled = handleDragEvent( rEvent ); + // messages from drag source + else if( + rEvent.xclient.message_type == m_nXdndEnter || + rEvent.xclient.message_type == m_nXdndLeave || + rEvent.xclient.message_type == m_nXdndPosition || + rEvent.xclient.message_type == m_nXdndDrop + ) + bHandled = handleDropEvent( rEvent.xclient ); + break; + case EnterNotify: + case LeaveNotify: + case MotionNotify: + case ButtonPress: + case ButtonRelease: + case KeyPress: + case KeyRelease: + bHandled = handleDragEvent( rEvent ); + break; + default: + ; + } + return bHandled; +} + +void SelectionManager::dispatchEvent( int millisec ) +{ + // acquire the mutex to prevent other threads + // from using the same X connection + osl::ResettableMutexGuard aGuard(m_aMutex); + + if( !XPending( m_pDisplay )) + { + int nfds = 1; + // wait for any events if none are already queued + pollfd aPollFD[2]; + aPollFD[0].fd = XConnectionNumber( m_pDisplay ); + aPollFD[0].events = POLLIN; + aPollFD[0].revents = 0; + + // on infinite timeout we need endthreadpipe monitoring too + if (millisec < 0) + { + aPollFD[1].fd = m_EndThreadPipe[0]; + aPollFD[1].events = POLLIN | POLLERR; + aPollFD[1].revents = 0; + nfds = 2; + } + + // release mutex for the time of waiting for possible data + aGuard.clear(); + if( poll( aPollFD, nfds, millisec ) <= 0 ) + return; + aGuard.reset(); + } + while( XPending( m_pDisplay )) + { + XEvent event; + XNextEvent( m_pDisplay, &event ); + aGuard.clear(); + handleXEvent( event ); + aGuard.reset(); + } +} + +void SelectionManager::run( void* pThis ) +{ +#if OSL_DEBUG_LEVEL > 1 + SAL_INFO("vcl.unx.dtrans", "SelectionManager::run."); +#endif + osl::Thread::setName("SelectionManager"); + // dispatch until the cows come home + + SelectionManager* This = static_cast<SelectionManager*>(pThis); + + timeval aLast; + gettimeofday( &aLast, nullptr ); + + css::uno::Reference< XComponentContext > xContext( ::comphelper::getProcessComponentContext() ); + This->m_xDesktop.set( Desktop::create(xContext) ); + This->m_xDesktop->addTerminateListener(This); + + // if end thread pipe properly initialized, allow infinite wait in poll + // otherwise, fallback on 1 sec timeout + const int timeout = (This->m_EndThreadPipe[0] != This->m_EndThreadPipe[1]) ? -1 : 1000; + + while( osl_scheduleThread(This->m_aThread) ) + { + This->dispatchEvent( timeout ); + + timeval aNow; + gettimeofday( &aNow, nullptr ); + + if( (aNow.tv_sec - aLast.tv_sec) > 0 ) + { + osl::ClearableMutexGuard aGuard(This->m_aMutex); + std::vector< std::pair< SelectionAdaptor*, css::uno::Reference< XInterface > > > aChangeVector; + + for (auto const& selection : This->m_aSelections) + { + if( selection.first != This->m_nXdndSelection && ! selection.second->m_bOwner ) + { + ::Window aOwner = XGetSelectionOwner( This->m_pDisplay, selection.first ); + if( aOwner != selection.second->m_aLastOwner ) + { + selection.second->m_aLastOwner = aOwner; + std::pair< SelectionAdaptor*, css::uno::Reference< XInterface > > + aKeep( selection.second->m_pAdaptor, selection.second->m_pAdaptor->getReference() ); + aChangeVector.push_back( aKeep ); + } + } + } + aGuard.clear(); + for (auto const& change : aChangeVector) + { + change.first->fireContentsChanged(); + } + aLast = aNow; + } + } + // close write end on exit so write() fails and other thread does not block + // forever + close(This->m_EndThreadPipe[1]); + close(This->m_EndThreadPipe[0]); +#if OSL_DEBUG_LEVEL > 1 + SAL_INFO("vcl.unx.dtrans", "SelectionManager::run end."); +#endif +} + +void SelectionManager::shutdown() noexcept +{ +#if OSL_DEBUG_LEVEL > 1 + SAL_INFO("vcl.unx.dtrans", "SelectionManager got app termination event."); +#endif + + osl::ResettableMutexGuard aGuard(m_aMutex); + + if( m_bShutDown ) + return; + m_bShutDown = true; + + if ( m_xDesktop.is() ) + m_xDesktop->removeTerminateListener(this); + + if( m_xDisplayConnection.is() ) + m_xDisplayConnection->removeEventHandler(Any(), this); + + // stop dispatching + if( m_aThread ) + { + osl_terminateThread( m_aThread ); + /* + * Allow thread to finish before app exits to avoid pulling the carpet + * out from under it if pasting is occurring during shutdown + * + * a) allow it to have the Mutex and + * b) reschedule to allow it to complete callbacks to any + * Application::GetSolarMutex protected regions, etc. e.g. + * TransferableHelper::getTransferDataFlavors (via + * SelectionManager::handleSelectionRequest) which it might + * currently be trying to enter. + * + * Otherwise the thread may be left still waiting on a GlobalMutex + * when that gets destroyed, letting the thread blow up and die + * when enters the section in a now dead OOo instance. + */ + aGuard.clear(); + while (osl_isThreadRunning(m_aThread)) + { + { // drop mutex before write - otherwise may deadlock + SolarMutexGuard guard2; + Application::Reschedule(); + } + // trigger poll()'s wait end by writing a dummy value + char dummy=0; + dummy = write(m_EndThreadPipe[1], &dummy, 1); + } + osl_joinWithThread( m_aThread ); + osl_destroyThread( m_aThread ); + m_aThread = nullptr; + aGuard.reset(); + } + m_xDesktop.clear(); + m_xDisplayConnection.clear(); + m_xDropTransferable.clear(); +} + +sal_Bool SelectionManager::handleEvent(const Any& event) +{ + Sequence< sal_Int8 > aSeq; + if( event >>= aSeq ) + { + XEvent* pEvent = reinterpret_cast<XEvent*>(aSeq.getArray()); + Time nTimestamp = CurrentTime; + if( pEvent->type == ButtonPress || pEvent->type == ButtonRelease ) + nTimestamp = pEvent->xbutton.time; + else if( pEvent->type == KeyPress || pEvent->type == KeyRelease ) + nTimestamp = pEvent->xkey.time; + else if( pEvent->type == MotionNotify ) + nTimestamp = pEvent->xmotion.time; + else if( pEvent->type == PropertyNotify ) + nTimestamp = pEvent->xproperty.time; + + if( nTimestamp != CurrentTime ) + { + osl::MutexGuard aGuard(m_aMutex); + + m_nSelectionTimestamp = nTimestamp; + } + + return handleXEvent( *pEvent ); + } + else + { +#if OSL_DEBUG_LEVEL > 1 + SAL_INFO("vcl.unx.dtrans", "SelectionManager got downing event."); +#endif + shutdown(); + } + return true; +} + +void SAL_CALL SelectionManager::disposing( const css::lang::EventObject& rEvt ) +{ + if (rEvt.Source == m_xDesktop || rEvt.Source == m_xDisplayConnection) + shutdown(); +} + +void SAL_CALL SelectionManager::queryTermination( const css::lang::EventObject& ) +{ +} + +/* + * To be safe, shutdown needs to be called before the ~SfxApplication is called, waiting until + * the downing event can be too late if paste are requested during shutdown and ~SfxApplication + * has been called before vcl is shutdown + */ +void SAL_CALL SelectionManager::notifyTermination( const css::lang::EventObject& rEvent ) +{ + disposing(rEvent); +} + +void SelectionManager::registerHandler( Atom selection, SelectionAdaptor& rAdaptor ) +{ + osl::MutexGuard aGuard(m_aMutex); + + Selection* pNewSelection = new Selection(); + pNewSelection->m_pAdaptor = &rAdaptor; + m_aSelections[ selection ] = pNewSelection; +} + +void SelectionManager::deregisterHandler( Atom selection ) +{ + osl::MutexGuard aGuard(m_aMutex); + + std::unordered_map< Atom, Selection* >::iterator it = + m_aSelections.find( selection ); + if( it != m_aSelections.end() ) + { + delete it->second->m_pPixmap; + delete it->second; + m_aSelections.erase( it ); + } +} + +static bool bWasError = false; + +extern "C" +{ + static int local_xerror_handler(Display* , XErrorEvent*) + { + bWasError = true; + return 0; + } + typedef int(*xerror_hdl_t)(Display*,XErrorEvent*); +} + +void SelectionManager::registerDropTarget( ::Window aWindow, DropTarget* pTarget ) +{ + osl::MutexGuard aGuard(m_aMutex); + + // sanity check + std::unordered_map< ::Window, DropTargetEntry >::const_iterator it = + m_aDropTargets.find( aWindow ); + if( it != m_aDropTargets.end() ) + OSL_FAIL( "attempt to register window as drop target twice" ); + else if( aWindow && m_pDisplay ) + { + DropTargetEntry aEntry( pTarget ); + bWasError=false; + /* #i100000# ugly workaround: gtk sets its own XErrorHandler which is not suitable for us + unfortunately XErrorHandler is not per display, so this is just and ugly hack + Need to remove separate display and integrate clipboard/dnd into vcl's unx code ASAP + */ + xerror_hdl_t pOldHandler = XSetErrorHandler( local_xerror_handler ); + XSelectInput( m_pDisplay, aWindow, PropertyChangeMask ); + if( ! bWasError ) + { + // set XdndAware + XChangeProperty( m_pDisplay, aWindow, m_nXdndAware, XA_ATOM, 32, PropModeReplace, reinterpret_cast<unsigned char const *>(&nXdndProtocolRevision), 1 ); + if( ! bWasError ) + { + // get root window of window (in 99.999% of all cases this will be + // DefaultRootWindow( m_pDisplay ) + int x, y; + unsigned int w, h, bw, d; + XGetGeometry( m_pDisplay, aWindow, &aEntry.m_aRootWindow, + &x, &y, &w, &h, &bw, &d ); + } + } + XSetErrorHandler( pOldHandler ); + if(bWasError) + return; + m_aDropTargets[ aWindow ] = aEntry; + } + else + OSL_FAIL( "attempt to register None as drop target" ); +} + +void SelectionManager::deregisterDropTarget( ::Window aWindow ) +{ + osl::ClearableMutexGuard aGuard(m_aMutex); + + m_aDropTargets.erase( aWindow ); + if( aWindow != m_aDragSourceWindow || !m_aDragRunning.check() ) + return; + + // abort drag + std::unordered_map< ::Window, DropTargetEntry >::const_iterator it = + m_aDropTargets.find( m_aDropWindow ); + if( it != m_aDropTargets.end() ) + { + DropTargetEvent dte; + dte.Source = static_cast< OWeakObject* >( it->second.m_pTarget ); + aGuard.clear(); + it->second.m_pTarget->dragExit( dte ); + } + else if( m_aDropProxy != None && m_nCurrentProtocolVersion >= 0 ) + { + // send XdndLeave + XEvent aEvent; + aEvent.type = ClientMessage; + aEvent.xclient.display = m_pDisplay; + aEvent.xclient.format = 32; + aEvent.xclient.message_type = m_nXdndLeave; + aEvent.xclient.window = m_aDropWindow; + aEvent.xclient.data.l[0] = m_aWindow; + memset( aEvent.xclient.data.l+1, 0, sizeof(long)*4); + m_aDropWindow = m_aDropProxy = None; + XSendEvent( m_pDisplay, m_aDropProxy, False, NoEventMask, &aEvent ); + } + // notify the listener + DragSourceDropEvent dsde; + dsde.Source = static_cast< OWeakObject* >(this); + dsde.DragSourceContext = new DragSourceContext( m_aDropWindow, *this ); + dsde.DragSource = static_cast< XDragSource* >(this); + dsde.DropAction = DNDConstants::ACTION_NONE; + dsde.DropSuccess = false; + css::uno::Reference< XDragSourceListener > xListener( m_xDragSourceListener ); + m_xDragSourceListener.clear(); + aGuard.clear(); + xListener->dragDropEnd( dsde ); + +} + +/* + * SelectionAdaptor + */ + +css::uno::Reference< XTransferable > SelectionManager::getTransferable() noexcept +{ + return m_xDragSourceTransferable; +} + +void SelectionManager::clearTransferable() noexcept +{ + m_xDragSourceTransferable.clear(); +} + +void SelectionManager::fireContentsChanged() noexcept +{ +} + +css::uno::Reference< XInterface > SelectionManager::getReference() noexcept +{ + return css::uno::Reference< XInterface >( static_cast<OWeakObject*>(this) ); +} + +/* + * SelectionManagerHolder + */ + +SelectionManagerHolder::SelectionManagerHolder() : + ::cppu::WeakComponentImplHelper< + XDragSource, + XInitialization, + XServiceInfo > (m_aMutex) +{ +} + +SelectionManagerHolder::~SelectionManagerHolder() +{ +} + +void SelectionManagerHolder::initialize( const Sequence< Any >& arguments ) +{ + OUString aDisplayName; + + if( arguments.hasElements() ) + { + css::uno::Reference< XDisplayConnection > xConn; + arguments.getConstArray()[0] >>= xConn; + if( xConn.is() ) + { + Any aIdentifier; + aIdentifier >>= aDisplayName; + } + } + + SelectionManager& rManager = SelectionManager::get( aDisplayName ); + rManager.initialize( arguments ); + m_xRealDragSource = static_cast< XDragSource* >(&rManager); +} + +/* + * XDragSource + */ + +sal_Bool SelectionManagerHolder::isDragImageSupported() +{ + return m_xRealDragSource.is() && m_xRealDragSource->isDragImageSupported(); +} + +sal_Int32 SelectionManagerHolder::getDefaultCursor( sal_Int8 dragAction ) +{ + return m_xRealDragSource.is() ? m_xRealDragSource->getDefaultCursor( dragAction ) : 0; +} + +void SelectionManagerHolder::startDrag( + const css::datatransfer::dnd::DragGestureEvent& trigger, + sal_Int8 sourceActions, sal_Int32 cursor, sal_Int32 image, + const css::uno::Reference< css::datatransfer::XTransferable >& transferable, + const css::uno::Reference< css::datatransfer::dnd::XDragSourceListener >& listener + ) +{ + if( m_xRealDragSource.is() ) + m_xRealDragSource->startDrag( trigger, sourceActions, cursor, image, transferable, listener ); +} + +/* + * XServiceInfo + */ + +OUString SelectionManagerHolder::getImplementationName() +{ + return "com.sun.star.datatransfer.dnd.XdndSupport"; +} + +sal_Bool SelectionManagerHolder::supportsService( const OUString& ServiceName ) +{ + return cppu::supportsService(this, ServiceName); +} + +Sequence< OUString > SelectionManagerHolder::getSupportedServiceNames() +{ + return Xdnd_getSupportedServiceNames(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/generic/dtrans/X11_selection.hxx b/vcl/unx/generic/dtrans/X11_selection.hxx new file mode 100644 index 000000000..c0ae171c6 --- /dev/null +++ b/vcl/unx/generic/dtrans/X11_selection.hxx @@ -0,0 +1,496 @@ +/* -*- 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 . + */ + +#pragma once + +#include <cppuhelper/compbase.hxx> +#include <cppuhelper/implbase.hxx> +#include <com/sun/star/datatransfer/XTransferable.hpp> +#include <com/sun/star/datatransfer/dnd/XDropTarget.hpp> +#include <com/sun/star/datatransfer/dnd/XDragSource.hpp> +#include <com/sun/star/awt/XDisplayConnection.hpp> +#include <com/sun/star/lang/XInitialization.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/lang/XMultiServiceFactory.hpp> +#include <com/sun/star/frame/XDesktop2.hpp> +#include <osl/thread.h> +#include <osl/conditn.hxx> +#include <rtl/ref.hxx> + +#include <list> +#include <unordered_map> +#include <vector> + +#include <X11/Xlib.h> + + +namespace x11 { + + class PixmapHolder; // in bmp.hxx + class SelectionManager; + + rtl_TextEncoding getTextPlainEncoding( const OUString& rMimeType ); + + class SelectionAdaptor + { + public: + virtual css::uno::Reference< css::datatransfer::XTransferable > getTransferable() = 0; + virtual void clearTransferable() = 0; + virtual void fireContentsChanged() = 0; + virtual css::uno::Reference< css::uno::XInterface > getReference() = 0; + // returns a reference that will keep the SelectionAdaptor alive until the + // reference is released + + protected: + ~SelectionAdaptor() {} + }; + + class DropTarget : + public ::cppu::WeakComponentImplHelper< + css::datatransfer::dnd::XDropTarget, + css::lang::XInitialization, + css::lang::XServiceInfo + > + { + public: + ::osl::Mutex m_aMutex; + bool m_bActive; + sal_Int8 m_nDefaultActions; + ::Window m_aTargetWindow; + rtl::Reference<SelectionManager> + m_xSelectionManager; + ::std::vector< css::uno::Reference< css::datatransfer::dnd::XDropTargetListener > > + m_aListeners; + + DropTarget(); + virtual ~DropTarget() override; + + // convenience functions that loop over listeners + void dragEnter( const css::datatransfer::dnd::DropTargetDragEnterEvent& dtde ) noexcept; + void dragExit( const css::datatransfer::dnd::DropTargetEvent& dte ) noexcept; + void dragOver( const css::datatransfer::dnd::DropTargetDragEvent& dtde ) noexcept; + void drop( const css::datatransfer::dnd::DropTargetDropEvent& dtde ) noexcept; + + // XInitialization + virtual void SAL_CALL initialize( const css::uno::Sequence< css::uno::Any >& args ) override; + + // XDropTarget + virtual void SAL_CALL addDropTargetListener( const css::uno::Reference< css::datatransfer::dnd::XDropTargetListener >& ) override; + virtual void SAL_CALL removeDropTargetListener( const css::uno::Reference< css::datatransfer::dnd::XDropTargetListener >& ) override; + virtual sal_Bool SAL_CALL isActive() override; + virtual void SAL_CALL setActive( sal_Bool active ) override; + virtual sal_Int8 SAL_CALL getDefaultActions() override; + virtual void SAL_CALL setDefaultActions( sal_Int8 actions ) override; + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName() override; + virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override; + virtual css::uno::Sequence< OUString > + SAL_CALL getSupportedServiceNames() override; + }; + + class SelectionManagerHolder : + public ::cppu::WeakComponentImplHelper< + css::datatransfer::dnd::XDragSource, + css::lang::XInitialization, + css::lang::XServiceInfo + > + { + ::osl::Mutex m_aMutex; + css::uno::Reference< css::datatransfer::dnd::XDragSource > + m_xRealDragSource; + public: + SelectionManagerHolder(); + virtual ~SelectionManagerHolder() override; + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName() override; + virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override; + virtual css::uno::Sequence< OUString > + SAL_CALL getSupportedServiceNames() override; + + // XInitialization + virtual void SAL_CALL initialize( const css::uno::Sequence< css::uno::Any >& arguments ) override; + + // XDragSource + virtual sal_Bool SAL_CALL isDragImageSupported() override; + virtual sal_Int32 SAL_CALL getDefaultCursor( sal_Int8 dragAction ) override; + virtual void SAL_CALL startDrag( + const css::datatransfer::dnd::DragGestureEvent& trigger, + sal_Int8 sourceActions, sal_Int32 cursor, sal_Int32 image, + const css::uno::Reference< css::datatransfer::XTransferable >& transferable, + const css::uno::Reference< css::datatransfer::dnd::XDragSourceListener >& listener + ) override; + + }; + + class SelectionManager : + public ::cppu::WeakImplHelper< + css::datatransfer::dnd::XDragSource, + css::lang::XInitialization, + css::awt::XEventHandler, + css::frame::XTerminateListener + >, + public SelectionAdaptor + { + static std::unordered_map< OUString, SelectionManager* >& getInstances(); + + // for INCR type selection transfer + // INCR protocol is used if the data cannot + // be transported at once but in parts + // IncrementalTransfer holds the bytes to be transmitted + // as well as the current position + // INCR triggers the delivery of the next part by deleting the + // property used to transfer the data + struct IncrementalTransfer + { + css::uno::Sequence< sal_Int8 > m_aData; + int m_nBufferPos; + ::Window m_aRequestor; + Atom m_aProperty; + Atom m_aTarget; + int m_nFormat; + int m_nTransferStartTime; + }; + int m_nIncrementalThreshold; + + // a struct to hold the data associated with a selection + struct Selection + { + enum State + { + Inactive, WaitingForResponse, WaitingForData, IncrementalTransfer + }; + + State m_eState; + SelectionAdaptor* m_pAdaptor; + ::osl::Condition m_aDataArrived; + css::uno::Sequence< sal_Int8 > m_aData; + css::uno::Sequence< css::datatransfer::DataFlavor > + m_aTypes; + std::vector< Atom > m_aNativeTypes; + // this is used for caching + // m_aTypes is invalid after 2 seconds + // m_aNativeTypes contains the corresponding original atom + Atom m_aRequestedType; + // m_aRequestedType is only valid while WaitingForResponse and WaitingFotData + int m_nLastTimestamp; + bool m_bHaveUTF16; + Atom m_aUTF8Type; + bool m_bHaveCompound; + bool m_bOwner; + ::Window m_aLastOwner; + PixmapHolder* m_pPixmap; + // m_nOrigTimestamp contains the Timestamp at which the selection + // was acquired; needed for TimeSTAMP target + Time m_nOrigTimestamp; + + Selection() : m_eState( Inactive ), + m_pAdaptor( nullptr ), + m_aRequestedType( None ), + m_nLastTimestamp( 0 ), + m_bHaveUTF16( false ), + m_aUTF8Type( None ), + m_bHaveCompound( false ), + m_bOwner( false ), + m_aLastOwner( None ), + m_pPixmap( nullptr ), + m_nOrigTimestamp( CurrentTime ) + {} + }; + + // a struct to hold data associated with a XDropTarget + struct DropTargetEntry + { + DropTarget* m_pTarget; + ::Window m_aRootWindow; + + DropTargetEntry() : m_pTarget( nullptr ), m_aRootWindow( None ) {} + explicit DropTargetEntry( DropTarget* pTarget ) : + m_pTarget( pTarget ), + m_aRootWindow( None ) + {} + DropTargetEntry( const DropTargetEntry& rEntry ) : + m_pTarget( rEntry.m_pTarget ), + m_aRootWindow( rEntry.m_aRootWindow ) + {} + + DropTarget* operator->() const { return m_pTarget; } + DropTargetEntry& operator=(const DropTargetEntry& rEntry) + { m_pTarget = rEntry.m_pTarget; m_aRootWindow = rEntry.m_aRootWindow; return *this; } + }; + + // internal data + Display* m_pDisplay; + oslThread m_aThread; + int m_EndThreadPipe[2]; + oslThread m_aDragExecuteThread; + ::osl::Condition m_aDragRunning; + ::Window m_aWindow; + css::uno::Reference< css::frame::XDesktop2 > m_xDesktop; + css::uno::Reference< css::awt::XDisplayConnection > + m_xDisplayConnection; + sal_Int32 m_nSelectionTimeout; + Time m_nSelectionTimestamp; + + // members used for Xdnd + + // drop only + + // contains the XdndEnterEvent of a drop action running + // with one of our targets. The data.l[0] member + // (containing the drag source ::Window) is set + // to None while that is not the case + XClientMessageEvent m_aDropEnterEvent; + // set to false on XdndEnter + // set to true on first XdndPosition or XdndLeave + bool m_bDropEnterSent; + ::Window m_aCurrentDropWindow; + // Time code of XdndDrop + Time m_nDropTime; + sal_Int8 m_nLastDropAction; + // XTransferable for Xdnd with foreign drag source + css::uno::Reference< css::datatransfer::XTransferable > + m_xDropTransferable; + int m_nLastX, m_nLastY; + // set to true when calling drop() + // if another XdndEnter is received this shows that + // someone forgot to call dropComplete - we should reset + // and react to the new drop + bool m_bDropWaitingForCompletion; + + // drag only + + // None if no Dnd action is running with us as source + ::Window m_aDropWindow; + // either m_aDropWindow or its XdndProxy + ::Window m_aDropProxy; + ::Window m_aDragSourceWindow; + // XTransferable for Xdnd when we are drag source + css::uno::Reference< css::datatransfer::XTransferable > + m_xDragSourceTransferable; + css::uno::Reference< css::datatransfer::dnd::XDragSourceListener > + m_xDragSourceListener; + // root coordinates + int m_nLastDragX, m_nLastDragY; + css::uno::Sequence< css::datatransfer::DataFlavor > + m_aDragFlavors; + // the rectangle the pointer must leave until a new XdndPosition should + // be sent. empty unless the drop target told to fill + int m_nNoPosX, m_nNoPosY, m_nNoPosWidth, m_nNoPosHeight; + unsigned int m_nDragButton; + sal_Int8 m_nUserDragAction; + sal_Int8 m_nTargetAcceptAction; + sal_Int8 m_nSourceActions; + bool m_bLastDropAccepted; + bool m_bDropSuccess; + bool m_bDropSent; + time_t m_nDropTimeout; + bool m_bWaitingForPrimaryConversion; + + // drag cursors + Cursor m_aMoveCursor; + Cursor m_aCopyCursor; + Cursor m_aLinkCursor; + Cursor m_aNoneCursor; + Cursor m_aCurrentCursor; + + // drag and drop + + int m_nCurrentProtocolVersion; + std::unordered_map< ::Window, DropTargetEntry > + m_aDropTargets; + + // some special atoms that are needed often + Atom m_nTARGETSAtom; + Atom m_nTIMESTAMPAtom; + Atom m_nTEXTAtom; + Atom m_nINCRAtom; + Atom m_nCOMPOUNDAtom; + Atom m_nMULTIPLEAtom; + Atom m_nImageBmpAtom; + Atom m_nXdndAware; + Atom m_nXdndEnter; + Atom m_nXdndLeave; + Atom m_nXdndPosition; + Atom m_nXdndStatus; + Atom m_nXdndDrop; + Atom m_nXdndFinished; + Atom m_nXdndSelection; + Atom m_nXdndTypeList; + Atom m_nXdndProxy; + Atom m_nXdndActionCopy; + Atom m_nXdndActionMove; + Atom m_nXdndActionLink; + Atom m_nXdndActionAsk; + + // caching for atoms + std::unordered_map< Atom, OUString > + m_aAtomToString; + std::unordered_map< OUString, Atom > + m_aStringToAtom; + + // the registered selections + std::unordered_map< Atom, Selection* > + m_aSelections; + // IncrementalTransfers in progress + std::unordered_map< ::Window, std::unordered_map< Atom, IncrementalTransfer > > + m_aIncrementals; + + // do not use X11 multithreading capabilities + // since this leads to deadlocks in different Xlib implementations + // (XFree as well as Xsun) use an own mutex instead + ::osl::Mutex m_aMutex; + bool m_bShutDown; + + SelectionManager(); + virtual ~SelectionManager() override; + + SelectionAdaptor* getAdaptor( Atom selection ); + PixmapHolder* getPixmapHolder( Atom selection ); + + // handle various events + bool handleSelectionRequest( XSelectionRequestEvent& rRequest ); + bool handleSendPropertyNotify( XPropertyEvent const & rNotify ); + bool handleReceivePropertyNotify( XPropertyEvent const & rNotify ); + bool handleSelectionNotify( XSelectionEvent const & rNotify ); + bool handleDragEvent( XEvent const & rMessage ); + bool handleDropEvent( XClientMessageEvent const & rMessage ); + + // dnd helpers + void sendDragStatus( Atom nDropAction ); + void sendDropPosition( bool bForce, Time eventTime ); + bool updateDragAction( int modifierState ); + int getXdndVersion( ::Window aXLIB_Window, ::Window& rProxy ); + Cursor createCursor( const unsigned char* pPointerData, const unsigned char* pMaskData, int width, int height, int hotX, int hotY ); + // coordinates on root ::Window + void updateDragWindow( int nX, int nY, ::Window aRoot ); + + bool getPasteData( Atom selection, Atom type, css::uno::Sequence< sal_Int8 >& rData ); + // returns true if conversion was successful + bool convertData( const css::uno::Reference< css::datatransfer::XTransferable >& xTransferable, + Atom nType, + Atom nSelection, + int & rFormat, + css::uno::Sequence< sal_Int8 >& rData ); + bool sendData( SelectionAdaptor* pAdaptor, ::Window requestor, Atom target, Atom property, Atom selection ); + + // thread dispatch loop + public: + // public for extern "C" stub + static void run( void* ); + private: + void dispatchEvent( int millisec ); + // drag thread dispatch + public: + // public for extern "C" stub + static void runDragExecute( void* ); + private: + void dragDoDispatch(); + bool handleXEvent( XEvent& rEvent ); + + // compound text conversion + OString convertToCompound( const OUString& rText ); + OUString convertFromCompound( const char* pText, int nLen ); + + sal_Int8 getUserDragAction() const; + sal_Int32 getSelectionTimeout(); + public: + static SelectionManager& get( const OUString& rDisplayName = OUString() ); + + Display * getDisplay() { return m_pDisplay; }; + + void registerHandler( Atom selection, SelectionAdaptor& rAdaptor ); + void deregisterHandler( Atom selection ); + bool requestOwnership( Atom selection ); + + // allow for synchronization over one mutex for XClipboard + osl::Mutex& getMutex() { return m_aMutex; } + + Atom getAtom( const OUString& rString ); + OUString getString( Atom nAtom ); + + // type conversion + // note: convertTypeToNative does NOT clear the list, so you can append + // multiple types to the same list + void convertTypeToNative( const OUString& rType, Atom selection, int& rFormat, ::std::list< Atom >& rConversions, bool bPushFront = false ); + OUString convertTypeFromNative( Atom nType, Atom selection, int& rFormat ); + void getNativeTypeList( const css::uno::Sequence< css::datatransfer::DataFlavor >& rTypes, std::list< Atom >& rOutTypeList, Atom targetselection ); + + // methods for transferable + bool getPasteDataTypes( Atom selection, css::uno::Sequence< css::datatransfer::DataFlavor >& rTypes ); + bool getPasteData( Atom selection, const OUString& rType, css::uno::Sequence< sal_Int8 >& rData ); + + // for XDropTarget to register/deregister itself + void registerDropTarget( ::Window aXLIB_Window, DropTarget* pTarget ); + void deregisterDropTarget( ::Window aXLIB_Window ); + + // for XDropTarget{Drag|Drop}Context + void accept( sal_Int8 dragOperation, ::Window aDropXLIB_Window ); + void reject( ::Window aDropXLIB_Window ); + void dropComplete( bool success, ::Window aDropXLIB_Window ); + + // for XDragSourceContext + sal_Int32 getCurrentCursor() const { return m_aCurrentCursor;} + void setCursor( sal_Int32 cursor, ::Window aDropXLIB_Window ); + void transferablesFlavorsChanged(); + + void shutdown() noexcept; + + // XInitialization + virtual void SAL_CALL initialize( const css::uno::Sequence< css::uno::Any >& arguments ) override; + + // XEventHandler + virtual sal_Bool SAL_CALL handleEvent(const css::uno::Any& event) override; + + // XDragSource + virtual sal_Bool SAL_CALL isDragImageSupported() override; + virtual sal_Int32 SAL_CALL getDefaultCursor( sal_Int8 dragAction ) override; + virtual void SAL_CALL startDrag( + const css::datatransfer::dnd::DragGestureEvent& trigger, + sal_Int8 sourceActions, sal_Int32 cursor, sal_Int32 image, + const css::uno::Reference< css::datatransfer::XTransferable >& transferable, + const css::uno::Reference< css::datatransfer::dnd::XDragSourceListener >& listener + ) override; + + // SelectionAdaptor for XdndSelection Drag (we are drag source) + virtual css::uno::Reference< css::datatransfer::XTransferable > getTransferable() noexcept override; + virtual void clearTransferable() noexcept override; + virtual void fireContentsChanged() noexcept override; + virtual css::uno::Reference< css::uno::XInterface > getReference() noexcept override; + + // XEventListener + virtual void SAL_CALL disposing( const css::lang::EventObject& Source ) override; + + // XTerminateListener + virtual void SAL_CALL queryTermination( const css::lang::EventObject& aEvent ) override; + virtual void SAL_CALL notifyTermination( const css::lang::EventObject& aEvent ) override; + }; + + css::uno::Sequence< OUString > Xdnd_getSupportedServiceNames(); + css::uno::Reference< css::uno::XInterface > SAL_CALL Xdnd_createInstance( + const css::uno::Reference< css::lang::XMultiServiceFactory > & xMultiServiceFactory); + + css::uno::Sequence< OUString > Xdnd_dropTarget_getSupportedServiceNames(); + css::uno::Reference< css::uno::XInterface > SAL_CALL Xdnd_dropTarget_createInstance( + const css::uno::Reference< css::lang::XMultiServiceFactory > & xMultiServiceFactory); + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/generic/dtrans/X11_service.cxx b/vcl/unx/generic/dtrans/X11_service.cxx new file mode 100644 index 000000000..e633020b6 --- /dev/null +++ b/vcl/unx/generic/dtrans/X11_service.cxx @@ -0,0 +1,88 @@ +/* -*- 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 <unx/salinst.h> +#include <dndhelper.hxx> +#include <vcl/sysdata.hxx> + +#include "X11_clipboard.hxx" +#include <com/sun/star/lang/IllegalArgumentException.hpp> + +using namespace cppu; +using namespace com::sun::star::uno; +using namespace com::sun::star::lang; +using namespace com::sun::star::datatransfer::clipboard; +using namespace com::sun::star::awt; +using namespace x11; + +Sequence< OUString > x11::X11Clipboard_getSupportedServiceNames() +{ + return { "com.sun.star.datatransfer.clipboard.SystemClipboard" }; +} + +Sequence< OUString > x11::Xdnd_getSupportedServiceNames() +{ + return { "com.sun.star.datatransfer.dnd.X11DragSource" }; +} + +Sequence< OUString > x11::Xdnd_dropTarget_getSupportedServiceNames() +{ + return { "com.sun.star.datatransfer.dnd.X11DropTarget" }; +} + +css::uno::Reference< XInterface > X11SalInstance::CreateClipboard( const Sequence< Any >& arguments ) +{ + if ( IsRunningUnitTest() ) + return SalInstance::CreateClipboard( arguments ); + + SelectionManager& rManager = SelectionManager::get(); + css::uno::Sequence<css::uno::Any> mgrArgs{ css::uno::Any(Application::GetDisplayConnection()) }; + rManager.initialize(mgrArgs); + + OUString sel; + if (!arguments.hasElements()) { + sel = "CLIPBOARD"; + } else if (arguments.getLength() != 1 || !(arguments[0] >>= sel)) { + throw css::lang::IllegalArgumentException( + "bad X11SalInstance::CreateClipboard arguments", + css::uno::Reference<css::uno::XInterface>(), -1); + } + Atom nSelection = rManager.getAtom(sel); + + std::unordered_map< Atom, css::uno::Reference< XClipboard > >::iterator it = m_aInstances.find( nSelection ); + if( it != m_aInstances.end() ) + return it->second; + + css::uno::Reference<css::datatransfer::clipboard::XClipboard> pClipboard = X11Clipboard::create( rManager, nSelection ); + m_aInstances[ nSelection ] = pClipboard; + + return pClipboard; +} + +css::uno::Reference<XInterface> X11SalInstance::ImplCreateDragSource(const SystemEnvData* pSysEnv) +{ + return vcl::X11DnDHelper(new SelectionManagerHolder(), pSysEnv->aShellWindow); +} + +css::uno::Reference<XInterface> X11SalInstance::ImplCreateDropTarget(const SystemEnvData* pSysEnv) +{ + return vcl::X11DnDHelper(new DropTarget(), pSysEnv->aShellWindow); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/generic/dtrans/X11_transferable.cxx b/vcl/unx/generic/dtrans/X11_transferable.cxx new file mode 100644 index 000000000..a6ad1b4a1 --- /dev/null +++ b/vcl/unx/generic/dtrans/X11_transferable.cxx @@ -0,0 +1,101 @@ +/* -*- 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 "X11_transferable.hxx" +#include <X11/Xatom.h> +#include <com/sun/star/datatransfer/UnsupportedFlavorException.hpp> +#include <com/sun/star/io/IOException.hpp> +#include <sal/log.hxx> + +using namespace com::sun::star::datatransfer; +using namespace com::sun::star::lang; +using namespace com::sun::star::io; +using namespace com::sun::star::uno; +using namespace cppu; +using namespace osl; + +using namespace x11; + +X11Transferable::X11Transferable( + SelectionManager& rManager, + Atom selection + ) : + m_rManager( rManager ), + m_aSelection( selection ) +{ +} + +X11Transferable::~X11Transferable() +{ +} + +Any SAL_CALL X11Transferable::getTransferData( const DataFlavor& rFlavor ) +{ + Any aRet; + Sequence< sal_Int8 > aData; + bool bSuccess = m_rManager.getPasteData( m_aSelection ? m_aSelection : XA_PRIMARY, rFlavor.MimeType, aData ); + if( ! bSuccess && m_aSelection == 0 ) + bSuccess = m_rManager.getPasteData( m_rManager.getAtom( "CLIPBOARD" ), rFlavor.MimeType, aData ); + + if( ! bSuccess ) + { + throw UnsupportedFlavorException( rFlavor.MimeType, static_cast < XTransferable * > ( this ) ); + } + if( rFlavor.MimeType.equalsIgnoreAsciiCase( "text/plain;charset=utf-16" ) ) + { + int nLen = aData.getLength()/2; + if( reinterpret_cast<sal_Unicode const *>(aData.getConstArray())[nLen-1] == 0 ) + nLen--; + OUString aString( reinterpret_cast<sal_Unicode const *>(aData.getConstArray()), nLen ); + SAL_INFO( "vcl.unx.dtrans", "X11Transferable::getTransferData( \"" << rFlavor.MimeType << "\" )\n -> \"" << aString << "\""); + aRet <<= aString.replaceAll("\r\n", "\n"); + } + else + aRet <<= aData; + return aRet; +} + +Sequence< DataFlavor > SAL_CALL X11Transferable::getTransferDataFlavors() +{ + Sequence< DataFlavor > aFlavorList; + bool bSuccess = m_rManager.getPasteDataTypes( m_aSelection ? m_aSelection : XA_PRIMARY, aFlavorList ); + if( ! bSuccess && m_aSelection == 0 ) + m_rManager.getPasteDataTypes( m_rManager.getAtom( "CLIPBOARD" ), aFlavorList ); + + return aFlavorList; +} + +sal_Bool SAL_CALL X11Transferable::isDataFlavorSupported( const DataFlavor& aFlavor ) +{ + if( aFlavor.DataType != cppu::UnoType<Sequence< sal_Int8 >>::get() ) + { + if( ! aFlavor.MimeType.equalsIgnoreAsciiCase( "text/plain;charset=utf-16" ) && + aFlavor.DataType == cppu::UnoType<OUString>::get() ) + return false; + } + + const Sequence< DataFlavor > aFlavors( getTransferDataFlavors() ); + return std::any_of(aFlavors.begin(), aFlavors.end(), + [&aFlavor](const DataFlavor& rFlavor) { + return aFlavor.MimeType.equalsIgnoreAsciiCase( rFlavor.MimeType ) + && aFlavor.DataType == rFlavor.DataType; + }); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/generic/dtrans/X11_transferable.hxx b/vcl/unx/generic/dtrans/X11_transferable.hxx new file mode 100644 index 000000000..23cb9dd37 --- /dev/null +++ b/vcl/unx/generic/dtrans/X11_transferable.hxx @@ -0,0 +1,50 @@ +/* -*- 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 . + */ + +#pragma once + +#include "X11_selection.hxx" +#include <com/sun/star/datatransfer/XTransferable.hpp> + +#include <cppuhelper/implbase.hxx> + +namespace x11 { + + class X11Transferable : public ::cppu::WeakImplHelper< css::datatransfer::XTransferable > + { + SelectionManager& m_rManager; + Atom m_aSelection; + public: + X11Transferable( SelectionManager& rManager, Atom selection ); + virtual ~X11Transferable() override; + + /* + * XTransferable + */ + + virtual css::uno::Any SAL_CALL getTransferData( const css::datatransfer::DataFlavor& aFlavor ) override; + + virtual css::uno::Sequence< css::datatransfer::DataFlavor > SAL_CALL getTransferDataFlavors( ) override; + + virtual sal_Bool SAL_CALL isDataFlavorSupported( const css::datatransfer::DataFlavor& aFlavor ) override; + }; + +} // namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/generic/dtrans/bmp.cxx b/vcl/unx/generic/dtrans/bmp.cxx new file mode 100644 index 000000000..ac8e50cc2 --- /dev/null +++ b/vcl/unx/generic/dtrans/bmp.cxx @@ -0,0 +1,786 @@ +/* -*- 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 <tools/stream.hxx> + +#include <vcl/dibtools.hxx> +#include <vcl/svapp.hxx> +#include <vcl/bitmap.hxx> +#include <vcl/bitmapex.hxx> +#include <vcl/BitmapSimpleColorQuantizationFilter.hxx> + +#include <sal/log.hxx> +#include <unx/x11/xlimits.hxx> + +#include "bmp.hxx" + +using namespace x11; + +/* + * helper functions + */ + +static void writeLE( sal_uInt16 nNumber, sal_uInt8* pBuffer ) +{ + pBuffer[ 0 ] = (nNumber & 0xff); + pBuffer[ 1 ] = ((nNumber>>8)&0xff); +} + +static void writeLE( sal_uInt32 nNumber, sal_uInt8* pBuffer ) +{ + pBuffer[ 0 ] = (nNumber & 0xff); + pBuffer[ 1 ] = ((nNumber>>8)&0xff); + pBuffer[ 2 ] = ((nNumber>>16)&0xff); + pBuffer[ 3 ] = ((nNumber>>24)&0xff); +} + +static sal_uInt16 readLE16( const sal_uInt8* pBuffer ) +{ + //This is untainted data which comes from a controlled source + //so, using a byte-swapping pattern which coverity doesn't + //detect as such + //http://security.coverity.com/blog/2014/Apr/on-detecting-heartbleed-with-static-analysis.html + sal_uInt16 v = pBuffer[1]; v <<= 8; + v |= pBuffer[0]; + return v; +} + +static sal_uInt32 readLE32( const sal_uInt8* pBuffer ) +{ + //This is untainted data which comes from a controlled source + //so, using a byte-swapping pattern which coverity doesn't + //detect as such + //http://security.coverity.com/blog/2014/Apr/on-detecting-heartbleed-with-static-analysis.html + sal_uInt32 v = pBuffer[3]; v <<= 8; + v |= pBuffer[2]; v <<= 8; + v |= pBuffer[1]; v <<= 8; + v |= pBuffer[0]; + return v; +} + +/* + * scanline helpers + */ + +static void X11_writeScanlinePixel( unsigned long nColor, sal_uInt8* pScanline, int depth, int x ) +{ + switch( depth ) + { + case 1: + pScanline[ x/8 ] &= ~(1 << (x&7)); + pScanline[ x/8 ] |= ((nColor & 1) << (x&7)); + break; + case 4: + pScanline[ x/2 ] &= ((x&1) ? 0x0f : 0xf0); + pScanline[ x/2 ] |= ((x&1) ? (nColor & 0x0f) : ((nColor & 0x0f) << 4)); + break; + default: + case 8: + pScanline[ x ] = (nColor & 0xff); + break; + } +} + +static sal_uInt8* X11_getPaletteBmpFromImage( + Display* pDisplay, + XImage* pImage, + Colormap aColormap, + sal_Int32& rOutSize + ) +{ + sal_uInt32 nColors = 0; + + rOutSize = 0; + + sal_uInt8* pBuffer = nullptr; + sal_uInt32 nHeaderSize, nScanlineSize; + sal_uInt16 nBitCount; + // determine header and scanline size + switch( pImage->depth ) + { + case 1: + nHeaderSize = 64; + nScanlineSize = (pImage->width+31)/32; + nBitCount = 1; + break; + case 4: + nHeaderSize = 72; + nScanlineSize = (pImage->width+1)/2; + nBitCount = 4; + break; + default: + case 8: + nHeaderSize = 1084; + nScanlineSize = pImage->width; + nBitCount = 8; + break; + } + // adjust scan lines to begin on %4 boundaries + if( nScanlineSize & 3 ) + { + nScanlineSize &= 0xfffffffc; + nScanlineSize += 4; + } + + // allocate buffer to hold header and scanlines, initialize to zero + rOutSize = nHeaderSize + nScanlineSize*pImage->height; + pBuffer = static_cast<sal_uInt8*>(rtl_allocateZeroMemory( rOutSize )); + for( int y = 0; y < pImage->height; y++ ) + { + sal_uInt8* pScanline = pBuffer + nHeaderSize + (pImage->height-1-y)*nScanlineSize; + for( int x = 0; x < pImage->width; x++ ) + { + unsigned long nPixel = XGetPixel( pImage, x, y ); + if( nPixel >= nColors ) + nColors = nPixel+1; + X11_writeScanlinePixel( nPixel, pScanline, pImage->depth, x ); + } + } + + // fill in header fields + pBuffer[ 0 ] = 'B'; + pBuffer[ 1 ] = 'M'; + + writeLE( nHeaderSize, pBuffer+10 ); + writeLE( sal_uInt32(40), pBuffer+14 ); + writeLE( static_cast<sal_uInt32>(pImage->width), pBuffer+18 ); + writeLE( static_cast<sal_uInt32>(pImage->height), pBuffer+22 ); + writeLE( sal_uInt16(1), pBuffer+26 ); + writeLE( nBitCount, pBuffer+28 ); + writeLE( static_cast<sal_uInt32>(DisplayWidth(pDisplay,DefaultScreen(pDisplay))*1000/DisplayWidthMM(pDisplay,DefaultScreen(pDisplay))), pBuffer+38); + writeLE( static_cast<sal_uInt32>(DisplayHeight(pDisplay,DefaultScreen(pDisplay))*1000/DisplayHeightMM(pDisplay,DefaultScreen(pDisplay))), pBuffer+42); + writeLE( nColors, pBuffer+46 ); + writeLE( nColors, pBuffer+50 ); + + XColor aColors[256]; + if( nColors > (1U << nBitCount) ) // paranoia + nColors = (1U << nBitCount); + for( unsigned long nPixel = 0; nPixel < nColors; nPixel++ ) + { + aColors[nPixel].flags = DoRed | DoGreen | DoBlue; + aColors[nPixel].pixel = nPixel; + } + XQueryColors( pDisplay, aColormap, aColors, nColors ); + for( sal_uInt32 i = 0; i < nColors; i++ ) + { + pBuffer[ 54 + i*4 ] = static_cast<sal_uInt8>(aColors[i].blue >> 8); + pBuffer[ 55 + i*4 ] = static_cast<sal_uInt8>(aColors[i].green >> 8); + pBuffer[ 56 + i*4 ] = static_cast<sal_uInt8>(aColors[i].red >> 8); + } + + // done + + return pBuffer; +} + +static unsigned long doRightShift( unsigned long nValue, int nShift ) +{ + return (nShift > 0) ? (nValue >> nShift) : (nValue << (-nShift)); +} + +static unsigned long doLeftShift( unsigned long nValue, int nShift ) +{ + return (nShift > 0) ? (nValue << nShift) : (nValue >> (-nShift)); +} + +static void getShift( unsigned long nMask, int& rShift, int& rSigBits, int& rShift2 ) +{ + unsigned long nUseMask = nMask; + rShift = 0; + while( nMask & 0xffffff00 ) + { + rShift++; + nMask >>= 1; + } + if( rShift == 0 ) + while( ! (nMask & 0x00000080) ) + { + rShift--; + nMask <<= 1; + } + + int nRotate = sizeof(unsigned long)*8 - rShift; + rSigBits = 0; + nMask = doRightShift( nUseMask, rShift) ; + while( nRotate-- ) + { + if( nMask & 1 ) + rSigBits++; + nMask >>= 1; + } + + rShift2 = 0; + if( rSigBits < 8 ) + rShift2 = 8-rSigBits; +} + +static sal_uInt8* X11_getTCBmpFromImage( + Display* pDisplay, + XImage* pImage, + sal_Int32& rOutSize, + int nScreenNo + ) +{ + // get masks from visual info (guesswork) + XVisualInfo aVInfo; + if( ! XMatchVisualInfo( pDisplay, nScreenNo, pImage->depth, TrueColor, &aVInfo ) ) + return nullptr; + + rOutSize = 0; + + sal_uInt8* pBuffer = nullptr; + sal_uInt32 nHeaderSize = 60; + sal_uInt32 nScanlineSize = pImage->width*3; + + // adjust scan lines to begin on %4 boundaries + if( nScanlineSize & 3 ) + { + nScanlineSize &= 0xfffffffc; + nScanlineSize += 4; + } + int nRedShift, nRedSig, nRedShift2 = 0; + getShift( aVInfo.red_mask, nRedShift, nRedSig, nRedShift2 ); + int nGreenShift, nGreenSig, nGreenShift2 = 0; + getShift( aVInfo.green_mask, nGreenShift, nGreenSig, nGreenShift2 ); + int nBlueShift, nBlueSig, nBlueShift2 = 0; + getShift( aVInfo.blue_mask, nBlueShift, nBlueSig, nBlueShift2 ); + + // allocate buffer to hold header and scanlines, initialize to zero + rOutSize = nHeaderSize + nScanlineSize*pImage->height; + pBuffer = static_cast<sal_uInt8*>(rtl_allocateZeroMemory( rOutSize )); + for( int y = 0; y < pImage->height; y++ ) + { + sal_uInt8* pScanline = pBuffer + nHeaderSize + (pImage->height-1-y)*nScanlineSize; + for( int x = 0; x < pImage->width; x++ ) + { + unsigned long nPixel = XGetPixel( pImage, x, y ); + + sal_uInt8 nValue = static_cast<sal_uInt8>(doRightShift( nPixel&aVInfo.blue_mask, nBlueShift)); + if( nBlueShift2 ) + nValue |= (nValue >> nBlueShift2 ); + *pScanline++ = nValue; + + nValue = static_cast<sal_uInt8>(doRightShift( nPixel&aVInfo.green_mask, nGreenShift)); + if( nGreenShift2 ) + nValue |= (nValue >> nGreenShift2 ); + *pScanline++ = nValue; + + nValue = static_cast<sal_uInt8>(doRightShift( nPixel&aVInfo.red_mask, nRedShift)); + if( nRedShift2 ) + nValue |= (nValue >> nRedShift2 ); + *pScanline++ = nValue; + } + } + + // fill in header fields + pBuffer[ 0 ] = 'B'; + pBuffer[ 1 ] = 'M'; + + writeLE( nHeaderSize, pBuffer+10 ); + writeLE( sal_uInt32(40), pBuffer+14 ); + writeLE( static_cast<sal_uInt32>(pImage->width), pBuffer+18 ); + writeLE( static_cast<sal_uInt32>(pImage->height), pBuffer+22 ); + writeLE( sal_uInt16(1), pBuffer+26 ); + writeLE( sal_uInt16(24), pBuffer+28 ); + writeLE( static_cast<sal_uInt32>(DisplayWidth(pDisplay,DefaultScreen(pDisplay))*1000/DisplayWidthMM(pDisplay,DefaultScreen(pDisplay))), pBuffer+38); + writeLE( static_cast<sal_uInt32>(DisplayHeight(pDisplay,DefaultScreen(pDisplay))*1000/DisplayHeightMM(pDisplay,DefaultScreen(pDisplay))), pBuffer+42); + + // done + + return pBuffer; +} + +sal_uInt8* x11::X11_getBmpFromPixmap( + Display* pDisplay, + Drawable aDrawable, + Colormap aColormap, + sal_Int32& rOutSize + ) +{ + // get geometry of drawable + ::Window aRoot; + int x,y; + unsigned int w, h, bw, d; + XGetGeometry( pDisplay, aDrawable, &aRoot, &x, &y, &w, &h, &bw, &d ); + + // find which screen we are on + int nScreenNo = ScreenCount( pDisplay ); + while( nScreenNo-- ) + { + if( RootWindow( pDisplay, nScreenNo ) == aRoot ) + break; + } + if( nScreenNo < 0 ) + return nullptr; + + if( aColormap == None ) + aColormap = DefaultColormap( pDisplay, nScreenNo ); + + // get the image + XImage* pImage = XGetImage( pDisplay, aDrawable, 0, 0, w, h, AllPlanes, ZPixmap ); + if( ! pImage ) + return nullptr; + + sal_uInt8* pBmp = d <= 8 ? + X11_getPaletteBmpFromImage( pDisplay, pImage, aColormap, rOutSize ) : + X11_getTCBmpFromImage( pDisplay, pImage, rOutSize, nScreenNo ); + XDestroyImage( pImage ); + + return pBmp; +} + +/* + * PixmapHolder + */ + +PixmapHolder::PixmapHolder( Display* pDisplay ) + : m_pDisplay(pDisplay) + , m_aColormap(None) + , m_aPixmap(None) + , m_aBitmap(None) + , m_nRedShift(0) + , m_nGreenShift(0) + , m_nBlueShift(0) + , m_nBlueShift2Mask(0) + , m_nRedShift2Mask(0) + , m_nGreenShift2Mask(0) +{ + /* try to get a 24 bit true color visual, if that fails, + * revert to default visual + */ + if( ! XMatchVisualInfo( m_pDisplay, DefaultScreen( m_pDisplay ), 24, TrueColor, &m_aInfo ) ) + { +#if OSL_DEBUG_LEVEL > 1 + SAL_INFO("vcl.unx.dtrans", "PixmapHolder reverting to default visual."); +#endif + Visual* pVisual = DefaultVisual( m_pDisplay, DefaultScreen( m_pDisplay ) ); + m_aInfo.screen = DefaultScreen( m_pDisplay ); + m_aInfo.visual = pVisual; + m_aInfo.visualid = pVisual->visualid; + m_aInfo.c_class = pVisual->c_class; + m_aInfo.red_mask = pVisual->red_mask; + m_aInfo.green_mask = pVisual->green_mask; + m_aInfo.blue_mask = pVisual->blue_mask; + m_aInfo.depth = DefaultDepth( m_pDisplay, m_aInfo.screen ); + } + m_aColormap = DefaultColormap( m_pDisplay, m_aInfo.screen ); +#if OSL_DEBUG_LEVEL > 1 + static const char* pClasses[] = + { "StaticGray", "GrayScale", "StaticColor", "PseudoColor", "TrueColor", "DirectColor" }; + SAL_INFO("vcl.unx.dtrans", "PixmapHolder visual: id = " + << std::showbase << std::hex + << m_aInfo.visualid + << ", class = " + << ((m_aInfo.c_class >= 0 && + unsigned(m_aInfo.c_class) < + SAL_N_ELEMENTS(pClasses)) ? + pClasses[m_aInfo.c_class] : + "<unknown>") + << " (" + << std::dec + << m_aInfo.c_class + << "), depth=" + << m_aInfo.depth + << "; color map = " + << std::showbase << std::hex + << m_aColormap); +#endif + if( m_aInfo.c_class != TrueColor ) + return; + + int nRedShift2(0); + int nGreenShift2(0); + int nBlueShift2(0); + int nRedSig, nGreenSig, nBlueSig; + getShift( m_aInfo.red_mask, m_nRedShift, nRedSig, nRedShift2 ); + getShift( m_aInfo.green_mask, m_nGreenShift, nGreenSig, nGreenShift2 ); + getShift( m_aInfo.blue_mask, m_nBlueShift, nBlueSig, nBlueShift2 ); + + m_nBlueShift2Mask = nBlueShift2 ? ~static_cast<unsigned long>((1<<nBlueShift2)-1) : ~0L; + m_nGreenShift2Mask = nGreenShift2 ? ~static_cast<unsigned long>((1<<nGreenShift2)-1) : ~0L; + m_nRedShift2Mask = nRedShift2 ? ~static_cast<unsigned long>((1<<nRedShift2)-1) : ~0L; +} + +PixmapHolder::~PixmapHolder() +{ + if( m_aPixmap != None ) + XFreePixmap( m_pDisplay, m_aPixmap ); + if( m_aBitmap != None ) + XFreePixmap( m_pDisplay, m_aBitmap ); +} + +unsigned long PixmapHolder::getTCPixel( sal_uInt8 r, sal_uInt8 g, sal_uInt8 b ) const +{ + unsigned long nPixel = 0; + unsigned long nValue = static_cast<unsigned long>(b); + nValue &= m_nBlueShift2Mask; + nPixel |= doLeftShift( nValue, m_nBlueShift ); + + nValue = static_cast<unsigned long>(g); + nValue &= m_nGreenShift2Mask; + nPixel |= doLeftShift( nValue, m_nGreenShift ); + + nValue = static_cast<unsigned long>(r); + nValue &= m_nRedShift2Mask; + nPixel |= doLeftShift( nValue, m_nRedShift ); + + return nPixel; +} + +void PixmapHolder::setBitmapDataPalette( const sal_uInt8* pData, XImage* pImage ) +{ + // setup palette + XColor aPalette[256]; + + sal_uInt32 nColors = readLE32( pData+32 ); + sal_uInt32 nWidth = readLE32( pData+4 ); + sal_uInt32 nHeight = readLE32( pData+8 ); + sal_uInt16 nDepth = readLE16( pData+14 ); + + for( sal_uInt32 i = 0 ; i < nColors; i++ ) + { + if( m_aInfo.c_class != TrueColor ) + { + //This is untainted data which comes from a controlled source + //so, using a byte-swapping pattern which coverity doesn't + //detect as such + //http://security.coverity.com/blog/2014/Apr/on-detecting-heartbleed-with-static-analysis.html + aPalette[i].red = static_cast<unsigned short>(pData[42 + i*4]); + aPalette[i].red <<= 8; + aPalette[i].red |= static_cast<unsigned short>(pData[42 + i*4]); + + aPalette[i].green = static_cast<unsigned short>(pData[41 + i*4]); + aPalette[i].green <<= 8; + aPalette[i].green |= static_cast<unsigned short>(pData[41 + i*4]); + + aPalette[i].blue = static_cast<unsigned short>(pData[40 + i*4]); + aPalette[i].blue <<= 8; + aPalette[i].blue |= static_cast<unsigned short>(pData[40 + i*4]); + XAllocColor( m_pDisplay, m_aColormap, aPalette+i ); + } + else + aPalette[i].pixel = getTCPixel( pData[42+i*4], pData[41+i*4], pData[40+i*4] ); + } + const sal_uInt8* pBMData = pData + readLE32( pData ) + 4*nColors; + + sal_uInt32 nScanlineSize = 0; + switch( nDepth ) + { + case 1: + nScanlineSize = (nWidth+31)/32; + break; + case 4: + nScanlineSize = (nWidth+1)/2; + break; + case 8: + nScanlineSize = nWidth; + break; + } + // adjust scan lines to begin on %4 boundaries + if( nScanlineSize & 3 ) + { + nScanlineSize &= 0xfffffffc; + nScanlineSize += 4; + } + + // allocate buffer to hold header and scanlines, initialize to zero + for( unsigned int y = 0; y < nHeight; y++ ) + { + const sal_uInt8* pScanline = pBMData + (nHeight-1-y)*nScanlineSize; + for( unsigned int x = 0; x < nWidth; x++ ) + { + int nCol = 0; + switch( nDepth ) + { + case 1: nCol = (pScanline[ x/8 ] & (0x80 >> (x&7))) != 0 ? 0 : 1; break; + case 4: + if( x & 1 ) + nCol = static_cast<int>(pScanline[ x/2 ] >> 4); + else + nCol = static_cast<int>(pScanline[ x/2 ] & 0x0f); + break; + case 8: nCol = static_cast<int>(pScanline[x]); + } + XPutPixel( pImage, x, y, aPalette[nCol].pixel ); + } + } +} + +void PixmapHolder::setBitmapDataTCDither( const sal_uInt8* pData, XImage* pImage ) +{ + XColor aPalette[216]; + + int nNonAllocs = 0; + + for( int r = 0; r < 6; r++ ) + { + for( int g = 0; g < 6; g++ ) + { + for( int b = 0; b < 6; b++ ) + { + int i = r*36+g*6+b; + aPalette[i].red = r == 5 ? 0xffff : r*10922; + aPalette[i].green = g == 5 ? 0xffff : g*10922; + aPalette[i].blue = b == 5 ? 0xffff : b*10922; + aPalette[i].pixel = 0; + if( ! XAllocColor( m_pDisplay, m_aColormap, aPalette+i ) ) + nNonAllocs++; + } + } + } + + if( nNonAllocs ) + { + XColor aRealPalette[256]; + int nColors = 1 << m_aInfo.depth; + int i; + for( i = 0; i < nColors; i++ ) + aRealPalette[i].pixel = static_cast<unsigned long>(i); + XQueryColors( m_pDisplay, m_aColormap, aRealPalette, nColors ); + for( i = 0; i < nColors; i++ ) + { + sal_uInt8 nIndex = + 36*static_cast<sal_uInt8>(aRealPalette[i].red/10923) + + 6*static_cast<sal_uInt8>(aRealPalette[i].green/10923) + + static_cast<sal_uInt8>(aRealPalette[i].blue/10923); + if( aPalette[nIndex].pixel == 0 ) + aPalette[nIndex] = aRealPalette[i]; + } + } + + sal_uInt32 nWidth = readLE32( pData+4 ); + sal_uInt32 nHeight = readLE32( pData+8 ); + + const sal_uInt8* pBMData = pData + readLE32( pData ); + sal_uInt32 nScanlineSize = nWidth*3; + // adjust scan lines to begin on %4 boundaries + if( nScanlineSize & 3 ) + { + nScanlineSize &= 0xfffffffc; + nScanlineSize += 4; + } + + for( int y = 0; y < static_cast<int>(nHeight); y++ ) + { + const sal_uInt8* pScanline = pBMData + (nHeight-1-static_cast<sal_uInt32>(y))*nScanlineSize; + for( int x = 0; x < static_cast<int>(nWidth); x++ ) + { + sal_uInt8 b = pScanline[3*x]; + sal_uInt8 g = pScanline[3*x+1]; + sal_uInt8 r = pScanline[3*x+2]; + sal_uInt8 i = 36*(r/43) + 6*(g/43) + (b/43); + + XPutPixel( pImage, x, y, aPalette[ i ].pixel ); + } + } +} + +void PixmapHolder::setBitmapDataTC( const sal_uInt8* pData, XImage* pImage ) +{ + sal_uInt32 nWidth = readLE32( pData+4 ); + sal_uInt32 nHeight = readLE32( pData+8 ); + + if (!nWidth || !nHeight) + return; + + const sal_uInt8* pBMData = pData + readLE32( pData ); + sal_uInt32 nScanlineSize = nWidth*3; + // adjust scan lines to begin on %4 boundaries + if( nScanlineSize & 3 ) + { + nScanlineSize &= 0xfffffffc; + nScanlineSize += 4; + } + + for( int y = 0; y < static_cast<int>(nHeight); y++ ) + { + const sal_uInt8* pScanline = pBMData + (nHeight-1-static_cast<sal_uInt32>(y))*nScanlineSize; + for( int x = 0; x < static_cast<int>(nWidth); x++ ) + { + unsigned long nPixel = getTCPixel( pScanline[3*x+2], pScanline[3*x+1], pScanline[3*x] ); + XPutPixel( pImage, x, y, nPixel ); + } + } +} + +bool PixmapHolder::needsConversion( const sal_uInt8* pData ) const +{ + if( pData[0] != 'B' || pData[1] != 'M' ) + return true; + + pData = pData+14; + sal_uInt32 nDepth = readLE32( pData+14 ); + if( nDepth == 24 ) + { + if( m_aInfo.c_class != TrueColor ) + return true; + } + else if( nDepth != static_cast<sal_uInt32>(m_aInfo.depth) ) + { + if( m_aInfo.c_class != TrueColor ) + return true; + } + + return false; +} + +Pixmap PixmapHolder::setBitmapData( const sal_uInt8* pData ) +{ + if( pData[0] != 'B' || pData[1] != 'M' ) + return None; + + pData = pData+14; + + // reject compressed data + if( readLE32( pData + 16 ) != 0 ) + return None; + + sal_uInt32 nWidth = readLE32( pData+4 ); + sal_uInt32 nHeight = readLE32( pData+8 ); + + if( m_aPixmap != None ) + { + XFreePixmap( m_pDisplay, m_aPixmap ); + m_aPixmap = None; + } + if( m_aBitmap != None ) + { + XFreePixmap( m_pDisplay, m_aBitmap ); + m_aBitmap = None; + } + + m_aPixmap = limitXCreatePixmap( m_pDisplay, + RootWindow( m_pDisplay, m_aInfo.screen ), + nWidth, nHeight, m_aInfo.depth ); + + if( m_aPixmap != None ) + { + XImage aImage; + aImage.width = static_cast<int>(nWidth); + aImage.height = static_cast<int>(nHeight); + aImage.xoffset = 0; + aImage.format = ZPixmap; + aImage.data = nullptr; + aImage.byte_order = ImageByteOrder( m_pDisplay ); + aImage.bitmap_unit = BitmapUnit( m_pDisplay ); + aImage.bitmap_bit_order = BitmapBitOrder( m_pDisplay ); + aImage.bitmap_pad = BitmapPad( m_pDisplay ); + aImage.depth = m_aInfo.depth; + aImage.red_mask = m_aInfo.red_mask; + aImage.green_mask = m_aInfo.green_mask; + aImage.blue_mask = m_aInfo.blue_mask; + aImage.bytes_per_line = 0; // filled in by XInitImage + if( m_aInfo.depth <= 8 ) + aImage.bits_per_pixel = m_aInfo.depth; + else + aImage.bits_per_pixel = 8*((m_aInfo.depth+7)/8); + aImage.obdata = nullptr; + + XInitImage( &aImage ); + aImage.data = static_cast<char*>(std::malloc( nHeight*aImage.bytes_per_line )); + + if( readLE32( pData+14 ) == 24 ) + { + if( m_aInfo.c_class == TrueColor ) + setBitmapDataTC( pData, &aImage ); + else + setBitmapDataTCDither( pData, &aImage ); + } + else + setBitmapDataPalette( pData, &aImage ); + + // put the image + XPutImage( m_pDisplay, + m_aPixmap, + DefaultGC( m_pDisplay, m_aInfo.screen ), + &aImage, + 0, 0, + 0, 0, + nWidth, nHeight ); + + // clean up + std::free( aImage.data ); + + // prepare bitmap (mask) + m_aBitmap = limitXCreatePixmap( m_pDisplay, + RootWindow( m_pDisplay, m_aInfo.screen ), + nWidth, nHeight, 1 ); + XGCValues aVal; + aVal.function = GXcopy; + aVal.foreground = 0xffffffff; + GC aGC = XCreateGC( m_pDisplay, m_aBitmap, GCFunction | GCForeground, &aVal ); + XFillRectangle( m_pDisplay, m_aBitmap, aGC, 0, 0, nWidth, nHeight ); + XFreeGC( m_pDisplay, aGC ); + } + + return m_aPixmap; +} + +css::uno::Sequence<sal_Int8> x11::convertBitmapDepth( + css::uno::Sequence<sal_Int8> const & data, int depth) +{ + if (depth < 4) { + depth = 1; + } else if (depth < 8) { + depth = 4; + } else if (depth > 8 && depth < 24) { + depth = 24; + } + SolarMutexGuard g; + SvMemoryStream in( + const_cast<sal_Int8 *>(data.getConstArray()), data.getLength(), + StreamMode::READ); + Bitmap bm; + ReadDIB(bm, in, true); + if (bm.getPixelFormat() == vcl::PixelFormat::N24_BPP && depth <= 8) { + bm.Dither(); + } + if (vcl::pixelFormatBitCount(bm.getPixelFormat()) != depth) { + switch (depth) { + case 1: + bm.Convert(BmpConversion::N1BitThreshold); + break; + case 4: + { + BitmapEx aBmpEx(bm); + BitmapFilter::Filter(aBmpEx, BitmapSimpleColorQuantizationFilter(1<<4)); + bm = aBmpEx.GetBitmap(); + } + break; + + case 8: + { + BitmapEx aBmpEx(bm); + BitmapFilter::Filter(aBmpEx, BitmapSimpleColorQuantizationFilter(1<<8)); + bm = aBmpEx.GetBitmap(); + } + break; + + case 24: + bm.Convert(BmpConversion::N24Bit); + break; + } + } + SvMemoryStream out; + WriteDIB(bm, out, false, true); + return css::uno::Sequence<sal_Int8>( + static_cast<sal_Int8 const *>(out.GetData()), out.GetEndOfData()); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/generic/dtrans/bmp.hxx b/vcl/unx/generic/dtrans/bmp.hxx new file mode 100644 index 000000000..3a37158db --- /dev/null +++ b/vcl/unx/generic/dtrans/bmp.hxx @@ -0,0 +1,75 @@ +/* -*- 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 . + */ + +#pragma once + +#include <X11/Xlib.h> +#include <X11/Xutil.h> + +#include <com/sun/star/uno/Sequence.hxx> +#include <sal/types.h> + +namespace x11 { + +// helper methods +sal_uInt8* X11_getBmpFromPixmap( Display* pDisplay, + Drawable aDrawable, + Colormap aColormap, + sal_Int32& rOutSize ); + +class PixmapHolder +{ + Display* m_pDisplay; + Colormap m_aColormap; + Pixmap m_aPixmap; + Pixmap m_aBitmap; + XVisualInfo m_aInfo; + + int m_nRedShift; + int m_nGreenShift; + int m_nBlueShift; + tools::ULong m_nBlueShift2Mask, m_nRedShift2Mask, m_nGreenShift2Mask; + + // these expect data pointers to bitmapinfo header + void setBitmapDataTC( const sal_uInt8* pData, XImage* pImage ); + void setBitmapDataTCDither( const sal_uInt8* pData, XImage* pImage ); + void setBitmapDataPalette( const sal_uInt8* pData, XImage* pImage ); + + tools::ULong getTCPixel( sal_uInt8 r, sal_uInt8 g, sal_uInt8 b ) const; +public: + PixmapHolder( Display* pDisplay ); + ~PixmapHolder(); + + // accepts bitmap file (including bitmap file header) + Pixmap setBitmapData( const sal_uInt8* pData ); + bool needsConversion( const sal_uInt8* pData ) const; + + Colormap getColormap() const { return m_aColormap; } + Pixmap getPixmap() const { return m_aPixmap; } + Pixmap getBitmap() const { return m_aBitmap; } + VisualID getVisualID() const { return m_aInfo.visualid; } + int getDepth() const { return m_aInfo.depth; } +}; + +css::uno::Sequence<sal_Int8> convertBitmapDepth( + css::uno::Sequence<sal_Int8> const & data, int depth); + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/generic/dtrans/config.cxx b/vcl/unx/generic/dtrans/config.cxx new file mode 100644 index 000000000..1d851a28d --- /dev/null +++ b/vcl/unx/generic/dtrans/config.cxx @@ -0,0 +1,123 @@ +/* -*- 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 <o3tl/any.hxx> +#include <sal/log.hxx> +#include <unotools/configitem.hxx> + +#include "X11_selection.hxx" + +constexpr OUStringLiteral SETTINGS_CONFIGNODE = u"VCL/Settings/Transfer"; +constexpr OUStringLiteral SELECTION_PROPERTY = u"SelectionTimeout"; + +namespace x11 +{ + +namespace { + +class DtransX11ConfigItem : public ::utl::ConfigItem +{ + sal_Int32 m_nSelectionTimeout; + + virtual void Notify( const css::uno::Sequence< OUString >& rPropertyNames ) override; + virtual void ImplCommit() override; + +public: + DtransX11ConfigItem(); + + sal_Int32 getSelectionTimeout() const { return m_nSelectionTimeout; } +}; + +} + +} + +using namespace com::sun::star::lang; +using namespace com::sun::star::uno; +using namespace x11; + +sal_Int32 SelectionManager::getSelectionTimeout() +{ + if( m_nSelectionTimeout < 1 ) + { + DtransX11ConfigItem aCfg; + m_nSelectionTimeout = aCfg.getSelectionTimeout(); +#if OSL_DEBUG_LEVEL > 1 + SAL_INFO("vcl.unx.dtrans", "initialized selection timeout to " + << m_nSelectionTimeout + << " seconds."); +#endif + } + return m_nSelectionTimeout; +} + +/* + * DtransX11ConfigItem constructor + */ + +DtransX11ConfigItem::DtransX11ConfigItem() : + ConfigItem( SETTINGS_CONFIGNODE, + ConfigItemMode::NONE ), + m_nSelectionTimeout( 3 ) +{ + Sequence<OUString> aKeys { SELECTION_PROPERTY }; + const Sequence< Any > aValues = GetProperties( aKeys ); +#if OSL_DEBUG_LEVEL > 1 + SAL_INFO("vcl.unx.dtrans", "found " + << aValues.getLength() + << " properties for " + << SELECTION_PROPERTY); +#endif + for( Any const & value : aValues ) + { + if( auto pLine = o3tl::tryAccess<OUString>(value) ) + { + if( !pLine->isEmpty() ) + { + m_nSelectionTimeout = pLine->toInt32(); + if( m_nSelectionTimeout < 1 ) + m_nSelectionTimeout = 1; + } +#if OSL_DEBUG_LEVEL > 1 + SAL_INFO("vcl.unx.dtrans", "found SelectionTimeout \"" << *pLine << "\"."); +#endif + } +#if OSL_DEBUG_LEVEL > 1 + else + SAL_INFO("vcl.unx.dtrans", "found SelectionTimeout of type \"" + << value.getValueType().getTypeName() << "\"."); +#endif + } +} + +void DtransX11ConfigItem::ImplCommit() +{ + // for the clipboard service this is readonly, so + // there is nothing to commit +} + +/* + * DtransX11ConfigItem::Notify + */ + +void DtransX11ConfigItem::Notify( const Sequence< OUString >& /*rPropertyNames*/ ) +{ +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/generic/dtrans/copydata_curs.h b/vcl/unx/generic/dtrans/copydata_curs.h new file mode 100644 index 000000000..4cc36ebde --- /dev/null +++ b/vcl/unx/generic/dtrans/copydata_curs.h @@ -0,0 +1,36 @@ +/* -*- 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 . + */ +#define copydata_curs_width 32 +#define copydata_curs_height 32 +#define copydata_curs_x_hot 1 +#define copydata_curs_y_hot 1 +static unsigned char copydata_curs_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, + 0x0e, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00, + 0x7e, 0x00, 0x00, 0x00, 0xfe, 0x00, 0x00, 0x00, 0xfe, 0x01, 0x00, 0x00, + 0xfe, 0x03, 0x00, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x6e, 0x00, 0x00, 0x00, + 0x66, 0x00, 0x00, 0x00, 0xc2, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, + 0x80, 0x01, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0x10, 0x53, 0x00, 0x00, + 0x28, 0xa3, 0x00, 0x00, 0x10, 0x40, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x10, 0xf0, 0x1f, 0x00, 0x08, 0xf0, 0x1f, 0x00, 0x10, 0xf0, 0x1e, 0x00, + 0xa8, 0xf2, 0x1e, 0x00, 0x50, 0x35, 0x18, 0x00, 0x00, 0xf0, 0x1e, 0x00, + 0x00, 0xf0, 0x1e, 0x00, 0x00, 0xf0, 0x1f, 0x00, 0x00, 0xf0, 0x1f, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/generic/dtrans/copydata_mask.h b/vcl/unx/generic/dtrans/copydata_mask.h new file mode 100644 index 000000000..a3538c952 --- /dev/null +++ b/vcl/unx/generic/dtrans/copydata_mask.h @@ -0,0 +1,36 @@ +/* -*- 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 . + */ +#define copydata_mask_width 32 +#define copydata_mask_height 32 +#define copydata_mask_x_hot 1 +#define copydata_mask_y_hot 1 +static unsigned char copydata_mask_bits[] = { + 0x07, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, + 0x3f, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, + 0xff, 0x01, 0x00, 0x00, 0xff, 0x03, 0x00, 0x00, 0xff, 0x07, 0x00, 0x00, + 0xff, 0x07, 0x00, 0x00, 0xff, 0x07, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, + 0xff, 0x01, 0x00, 0x00, 0xff, 0x01, 0x00, 0x00, 0xe7, 0x03, 0x00, 0x00, + 0xe0, 0x03, 0x00, 0x00, 0xf8, 0xff, 0x00, 0x00, 0xfc, 0xff, 0x01, 0x00, + 0xfc, 0xff, 0x01, 0x00, 0xfc, 0xff, 0x01, 0x00, 0x3c, 0xf8, 0x3f, 0x00, + 0x3c, 0xf8, 0x3f, 0x00, 0x3c, 0xf8, 0x3f, 0x00, 0xfc, 0xff, 0x3f, 0x00, + 0xfc, 0xff, 0x3f, 0x00, 0xfc, 0xff, 0x3f, 0x00, 0xf8, 0xff, 0x3f, 0x00, + 0x00, 0xf8, 0x3f, 0x00, 0x00, 0xf8, 0x3f, 0x00, 0x00, 0xf8, 0x3f, 0x00, + 0x00, 0xf8, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/generic/dtrans/linkdata_curs.h b/vcl/unx/generic/dtrans/linkdata_curs.h new file mode 100644 index 000000000..8a4e6db38 --- /dev/null +++ b/vcl/unx/generic/dtrans/linkdata_curs.h @@ -0,0 +1,36 @@ +/* -*- 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 . + */ +#define linkdata_curs_width 32 +#define linkdata_curs_height 32 +#define linkdata_curs_x_hot 1 +#define linkdata_curs_y_hot 1 +static unsigned char linkdata_curs_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, + 0x0e, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00, + 0x7e, 0x00, 0x00, 0x00, 0xfe, 0x00, 0x00, 0x00, 0xfe, 0x01, 0x00, 0x00, + 0xfe, 0x03, 0x00, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x6e, 0x00, 0x00, 0x00, + 0x66, 0x00, 0x00, 0x00, 0xc2, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, + 0x80, 0x01, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0x10, 0x53, 0x00, 0x00, + 0x28, 0xa3, 0x00, 0x00, 0x10, 0x40, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x10, 0xf0, 0x1f, 0x00, 0x08, 0x70, 0x18, 0x00, 0x10, 0xf0, 0x18, 0x00, + 0xa8, 0x72, 0x18, 0x00, 0x50, 0x35, 0x1a, 0x00, 0x00, 0x30, 0x1f, 0x00, + 0x00, 0xb0, 0x1f, 0x00, 0x00, 0x70, 0x1f, 0x00, 0x00, 0xf0, 0x1f, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/generic/dtrans/linkdata_mask.h b/vcl/unx/generic/dtrans/linkdata_mask.h new file mode 100644 index 000000000..a1875a8e0 --- /dev/null +++ b/vcl/unx/generic/dtrans/linkdata_mask.h @@ -0,0 +1,36 @@ +/* -*- 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 . + */ +#define linkdata_mask_width 32 +#define linkdata_mask_height 32 +#define linkdata_mask_x_hot 1 +#define linkdata_mask_y_hot 1 +static unsigned char linkdata_mask_bits[] = { + 0x07, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, + 0x3f, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, + 0xff, 0x01, 0x00, 0x00, 0xff, 0x03, 0x00, 0x00, 0xff, 0x07, 0x00, 0x00, + 0xff, 0x07, 0x00, 0x00, 0xff, 0x07, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, + 0xff, 0x01, 0x00, 0x00, 0xff, 0x01, 0x00, 0x00, 0xe7, 0x03, 0x00, 0x00, + 0xe0, 0x03, 0x00, 0x00, 0xf8, 0xff, 0x00, 0x00, 0xfc, 0xff, 0x01, 0x00, + 0xfc, 0xff, 0x01, 0x00, 0xfc, 0xff, 0x01, 0x00, 0x3c, 0xf8, 0x3f, 0x00, + 0x3c, 0xf8, 0x3f, 0x00, 0x3c, 0xf8, 0x3f, 0x00, 0xfc, 0xff, 0x3f, 0x00, + 0xfc, 0xff, 0x3f, 0x00, 0xfc, 0xff, 0x3f, 0x00, 0xf8, 0xff, 0x3f, 0x00, + 0x00, 0xf8, 0x3f, 0x00, 0x00, 0xf8, 0x3f, 0x00, 0x00, 0xf8, 0x3f, 0x00, + 0x00, 0xf8, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/generic/dtrans/movedata_curs.h b/vcl/unx/generic/dtrans/movedata_curs.h new file mode 100644 index 000000000..b253ce70c --- /dev/null +++ b/vcl/unx/generic/dtrans/movedata_curs.h @@ -0,0 +1,36 @@ +/* -*- 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 . + */ +#define movedata_curs_width 32 +#define movedata_curs_height 32 +#define movedata_curs_x_hot 1 +#define movedata_curs_y_hot 1 +static unsigned char movedata_curs_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, + 0x0e, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00, + 0x7e, 0x00, 0x00, 0x00, 0xfe, 0x00, 0x00, 0x00, 0xfe, 0x01, 0x00, 0x00, + 0xfe, 0x03, 0x00, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x6e, 0x00, 0x00, 0x00, + 0x66, 0x00, 0x00, 0x00, 0xc2, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, + 0x80, 0x01, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0x10, 0x53, 0x00, 0x00, + 0x28, 0xa3, 0x00, 0x00, 0x10, 0x40, 0x00, 0x00, 0x08, 0x80, 0x00, 0x00, + 0x10, 0x40, 0x00, 0x00, 0x08, 0x80, 0x00, 0x00, 0x10, 0x40, 0x00, 0x00, + 0xa8, 0xaa, 0x00, 0x00, 0x50, 0x55, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/generic/dtrans/movedata_mask.h b/vcl/unx/generic/dtrans/movedata_mask.h new file mode 100644 index 000000000..d317b1556 --- /dev/null +++ b/vcl/unx/generic/dtrans/movedata_mask.h @@ -0,0 +1,36 @@ +/* -*- 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 . + */ +#define movedata_mask_width 32 +#define movedata_mask_height 32 +#define movedata_mask_x_hot 1 +#define movedata_mask_y_hot 1 +static unsigned char movedata_mask_bits[] = { + 0x07, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, + 0x3f, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, + 0xff, 0x01, 0x00, 0x00, 0xff, 0x03, 0x00, 0x00, 0xff, 0x07, 0x00, 0x00, + 0xff, 0x07, 0x00, 0x00, 0xff, 0x07, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, + 0xff, 0x01, 0x00, 0x00, 0xff, 0x01, 0x00, 0x00, 0xe7, 0x03, 0x00, 0x00, + 0xe0, 0x03, 0x00, 0x00, 0xf8, 0xff, 0x00, 0x00, 0xfc, 0xff, 0x01, 0x00, + 0xfc, 0xff, 0x01, 0x00, 0xfc, 0xff, 0x01, 0x00, 0x3c, 0xe0, 0x01, 0x00, + 0x3c, 0xe0, 0x01, 0x00, 0x3c, 0xe0, 0x01, 0x00, 0xfc, 0xff, 0x01, 0x00, + 0xfc, 0xff, 0x01, 0x00, 0xfc, 0xff, 0x01, 0x00, 0xf8, 0xff, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/generic/dtrans/nodrop_curs.h b/vcl/unx/generic/dtrans/nodrop_curs.h new file mode 100644 index 000000000..958257518 --- /dev/null +++ b/vcl/unx/generic/dtrans/nodrop_curs.h @@ -0,0 +1,36 @@ +/* -*- 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 . + */ +#define nodrop_curs_width 32 +#define nodrop_curs_height 32 +#define nodrop_curs_x_hot 9 +#define nodrop_curs_y_hot 9 +static unsigned char nodrop_curs_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0xc0, 0x0f, 0x00, 0x00, 0xf0, 0x3f, 0x00, 0x00, + 0xf8, 0x7f, 0x00, 0x00, 0x7c, 0xf8, 0x00, 0x00, 0x1c, 0xfc, 0x00, 0x00, + 0x1e, 0xfe, 0x01, 0x00, 0x0e, 0xdf, 0x01, 0x00, 0x8e, 0xcf, 0x01, 0x00, + 0xce, 0xc7, 0x01, 0x00, 0xee, 0xc3, 0x01, 0x00, 0xfe, 0xe1, 0x01, 0x00, + 0xfc, 0xe0, 0x00, 0x00, 0x7c, 0xf8, 0x00, 0x00, 0xf8, 0x7f, 0x00, 0x00, + 0xf0, 0x3f, 0x00, 0x00, 0xc0, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/generic/dtrans/nodrop_mask.h b/vcl/unx/generic/dtrans/nodrop_mask.h new file mode 100644 index 000000000..662a30064 --- /dev/null +++ b/vcl/unx/generic/dtrans/nodrop_mask.h @@ -0,0 +1,36 @@ +/* -*- 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 . + */ +#define nodrop_mask_width 32 +#define nodrop_mask_height 32 +#define nodrop_mask_x_hot 9 +#define nodrop_mask_y_hot 9 +static unsigned char nodrop_mask_bits[] = { + 0xc0, 0x0f, 0x00, 0x00, 0xf0, 0x3f, 0x00, 0x00, 0xf8, 0x7f, 0x00, 0x00, + 0xfc, 0xff, 0x00, 0x00, 0xfe, 0xff, 0x01, 0x00, 0x7e, 0xfe, 0x01, 0x00, + 0x3f, 0xff, 0x03, 0x00, 0x9f, 0xff, 0x03, 0x00, 0xdf, 0xff, 0x03, 0x00, + 0xff, 0xef, 0x03, 0x00, 0xff, 0xe7, 0x03, 0x00, 0xff, 0xf3, 0x03, 0x00, + 0xfe, 0xf9, 0x01, 0x00, 0xfe, 0xff, 0x01, 0x00, 0xfc, 0xff, 0x00, 0x00, + 0xf8, 0x7f, 0x00, 0x00, 0xf0, 0x3f, 0x00, 0x00, 0xc0, 0x0f, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/generic/fontmanager/fontconfig.cxx b/vcl/unx/generic/fontmanager/fontconfig.cxx new file mode 100644 index 000000000..c44cdd2ee --- /dev/null +++ b/vcl/unx/generic/fontmanager/fontconfig.cxx @@ -0,0 +1,1316 @@ +/* -*- 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 . + */ + +#if defined __GNUC__ && !defined __clang_ && __GNUC__ == 10 +// gcc 10.2.0 gets unhappy about one of the OString inside PrintFont at line 656 (while at least a +// recent GCC 12 trunk is happy); +// I have to turn it off here because the warning actually occurs inside rtl::OString +#pragma GCC diagnostic ignored "-Wmaybe-uninitialized" +#endif + +#include <sal/config.h> + +#include <memory> +#include <string_view> + +#include <o3tl/lru_map.hxx> +#include <unx/fontmanager.hxx> +#include <unx/helper.hxx> +#include <comphelper/sequence.hxx> +#include <vcl/svapp.hxx> +#include <vcl/vclenum.hxx> +#include <font/FontSelectPattern.hxx> +#include <i18nlangtag/languagetag.hxx> +#include <i18nutil/unicode.hxx> +#include <rtl/strbuf.hxx> +#include <sal/log.hxx> +#include <tools/diagnose_ex.h> +#include <unicode/uchar.h> +#include <unicode/uscript.h> +#include <officecfg/Office/Common.hxx> +#include <org/freedesktop/PackageKit/SyncDbusSessionHelper.hpp> +#include <config_fonts.h> + +using namespace psp; + +#include <fontconfig/fontconfig.h> + +#include <cstdio> + +#include <unotools/configmgr.hxx> +#include <unotools/syslocaleoptions.hxx> + +#include <osl/process.h> + +#include <o3tl/hash_combine.hxx> +#include <utility> +#include <algorithm> + +using namespace osl; + +namespace +{ + +struct FontOptionsKey +{ + OUString m_sFamilyName; + int m_nFontSize; + FontItalic m_eItalic; + FontWeight m_eWeight; + FontWidth m_eWidth; + FontPitch m_ePitch; + + bool operator==(const FontOptionsKey& rOther) const + { + return m_sFamilyName == rOther.m_sFamilyName && + m_nFontSize == rOther.m_nFontSize && + m_eItalic == rOther.m_eItalic && + m_eWeight == rOther.m_eWeight && + m_eWidth == rOther.m_eWidth && + m_ePitch == rOther.m_ePitch; + } +}; + +} + +namespace std +{ + +template <> struct hash<FontOptionsKey> +{ + std::size_t operator()(const FontOptionsKey& k) const noexcept + { + std::size_t seed = k.m_sFamilyName.hashCode(); + o3tl::hash_combine(seed, k.m_nFontSize); + o3tl::hash_combine(seed, k.m_eItalic); + o3tl::hash_combine(seed, k.m_eWeight); + o3tl::hash_combine(seed, k.m_eWidth); + o3tl::hash_combine(seed, k.m_ePitch); + return seed; + } +}; + +} // end std namespace + +namespace +{ + +struct FcPatternDeleter +{ + void operator()(FcPattern* pPattern) const + { + FcPatternDestroy(pPattern); + } +}; + +typedef std::unique_ptr<FcPattern, FcPatternDeleter> FcPatternUniquePtr; + +class CachedFontConfigFontOptions +{ +private: + o3tl::lru_map<FontOptionsKey, FcPatternUniquePtr> lru_options_cache; + +public: + CachedFontConfigFontOptions() + : lru_options_cache(10) // arbitrary cache size of 10 + { + } + + std::unique_ptr<FontConfigFontOptions> lookup(const FontOptionsKey& rKey) + { + auto it = lru_options_cache.find(rKey); + if (it != lru_options_cache.end()) + return std::make_unique<FontConfigFontOptions>(FcPatternDuplicate(it->second.get())); + return nullptr; + } + + void cache(const FontOptionsKey& rKey, const FcPattern* pPattern) + { + lru_options_cache.insert(std::make_pair(rKey, FcPatternUniquePtr(FcPatternDuplicate(pPattern)))); + } + +}; + +typedef std::pair<FcChar8*, FcChar8*> lang_and_element; + +class FontCfgWrapper +{ + FcFontSet* m_pFontSet; + + void addFontSet( FcSetName ); + + FontCfgWrapper(); + ~FontCfgWrapper(); + +public: + static FontCfgWrapper& get(); + static void release(); + + FcFontSet* getFontSet(); + + void clear(); + +public: + FcResult LocalizedElementFromPattern(FcPattern const * pPattern, FcChar8 **family, + const char *elementtype, const char *elementlangtype); +//to-do, make private and add some cleaner accessor methods + std::unordered_map< OString, OString > m_aFontNameToLocalized; + std::unordered_map< OString, OString > m_aLocalizedToCanonical; + CachedFontConfigFontOptions m_aCachedFontOptions; +private: + void cacheLocalizedFontNames(const FcChar8 *origfontname, const FcChar8 *bestfontname, const std::vector< lang_and_element > &lang_and_elements); + + std::unique_ptr<LanguageTag> m_pLanguageTag; +}; + +} + +FontCfgWrapper::FontCfgWrapper() + : m_pFontSet( nullptr ) +{ + FcInit(); +} + +void FontCfgWrapper::addFontSet( FcSetName eSetName ) +{ + // Add only acceptable fonts to our config, for future fontconfig use. + FcFontSet* pOrig = FcConfigGetFonts( FcConfigGetCurrent(), eSetName ); + if( !pOrig ) + return; + + // filter the font sets to remove obsolete faces + for( int i = 0; i < pOrig->nfont; ++i ) + { + FcPattern* pPattern = pOrig->fonts[i]; + // #i115131# ignore non-scalable fonts + // Scalable fonts are usually outline fonts, but some bitmaps fonts + // (like Noto Color Emoji) are also scalable. + FcBool bScalable = FcFalse; + FcResult eScalableRes = FcPatternGetBool(pPattern, FC_SCALABLE, 0, &bScalable); + if ((eScalableRes != FcResultMatch) || (bScalable == FcFalse)) + continue; + + // Ignore Type 1 fonts, too. + FcChar8* pFormat = nullptr; + FcResult eFormatRes = FcPatternGetString(pPattern, FC_FONTFORMAT, 0, &pFormat); + if ((eFormatRes == FcResultMatch) && (strcmp(reinterpret_cast<char*>(pFormat), "Type 1") == 0)) + continue; + + FcPatternReference( pPattern ); + FcFontSetAdd( m_pFontSet, pPattern ); + } + + // TODO?: FcFontSetDestroy( pOrig ); +} + +namespace +{ + int compareFontNames(const FcPattern *a, const FcPattern *b) + { + FcChar8 *pNameA=nullptr, *pNameB=nullptr; + + bool bHaveA = FcPatternGetString(a, FC_FAMILY, 0, &pNameA) == FcResultMatch; + bool bHaveB = FcPatternGetString(b, FC_FAMILY, 0, &pNameB) == FcResultMatch; + + if (bHaveA && bHaveB) + return strcmp(reinterpret_cast<const char*>(pNameA), reinterpret_cast<const char*>(pNameB)); + + return int(bHaveA) - int(bHaveB); + } + + //Sort fonts so that fonts with the same family name are side-by-side, with + //those with higher version numbers first + class SortFont + { + public: + bool operator()(const FcPattern *a, const FcPattern *b) + { + int comp = compareFontNames(a, b); + if (comp != 0) + return comp < 0; + + int nVersionA=0, nVersionB=0; + + bool bHaveA = FcPatternGetInteger(a, FC_FONTVERSION, 0, &nVersionA) == FcResultMatch; + bool bHaveB = FcPatternGetInteger(b, FC_FONTVERSION, 0, &nVersionB) == FcResultMatch; + + if (bHaveA && bHaveB) + return nVersionA > nVersionB; + + return bHaveA > bHaveB; + } + }; + + //See fdo#30729 for where an old opensymbol installed system-wide can + //clobber the new opensymbol installed locally + + //See if this font is a duplicate with equal attributes which has already been + //inserted, or if it an older version of an inserted fonts. Depends on FcFontSet + //on being sorted with SortFont + bool isPreviouslyDuplicateOrObsoleted(FcFontSet const *pFSet, int i) + { + const FcPattern *a = pFSet->fonts[i]; + + FcPattern* pTestPatternA = FcPatternDuplicate(a); + FcPatternDel(pTestPatternA, FC_FILE); + FcPatternDel(pTestPatternA, FC_CHARSET); + FcPatternDel(pTestPatternA, FC_CAPABILITY); + FcPatternDel(pTestPatternA, FC_FONTVERSION); + FcPatternDel(pTestPatternA, FC_LANG); + + bool bIsDup(false); + + // fdo#66715: loop for case of several font files for same font + for (int j = i - 1; 0 <= j && !bIsDup; --j) + { + const FcPattern *b = pFSet->fonts[j]; + + if (compareFontNames(a, b) != 0) + break; + + FcPattern* pTestPatternB = FcPatternDuplicate(b); + FcPatternDel(pTestPatternB, FC_FILE); + FcPatternDel(pTestPatternB, FC_CHARSET); + FcPatternDel(pTestPatternB, FC_CAPABILITY); + FcPatternDel(pTestPatternB, FC_FONTVERSION); + FcPatternDel(pTestPatternB, FC_LANG); + + bIsDup = FcPatternEqual(pTestPatternA, pTestPatternB); + + FcPatternDestroy(pTestPatternB); + } + + FcPatternDestroy(pTestPatternA); + + return bIsDup; + } +} + +FcFontSet* FontCfgWrapper::getFontSet() +{ + if( !m_pFontSet ) + { + m_pFontSet = FcFontSetCreate(); + bool bRestrictFontSetToApplicationFonts = false; +#if HAVE_MORE_FONTS + bRestrictFontSetToApplicationFonts = getenv("SAL_ABORT_ON_NON_APPLICATION_FONT_USE") != nullptr; +#endif + if (!bRestrictFontSetToApplicationFonts) + addFontSet( FcSetSystem ); + addFontSet( FcSetApplication ); + + std::stable_sort(m_pFontSet->fonts,m_pFontSet->fonts+m_pFontSet->nfont,SortFont()); + } + + return m_pFontSet; +} + +FontCfgWrapper::~FontCfgWrapper() +{ + clear(); + //To-Do: get gtk vclplug smoketest to pass + //FcFini(); +} + +static FontCfgWrapper* pOneInstance = nullptr; + +FontCfgWrapper& FontCfgWrapper::get() +{ + if( ! pOneInstance ) + pOneInstance = new FontCfgWrapper(); + return *pOneInstance; +} + +void FontCfgWrapper::release() +{ + if( pOneInstance ) + { + delete pOneInstance; + pOneInstance = nullptr; + } +} + +namespace +{ + FcChar8* bestname(const std::vector<lang_and_element> &elements, const LanguageTag & rLangTag); + + FcChar8* bestname(const std::vector<lang_and_element> &elements, const LanguageTag & rLangTag) + { + FcChar8* candidate = elements.begin()->second; + /* FIXME-BCP47: once fontconfig supports language tags this + * language-territory stuff needs to be changed! */ + SAL_INFO_IF( !rLangTag.isIsoLocale(), "vcl.fonts", "localizedsorter::bestname - not an ISO locale"); + OString sLangMatch(OUStringToOString(rLangTag.getLanguage().toAsciiLowerCase(), RTL_TEXTENCODING_UTF8)); + OString sFullMatch = sLangMatch + + "-" + + OUStringToOString(rLangTag.getCountry().toAsciiLowerCase(), RTL_TEXTENCODING_UTF8); + + bool alreadyclosematch = false; + bool found_fallback_englishname = false; + for (auto const& element : elements) + { + const char *pLang = reinterpret_cast<const char*>(element.first); + if( sFullMatch == pLang) + { + // both language and country match + candidate = element.second; + break; + } + else if( alreadyclosematch ) + { + // current candidate matches lang of lang-TERRITORY + // override candidate only if there is a full match + continue; + } + else if( sLangMatch == pLang) + { + // just the language matches + candidate = element.second; + alreadyclosematch = true; + } + else if( found_fallback_englishname ) + { + // already found an english fallback, don't override candidate + // unless there is a better language match + continue; + } + else if( rtl_str_compare( pLang, "en") == 0) + { + // select a fallback candidate of the first english element + // name + candidate = element.second; + found_fallback_englishname = true; + } + } + return candidate; + } +} + +//Set up maps to quickly map between a fonts best UI name and all the rest of its names, and vice versa +void FontCfgWrapper::cacheLocalizedFontNames(const FcChar8 *origfontname, const FcChar8 *bestfontname, + const std::vector< lang_and_element > &lang_and_elements) +{ + for (auto const& element : lang_and_elements) + { + const char *candidate = reinterpret_cast<const char*>(element.second); + if (rtl_str_compare(candidate, reinterpret_cast<const char*>(bestfontname)) != 0) + m_aFontNameToLocalized[OString(candidate)] = OString(reinterpret_cast<const char*>(bestfontname)); + } + if (rtl_str_compare(reinterpret_cast<const char*>(origfontname), reinterpret_cast<const char*>(bestfontname)) != 0) + m_aLocalizedToCanonical[OString(reinterpret_cast<const char*>(bestfontname))] = OString(reinterpret_cast<const char*>(origfontname)); +} + +FcResult FontCfgWrapper::LocalizedElementFromPattern(FcPattern const * pPattern, FcChar8 **element, + const char *elementtype, const char *elementlangtype) +{ /* e. g.: ^ FC_FAMILY ^ FC_FAMILYLANG */ + FcChar8 *origelement; + FcResult eElementRes = FcPatternGetString( pPattern, elementtype, 0, &origelement ); + *element = origelement; + + if( eElementRes == FcResultMatch) + { + FcChar8* elementlang = nullptr; + if (FcPatternGetString( pPattern, elementlangtype, 0, &elementlang ) == FcResultMatch) + { + std::vector< lang_and_element > lang_and_elements; + lang_and_elements.emplace_back(elementlang, *element); + int k = 1; + while (true) + { + if (FcPatternGetString( pPattern, elementlangtype, k, &elementlang ) != FcResultMatch) + break; + if (FcPatternGetString( pPattern, elementtype, k, element ) != FcResultMatch) + break; + lang_and_elements.emplace_back(elementlang, *element); + ++k; + } + + if (!m_pLanguageTag) + m_pLanguageTag.reset(new LanguageTag(SvtSysLocaleOptions().GetRealUILanguageTag())); + + *element = bestname(lang_and_elements, *m_pLanguageTag); + + //if this element is a fontname, map the other names to this best-name + if (rtl_str_compare(elementtype, FC_FAMILY) == 0) + cacheLocalizedFontNames(origelement, *element, lang_and_elements); + } + } + + return eElementRes; +} + +void FontCfgWrapper::clear() +{ + m_aFontNameToLocalized.clear(); + m_aLocalizedToCanonical.clear(); + if( m_pFontSet ) + { + FcFontSetDestroy( m_pFontSet ); + m_pFontSet = nullptr; + } + m_pLanguageTag.reset(); +} + +/* + * PrintFontManager::initFontconfig + */ +void PrintFontManager::initFontconfig() +{ + FontCfgWrapper& rWrapper = FontCfgWrapper::get(); + rWrapper.clear(); +} + +namespace +{ + FontWeight convertWeight(int weight) + { + // set weight + if( weight <= FC_WEIGHT_THIN ) + return WEIGHT_THIN; + else if( weight <= FC_WEIGHT_ULTRALIGHT ) + return WEIGHT_ULTRALIGHT; + else if( weight <= FC_WEIGHT_LIGHT ) + return WEIGHT_LIGHT; + else if( weight <= FC_WEIGHT_BOOK ) + return WEIGHT_SEMILIGHT; + else if( weight <= FC_WEIGHT_NORMAL ) + return WEIGHT_NORMAL; + else if( weight <= FC_WEIGHT_MEDIUM ) + return WEIGHT_MEDIUM; + else if( weight <= FC_WEIGHT_SEMIBOLD ) + return WEIGHT_SEMIBOLD; + else if( weight <= FC_WEIGHT_BOLD ) + return WEIGHT_BOLD; + else if( weight <= FC_WEIGHT_ULTRABOLD ) + return WEIGHT_ULTRABOLD; + return WEIGHT_BLACK; + } + + FontItalic convertSlant(int slant) + { + // set italic + if( slant == FC_SLANT_ITALIC ) + return ITALIC_NORMAL; + else if( slant == FC_SLANT_OBLIQUE ) + return ITALIC_OBLIQUE; + return ITALIC_NONE; + } + + FontPitch convertSpacing(int spacing) + { + // set pitch + if( spacing == FC_MONO || spacing == FC_CHARCELL ) + return PITCH_FIXED; + return PITCH_VARIABLE; + } + + // translation: fontconfig enum -> vcl enum + FontWidth convertWidth(int width) + { + if (width == FC_WIDTH_ULTRACONDENSED) + return WIDTH_ULTRA_CONDENSED; + else if (width == FC_WIDTH_EXTRACONDENSED) + return WIDTH_EXTRA_CONDENSED; + else if (width == FC_WIDTH_CONDENSED) + return WIDTH_CONDENSED; + else if (width == FC_WIDTH_SEMICONDENSED) + return WIDTH_SEMI_CONDENSED; + else if (width == FC_WIDTH_SEMIEXPANDED) + return WIDTH_SEMI_EXPANDED; + else if (width == FC_WIDTH_EXPANDED) + return WIDTH_EXPANDED; + else if (width == FC_WIDTH_EXTRAEXPANDED) + return WIDTH_EXTRA_EXPANDED; + else if (width == FC_WIDTH_ULTRAEXPANDED) + return WIDTH_ULTRA_EXPANDED; + return WIDTH_NORMAL; + } +} + +//FontConfig doesn't come with a way to remove an element from a FontSet as far +//as I can see +static void lcl_FcFontSetRemove(FcFontSet* pFSet, int i) +{ + FcPatternDestroy(pFSet->fonts[i]); + + int nTail = pFSet->nfont - (i + 1); + --pFSet->nfont; + if (!nTail) + return; + memmove(pFSet->fonts + i, pFSet->fonts + i + 1, nTail*sizeof(FcPattern*)); +} + +namespace +{ + // for variable fonts, FC_INDEX has been changed such that the lower half is now the + // index of the font within the collection, and the upper half has been repurposed + // as the index within the variations + unsigned int GetCollectionIndex(unsigned int nEntryId) + { + return nEntryId & 0xFFFF; + } + + unsigned int GetVariationIndex(unsigned int nEntryId) + { + return nEntryId >> 16; + } +} + +void PrintFontManager::countFontconfigFonts() +{ + int nFonts = 0; + FontCfgWrapper& rWrapper = FontCfgWrapper::get(); + + FcFontSet* pFSet = rWrapper.getFontSet(); + const bool bMinimalFontset = utl::ConfigManager::IsFuzzing(); + if( pFSet ) + { + SAL_INFO("vcl.fonts", "found " << pFSet->nfont << " entries in fontconfig fontset"); + for( int i = 0; i < pFSet->nfont; i++ ) + { + FcChar8* file = nullptr; + FcChar8* family = nullptr; + FcChar8* style = nullptr; + FcChar8* format = nullptr; + int slant = 0; + int weight = 0; + int width = 0; + int spacing = 0; + int nEntryId = -1; + FcBool scalable = false; + + FcResult eFileRes = FcPatternGetString(pFSet->fonts[i], FC_FILE, 0, &file); + FcResult eFamilyRes = rWrapper.LocalizedElementFromPattern( pFSet->fonts[i], &family, FC_FAMILY, FC_FAMILYLANG ); + if (bMinimalFontset && strncmp(reinterpret_cast<char*>(family), "Liberation", strlen("Liberation"))) + continue; + FcResult eStyleRes = rWrapper.LocalizedElementFromPattern( pFSet->fonts[i], &style, FC_STYLE, FC_STYLELANG ); + FcResult eSlantRes = FcPatternGetInteger(pFSet->fonts[i], FC_SLANT, 0, &slant); + FcResult eWeightRes = FcPatternGetInteger(pFSet->fonts[i], FC_WEIGHT, 0, &weight); + FcResult eWidthRes = FcPatternGetInteger(pFSet->fonts[i], FC_WIDTH, 0, &width); + FcResult eSpacRes = FcPatternGetInteger(pFSet->fonts[i], FC_SPACING, 0, &spacing); + FcResult eScalableRes = FcPatternGetBool(pFSet->fonts[i], FC_SCALABLE, 0, &scalable); + FcResult eIndexRes = FcPatternGetInteger(pFSet->fonts[i], FC_INDEX, 0, &nEntryId); + FcResult eFormatRes = FcPatternGetString(pFSet->fonts[i], FC_FONTFORMAT, 0, &format); + + if( eFileRes != FcResultMatch || eFamilyRes != FcResultMatch || eScalableRes != FcResultMatch ) + continue; + + SAL_INFO( + "vcl.fonts.detail", + "found font \"" << family << "\" in file " << file << ", weight = " + << (eWeightRes == FcResultMatch ? weight : -1) << ", slant = " + << (eSpacRes == FcResultMatch ? slant : -1) << ", style = \"" + << (eStyleRes == FcResultMatch ? reinterpret_cast<const char*>(style) : "<nil>") + << "\", width = " << (eWeightRes == FcResultMatch ? width : -1) << ", spacing = " + << (eSpacRes == FcResultMatch ? spacing : -1) << ", scalable = " + << (eScalableRes == FcResultMatch ? scalable : -1) << ", format " + << (eFormatRes == FcResultMatch + ? reinterpret_cast<const char*>(format) : "<unknown>")); + +// OSL_ASSERT(eScalableRes != FcResultMatch || scalable); + + // only scalable fonts are usable to psprint anyway + if( eScalableRes == FcResultMatch && ! scalable ) + continue; + + if (isPreviouslyDuplicateOrObsoleted(pFSet, i)) + { + SAL_INFO("vcl.fonts.detail", "Ditching " << file << " as duplicate/obsolete"); + continue; + } + + // see if this font is already cached + // update attributes + OString aDir, aBase, aOrgPath( reinterpret_cast<char*>(file) ); + splitPath( aOrgPath, aDir, aBase ); + + int nDirID = getDirectoryAtom( aDir ); + SAL_INFO("vcl.fonts.detail", "file " << aBase << " not cached"); + // not known, analyze font file to get attributes + // not described by fontconfig (e.g. alias names, PSName) + if (eFormatRes != FcResultMatch) + format = nullptr; + std::vector<PrintFont> aFonts = analyzeFontFile( nDirID, aBase, reinterpret_cast<char*>(format) ); + if(aFonts.empty()) + { + SAL_INFO( + "vcl.fonts", "Warning: file \"" << aOrgPath << "\" is unusable to psprint"); + //remove font, reuse index + //we want to remove unusable fonts here, in case there is a usable font + //which duplicates the properties of the unusable one + + //not removing the unusable font will risk the usable font being rejected + //as a duplicate by isPreviouslyDuplicateOrObsoleted + lcl_FcFontSetRemove(pFSet, i--); + continue; + } + + std::optional<PrintFont> xUpdate; + + if (aFonts.size() == 1) // one font + xUpdate = aFonts.front(); + else // more than one font + { + // a collection entry, get the correct index + if( eIndexRes == FcResultMatch && nEntryId != -1 ) + { + int nCollectionEntry = GetCollectionIndex(nEntryId); + for (const auto & font : aFonts) + { + if( font.m_nCollectionEntry == nCollectionEntry ) + { + xUpdate = font; + break; + } + } + } + + if (xUpdate) + { + // update collection entry + // additional entries will be created in the cache + // if this is a new index (that is if the loop above + // ran to the end of the list) + xUpdate->m_nCollectionEntry = GetCollectionIndex(nEntryId); + } + else + { + SAL_INFO( + "vcl.fonts", + "multiple fonts for file, but no index in fontconfig pattern ! (index res =" + << eIndexRes << " collection entry = " << nEntryId + << "; file will not be used"); + // we have found more than one font in this file + // but fontconfig will not tell us which index is meant + // -> something is in disorder, do not use this font + } + } + + if (xUpdate) + { + // set family name + if( eWeightRes == FcResultMatch ) + xUpdate->m_eWeight = convertWeight(weight); + if( eWidthRes == FcResultMatch ) + xUpdate->m_eWidth = convertWidth(width); + if( eSpacRes == FcResultMatch ) + xUpdate->m_ePitch = convertSpacing(spacing); + if( eSlantRes == FcResultMatch ) + xUpdate->m_eItalic = convertSlant(slant); + if( eStyleRes == FcResultMatch ) + xUpdate->m_aStyleName = OStringToOUString( std::string_view( reinterpret_cast<char*>(style) ), RTL_TEXTENCODING_UTF8 ); + if( eIndexRes == FcResultMatch ) + xUpdate->m_nVariationEntry = GetVariationIndex(nEntryId); + + // sort into known fonts + fontID aFont = m_nNextFontID++; + m_aFonts.emplace( aFont, *xUpdate ); + m_aFontFileToFontID[ aBase ].insert( aFont ); + nFonts++; + SAL_INFO("vcl.fonts.detail", "inserted font " << family << " as fontID " << aFont); + } + } + } + + // how does one get rid of the config ? + SAL_INFO("vcl.fonts", "inserted " << nFonts << " fonts from fontconfig"); +} + +void PrintFontManager::deinitFontconfig() +{ + FontCfgWrapper::release(); +} + +void PrintFontManager::addFontconfigDir( const OString& rDirName ) +{ + const char* pDirName = rDirName.getStr(); + bool bDirOk = (FcConfigAppFontAddDir(FcConfigGetCurrent(), reinterpret_cast<FcChar8 const *>(pDirName) ) == FcTrue); + + SAL_INFO("vcl.fonts", "FcConfigAppFontAddDir( \"" << pDirName << "\") => " << bDirOk); + + if( !bDirOk ) + return; + + // load dir-specific fc-config file too if available + const OString aConfFileName = rDirName + "/fc_local.conf"; + FILE* pCfgFile = fopen( aConfFileName.getStr(), "rb" ); + if( pCfgFile ) + { + fclose( pCfgFile); + bool bCfgOk = FcConfigParseAndLoad(FcConfigGetCurrent(), + reinterpret_cast<FcChar8 const *>(aConfFileName.getStr()), FcTrue); + + SAL_INFO_IF(!bCfgOk, + "vcl.fonts", "FcConfigParseAndLoad( \"" + << aConfFileName << "\") => " << bCfgOk); + } else { + SAL_INFO("vcl.fonts", "cannot open " << aConfFileName); + } +} + +static void addtopattern(FcPattern *pPattern, + FontItalic eItalic, FontWeight eWeight, FontWidth eWidth, FontPitch ePitch) +{ + if( eItalic != ITALIC_DONTKNOW ) + { + int nSlant = FC_SLANT_ROMAN; + switch( eItalic ) + { + case ITALIC_NORMAL: + nSlant = FC_SLANT_ITALIC; + break; + case ITALIC_OBLIQUE: + nSlant = FC_SLANT_OBLIQUE; + break; + default: + break; + } + FcPatternAddInteger(pPattern, FC_SLANT, nSlant); + } + if( eWeight != WEIGHT_DONTKNOW ) + { + int nWeight = FC_WEIGHT_NORMAL; + switch( eWeight ) + { + case WEIGHT_THIN: nWeight = FC_WEIGHT_THIN;break; + case WEIGHT_ULTRALIGHT: nWeight = FC_WEIGHT_ULTRALIGHT;break; + case WEIGHT_LIGHT: nWeight = FC_WEIGHT_LIGHT;break; + case WEIGHT_SEMILIGHT: nWeight = FC_WEIGHT_BOOK;break; + case WEIGHT_NORMAL: nWeight = FC_WEIGHT_NORMAL;break; + case WEIGHT_MEDIUM: nWeight = FC_WEIGHT_MEDIUM;break; + case WEIGHT_SEMIBOLD: nWeight = FC_WEIGHT_SEMIBOLD;break; + case WEIGHT_BOLD: nWeight = FC_WEIGHT_BOLD;break; + case WEIGHT_ULTRABOLD: nWeight = FC_WEIGHT_ULTRABOLD;break; + case WEIGHT_BLACK: nWeight = FC_WEIGHT_BLACK;break; + default: + break; + } + FcPatternAddInteger(pPattern, FC_WEIGHT, nWeight); + } + if( eWidth != WIDTH_DONTKNOW ) + { + int nWidth = FC_WIDTH_NORMAL; + switch( eWidth ) + { + case WIDTH_ULTRA_CONDENSED: nWidth = FC_WIDTH_ULTRACONDENSED;break; + case WIDTH_EXTRA_CONDENSED: nWidth = FC_WIDTH_EXTRACONDENSED;break; + case WIDTH_CONDENSED: nWidth = FC_WIDTH_CONDENSED;break; + case WIDTH_SEMI_CONDENSED: nWidth = FC_WIDTH_SEMICONDENSED;break; + case WIDTH_NORMAL: nWidth = FC_WIDTH_NORMAL;break; + case WIDTH_SEMI_EXPANDED: nWidth = FC_WIDTH_SEMIEXPANDED;break; + case WIDTH_EXPANDED: nWidth = FC_WIDTH_EXPANDED;break; + case WIDTH_EXTRA_EXPANDED: nWidth = FC_WIDTH_EXTRAEXPANDED;break; + case WIDTH_ULTRA_EXPANDED: nWidth = FC_WIDTH_ULTRAEXPANDED;break; + default: + break; + } + FcPatternAddInteger(pPattern, FC_WIDTH, nWidth); + } + if( ePitch == PITCH_DONTKNOW ) + return; + + int nSpacing = FC_PROPORTIONAL; + switch( ePitch ) + { + case PITCH_FIXED: nSpacing = FC_MONO;break; + case PITCH_VARIABLE: nSpacing = FC_PROPORTIONAL;break; + default: + break; + } + FcPatternAddInteger(pPattern, FC_SPACING, nSpacing); + if (nSpacing == FC_MONO) + FcPatternAddString(pPattern, FC_FAMILY, reinterpret_cast<FcChar8 const *>("monospace")); +} + +namespace +{ + //Someday fontconfig will hopefully use bcp47, see fdo#19869 + //In the meantime try something that will fit to workaround fdo#35118 + OString mapToFontConfigLangTag(const LanguageTag &rLangTag) + { +#if defined(FC_VERSION) && (FC_VERSION >= 20492) + std::shared_ptr<FcStrSet> xLangSet(FcGetLangs(), FcStrSetDestroy); + OString sLangAttrib; + + sLangAttrib = OUStringToOString(rLangTag.getBcp47(), RTL_TEXTENCODING_UTF8).toAsciiLowerCase(); + if (FcStrSetMember(xLangSet.get(), reinterpret_cast<const FcChar8*>(sLangAttrib.getStr()))) + { + return sLangAttrib; + } + + sLangAttrib = OUStringToOString(rLangTag.getLanguageAndScript(), RTL_TEXTENCODING_UTF8).toAsciiLowerCase(); + if (FcStrSetMember(xLangSet.get(), reinterpret_cast<const FcChar8*>(sLangAttrib.getStr()))) + { + return sLangAttrib; + } + + OString sLang = OUStringToOString(rLangTag.getLanguage(), RTL_TEXTENCODING_UTF8).toAsciiLowerCase(); + OString sRegion = OUStringToOString(rLangTag.getCountry(), RTL_TEXTENCODING_UTF8).toAsciiLowerCase(); + + if (!sRegion.isEmpty()) + { + sLangAttrib = sLang + "-" + sRegion; + if (FcStrSetMember(xLangSet.get(), reinterpret_cast<const FcChar8*>(sLangAttrib.getStr()))) + { + return sLangAttrib; + } + } + + if (FcStrSetMember(xLangSet.get(), reinterpret_cast<const FcChar8*>(sLang.getStr()))) + { + return sLang; + } + + return OString(); +#else + OString sLangAttrib = OUStringToOString(rLangTag.getLanguageAndScript(), RTL_TEXTENCODING_UTF8).toAsciiLowerCase(); + if (sLangAttrib.equalsIgnoreAsciiCase("pa-in")) + sLangAttrib = "pa"; + return sLangAttrib; +#endif + } + + bool isEmoji(sal_uInt32 nCurrentChar) + { +#if U_ICU_VERSION_MAJOR_NUM >= 57 + return u_hasBinaryProperty(nCurrentChar, UCHAR_EMOJI); +#else + return false; +#endif + } + + //returns true if the given code-point couldn't possibly be in rLangTag. + bool isImpossibleCodePointForLang(const LanguageTag &rLangTag, sal_uInt32 currentChar) + { + //a non-default script is set, lets believe it + if (rLangTag.hasScript()) + return false; + + int32_t script = u_getIntPropertyValue(currentChar, UCHAR_SCRIPT); + UScriptCode eScript = static_cast<UScriptCode>(script); + bool bIsImpossible = false; + OUString sLang = rLangTag.getLanguage(); + switch (eScript) + { + //http://en.wiktionary.org/wiki/Category:Oriya_script_languages + case USCRIPT_ORIYA: + bIsImpossible = + sLang != "or" && + sLang != "kxv"; + break; + //http://en.wiktionary.org/wiki/Category:Telugu_script_languages + case USCRIPT_TELUGU: + bIsImpossible = + sLang != "te" && + sLang != "gon" && + sLang != "kfc"; + break; + //http://en.wiktionary.org/wiki/Category:Bengali_script_languages + case USCRIPT_BENGALI: + bIsImpossible = + sLang != "bn" && + sLang != "as" && + sLang != "bpy" && + sLang != "ctg" && + sLang != "sa"; + break; + default: + break; + } + SAL_WARN_IF(bIsImpossible, "vcl.fonts", "In glyph fallback throwing away the language property of " + << sLang << " because the detected script for '0x" + << OUString::number(currentChar, 16) + << "' is " << uscript_getName(eScript) + << " and that language doesn't make sense. Autodetecting instead."); + return bIsImpossible; + } + + OUString getExemplarLangTagForCodePoint(sal_uInt32 currentChar) + { + if (isEmoji(currentChar)) + return "und-zsye"; + int32_t script = u_getIntPropertyValue(currentChar, UCHAR_SCRIPT); + UScriptCode eScript = static_cast<UScriptCode>(script); + OStringBuffer aBuf(unicode::getExemplarLanguageForUScriptCode(eScript)); + if (const char* pScriptCode = uscript_getShortName(eScript)) + aBuf.append('-').append(pScriptCode); + return OStringToOUString(aBuf, RTL_TEXTENCODING_UTF8); + } +} + +IMPL_LINK_NOARG(PrintFontManager, autoInstallFontLangSupport, Timer *, void) +{ + try + { + using namespace org::freedesktop::PackageKit; + css::uno::Reference<XSyncDbusSessionHelper> xSyncDbusSessionHelper(SyncDbusSessionHelper::create(comphelper::getProcessComponentContext())); + xSyncDbusSessionHelper->InstallFontconfigResources(comphelper::containerToSequence(m_aCurrentRequests), "hide-finished"); + } + catch (const css::uno::Exception&) + { + TOOLS_INFO_EXCEPTION("vcl.fonts", "InstallFontconfigResources problem"); + // Disable this method from now on. It's simply not available on some systems + // and leads to an error dialog being shown each time this is called tdf#104883 + std::shared_ptr<comphelper::ConfigurationChanges> batch( comphelper::ConfigurationChanges::create() ); + officecfg::Office::Common::PackageKit::EnableFontInstallation::set(false, batch); + batch->commit(); + } + + m_aCurrentRequests.clear(); +} + +void PrintFontManager::Substitute(vcl::font::FontSelectPattern &rPattern, OUString& rMissingCodes) +{ + FontCfgWrapper& rWrapper = FontCfgWrapper::get(); + + // build pattern argument for fontconfig query + FcPattern* pPattern = FcPatternCreate(); + + // Prefer scalable fonts + FcPatternAddBool(pPattern, FC_SCALABLE, FcTrue); + + const OString aTargetName = OUStringToOString( rPattern.maTargetName, RTL_TEXTENCODING_UTF8 ); + const FcChar8* pTargetNameUtf8 = reinterpret_cast<FcChar8 const *>(aTargetName.getStr()); + FcPatternAddString(pPattern, FC_FAMILY, pTargetNameUtf8); + + LanguageTag aLangTag(rPattern.meLanguage); + OString aLangAttrib = mapToFontConfigLangTag(aLangTag); + + bool bMissingJustBullet = false; + + // Add required Unicode characters, if any + if ( !rMissingCodes.isEmpty() ) + { + FcCharSet *codePoints = FcCharSetCreate(); + bMissingJustBullet = rMissingCodes.getLength() == 1 && rMissingCodes[0] == 0xb7; + for( sal_Int32 nStrIndex = 0; nStrIndex < rMissingCodes.getLength(); ) + { + // also handle unicode surrogates + const sal_uInt32 nCode = rMissingCodes.iterateCodePoints( &nStrIndex ); + FcCharSetAddChar( codePoints, nCode ); + //if the codepoint is impossible for this lang tag, then clear it + //and autodetect something useful + if (!aLangAttrib.isEmpty() && (isImpossibleCodePointForLang(aLangTag, nCode) || isEmoji(nCode))) + aLangAttrib.clear(); + //#i105784#/rhbz#527719 improve selection of fallback font + if (aLangAttrib.isEmpty()) + { + aLangTag.reset(getExemplarLangTagForCodePoint(nCode)); + aLangAttrib = mapToFontConfigLangTag(aLangTag); + } + } + FcPatternAddCharSet(pPattern, FC_CHARSET, codePoints); + FcCharSetDestroy(codePoints); + } + + if (!aLangAttrib.isEmpty()) + FcPatternAddString(pPattern, FC_LANG, reinterpret_cast<FcChar8 const *>(aLangAttrib.getStr())); + + addtopattern(pPattern, rPattern.GetItalic(), rPattern.GetWeight(), + rPattern.GetWidthType(), rPattern.GetPitch()); + + // query fontconfig for a substitute + FcConfigSubstitute(FcConfigGetCurrent(), pPattern, FcMatchPattern); + FcDefaultSubstitute(pPattern); + + // process the result of the fontconfig query + FcResult eResult = FcResultNoMatch; + FcFontSet* pFontSet = rWrapper.getFontSet(); + FcPattern* pResult = FcFontSetMatch(FcConfigGetCurrent(), &pFontSet, 1, pPattern, &eResult); + FcPatternDestroy( pPattern ); + + FcFontSet* pSet = nullptr; + if( pResult ) + { + pSet = FcFontSetCreate(); + // info: destroying the pSet destroys pResult implicitly + // since pResult was "added" to pSet + FcFontSetAdd( pSet, pResult ); + } + + if( pSet ) + { + if( pSet->nfont > 0 ) + { + bool bRet = false; + + //extract the closest match + FcChar8* file = nullptr; + FcResult eFileRes = FcPatternGetString(pSet->fonts[0], FC_FILE, 0, &file); + int nEntryId = 0; + FcResult eIndexRes = FcPatternGetInteger(pSet->fonts[0], FC_INDEX, 0, &nEntryId); + if (eIndexRes != FcResultMatch) + nEntryId = 0; + if( eFileRes == FcResultMatch ) + { + OString aDir, aBase, aOrgPath( reinterpret_cast<char*>(file) ); + splitPath( aOrgPath, aDir, aBase ); + int nDirID = getDirectoryAtom( aDir ); + fontID aFont = findFontFileID(nDirID, aBase, GetCollectionIndex(nEntryId), GetVariationIndex(nEntryId)); + if( aFont > 0 ) + { + FastPrintFontInfo aInfo; + bRet = getFontFastInfo( aFont, aInfo ); + rPattern.maSearchName = aInfo.m_aFamilyName; + } + } + + SAL_WARN_IF(!bRet, "vcl.fonts", "no FC_FILE found, falling back to name search"); + + if (!bRet) + { + FcChar8* family = nullptr; + FcResult eFamilyRes = FcPatternGetString( pSet->fonts[0], FC_FAMILY, 0, &family ); + + // get the family name + if( eFamilyRes == FcResultMatch ) + { + OString sFamily(reinterpret_cast<char*>(family)); + std::unordered_map< OString, OString >::const_iterator aI = + rWrapper.m_aFontNameToLocalized.find(sFamily); + if (aI != rWrapper.m_aFontNameToLocalized.end()) + sFamily = aI->second; + rPattern.maSearchName = OStringToOUString( sFamily, RTL_TEXTENCODING_UTF8 ); + bRet = true; + } + } + + if (bRet) + { + int val = 0; + if (FcResultMatch == FcPatternGetInteger(pSet->fonts[0], FC_WEIGHT, 0, &val)) + rPattern.SetWeight( convertWeight(val) ); + if (FcResultMatch == FcPatternGetInteger(pSet->fonts[0], FC_SLANT, 0, &val)) + rPattern.SetItalic( convertSlant(val) ); + if (FcResultMatch == FcPatternGetInteger(pSet->fonts[0], FC_SPACING, 0, &val)) + rPattern.SetPitch ( convertSpacing(val) ); + if (FcResultMatch == FcPatternGetInteger(pSet->fonts[0], FC_WIDTH, 0, &val)) + rPattern.SetWidthType ( convertWidth(val) ); + FcBool bEmbolden; + if (FcResultMatch == FcPatternGetBool(pSet->fonts[0], FC_EMBOLDEN, 0, &bEmbolden)) + rPattern.mbEmbolden = bEmbolden; + FcMatrix *pMatrix = nullptr; + if (FcResultMatch == FcPatternGetMatrix(pSet->fonts[0], FC_MATRIX, 0, &pMatrix)) + { + rPattern.maItalicMatrix.xx = pMatrix->xx; + rPattern.maItalicMatrix.xy = pMatrix->xy; + rPattern.maItalicMatrix.yx = pMatrix->yx; + rPattern.maItalicMatrix.yy = pMatrix->yy; + } + } + + // update rMissingCodes by removing resolved code points + if( !rMissingCodes.isEmpty() ) + { + std::unique_ptr<sal_uInt32[]> const pRemainingCodes(new sal_uInt32[rMissingCodes.getLength()]); + int nRemainingLen = 0; + FcCharSet* codePoints; + if (!FcPatternGetCharSet(pSet->fonts[0], FC_CHARSET, 0, &codePoints)) + { + for( sal_Int32 nStrIndex = 0; nStrIndex < rMissingCodes.getLength(); ) + { + // also handle surrogates + const sal_uInt32 nCode = rMissingCodes.iterateCodePoints( &nStrIndex ); + if (FcCharSetHasChar(codePoints, nCode) != FcTrue) + pRemainingCodes[ nRemainingLen++ ] = nCode; + } + } + OUString sStillMissing(pRemainingCodes.get(), nRemainingLen); + if (!Application::IsHeadlessModeEnabled() && officecfg::Office::Common::PackageKit::EnableFontInstallation::get()) + { + if (sStillMissing == rMissingCodes) //replaced nothing + { + //It'd be better if we could ask packagekit using the + //missing codepoints or some such rather than using + //"language" as a proxy to how fontconfig considers + //scripts to default to a given language. + for (sal_Int32 i = 0; i < nRemainingLen; ++i) + { + LanguageTag aOurTag(getExemplarLangTagForCodePoint(pRemainingCodes[i])); + OString sTag = OUStringToOString(aOurTag.getBcp47(), RTL_TEXTENCODING_UTF8); + if (!m_aPreviousLangSupportRequests.insert(sTag).second) + continue; + sTag = mapToFontConfigLangTag(aOurTag); + if (!sTag.isEmpty() && m_aPreviousLangSupportRequests.find(sTag) == m_aPreviousLangSupportRequests.end()) + { + OString sReq = OString::Concat(":lang=") + sTag; + m_aCurrentRequests.push_back(OUString::fromUtf8(sReq)); + m_aPreviousLangSupportRequests.insert(sTag); + } + } + } + if (!m_aCurrentRequests.empty()) + m_aFontInstallerTimer.Start(); + } + rMissingCodes = sStillMissing; + } + } + + FcFontSetDestroy( pSet ); + } + + SAL_INFO("vcl.fonts", "PrintFontManager::Substitute: replacing missing font: '" + << rPattern.maTargetName << "' with '" << rPattern.maSearchName + << "'"); + + static bool bAbortOnFontSubstitute = getenv("SAL_ABORT_ON_NON_APPLICATION_FONT_USE") != nullptr; + if (bAbortOnFontSubstitute && rPattern.maTargetName != rPattern.maSearchName) + { + if (bMissingJustBullet) + { + assert(rPattern.maTargetName == "Amiri Quran" || rPattern.maTargetName == "David CLM" || + rPattern.maTargetName == "EmojiOne Color" || rPattern.maTargetName == "Frank Ruehl CLM" || + rPattern.maTargetName == "KacstBook" || rPattern.maTargetName == "KacstOffice"); + // These fonts exist in "more_fonts", but have no U+00B7 MIDDLE DOT + // so will always glyph fallback on measuring mnBulletOffset in + // ImplFontMetricData::ImplInitTextLineSize + return; + } + if (rPattern.maTargetName == "Linux Libertine G" && rPattern.maSearchName == "Linux Libertine O") + return; + SAL_WARN("vcl.fonts", "PrintFontManager::Substitute: missing font: '" << rPattern.maTargetName << + "' try: " << rPattern.maSearchName << " instead"); + std::abort(); + } +} + +FontConfigFontOptions::~FontConfigFontOptions() +{ + FcPatternDestroy(mpPattern); +} + +FcPattern *FontConfigFontOptions::GetPattern() const +{ + return mpPattern; +} + +void FontConfigFontOptions::SyncPattern(const OString& rFileName, sal_uInt32 nIndex, sal_uInt32 nVariation, bool bEmbolden) +{ + FcPatternDel(mpPattern, FC_FILE); + FcPatternAddString(mpPattern, FC_FILE, reinterpret_cast<FcChar8 const *>(rFileName.getStr())); + FcPatternDel(mpPattern, FC_INDEX); + sal_uInt32 nFcIndex = (nVariation << 16) | nIndex; + FcPatternAddInteger(mpPattern, FC_INDEX, nFcIndex); + FcPatternDel(mpPattern, FC_EMBOLDEN); + FcPatternAddBool(mpPattern, FC_EMBOLDEN, bEmbolden ? FcTrue : FcFalse); +} + +std::unique_ptr<FontConfigFontOptions> PrintFontManager::getFontOptions(const FontAttributes& rInfo, int nSize) +{ + FontOptionsKey aKey{ rInfo.GetFamilyName(), nSize, rInfo.GetItalic(), + rInfo.GetWeight(), rInfo.GetWidthType(), rInfo.GetPitch() }; + + FontCfgWrapper& rWrapper = FontCfgWrapper::get(); + + std::unique_ptr<FontConfigFontOptions> pOptions = rWrapper.m_aCachedFontOptions.lookup(aKey); + if (pOptions) + return pOptions; + + FcConfig* pConfig = FcConfigGetCurrent(); + FcPattern* pPattern = FcPatternCreate(); + + OString sFamily = OUStringToOString(aKey.m_sFamilyName, RTL_TEXTENCODING_UTF8); + + std::unordered_map< OString, OString >::const_iterator aI = rWrapper.m_aLocalizedToCanonical.find(sFamily); + if (aI != rWrapper.m_aLocalizedToCanonical.end()) + sFamily = aI->second; + if( !sFamily.isEmpty() ) + FcPatternAddString(pPattern, FC_FAMILY, reinterpret_cast<FcChar8 const *>(sFamily.getStr())); + + addtopattern(pPattern, aKey.m_eItalic, aKey.m_eWeight, aKey.m_eWidth, aKey.m_ePitch); + FcPatternAddDouble(pPattern, FC_PIXEL_SIZE, nSize); + + FcConfigSubstitute(pConfig, pPattern, FcMatchPattern); + FontConfigFontOptions::cairo_font_options_substitute(pPattern); + FcDefaultSubstitute(pPattern); + + FcResult eResult = FcResultNoMatch; + FcFontSet* pFontSet = rWrapper.getFontSet(); + if (FcPattern* pResult = FcFontSetMatch(pConfig, &pFontSet, 1, pPattern, &eResult)) + { + rWrapper.m_aCachedFontOptions.cache(aKey, pResult); + pOptions.reset(new FontConfigFontOptions(pResult)); + } + + // cleanup + FcPatternDestroy( pPattern ); + + return pOptions; +} + + +void PrintFontManager::matchFont( FastPrintFontInfo& rInfo, const css::lang::Locale& rLocale ) +{ + FontCfgWrapper& rWrapper = FontCfgWrapper::get(); + + FcConfig* pConfig = FcConfigGetCurrent(); + FcPattern* pPattern = FcPatternCreate(); + + // populate pattern with font characteristics + const LanguageTag aLangTag(rLocale); + const OString aLangAttrib = mapToFontConfigLangTag(aLangTag); + if (!aLangAttrib.isEmpty()) + FcPatternAddString(pPattern, FC_LANG, reinterpret_cast<FcChar8 const *>(aLangAttrib.getStr())); + + OString aFamily = OUStringToOString( rInfo.m_aFamilyName, RTL_TEXTENCODING_UTF8 ); + if( !aFamily.isEmpty() ) + FcPatternAddString(pPattern, FC_FAMILY, reinterpret_cast<FcChar8 const *>(aFamily.getStr())); + + addtopattern(pPattern, rInfo.m_eItalic, rInfo.m_eWeight, rInfo.m_eWidth, rInfo.m_ePitch); + + FcConfigSubstitute(pConfig, pPattern, FcMatchPattern); + FcDefaultSubstitute(pPattern); + FcResult eResult = FcResultNoMatch; + FcFontSet *pFontSet = rWrapper.getFontSet(); + FcPattern* pResult = FcFontSetMatch(pConfig, &pFontSet, 1, pPattern, &eResult); + if( pResult ) + { + FcFontSet* pSet = FcFontSetCreate(); + FcFontSetAdd( pSet, pResult ); + if( pSet->nfont > 0 ) + { + //extract the closest match + FcChar8* file = nullptr; + FcResult eFileRes = FcPatternGetString(pSet->fonts[0], FC_FILE, 0, &file); + int nEntryId = 0; + FcResult eIndexRes = FcPatternGetInteger(pSet->fonts[0], FC_INDEX, 0, &nEntryId); + if (eIndexRes != FcResultMatch) + nEntryId = 0; + if( eFileRes == FcResultMatch ) + { + OString aDir, aBase, aOrgPath( reinterpret_cast<char*>(file) ); + splitPath( aOrgPath, aDir, aBase ); + int nDirID = getDirectoryAtom( aDir ); + fontID aFont = findFontFileID(nDirID, aBase, + GetCollectionIndex(nEntryId), + GetVariationIndex(nEntryId)); + if( aFont > 0 ) + getFontFastInfo( aFont, rInfo ); + } + } + // info: destroying the pSet destroys pResult implicitly + // since pResult was "added" to pSet + FcFontSetDestroy( pSet ); + } + + // cleanup + FcPatternDestroy( pPattern ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/generic/fontmanager/fontmanager.cxx b/vcl/unx/generic/fontmanager/fontmanager.cxx new file mode 100644 index 000000000..46e8ae162 --- /dev/null +++ b/vcl/unx/generic/fontmanager/fontmanager.cxx @@ -0,0 +1,1147 @@ +/* -*- 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 <memory> +#include <unistd.h> +#include <osl/thread.h> + +#include <unx/fontmanager.hxx> +#include <fontsubset.hxx> +#include <impfontcharmap.hxx> +#include <unx/gendata.hxx> +#include <unx/helper.hxx> +#include <vcl/fontcharmap.hxx> + +#include <tools/urlobj.hxx> + +#include <osl/file.hxx> + +#include <rtl/ustrbuf.hxx> +#include <rtl/strbuf.hxx> + +#include <sal/macros.h> +#include <sal/log.hxx> + +#include <i18nlangtag/applelangid.hxx> + +#include <sft.hxx> + +#if OSL_DEBUG_LEVEL > 1 +#include <sys/times.h> +#include <stdio.h> +#endif + +#include <algorithm> +#include <set> + +#ifdef CALLGRIND_COMPILE +#include <valgrind/callgrind.h> +#endif + +#include <com/sun/star/beans/XMaterialHolder.hpp> + +using namespace vcl; +using namespace utl; +using namespace psp; +using namespace osl; +using namespace com::sun::star::uno; +using namespace com::sun::star::beans; +using namespace com::sun::star::lang; + +/* + * static helpers + */ + +static sal_uInt16 getUInt16BE( const sal_uInt8*& pBuffer ) +{ + sal_uInt16 nRet = static_cast<sal_uInt16>(pBuffer[1]) | + (static_cast<sal_uInt16>(pBuffer[0]) << 8); + pBuffer+=2; + return nRet; +} + +/* + * PrintFont implementations + */ +PrintFontManager::PrintFont::PrintFont() +: m_eFamilyStyle(FAMILY_DONTKNOW) +, m_eItalic(ITALIC_DONTKNOW) +, m_eWidth(WIDTH_DONTKNOW) +, m_eWeight(WEIGHT_DONTKNOW) +, m_ePitch(PITCH_DONTKNOW) +, m_aEncoding(RTL_TEXTENCODING_DONTKNOW) +, m_nAscend(0) +, m_nDescend(0) +, m_nLeading(0) +, m_nXMin(0) +, m_nYMin(0) +, m_nXMax(0) +, m_nYMax(0) +, m_nDirectory(0) +, m_nCollectionEntry(0) +, m_nVariationEntry(0) +{ +} + +/* + * one instance only + */ +PrintFontManager& PrintFontManager::get() +{ + GenericUnixSalData* const pSalData(GetGenericUnixSalData()); + assert(pSalData); + return *pSalData->GetPrintFontManager(); +} + +/* + * the PrintFontManager + */ + +PrintFontManager::PrintFontManager() + : m_nNextFontID( 1 ) + , m_nNextDirAtom( 1 ) + , m_aFontInstallerTimer("PrintFontManager m_aFontInstallerTimer") +{ + m_aFontInstallerTimer.SetInvokeHandler(LINK(this, PrintFontManager, autoInstallFontLangSupport)); + m_aFontInstallerTimer.SetTimeout(5000); +} + +PrintFontManager::~PrintFontManager() +{ + m_aFontInstallerTimer.Stop(); + deinitFontconfig(); +} + +OString PrintFontManager::getDirectory( int nAtom ) const +{ + std::unordered_map< int, OString >::const_iterator it( m_aAtomToDir.find( nAtom ) ); + return it != m_aAtomToDir.end() ? it->second : OString(); +} + +int PrintFontManager::getDirectoryAtom( const OString& rDirectory ) +{ + int nAtom = 0; + std::unordered_map< OString, int >::const_iterator it + ( m_aDirToAtom.find( rDirectory ) ); + if( it != m_aDirToAtom.end() ) + nAtom = it->second; + else + { + nAtom = m_nNextDirAtom++; + m_aDirToAtom[ rDirectory ] = nAtom; + m_aAtomToDir[ nAtom ] = rDirectory; + } + return nAtom; +} + +std::vector<fontID> PrintFontManager::addFontFile( std::u16string_view rFileUrl ) +{ + rtl_TextEncoding aEncoding = osl_getThreadTextEncoding(); + INetURLObject aPath( rFileUrl ); + OString aName(OUStringToOString(aPath.GetLastName(INetURLObject::DecodeMechanism::WithCharset, aEncoding), aEncoding)); + OString aDir( OUStringToOString( + INetURLObject::decode( aPath.GetPath(), INetURLObject::DecodeMechanism::WithCharset, aEncoding ), aEncoding ) ); + + int nDirID = getDirectoryAtom( aDir ); + std::vector<fontID> aFontIds = findFontFileIDs( nDirID, aName ); + if( aFontIds.empty() ) + { + std::vector<PrintFont> aNewFonts = analyzeFontFile(nDirID, aName); + for (auto & font : aNewFonts) + { + fontID nFontId = m_nNextFontID++; + m_aFonts[nFontId] = std::move(font); + m_aFontFileToFontID[ aName ].insert( nFontId ); + aFontIds.push_back(nFontId); + } + } + return aFontIds; +} + +std::vector<PrintFontManager::PrintFont> PrintFontManager::analyzeFontFile( int nDirID, const OString& rFontFile, const char *pFormat ) const +{ + std::vector<PrintFontManager::PrintFont> aNewFonts; + + OString aDir( getDirectory( nDirID ) ); + + OString aFullPath = aDir + "/" + rFontFile; + + // #i1872# reject unreadable files + if( access( aFullPath.getStr(), R_OK ) ) + return aNewFonts; + + bool bSupported = false; + if (pFormat) + { + if (!strcmp(pFormat, "TrueType") || + !strcmp(pFormat, "CFF")) + bSupported = true; + } + if (!bSupported) + { + OString aExt( rFontFile.copy( rFontFile.lastIndexOf( '.' )+1 ) ); + if( aExt.equalsIgnoreAsciiCase("ttf") + || aExt.equalsIgnoreAsciiCase("ttc") + || aExt.equalsIgnoreAsciiCase("tte") // #i33947# for Gaiji support + || aExt.equalsIgnoreAsciiCase("otf") ) // check for TTF- and PS-OpenType too + bSupported = true; + } + + if (bSupported) + { + // get number of ttc entries + int nLength = CountTTCFonts( aFullPath.getStr() ); + if (nLength > 0) + { + SAL_INFO("vcl.fonts", "ttc: " << aFullPath << " contains " << nLength << " fonts"); + + sal_uInt64 fileSize = 0; + + OUString aURL; + if (osl::File::getFileURLFromSystemPath(OStringToOUString(aFullPath, osl_getThreadTextEncoding()), + aURL) == osl::File::E_None) + { + osl::File aFile(aURL); + if (aFile.open(osl_File_OpenFlag_Read | osl_File_OpenFlag_NoLock) == osl::File::E_None) + { + osl::DirectoryItem aItem; + if (osl::DirectoryItem::get(aURL, aItem) == osl::File::E_None) + { + osl::FileStatus aFileStatus( osl_FileStatus_Mask_FileSize ); + if (aItem.getFileStatus(aFileStatus) == osl::File::E_None) + fileSize = aFileStatus.getFileSize(); + } + } + } + + //Feel free to calc the exact max possible number of fonts a file + //could contain given its physical size. But this will clamp it to + //a sane starting point + //http://processingjs.nihongoresources.com/the_smallest_font/ + //https://github.com/grzegorzrolek/null-ttf + const int nMaxFontsPossible = fileSize / 528; + if (nLength > nMaxFontsPossible) + nLength = nMaxFontsPossible; + + for( int i = 0; i < nLength; i++ ) + { + PrintFont aFont; + aFont.m_nDirectory = nDirID; + aFont.m_aFontFile = rFontFile; + aFont.m_nCollectionEntry = i; + if (analyzeSfntFile(aFont)) + aNewFonts.push_back(aFont); + } + } + else + { + PrintFont aFont; + aFont.m_nDirectory = nDirID; + aFont.m_aFontFile = rFontFile; + aFont.m_nCollectionEntry = 0; + + // need to read the font anyway to get aliases inside the font file + if (analyzeSfntFile(aFont)) + aNewFonts.push_back(aFont); + } + } + return aNewFonts; +} + +fontID PrintFontManager::findFontFileID(int nDirID, const OString& rFontFile, int nFaceIndex, int nVariationIndex) const +{ + fontID nID = 0; + + std::unordered_map< OString, ::std::set< fontID > >::const_iterator set_it = m_aFontFileToFontID.find( rFontFile ); + if( set_it == m_aFontFileToFontID.end() ) + return nID; + + for (auto const& elem : set_it->second) + { + auto it = m_aFonts.find(elem); + if( it == m_aFonts.end() ) + continue; + const PrintFont& rFont = (*it).second; + if (rFont.m_nDirectory == nDirID && + rFont.m_aFontFile == rFontFile && + rFont.m_nCollectionEntry == nFaceIndex && + rFont.m_nVariationEntry == nVariationIndex) + { + nID = it->first; + if (nID) + break; + } + } + + return nID; +} + +std::vector<fontID> PrintFontManager::findFontFileIDs( int nDirID, const OString& rFontFile ) const +{ + std::vector<fontID> aIds; + + std::unordered_map< OString, ::std::set< fontID > >::const_iterator set_it = m_aFontFileToFontID.find( rFontFile ); + if( set_it == m_aFontFileToFontID.end() ) + return aIds; + + for (auto const& elem : set_it->second) + { + auto it = m_aFonts.find(elem); + if( it == m_aFonts.end() ) + continue; + const PrintFont& rFont = (*it).second; + if (rFont.m_nDirectory == nDirID && + rFont.m_aFontFile == rFontFile) + aIds.push_back(it->first); + } + + return aIds; +} + +OUString PrintFontManager::convertSfntName( void* pRecord ) +{ + NameRecord* pNameRecord = static_cast<NameRecord*>(pRecord); + OUString aValue; + if( + ( pNameRecord->platformID == 3 && ( pNameRecord->encodingID == 0 || pNameRecord->encodingID == 1 ) ) // MS, Unicode + || + ( pNameRecord->platformID == 0 ) // Apple, Unicode + ) + { + OUStringBuffer aName( pNameRecord->slen/2 ); + const sal_uInt8* pNameBuffer = pNameRecord->sptr; + for(int n = 0; n < pNameRecord->slen/2; n++ ) + aName.append( static_cast<sal_Unicode>(getUInt16BE( pNameBuffer )) ); + aValue = aName.makeStringAndClear(); + } + else if( pNameRecord->platformID == 3 ) + { + if( pNameRecord->encodingID >= 2 && pNameRecord->encodingID <= 6 ) + { + /* + * and now for a special kind of madness: + * some fonts encode their byte value string as BE uint16 + * (leading to stray zero bytes in the string) + * while others code two bytes as a uint16 and swap to BE + */ + OStringBuffer aName; + const sal_uInt8* pNameBuffer = pNameRecord->sptr; + for(int n = 0; n < pNameRecord->slen/2; n++ ) + { + sal_Unicode aCode = static_cast<sal_Unicode>(getUInt16BE( pNameBuffer )); + char aChar = aCode >> 8; + if( aChar ) + aName.append( aChar ); + aChar = aCode & 0x00ff; + if( aChar ) + aName.append( aChar ); + } + switch( pNameRecord->encodingID ) + { + case 2: + aValue = OStringToOUString( aName, RTL_TEXTENCODING_MS_932 ); + break; + case 3: + aValue = OStringToOUString( aName, RTL_TEXTENCODING_MS_936 ); + break; + case 4: + aValue = OStringToOUString( aName, RTL_TEXTENCODING_MS_950 ); + break; + case 5: + aValue = OStringToOUString( aName, RTL_TEXTENCODING_MS_949 ); + break; + case 6: + aValue = OStringToOUString( aName, RTL_TEXTENCODING_MS_1361 ); + break; + } + } + } + else if( pNameRecord->platformID == 1 ) + { + OString aName(reinterpret_cast<char*>(pNameRecord->sptr), pNameRecord->slen); + rtl_TextEncoding eEncoding = RTL_TEXTENCODING_DONTKNOW; + switch (pNameRecord->encodingID) + { + case 0: + eEncoding = RTL_TEXTENCODING_APPLE_ROMAN; + break; + case 1: + eEncoding = RTL_TEXTENCODING_APPLE_JAPANESE; + break; + case 2: + eEncoding = RTL_TEXTENCODING_APPLE_CHINTRAD; + break; + case 3: + eEncoding = RTL_TEXTENCODING_APPLE_KOREAN; + break; + case 4: + eEncoding = RTL_TEXTENCODING_APPLE_ARABIC; + break; + case 5: + eEncoding = RTL_TEXTENCODING_APPLE_HEBREW; + break; + case 6: + eEncoding = RTL_TEXTENCODING_APPLE_GREEK; + break; + case 7: + eEncoding = RTL_TEXTENCODING_APPLE_CYRILLIC; + break; + case 9: + eEncoding = RTL_TEXTENCODING_APPLE_DEVANAGARI; + break; + case 10: + eEncoding = RTL_TEXTENCODING_APPLE_GURMUKHI; + break; + case 11: + eEncoding = RTL_TEXTENCODING_APPLE_GUJARATI; + break; + case 21: + eEncoding = RTL_TEXTENCODING_APPLE_THAI; + break; + case 25: + eEncoding = RTL_TEXTENCODING_APPLE_CHINSIMP; + break; + case 29: + eEncoding = RTL_TEXTENCODING_APPLE_CENTEURO; + break; + case 32: //Uninterpreted + eEncoding = RTL_TEXTENCODING_UTF8; + break; + default: + if (aName.startsWith("Khmer OS")) + eEncoding = RTL_TEXTENCODING_UTF8; + SAL_WARN_IF(eEncoding == RTL_TEXTENCODING_DONTKNOW, "vcl.fonts", "Unimplemented mac encoding " << pNameRecord->encodingID << " to unicode conversion for fontname " << aName); + break; + } + if (eEncoding != RTL_TEXTENCODING_DONTKNOW) + aValue = OStringToOUString(aName, eEncoding); + } + + return aValue; +} + +//fdo#33349.There exists an archaic Berling Antiqua font which has a "Times New +//Roman" name field in it. We don't want the "Times New Roman" name to take +//precedence in this case. We take Berling Antiqua as a higher priority name, +//and erase the "Times New Roman" name +namespace +{ + bool isBadTNR(std::u16string_view rName, ::std::set< OUString >& rSet) + { + bool bRet = false; + if ( rName == u"Berling Antiqua" ) + { + ::std::set< OUString >::iterator aEnd = rSet.end(); + ::std::set< OUString >::iterator aI = rSet.find("Times New Roman"); + if (aI != aEnd) + { + bRet = true; + rSet.erase(aI); + } + } + return bRet; + } +} + +void PrintFontManager::analyzeSfntFamilyName( void const * pTTFont, ::std::vector< OUString >& rNames ) +{ + OUString aFamily; + + rNames.clear(); + ::std::set< OUString > aSet; + + NameRecord* pNameRecords = nullptr; + int nNameRecords = GetTTNameRecords( static_cast<TrueTypeFont const *>(pTTFont), &pNameRecords ); + if( nNameRecords && pNameRecords ) + { + LanguageTag aSystem(""); + LanguageType eLang = aSystem.getLanguageType(); + int nLastMatch = -1; + for( int i = 0; i < nNameRecords; i++ ) + { + if( pNameRecords[i].nameID != 1 || pNameRecords[i].sptr == nullptr ) + continue; + int nMatch = -1; + if( pNameRecords[i].platformID == 0 ) // Unicode + nMatch = 4000; + else if( pNameRecords[i].platformID == 3 ) + { + // this bases on the LanguageType actually being a Win LCID + if (pNameRecords[i].languageID == eLang) + nMatch = 8000; + else if( pNameRecords[i].languageID == LANGUAGE_ENGLISH_US ) + nMatch = 2000; + else if( pNameRecords[i].languageID == LANGUAGE_ENGLISH || + pNameRecords[i].languageID == LANGUAGE_ENGLISH_UK ) + nMatch = 1500; + else + nMatch = 1000; + } + else if (pNameRecords[i].platformID == 1) + { + AppleLanguageId aAppleId = static_cast<AppleLanguageId>(static_cast<sal_uInt16>(pNameRecords[i].languageID)); + LanguageTag aApple(makeLanguageTagFromAppleLanguageId(aAppleId)); + if (aApple == aSystem) + nMatch = 8000; + else if (aAppleId == AppleLanguageId::ENGLISH) + nMatch = 2000; + else + nMatch = 1000; + } + OUString aName = convertSfntName( pNameRecords + i ); + aSet.insert( aName ); + if (aName.isEmpty()) + continue; + if( nMatch > nLastMatch || isBadTNR(aName, aSet) ) + { + nLastMatch = nMatch; + aFamily = aName; + } + } + } + DisposeNameRecords( pNameRecords, nNameRecords ); + if( !aFamily.isEmpty() ) + { + rNames.push_back( aFamily ); + for (auto const& elem : aSet) + if( elem != aFamily ) + rNames.push_back(elem); + } +} + +bool PrintFontManager::analyzeSfntFile( PrintFont& rFont ) const +{ + bool bSuccess = false; + rtl_TextEncoding aEncoding = osl_getThreadTextEncoding(); + OString aFile = getFontFile( rFont ); + TrueTypeFont* pTTFont = nullptr; + + auto const e = OpenTTFontFile( aFile.getStr(), rFont.m_nCollectionEntry, &pTTFont ); + if( e == SFErrCodes::Ok ) + { + TTGlobalFontInfo aInfo; + GetTTGlobalFontInfo( pTTFont, & aInfo ); + + ::std::vector< OUString > aNames; + analyzeSfntFamilyName( pTTFont, aNames ); + + // set family name from XLFD if possible + if (rFont.m_aFamilyName.isEmpty()) + { + if( !aNames.empty() ) + { + rFont.m_aFamilyName = aNames.front(); + aNames.erase(aNames.begin()); + } + else + { + sal_Int32 dotIndex; + + // poor font does not have a family name + // name it to file name minus the extension + dotIndex = rFont.m_aFontFile.lastIndexOf( '.' ); + if ( dotIndex == -1 ) + dotIndex = rFont.m_aFontFile.getLength(); + + rFont.m_aFamilyName = OStringToOUString(rFont.m_aFontFile.subView(0, dotIndex), aEncoding); + } + } + for (auto const& aAlias : aNames) + { + if (!aAlias.isEmpty()) + { + if (rFont.m_aFamilyName != aAlias) + { + auto al_it = std::find(rFont.m_aAliases.begin(), rFont.m_aAliases.end(), aAlias); + if( al_it == rFont.m_aAliases.end() ) + rFont.m_aAliases.push_back(aAlias); + } + } + } + + if( aInfo.usubfamily ) + rFont.m_aStyleName = OUString( aInfo.usubfamily ); + + SAL_WARN_IF( !aInfo.psname, "vcl.fonts", "No PostScript name in font:" << aFile ); + + rFont.m_aPSName = aInfo.psname ? + OUString(aInfo.psname, rtl_str_getLength(aInfo.psname), aEncoding) : + rFont.m_aFamilyName; // poor font does not have a postscript name + + rFont.m_eFamilyStyle = matchFamilyName(rFont.m_aFamilyName); + + switch( aInfo.weight ) + { + case FW_THIN: rFont.m_eWeight = WEIGHT_THIN; break; + case FW_EXTRALIGHT: rFont.m_eWeight = WEIGHT_ULTRALIGHT; break; + case FW_LIGHT: rFont.m_eWeight = WEIGHT_LIGHT; break; + case FW_MEDIUM: rFont.m_eWeight = WEIGHT_MEDIUM; break; + case FW_SEMIBOLD: rFont.m_eWeight = WEIGHT_SEMIBOLD; break; + case FW_BOLD: rFont.m_eWeight = WEIGHT_BOLD; break; + case FW_EXTRABOLD: rFont.m_eWeight = WEIGHT_ULTRABOLD; break; + case FW_BLACK: rFont.m_eWeight = WEIGHT_BLACK; break; + + case FW_NORMAL: + default: rFont.m_eWeight = WEIGHT_NORMAL; break; + } + + switch( aInfo.width ) + { + case FWIDTH_ULTRA_CONDENSED: rFont.m_eWidth = WIDTH_ULTRA_CONDENSED; break; + case FWIDTH_EXTRA_CONDENSED: rFont.m_eWidth = WIDTH_EXTRA_CONDENSED; break; + case FWIDTH_CONDENSED: rFont.m_eWidth = WIDTH_CONDENSED; break; + case FWIDTH_SEMI_CONDENSED: rFont.m_eWidth = WIDTH_SEMI_CONDENSED; break; + case FWIDTH_SEMI_EXPANDED: rFont.m_eWidth = WIDTH_SEMI_EXPANDED; break; + case FWIDTH_EXPANDED: rFont.m_eWidth = WIDTH_EXPANDED; break; + case FWIDTH_EXTRA_EXPANDED: rFont.m_eWidth = WIDTH_EXTRA_EXPANDED; break; + case FWIDTH_ULTRA_EXPANDED: rFont.m_eWidth = WIDTH_ULTRA_EXPANDED; break; + + case FWIDTH_NORMAL: + default: rFont.m_eWidth = WIDTH_NORMAL; break; + } + + rFont.m_ePitch = aInfo.pitch ? PITCH_FIXED : PITCH_VARIABLE; + rFont.m_eItalic = aInfo.italicAngle == 0 ? ITALIC_NONE : ( aInfo.italicAngle < 0 ? ITALIC_NORMAL : ITALIC_OBLIQUE ); + // #104264# there are fonts that set italic angle 0 although they are + // italic; use macstyle bit here + if( aInfo.italicAngle == 0 && (aInfo.macStyle & 2) ) + rFont.m_eItalic = ITALIC_NORMAL; + + rFont.m_aEncoding = aInfo.symbolEncoded ? RTL_TEXTENCODING_SYMBOL : RTL_TEXTENCODING_UCS2; + + if( aInfo.ascender && aInfo.descender ) + { + rFont.m_nLeading = aInfo.linegap; + rFont.m_nAscend = aInfo.ascender; + rFont.m_nDescend = -aInfo.descender; + } + else if( aInfo.typoAscender && aInfo.typoDescender ) + { + rFont.m_nLeading = aInfo.typoLineGap; + rFont.m_nAscend = aInfo.typoAscender; + rFont.m_nDescend = -aInfo.typoDescender; + } + else if( aInfo.winAscent && aInfo.winDescent ) + { + rFont.m_nAscend = aInfo.winAscent; + rFont.m_nDescend = aInfo.winDescent; + rFont.m_nLeading = rFont.m_nAscend + rFont.m_nDescend - 1000; + } + + // last try: font bounding box + if( rFont.m_nAscend == 0 ) + rFont.m_nAscend = aInfo.yMax; + if( rFont.m_nDescend == 0 ) + rFont.m_nDescend = -aInfo.yMin; + if( rFont.m_nLeading == 0 ) + rFont.m_nLeading = 15 * (rFont.m_nAscend+rFont.m_nDescend) / 100; + + // get bounding box + rFont.m_nXMin = aInfo.xMin; + rFont.m_nYMin = aInfo.yMin; + rFont.m_nXMax = aInfo.xMax; + rFont.m_nYMax = aInfo.yMax; + + CloseTTFont( pTTFont ); + bSuccess = true; + } + else + SAL_WARN("vcl.fonts", "Could not OpenTTFont \"" << aFile << "\": " << int(e)); + + return bSuccess; +} + +void PrintFontManager::initialize() +{ + #ifdef CALLGRIND_COMPILE + CALLGRIND_TOGGLE_COLLECT(); + CALLGRIND_ZERO_STATS(); + #endif + + // initialize can be called more than once, e.g. + // gtk-fontconfig-timestamp changes to reflect new font installed and + // PrintFontManager::initialize called again + { + m_nNextFontID = 1; + m_aFonts.clear(); + } +#if OSL_DEBUG_LEVEL > 1 + clock_t aStart; + clock_t aStep1; + clock_t aStep2; + + struct tms tms; + + aStart = times( &tms ); +#endif + + // first try fontconfig + initFontconfig(); + + // part one - look for downloadable fonts + rtl_TextEncoding aEncoding = osl_getThreadTextEncoding(); + const OUString &rSalPrivatePath = psp::getFontPath(); + + // search for the fonts in SAL_PRIVATE_FONTPATH first; those are + // the fonts installed with the office + if( !rSalPrivatePath.isEmpty() ) + { + OString aPath = OUStringToOString( rSalPrivatePath, aEncoding ); + sal_Int32 nIndex = 0; + do + { + OString aToken = aPath.getToken( 0, ';', nIndex ); + normPath( aToken ); + if (!aToken.isEmpty()) + addFontconfigDir(aToken); + } while( nIndex >= 0 ); + } + + countFontconfigFonts(); + +#if OSL_DEBUG_LEVEL > 1 + aStep1 = times( &tms ); + + aStep2 = times( &tms ); + SAL_INFO("vcl.fonts", "PrintFontManager::initialize: collected " + << m_aFonts.size() + << " fonts."); + double fTick = (double)sysconf( _SC_CLK_TCK ); + SAL_INFO("vcl.fonts", "Step 1 took " + << ((double)(aStep1 - aStart)/fTick) + << " seconds."); + SAL_INFO("vcl.fonts", "Step 2 took " + << ((double)(aStep2 - aStep1)/fTick) + << " seconds."); +#endif + + #ifdef CALLGRIND_COMPILE + CALLGRIND_DUMP_STATS(); + CALLGRIND_TOGGLE_COLLECT(); + #endif +} + +void PrintFontManager::getFontList( ::std::vector< fontID >& rFontIDs ) +{ + rFontIDs.clear(); + + for (auto const& font : m_aFonts) + rFontIDs.push_back(font.first); +} + +void PrintFontManager::fillPrintFontInfo(const PrintFont& rFont, FastPrintFontInfo& rInfo) +{ + rInfo.m_aFamilyName = rFont.m_aFamilyName; + rInfo.m_aStyleName = rFont.m_aStyleName; + rInfo.m_eFamilyStyle = rFont.m_eFamilyStyle; + rInfo.m_eItalic = rFont.m_eItalic; + rInfo.m_eWidth = rFont.m_eWidth; + rInfo.m_eWeight = rFont.m_eWeight; + rInfo.m_ePitch = rFont.m_ePitch; + rInfo.m_aEncoding = rFont.m_aEncoding; + rInfo.m_aAliases = rFont.m_aAliases; +} + +void PrintFontManager::fillPrintFontInfo( PrintFont& rFont, PrintFontInfo& rInfo ) const +{ + if (rFont.m_nAscend == 0 && rFont.m_nDescend == 0) + { + analyzeSfntFile(rFont); + } + + fillPrintFontInfo( rFont, static_cast< FastPrintFontInfo& >( rInfo ) ); + + rInfo.m_nAscend = rFont.m_nAscend; + rInfo.m_nDescend = rFont.m_nDescend; +} + +bool PrintFontManager::getFontInfo( fontID nFontID, PrintFontInfo& rInfo ) const +{ + const PrintFont* pFont = getFont( nFontID ); + if( pFont ) + { + rInfo.m_nID = nFontID; + fillPrintFontInfo( *pFont, rInfo ); + } + return pFont != nullptr; +} + +bool PrintFontManager::getFontFastInfo( fontID nFontID, FastPrintFontInfo& rInfo ) const +{ + const PrintFont* pFont = getFont( nFontID ); + if( pFont ) + { + rInfo.m_nID = nFontID; + fillPrintFontInfo( *pFont, rInfo ); + } + return pFont != nullptr; +} + +void PrintFontManager::getFontBoundingBox( fontID nFontID, int& xMin, int& yMin, int& xMax, int& yMax ) +{ + PrintFont* pFont = getFont( nFontID ); + if( pFont ) + { + if( pFont->m_nXMin == 0 && pFont->m_nYMin == 0 && pFont->m_nXMax == 0 && pFont->m_nYMax == 0 ) + { + analyzeSfntFile(*pFont); + } + xMin = pFont->m_nXMin; + yMin = pFont->m_nYMin; + xMax = pFont->m_nXMax; + yMax = pFont->m_nYMax; + } +} + +int PrintFontManager::getFontFaceNumber( fontID nFontID ) const +{ + int nRet = 0; + const PrintFont* pFont = getFont( nFontID ); + if (pFont) + { + nRet = pFont->m_nCollectionEntry; + if (nRet < 0) + nRet = 0; + } + return nRet; +} + +int PrintFontManager::getFontFaceVariation( fontID nFontID ) const +{ + int nRet = 0; + const PrintFont* pFont = getFont( nFontID ); + if (pFont) + { + nRet = pFont->m_nVariationEntry; + if (nRet < 0) + nRet = 0; + } + return nRet; +} + +FontFamily PrintFontManager::matchFamilyName( std::u16string_view rFamily ) +{ + struct family_t { + const char* mpName; + sal_uInt16 mnLength; + FontFamily meType; + }; + +#define InitializeClass( p, a ) p, sizeof(p) - 1, a + static const family_t pFamilyMatch[] = { + { InitializeClass( "arial", FAMILY_SWISS ) }, + { InitializeClass( "arioso", FAMILY_SCRIPT ) }, + { InitializeClass( "avant garde", FAMILY_SWISS ) }, + { InitializeClass( "avantgarde", FAMILY_SWISS ) }, + { InitializeClass( "bembo", FAMILY_ROMAN ) }, + { InitializeClass( "bookman", FAMILY_ROMAN ) }, + { InitializeClass( "conga", FAMILY_ROMAN ) }, + { InitializeClass( "courier", FAMILY_MODERN ) }, + { InitializeClass( "curl", FAMILY_SCRIPT ) }, + { InitializeClass( "fixed", FAMILY_MODERN ) }, + { InitializeClass( "gill", FAMILY_SWISS ) }, + { InitializeClass( "helmet", FAMILY_MODERN ) }, + { InitializeClass( "helvetica", FAMILY_SWISS ) }, + { InitializeClass( "international", FAMILY_MODERN ) }, + { InitializeClass( "lucida", FAMILY_SWISS ) }, + { InitializeClass( "new century schoolbook", FAMILY_ROMAN ) }, + { InitializeClass( "palatino", FAMILY_ROMAN ) }, + { InitializeClass( "roman", FAMILY_ROMAN ) }, + { InitializeClass( "sans serif", FAMILY_SWISS ) }, + { InitializeClass( "sansserif", FAMILY_SWISS ) }, + { InitializeClass( "serf", FAMILY_ROMAN ) }, + { InitializeClass( "serif", FAMILY_ROMAN ) }, + { InitializeClass( "times", FAMILY_ROMAN ) }, + { InitializeClass( "utopia", FAMILY_ROMAN ) }, + { InitializeClass( "zapf chancery", FAMILY_SCRIPT ) }, + { InitializeClass( "zapfchancery", FAMILY_SCRIPT ) } + }; + + OString aFamily = OUStringToOString( rFamily, RTL_TEXTENCODING_ASCII_US ); + sal_uInt32 nLower = 0; + sal_uInt32 nUpper = SAL_N_ELEMENTS(pFamilyMatch); + + while( nLower < nUpper ) + { + sal_uInt32 nCurrent = (nLower + nUpper) / 2; + const family_t* pHaystack = pFamilyMatch + nCurrent; + sal_Int32 nComparison = + rtl_str_compareIgnoreAsciiCase_WithLength + ( + aFamily.getStr(), aFamily.getLength(), + pHaystack->mpName, pHaystack->mnLength + ); + + if( nComparison < 0 ) + nUpper = nCurrent; + else + if( nComparison > 0 ) + nLower = nCurrent + 1; + else + return pHaystack->meType; + } + + return FAMILY_DONTKNOW; +} + +OString PrintFontManager::getFontFile(const PrintFont& rFont) const +{ + std::unordered_map< int, OString >::const_iterator it = m_aAtomToDir.find(rFont.m_nDirectory); + OString aPath = it->second + "/" + rFont.m_aFontFile; + return aPath; +} + +OUString PrintFontManager::getPSName( fontID nFontID ) +{ + PrintFont* pFont = getFont( nFontID ); + if (pFont && pFont->m_aPSName.isEmpty()) + { + analyzeSfntFile(*pFont); + } + + return pFont ? pFont->m_aPSName : OUString(); +} + +int PrintFontManager::getFontAscend( fontID nFontID ) +{ + PrintFont* pFont = getFont( nFontID ); + if (pFont && pFont->m_nAscend == 0 && pFont->m_nDescend == 0) + { + analyzeSfntFile(*pFont); + } + return pFont ? pFont->m_nAscend : 0; +} + +int PrintFontManager::getFontDescend( fontID nFontID ) +{ + PrintFont* pFont = getFont( nFontID ); + if (pFont && pFont->m_nAscend == 0 && pFont->m_nDescend == 0) + { + analyzeSfntFile(*pFont); + } + return pFont ? pFont->m_nDescend : 0; +} + +// TODO: move most of this stuff into the central font-subsetting code +bool PrintFontManager::createFontSubset( + FontSubsetInfo& rInfo, + fontID nFont, + const OUString& rOutFile, + const sal_GlyphId* pGlyphIds, + const sal_uInt8* pNewEncoding, + sal_Int32* pWidths, + int nGlyphs + ) +{ + PrintFont* pFont = getFont( nFont ); + if( !pFont ) + return false; + + rInfo.m_nFontType = FontType::SFNT_TTF; + + // reshuffle array of requested glyphs to make sure glyph0==notdef + sal_uInt8 pEnc[256]; + sal_uInt16 pGID[256]; + sal_uInt8 pOldIndex[256]; + memset( pEnc, 0, sizeof( pEnc ) ); + memset( pGID, 0, sizeof( pGID ) ); + memset( pOldIndex, 0, sizeof( pOldIndex ) ); + if( nGlyphs > 256 ) + return false; + int nChar = 1; + for( int i = 0; i < nGlyphs; i++ ) + { + if( pNewEncoding[i] == 0 ) + { + pOldIndex[ 0 ] = i; + } + else + { + SAL_WARN_IF( (pGlyphIds[i] & 0x007f0000), "vcl.fonts", "overlong glyph id" ); + SAL_WARN_IF( static_cast<int>(pNewEncoding[i]) >= nGlyphs, "vcl.fonts", "encoding wrong" ); + SAL_WARN_IF( pEnc[pNewEncoding[i]] != 0 || pGID[pNewEncoding[i]] != 0, "vcl.fonts", "duplicate encoded glyph" ); + pEnc[ pNewEncoding[i] ] = pNewEncoding[i]; + pGID[ pNewEncoding[i] ] = static_cast<sal_uInt16>(pGlyphIds[ i ]); + pOldIndex[ pNewEncoding[i] ] = i; + nChar++; + } + } + nGlyphs = nChar; // either input value or increased by one + + // prepare system name for read access for subset source file + // TODO: since this file is usually already mmapped there is no need to open it again + const OString aFromFile = getFontFile( *pFont ); + + TrueTypeFont* pTTFont = nullptr; // TODO: rename to SfntFont + if( OpenTTFontFile( aFromFile.getStr(), pFont->m_nCollectionEntry, &pTTFont ) != SFErrCodes::Ok ) + return false; + + // prepare system name for write access for subset file target + OUString aSysPath; + if( osl_File_E_None != osl_getSystemPathFromFileURL( rOutFile.pData, &aSysPath.pData ) ) + return false; + const rtl_TextEncoding aEncoding = osl_getThreadTextEncoding(); + const OString aToFile( OUStringToOString( aSysPath, aEncoding ) ); + + // do CFF subsetting if possible + sal_uInt32 nCffLength = 0; + const sal_uInt8* pCffBytes = pTTFont->table(vcl::O_CFF, nCffLength); + if (pCffBytes) + { + rInfo.LoadFont( FontType::CFF_FONT, pCffBytes, nCffLength ); +#if 1 // TODO: remove 16bit->long conversion when related methods handle non-16bit glyphids + sal_GlyphId aRequestedGlyphIds[256]; + for( int i = 0; i < nGlyphs; ++i ) + aRequestedGlyphIds[i] = pGID[i]; +#endif + // create subset file at requested path + FILE* pOutFile = fopen( aToFile.getStr(), "wb" ); + if (!pOutFile) + { + CloseTTFont( pTTFont ); + return false; + } + // create font subset + const char* const pGlyphSetName = nullptr; // TODO: better name? + const bool bOK = rInfo.CreateFontSubset( + FontType::TYPE1_PFB, + pOutFile, pGlyphSetName, + aRequestedGlyphIds, pEnc, nGlyphs, pWidths ); + fclose( pOutFile ); + // For OTC, values from hhea or OS2 are better + psp::PrintFontInfo aFontInfo; + if( getFontInfo( nFont, aFontInfo ) ) + { + rInfo.m_nAscent = aFontInfo.m_nAscend; + rInfo.m_nDescent = -aFontInfo.m_nDescend; + } + // cleanup before early return + CloseTTFont( pTTFont ); + return bOK; + } + + // do TTF->Type42 or Type3 subsetting + // fill in font info + psp::PrintFontInfo aFontInfo; + if( ! getFontInfo( nFont, aFontInfo ) ) + return false; + + rInfo.m_nAscent = aFontInfo.m_nAscend; + rInfo.m_nDescent = aFontInfo.m_nDescend; + rInfo.m_aPSName = getPSName( nFont ); + + int xMin, yMin, xMax, yMax; + getFontBoundingBox( nFont, xMin, yMin, xMax, yMax ); + rInfo.m_aFontBBox = tools::Rectangle( Point( xMin, yMin ), Size( xMax-xMin, yMax-yMin ) ); + rInfo.m_nCapHeight = yMax; // Well ... + + if (pWidths) + { + // fill in glyph advance widths + std::unique_ptr<sal_uInt16[]> pMetrics = GetTTSimpleGlyphMetrics( pTTFont, + pGID, + nGlyphs, + false/*bVertical*/ ); + if( pMetrics ) + { + for( int i = 0; i < nGlyphs; i++ ) + pWidths[pOldIndex[i]] = pMetrics[i]; + pMetrics.reset(); + } + else + { + CloseTTFont( pTTFont ); + return false; + } + } + + bool bSuccess = ( SFErrCodes::Ok == CreateTTFromTTGlyphs( pTTFont, + aToFile.getStr(), + pGID, + pEnc, + nGlyphs ) ); + CloseTTFont( pTTFont ); + + return bSuccess; +} + +void PrintFontManager::getGlyphWidths( fontID nFont, + bool bVertical, + std::vector< sal_Int32 >& rWidths, + std::map< sal_Unicode, sal_uInt32 >& rUnicodeEnc ) +{ + PrintFont* pFont = getFont( nFont ); + if (!pFont) + return; + TrueTypeFont* pTTFont = nullptr; + OString aFromFile = getFontFile( *pFont ); + if( OpenTTFontFile( aFromFile.getStr(), pFont->m_nCollectionEntry, &pTTFont ) != SFErrCodes::Ok ) + return; + int nGlyphs = pTTFont->glyphCount(); + if (nGlyphs > 0) + { + rWidths.resize(nGlyphs); + std::vector<sal_uInt16> aGlyphIds(nGlyphs); + for (int i = 0; i < nGlyphs; i++) + aGlyphIds[i] = sal_uInt16(i); + std::unique_ptr<sal_uInt16[]> pMetrics = GetTTSimpleGlyphMetrics(pTTFont, + aGlyphIds.data(), + nGlyphs, + bVertical); + if (pMetrics) + { + for (int i = 0; i< nGlyphs; i++) + rWidths[i] = pMetrics[i]; + pMetrics.reset(); + rUnicodeEnc.clear(); + } + + // fill the unicode map + // TODO: isn't this map already available elsewhere in the fontmanager? + sal_uInt32 nCmapSize = 0; + const sal_uInt8* pCmapData = pTTFont->table(O_cmap, nCmapSize); + if (pCmapData) + { + CmapResult aCmapResult; + if (ParseCMAP(pCmapData, nCmapSize, aCmapResult)) + { + FontCharMapRef xFontCharMap(new FontCharMap(aCmapResult)); + for (sal_uInt32 cOld = 0;;) + { + // get next unicode covered by font + const sal_uInt32 c = xFontCharMap->GetNextChar(cOld); + if (c == cOld) + break; + cOld = c; +#if 1 // TODO: remove when sal_Unicode covers all of unicode + if (c > sal_Unicode(~0)) + break; +#endif + // get the matching glyph index + const sal_GlyphId aGlyphId = xFontCharMap->GetGlyphIndex(c); + // update the requested map + rUnicodeEnc[static_cast<sal_Unicode>(c)] = aGlyphId; + } + } + } + } + CloseTTFont(pTTFont); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/generic/fontmanager/fontsubst.cxx b/vcl/unx/generic/fontmanager/fontsubst.cxx new file mode 100644 index 000000000..afbe53dd4 --- /dev/null +++ b/vcl/unx/generic/fontmanager/fontsubst.cxx @@ -0,0 +1,223 @@ +/* -*- 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 <unx/geninst.h> +#include <font/PhysicalFontCollection.hxx> +#include <font/fontsubstitution.hxx> +#include <unx/fontmanager.hxx> + +// platform specific font substitution hooks + +namespace { + +class FcPreMatchSubstitution +: public vcl::font::PreMatchFontSubstitution +{ +public: + bool FindFontSubstitute( vcl::font::FontSelectPattern& ) const override; + typedef ::std::pair<vcl::font::FontSelectPattern, vcl::font::FontSelectPattern> value_type; +private: + typedef ::std::list<value_type> CachedFontMapType; + mutable CachedFontMapType maCachedFontMap; +}; + +class FcGlyphFallbackSubstitution +: public vcl::font::GlyphFallbackFontSubstitution +{ + // TODO: add a cache +public: + bool FindFontSubstitute(vcl::font::FontSelectPattern&, LogicalFontInstance* pLogicalFont, OUString& rMissingCodes) const override; +}; + +} + +void SalGenericInstance::RegisterFontSubstitutors(vcl::font::PhysicalFontCollection* pFontCollection) +{ + // register font fallback substitutions + static FcPreMatchSubstitution aSubstPreMatch; + pFontCollection->SetPreMatchHook( &aSubstPreMatch ); + + // register glyph fallback substitutions + static FcGlyphFallbackSubstitution aSubstFallback; + pFontCollection->SetFallbackHook( &aSubstFallback ); +} + +static vcl::font::FontSelectPattern GetFcSubstitute(const vcl::font::FontSelectPattern &rFontSelData, OUString& rMissingCodes) +{ + vcl::font::FontSelectPattern aSubstituted(rFontSelData); + psp::PrintFontManager& rMgr = psp::PrintFontManager::get(); + rMgr.Substitute(aSubstituted, rMissingCodes); + return aSubstituted; +} + +namespace +{ + bool uselessmatch(const vcl::font::FontSelectPattern &rOrig, const vcl::font::FontSelectPattern &rNew) + { + return + ( + rOrig.maTargetName == rNew.maSearchName && + rOrig.GetWeight() == rNew.GetWeight() && + rOrig.GetItalic() == rNew.GetItalic() && + rOrig.GetPitch() == rNew.GetPitch() && + rOrig.GetWidthType() == rNew.GetWidthType() + ); + } + + class equal + { + private: + const vcl::font::FontSelectPattern& mrAttributes; + public: + explicit equal(const vcl::font::FontSelectPattern& rAttributes) + : mrAttributes(rAttributes) + { + } + bool operator()(const FcPreMatchSubstitution::value_type& rOther) const + { return rOther.first == mrAttributes; } + }; +} + +bool FcPreMatchSubstitution::FindFontSubstitute(vcl::font::FontSelectPattern &rFontSelData) const +{ + // We don't actually want to talk to Fontconfig at all for symbol fonts + if( rFontSelData.IsSymbolFont() ) + return false; + // StarSymbol is a unicode font, but it still deserves the symbol flag + if ( IsStarSymbol(rFontSelData.maSearchName) ) + return false; + + //see fdo#41556 and fdo#47636 + //fontconfig can return e.g. an italic font for a non-italic input and/or + //different fonts depending on fontsize, bold, etc settings so don't cache + //just on the name, cache map all the input and all the output not just map + //from original selection to output fontname + vcl::font::FontSelectPattern& rPatternAttributes = rFontSelData; + CachedFontMapType &rCachedFontMap = maCachedFontMap; + CachedFontMapType::iterator itr = std::find_if(rCachedFontMap.begin(), rCachedFontMap.end(), equal(rPatternAttributes)); + if (itr != rCachedFontMap.end()) + { + // Cached substitution + rFontSelData = itr->second; + if (itr != rCachedFontMap.begin()) + { + // MRU, move it to the front + rCachedFontMap.splice(rCachedFontMap.begin(), rCachedFontMap, itr); + } + return true; + } + + OUString aDummy; + const vcl::font::FontSelectPattern aOut = GetFcSubstitute( rFontSelData, aDummy ); + + if( aOut.maSearchName.isEmpty() ) + return false; + + const bool bHaveSubstitute = !uselessmatch( rFontSelData, aOut ); + +#ifdef DEBUG + std::ostringstream oss; + oss << "FcPreMatchSubstitution \"" + << rFontSelData.maTargetName + << "\" bipw=" + << rFontSelData.GetWeight() + << rFontSelData.GetItalic() + << rFontSelData.GetPitch() + << rFontSelData.GetWidthType() + << " -> "; + if( !bHaveSubstitute ) + oss << "no substitute available."; + else + oss << "\"" + << aOut.maSearchName + << "\" bipw=" + << aOut.GetWeight() + << aOut.GetItalic() + << aOut.GetPitch() + << aOut.GetWidthType(); + SAL_INFO("vcl.fonts", oss.str()); +#endif + + if( bHaveSubstitute ) + { + rCachedFontMap.push_front(value_type(rFontSelData, aOut)); + // Fairly arbitrary limit in this case, but I recall measuring max 8 + // fonts as the typical max amount of fonts in medium sized documents, so make it + // a fair chunk larger to accommodate weird documents./ + if (rCachedFontMap.size() > 256) + rCachedFontMap.pop_back(); + rFontSelData = aOut; + } + + return bHaveSubstitute; +} + +bool FcGlyphFallbackSubstitution::FindFontSubstitute(vcl::font::FontSelectPattern& rFontSelData, + LogicalFontInstance* /*pLogicalFont*/, + OUString& rMissingCodes ) const +{ + // We don't actually want to talk to Fontconfig at all for symbol fonts + if( rFontSelData.IsSymbolFont() ) + return false; + // StarSymbol is a unicode font, but it still deserves the symbol flag + if ( IsStarSymbol(rFontSelData.maSearchName) ) + return false; + + const vcl::font::FontSelectPattern aOut = GetFcSubstitute( rFontSelData, rMissingCodes ); + // TODO: cache the unicode + srcfont specific result + // FC doing it would be preferable because it knows the invariables + // e.g. FC knows the FC rule that all Arial gets replaced by LiberationSans + // whereas we would have to check for every size or attribute + if( aOut.maSearchName.isEmpty() ) + return false; + + const bool bHaveSubstitute = !uselessmatch( rFontSelData, aOut ); + +#ifdef DEBUG + std::ostringstream oss; + oss << "FcGFSubstitution \"" + << rFontSelData.maTargetName + << "\" bipw=" + << rFontSelData.GetWeight() + << rFontSelData.GetItalic() + << rFontSelData.GetPitch() + << rFontSelData.GetWidthType() + << " -> "; + if( !bHaveSubstitute ) + oss << "no substitute available."; + else + oss << "\"" + << aOut.maSearchName + << "\" bipw=" + << aOut.GetWeight() + << aOut.GetItalic() + << aOut.GetPitch() + << aOut.GetWidthType(); + SAL_INFO("vcl.fonts", oss.str()); +#endif + + if( bHaveSubstitute ) + rFontSelData = aOut; + + return bHaveSubstitute; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/generic/fontmanager/helper.cxx b/vcl/unx/generic/fontmanager/helper.cxx new file mode 100644 index 000000000..6cea0998e --- /dev/null +++ b/vcl/unx/generic/fontmanager/helper.cxx @@ -0,0 +1,262 @@ +/* -*- 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 <config_folders.h> + +#include <sys/stat.h> +#include <limits.h> +#include <osl/file.hxx> +#include <osl/process.h> +#include <osl/thread.h> +#include <rtl/bootstrap.hxx> +#include <rtl/ustring.hxx> +#include <sal/log.hxx> +#include <tools/urlobj.hxx> +#include <unx/helper.hxx> + +#include <tuple> + +using ::rtl::Bootstrap; + +namespace psp { + +OUString getOfficePath( whichOfficePath ePath ) +{ + static const auto aPaths = [] { + OUString sRoot, sUser, sConfig; + Bootstrap::get("BRAND_BASE_DIR", sRoot); + Bootstrap aBootstrap(sRoot + "/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE("bootstrap")); + aBootstrap.getFrom("UserInstallation", sUser); + aBootstrap.getFrom("CustomDataUrl", sConfig); + OUString aUPath = sUser + "/user/psprint"; + if (sRoot.startsWith("file://")) + { + OUString aSysPath; + if (osl::FileBase::getSystemPathFromFileURL(sRoot, aSysPath) == osl::FileBase::E_None) + sRoot = aSysPath; + } + if (sUser.startsWith("file://")) + { + OUString aSysPath; + if (osl::FileBase::getSystemPathFromFileURL(sUser, aSysPath) == osl::FileBase::E_None) + sUser = aSysPath; + } + if (sConfig.startsWith("file://")) + { + OUString aSysPath; + if (osl::FileBase::getSystemPathFromFileURL(sConfig, aSysPath) == osl::FileBase::E_None) + sConfig = aSysPath; + } + + // ensure user path exists + SAL_INFO("vcl.fonts", "Trying to create: " << aUPath); + osl::Directory::createPath(aUPath); + + return std::make_tuple(sRoot, sUser, sConfig); + }(); + const auto& [aInstallationRootPath, aUserPath, aConfigPath] = aPaths; + + switch( ePath ) + { + case whichOfficePath::ConfigPath: return aConfigPath; + case whichOfficePath::InstallationRootPath: return aInstallationRootPath; + case whichOfficePath::UserPath: return aUserPath; + } + return OUString(); +} + +static OString getEnvironmentPath( const char* pKey ) +{ + OString aPath; + + const char* pValue = getenv( pKey ); + if( pValue && *pValue ) + { + aPath = OString( pValue ); + } + return aPath; +} + +} // namespace psp + +void psp::getPrinterPathList( std::vector< OUString >& rPathList, const char* pSubDir ) +{ + rPathList.clear(); + rtl_TextEncoding aEncoding = osl_getThreadTextEncoding(); + + OUStringBuffer aPathBuffer( 256 ); + + // append net path + aPathBuffer.append( getOfficePath( whichOfficePath::InstallationRootPath ) ); + if( !aPathBuffer.isEmpty() ) + { + aPathBuffer.append( "/" LIBO_SHARE_FOLDER "/psprint" ); + if( pSubDir ) + { + aPathBuffer.append( '/' ); + aPathBuffer.appendAscii( pSubDir ); + } + rPathList.push_back( aPathBuffer.makeStringAndClear() ); + } + // append user path + aPathBuffer.append( getOfficePath( whichOfficePath::UserPath ) ); + if( !aPathBuffer.isEmpty() ) + { + aPathBuffer.append( "/user/psprint" ); + if( pSubDir ) + { + aPathBuffer.append( '/' ); + aPathBuffer.appendAscii( pSubDir ); + } + rPathList.push_back( aPathBuffer.makeStringAndClear() ); + } + + OString aPath( getEnvironmentPath("SAL_PSPRINT") ); + sal_Int32 nIndex = 0; + do + { + OString aDir( aPath.getToken( 0, ':', nIndex ) ); + if( aDir.isEmpty() ) + continue; + + if( pSubDir ) + { + aDir += OString::Concat("/") + pSubDir; + } + struct stat aStat; + if( stat( aDir.getStr(), &aStat ) || ! S_ISDIR( aStat.st_mode ) ) + continue; + + rPathList.push_back( OStringToOUString( aDir, aEncoding ) ); + } while( nIndex != -1 ); + + #ifdef SYSTEM_PPD_DIR + if( pSubDir && rtl_str_compare( pSubDir, PRINTER_PPDDIR ) == 0 ) + { + rPathList.push_back( OStringToOUString( OString( SYSTEM_PPD_DIR ), RTL_TEXTENCODING_UTF8 ) ); + } + #endif + + if( !rPathList.empty() ) + return; + + // last resort: next to program file (mainly for setup) + OUString aExe; + if( osl_getExecutableFile( &aExe.pData ) == osl_Process_E_None ) + { + INetURLObject aDir( aExe ); + aDir.removeSegment(); + aExe = aDir.GetMainURL( INetURLObject::DecodeMechanism::NONE ); + OUString aSysPath; + if( osl_getSystemPathFromFileURL( aExe.pData, &aSysPath.pData ) == osl_File_E_None ) + { + rPathList.push_back( aSysPath ); + } + } +} + +OUString const & psp::getFontPath() +{ + static OUString aPath; + + if (aPath.isEmpty()) + { + OUStringBuffer aPathBuffer( 512 ); + + OUString aConfigPath( getOfficePath( whichOfficePath::ConfigPath ) ); + OUString aInstallationRootPath( getOfficePath( whichOfficePath::InstallationRootPath ) ); + OUString aUserPath( getOfficePath( whichOfficePath::UserPath ) ); + if (!aInstallationRootPath.isEmpty()) + { + // internal font resources, required for normal operation, like OpenSymbol + aPathBuffer.append(aInstallationRootPath + + "/" LIBO_SHARE_RESOURCE_FOLDER "/common/fonts;"); + } + if( !aConfigPath.isEmpty() ) + { + // #i53530# Path from CustomDataUrl will completely + // replace net share and user paths if the path exists + OUString sPath = aConfigPath + "/" LIBO_SHARE_FOLDER "/fonts"; + // check existence of config path + struct stat aStat; + if( 0 != stat( OUStringToOString( sPath, osl_getThreadTextEncoding() ).getStr(), &aStat ) + || ! S_ISDIR( aStat.st_mode ) ) + aConfigPath.clear(); + else + { + aPathBuffer.append(sPath); + } + } + if( aConfigPath.isEmpty() ) + { + if( !aInstallationRootPath.isEmpty() ) + { + aPathBuffer.append( aInstallationRootPath ); + aPathBuffer.append( "/" LIBO_SHARE_FOLDER "/fonts/truetype;"); + } + if( !aUserPath.isEmpty() ) + { + aPathBuffer.append( aUserPath ); + aPathBuffer.append( "/user/fonts" ); + } + } + + aPath = aPathBuffer.makeStringAndClear(); + SAL_INFO("vcl.fonts", "Initializing font path to: " << aPath); + } + return aPath; +} + +void psp::normPath( OString& rPath ) +{ + char buf[PATH_MAX]; + + // double slashes and slash at end are probably + // removed by realpath anyway, but since this runs + // on many different platforms let's play it safe + OString aPath = rPath.replaceAll("//", "/"); + + if( aPath.endsWith("/") ) + aPath = aPath.copy(0, aPath.getLength()-1); + + if( ( aPath.indexOf("./") != -1 || + aPath.indexOf( '~' ) != -1 ) + && realpath( aPath.getStr(), buf ) ) + { + rPath = buf; + } + else + { + rPath = aPath; + } +} + +void psp::splitPath( OString& rPath, OString& rDir, OString& rBase ) +{ + normPath( rPath ); + sal_Int32 nIndex = rPath.lastIndexOf( '/' ); + if( nIndex > 0 ) + rDir = rPath.copy( 0, nIndex ); + else if( nIndex == 0 ) // root dir + rDir = rPath.copy( 0, 1 ); + if( rPath.getLength() > nIndex+1 ) + rBase = rPath.copy( nIndex+1 ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/generic/gdi/X11CairoSalGraphicsImpl.cxx b/vcl/unx/generic/gdi/X11CairoSalGraphicsImpl.cxx new file mode 100644 index 000000000..2109e19b9 --- /dev/null +++ b/vcl/unx/generic/gdi/X11CairoSalGraphicsImpl.cxx @@ -0,0 +1,185 @@ +/* -*- 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 "X11CairoSalGraphicsImpl.hxx" + +#if ENABLE_CAIRO_CANVAS + +#include <basegfx/polygon/b2dpolypolygontools.hxx> +#include <basegfx/curve/b2dcubicbezier.hxx> + +X11CairoSalGraphicsImpl::X11CairoSalGraphicsImpl(X11SalGraphics& rParent, X11Common& rX11Common) + : X11SalGraphicsImpl(rParent) + , mrX11Common(rX11Common) + , mnPenColor(SALCOLOR_NONE) + , mnFillColor(SALCOLOR_NONE) +{ +} + +bool X11CairoSalGraphicsImpl::drawPolyPolygon(const basegfx::B2DHomMatrix& rObjectToDevice, + const basegfx::B2DPolyPolygon& rPolyPolygon, + double fTransparency) +{ + if (fTransparency >= 1.0) + { + return true; + } + + if (rPolyPolygon.count() == 0) + { + return true; + } + + // Fallback: Transform to DeviceCoordinates + basegfx::B2DPolyPolygon aPolyPolygon(rPolyPolygon); + aPolyPolygon.transform(rObjectToDevice); + + if (SALCOLOR_NONE == mnFillColor && SALCOLOR_NONE == mnPenColor) + { + return true; + } + + // enable by setting to something + static const char* pUseCairoForPolygons(getenv("SAL_ENABLE_USE_CAIRO_FOR_POLYGONS")); + + if (nullptr != pUseCairoForPolygons && mrX11Common.SupportsCairo()) + { + // snap to raster if requested + const bool bSnapPoints(!getAntiAlias()); + + if (bSnapPoints) + { + aPolyPolygon = basegfx::utils::snapPointsOfHorizontalOrVerticalEdges(aPolyPolygon); + } + + cairo_t* cr = mrX11Common.getCairoContext(); + clipRegion(cr); + + for (auto const& rPolygon : std::as_const(aPolyPolygon)) + { + const sal_uInt32 nPointCount(rPolygon.count()); + + if (nPointCount) + { + const sal_uInt32 nEdgeCount(rPolygon.isClosed() ? nPointCount : nPointCount - 1); + + if (nEdgeCount) + { + basegfx::B2DCubicBezier aEdge; + + for (sal_uInt32 b = 0; b < nEdgeCount; ++b) + { + rPolygon.getBezierSegment(b, aEdge); + + if (!b) + { + const basegfx::B2DPoint aStart(aEdge.getStartPoint()); + cairo_move_to(cr, aStart.getX(), aStart.getY()); + } + + const basegfx::B2DPoint aEnd(aEdge.getEndPoint()); + + if (aEdge.isBezier()) + { + const basegfx::B2DPoint aCP1(aEdge.getControlPointA()); + const basegfx::B2DPoint aCP2(aEdge.getControlPointB()); + cairo_curve_to(cr, aCP1.getX(), aCP1.getY(), aCP2.getX(), aCP2.getY(), + aEnd.getX(), aEnd.getY()); + } + else + { + cairo_line_to(cr, aEnd.getX(), aEnd.getY()); + } + } + + cairo_close_path(cr); + } + } + } + + if (SALCOLOR_NONE != mnFillColor) + { + cairo_set_source_rgba(cr, mnFillColor.GetRed() / 255.0, mnFillColor.GetGreen() / 255.0, + mnFillColor.GetBlue() / 255.0, 1.0 - fTransparency); + cairo_set_fill_rule(cr, CAIRO_FILL_RULE_EVEN_ODD); + cairo_fill_preserve(cr); + } + + if (SALCOLOR_NONE != mnPenColor) + { + cairo_set_source_rgba(cr, mnPenColor.GetRed() / 255.0, mnPenColor.GetGreen() / 255.0, + mnPenColor.GetBlue() / 255.0, 1.0 - fTransparency); + cairo_stroke_preserve(cr); + } + + X11Common::releaseCairoContext(cr); + return true; + } + + return X11SalGraphicsImpl::drawPolyPolygon(rObjectToDevice, rPolyPolygon, fTransparency); +} + +bool X11CairoSalGraphicsImpl::drawPolyLine(const basegfx::B2DHomMatrix& rObjectToDevice, + const basegfx::B2DPolygon& rPolygon, + double fTransparency, double fLineWidth, + const std::vector<double>* pStroke, + basegfx::B2DLineJoin eLineJoin, + css::drawing::LineCap eLineCap, + double fMiterMinimumAngle, bool bPixelSnapHairline) +{ + if (0 == rPolygon.count()) + { + return true; + } + + if (fTransparency >= 1.0) + { + return true; + } + + // disable by setting to something + static const char* pUseCairoForFatLines(getenv("SAL_DISABLE_USE_CAIRO_FOR_FATLINES")); + + if (nullptr == pUseCairoForFatLines && mrX11Common.SupportsCairo()) + { + cairo_t* cr = mrX11Common.getCairoContext(); + clipRegion(cr); + + // Use the now available static drawPolyLine from the Cairo-Headless-Fallback + // that will take care of all needed stuff + const bool bRetval(CairoCommon::drawPolyLine( + cr, nullptr, mnPenColor, getAntiAlias(), rObjectToDevice, rPolygon, fTransparency, + fLineWidth, pStroke, eLineJoin, eLineCap, fMiterMinimumAngle, bPixelSnapHairline)); + + X11Common::releaseCairoContext(cr); + + if (bRetval) + { + return true; + } + } + + return X11SalGraphicsImpl::drawPolyLine(rObjectToDevice, rPolygon, fTransparency, fLineWidth, + pStroke, eLineJoin, eLineCap, fMiterMinimumAngle, + bPixelSnapHairline); +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/generic/gdi/X11CairoSalGraphicsImpl.hxx b/vcl/unx/generic/gdi/X11CairoSalGraphicsImpl.hxx new file mode 100644 index 000000000..beb07e3ff --- /dev/null +++ b/vcl/unx/generic/gdi/X11CairoSalGraphicsImpl.hxx @@ -0,0 +1,97 @@ +/* -*- 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 <config_features.h> +#include <config_cairo_canvas.h> + +#if ENABLE_CAIRO_CANVAS + +#include <cairo-xlib.h> +#include <unx/salgdi.h> +#include "gdiimpl.hxx" +#include "cairo_xlib_cairo.hxx" + +#include <headless/CairoCommon.hxx> + +class X11CairoSalGraphicsImpl : public X11SalGraphicsImpl +{ +private: + X11Common& mrX11Common; + vcl::Region maClipRegion; + Color mnPenColor; + Color mnFillColor; + + using X11SalGraphicsImpl::drawPolyPolygon; + using X11SalGraphicsImpl::drawPolyLine; + +public: + X11CairoSalGraphicsImpl(X11SalGraphics& rParent, X11Common& rX11Common); + + void ResetClipRegion() override + { + maClipRegion.SetNull(); + X11SalGraphicsImpl::ResetClipRegion(); + } + + bool setClipRegion(const vcl::Region& i_rClip) override + { + maClipRegion = i_rClip; + return X11SalGraphicsImpl::setClipRegion(i_rClip); + } + + void SetLineColor() override + { + mnPenColor = SALCOLOR_NONE; + X11SalGraphicsImpl::SetLineColor(); + } + + void SetLineColor(Color nColor) override + { + mnPenColor = nColor; + X11SalGraphicsImpl::SetLineColor(nColor); + } + + void SetFillColor() override + { + mnFillColor = SALCOLOR_NONE; + X11SalGraphicsImpl::SetFillColor(); + } + + void SetFillColor(Color nColor) override + { + mnFillColor = nColor; + X11SalGraphicsImpl::SetFillColor(nColor); + } + + void clipRegion(cairo_t* cr) { CairoCommon::clipRegion(cr, maClipRegion); } + + bool drawPolyPolygon(const basegfx::B2DHomMatrix& rObjectToDevice, + const basegfx::B2DPolyPolygon& rPolyPolygon, + double fTransparency) override; + + bool drawPolyLine(const basegfx::B2DHomMatrix& rObjectToDevice, + const basegfx::B2DPolygon& rPolygon, double fTransparency, double fLineWidth, + const std::vector<double>* pStroke, basegfx::B2DLineJoin eLineJoin, + css::drawing::LineCap eLineCap, double fMiterMinimumAngle, + bool bPixelSnapHairline) override; +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/generic/gdi/cairo_xlib_cairo.cxx b/vcl/unx/generic/gdi/cairo_xlib_cairo.cxx new file mode 100644 index 000000000..17b34442b --- /dev/null +++ b/vcl/unx/generic/gdi/cairo_xlib_cairo.cxx @@ -0,0 +1,307 @@ +/* -*- 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 <X11/Xlib.h> +#include <X11/extensions/Xrender.h> + +#include "cairo_xlib_cairo.hxx" + +#include <vcl/sysdata.hxx> +#include <vcl/bitmap.hxx> +#include <vcl/virdev.hxx> +#include <sal/log.hxx> + +#include <cairo-xlib.h> +#include <cairo-xlib-xrender.h> + +namespace +{ + Pixmap limitXCreatePixmap(Display *display, Drawable d, unsigned int width, unsigned int height, unsigned int depth) + { + // The X protocol request CreatePixmap puts an upper bound + // of 16 bit to the size. And in practice some drivers + // fall over with values close to the max. + + // see, e.g. moz#424333, fdo#48961, rhbz#1086714 + // we've a duplicate of this in vcl :-( + if (width > SAL_MAX_INT16-10 || height > SAL_MAX_INT16-10) + { + SAL_WARN("canvas", "overlarge pixmap: " << width << " x " << height); + return None; + } + return XCreatePixmap(display, d, width, height, depth); + } +} + +namespace cairo +{ + + X11SysData::X11SysData() : + pDisplay(nullptr), + hDrawable(0), + pVisual(nullptr), + nScreen(0), + pRenderFormat(nullptr) + {} + + X11SysData::X11SysData( const SystemGraphicsData& pSysDat ) : + pDisplay(pSysDat.pDisplay), + hDrawable(pSysDat.hDrawable), + pVisual(pSysDat.pVisual), + nScreen(pSysDat.nScreen), + pRenderFormat(pSysDat.pXRenderFormat) + {} + + X11SysData::X11SysData( const SystemEnvData& pSysDat, const SalFrame* pReference ) : + pDisplay(pSysDat.pDisplay), + hDrawable(pSysDat.GetWindowHandle(pReference)), + pVisual(pSysDat.pVisual), + nScreen(pSysDat.nScreen), + pRenderFormat(nullptr) + {} + + X11Pixmap::~X11Pixmap() + { + if( mpDisplay && mhDrawable ) + XFreePixmap( static_cast<Display*>(mpDisplay), mhDrawable ); + } + + /** + * Surface::Surface: Create Canvas surface with existing data + * @param pSysData Platform native system environment data (struct SystemEnvData in vcl/inc/sysdata.hxx) + * @param pSurface Cairo surface + * + * pSysData contains the platform native Drawable reference + * This constructor only stores data, it does no processing. + * It is used by e.g. Surface::getSimilar() + * + * Set the mpSurface as pSurface + **/ + X11Surface::X11Surface( const X11SysData& rSysData, + const X11PixmapSharedPtr& rPixmap, + const CairoSurfaceSharedPtr& pSurface ) : + maSysData(rSysData), + mpPixmap(rPixmap), + mpSurface(pSurface) + {} + + /** + * Surface::Surface: Create generic Canvas surface using given Cairo Surface + * + * @param pSurface Cairo Surface + * + * This constructor only stores data, it does no processing. + * It is used with e.g. cairo_image_surface_create_for_data() + * Unlike other constructors, mpSysData is set to NULL + * + * Set the mpSurface as pSurface + **/ + X11Surface::X11Surface( const CairoSurfaceSharedPtr& pSurface ) : + maSysData(), + mpSurface(pSurface) + {} + + /** + * Surface::Surface: Create Canvas surface from Window reference. + * @param pSysData Platform native system environment data (struct SystemEnvData in vcl/inc/sysdata.hxx) + * @param x horizontal location of the new surface + * @param y vertical location of the new surface + * @param width width of the new surface + * @param height height of the new surface + * + * pSysData contains the platform native Window reference. + * + * pSysData is used to create a surface on the Window + * + * Set the mpSurface to the new surface or NULL + **/ + X11Surface::X11Surface( const X11SysData& rSysData, int x, int y, int width, int height ) : + maSysData(rSysData), + mpSurface( + cairo_xlib_surface_create( static_cast<Display*>(rSysData.pDisplay), + rSysData.hDrawable, + static_cast<Visual*>(rSysData.pVisual), + width + x, height + y ), + &cairo_surface_destroy) + { + cairo_surface_set_device_offset(mpSurface.get(), x, y ); + } + + /** + * Surface::Surface: Create platform native Canvas surface from BitmapSystemData + * @param pSysData Platform native system environment data (struct SystemEnvData in vcl/inc/sysdata.hxx) + * @param pBmpData Platform native image data (struct BitmapSystemData in vcl/inc/bitmap.hxx) + * @param width width of the new surface + * @param height height of the new surface + * + * The pBmpData provides the imagedata that the created surface should contain. + * + * Set the mpSurface to the new surface or NULL + **/ + X11Surface::X11Surface( const X11SysData& rSysData, + const BitmapSystemData& rData ) : + maSysData( rSysData ), + mpSurface( + cairo_xlib_surface_create( static_cast<Display*>(rSysData.pDisplay), + reinterpret_cast<Drawable>(rData.aPixmap), + static_cast<Visual*>(rSysData.pVisual), + rData.mnWidth, rData.mnHeight ), + &cairo_surface_destroy) + { + } + + /** + * Surface::getCairo: Create Cairo (drawing object) for the Canvas surface + * + * @return new Cairo or NULL + **/ + CairoSharedPtr X11Surface::getCairo() const + { + return CairoSharedPtr( cairo_create(mpSurface.get()), + &cairo_destroy ); + } + + /** + * Surface::getSimilar: Create new similar Canvas surface + * @param cairo_content_type format of the new surface (cairo_content_t from cairo/src/cairo.h) + * @param width width of the new surface + * @param height height of the new surface + * + * Creates a new Canvas surface. This normally creates platform native surface, even though + * generic function is used. + * + * Cairo surface from cairo_content_type (cairo_content_t) + * + * @return new surface or NULL + **/ + SurfaceSharedPtr X11Surface::getSimilar(int cairo_content_type, int width, int height ) const + { + if( maSysData.pDisplay && maSysData.hDrawable ) + { + XRenderPictFormat* pFormat; + int nFormat; + + switch (cairo_content_type) + { + case CAIRO_CONTENT_ALPHA: + nFormat = PictStandardA8; + break; + case CAIRO_CONTENT_COLOR: + nFormat = PictStandardRGB24; + break; + case CAIRO_CONTENT_COLOR_ALPHA: + default: + nFormat = PictStandardARGB32; + break; + } + + pFormat = XRenderFindStandardFormat( static_cast<Display*>(maSysData.pDisplay), nFormat ); + Pixmap hPixmap = limitXCreatePixmap( static_cast<Display*>(maSysData.pDisplay), maSysData.hDrawable, + width > 0 ? width : 1, height > 0 ? height : 1, + pFormat->depth ); + + X11SysData aSysData(maSysData); + aSysData.pRenderFormat = pFormat; + return SurfaceSharedPtr( + new X11Surface( aSysData, + std::make_shared<X11Pixmap>(hPixmap, maSysData.pDisplay), + CairoSurfaceSharedPtr( + cairo_xlib_surface_create_with_xrender_format( + static_cast<Display*>(maSysData.pDisplay), + hPixmap, + ScreenOfDisplay(static_cast<Display *>(maSysData.pDisplay), maSysData.nScreen), + pFormat, width, height ), + &cairo_surface_destroy) )); + } + else + return SurfaceSharedPtr( + new X11Surface( maSysData, + X11PixmapSharedPtr(), + CairoSurfaceSharedPtr( + cairo_surface_create_similar( mpSurface.get(), + static_cast<cairo_content_t>(cairo_content_type), width, height ), + &cairo_surface_destroy ))); + } + + VclPtr<VirtualDevice> X11Surface::createVirtualDevice() const + { + SystemGraphicsData aSystemGraphicsData; + + cairo_surface_t* pSurface = mpSurface.get(); + + aSystemGraphicsData.nSize = sizeof(SystemGraphicsData); + aSystemGraphicsData.hDrawable = mpPixmap ? mpPixmap->mhDrawable : maSysData.hDrawable; + aSystemGraphicsData.pXRenderFormat = maSysData.pRenderFormat; + aSystemGraphicsData.pSurface = pSurface; + + int width = cairo_xlib_surface_get_width(pSurface); + int height = cairo_xlib_surface_get_height(pSurface); + + return VclPtr<VirtualDevice>::Create(aSystemGraphicsData, + Size(width, height), + getFormat()); + } + + /** + * Surface::Resize: Resizes the Canvas surface. + * @param width new width of the surface + * @param height new height of the surface + * + * Only used on X11. + * + * @return The new surface or NULL + **/ + bool X11Surface::Resize(int width, int height) + { + cairo_xlib_surface_set_size(mpSurface.get(), width, height); + return true; + } + + void X11Surface::flush() const + { + XSync( static_cast<Display*>(maSysData.pDisplay), false ); + } + + /** + * Surface::getDepth: Get the color depth of the Canvas surface. + * + * @return color depth + **/ + int X11Surface::getDepth() const + { + if (maSysData.pRenderFormat) + return static_cast<XRenderPictFormat*>(maSysData.pRenderFormat)->depth; + return -1; + } + + /** + * Surface::getFormat: Get the device format of the Canvas surface. + * + * @return color format + **/ + DeviceFormat X11Surface::getFormat() const + { + if (!maSysData.pRenderFormat) + return DeviceFormat::DEFAULT; + assert (static_cast<XRenderPictFormat*>(maSysData.pRenderFormat)->depth != 1 && "unsupported"); + return DeviceFormat::DEFAULT; + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/generic/gdi/cairo_xlib_cairo.hxx b/vcl/unx/generic/gdi/cairo_xlib_cairo.hxx new file mode 100644 index 000000000..c44fde437 --- /dev/null +++ b/vcl/unx/generic/gdi/cairo_xlib_cairo.hxx @@ -0,0 +1,96 @@ +/* -*- 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 . + */ + +#pragma once + +#include <sal/config.h> +#include <vcl/cairo.hxx> +#include <vcl/salgtype.hxx> + +struct BitmapSystemData; +class SalFrame; +struct SystemEnvData; +struct SystemGraphicsData; + +namespace cairo { + + /// Holds all X11-output relevant data + struct X11SysData + { + X11SysData(); + explicit X11SysData( const SystemGraphicsData& ); + explicit X11SysData( const SystemEnvData&, const SalFrame* pReference ); + + void* pDisplay; // the relevant display connection + Drawable hDrawable; // a drawable + void* pVisual; // the visual in use + int nScreen; // the current screen of the drawable + void* pRenderFormat; // render format for drawable + }; + + /// RAII wrapper for a pixmap + struct X11Pixmap + { + void* mpDisplay; // the relevant display connection + Pixmap mhDrawable; // a drawable + + X11Pixmap( Pixmap hDrawable, void* pDisplay ) : + mpDisplay(pDisplay), + mhDrawable(hDrawable) + {} + + ~X11Pixmap(); + }; + + typedef std::shared_ptr<X11Pixmap> X11PixmapSharedPtr; + + class X11Surface : public Surface + { + const X11SysData maSysData; + X11PixmapSharedPtr mpPixmap; + CairoSurfaceSharedPtr mpSurface; + + X11Surface( const X11SysData& rSysData, const X11PixmapSharedPtr& rPixmap, const CairoSurfaceSharedPtr& pSurface ); + + public: + /// takes over ownership of passed cairo_surface + explicit X11Surface( const CairoSurfaceSharedPtr& pSurface ); + /// create surface on subarea of given drawable + X11Surface( const X11SysData& rSysData, int x, int y, int width, int height ); + /// create surface for given bitmap data + X11Surface( const X11SysData& rSysData, const BitmapSystemData& rBmpData ); + + // Surface interface + virtual CairoSharedPtr getCairo() const override; + virtual CairoSurfaceSharedPtr getCairoSurface() const override { return mpSurface; } + virtual SurfaceSharedPtr getSimilar(int cairo_content_type, int width, int height) const override; + + virtual VclPtr<VirtualDevice> createVirtualDevice() const override; + + virtual bool Resize( int width, int height ) override; + + virtual void flush() const override; + + int getDepth() const; + DeviceFormat getFormat() const; + const X11PixmapSharedPtr& getPixmap() const { return mpPixmap; } + }; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/generic/gdi/cairotextrender.cxx b/vcl/unx/generic/gdi/cairotextrender.cxx new file mode 100644 index 000000000..898110fee --- /dev/null +++ b/vcl/unx/generic/gdi/cairotextrender.cxx @@ -0,0 +1,382 @@ +/* -*- 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 <unx/cairotextrender.hxx> + +#include <unx/fc_fontoptions.hxx> +#include <unx/freetype_glyphcache.hxx> +#include <vcl/svapp.hxx> +#include <sallayout.hxx> +#include <salinst.hxx> + +#include <cairo.h> +#include <cairo-ft.h> +#if defined(CAIRO_HAS_SVG_SURFACE) +#include <cairo-svg.h> +#elif defined(CAIRO_HAS_PDF_SURFACE) +#include <cairo-pdf.h> +#endif + +#include <deque> + +namespace { + +typedef struct FT_FaceRec_* FT_Face; + +class CairoFontsCache +{ +public: + struct CacheId + { + FT_Face maFace; + const FontConfigFontOptions *mpOptions; + bool mbEmbolden; + bool mbVerticalMetrics; + bool operator ==(const CacheId& rOther) const + { + return maFace == rOther.maFace && + mpOptions == rOther.mpOptions && + mbEmbolden == rOther.mbEmbolden && + mbVerticalMetrics == rOther.mbVerticalMetrics; + } + }; + +private: + typedef std::deque< std::pair<cairo_font_face_t*, CacheId> > LRUFonts; + static LRUFonts maLRUFonts; +public: + CairoFontsCache() = delete; + + static void CacheFont(cairo_font_face_t* pFont, const CacheId &rId); + static cairo_font_face_t* FindCachedFont(const CacheId &rId); +}; + +CairoFontsCache::LRUFonts CairoFontsCache::maLRUFonts; + +void CairoFontsCache::CacheFont(cairo_font_face_t* pFont, const CairoFontsCache::CacheId &rId) +{ + maLRUFonts.push_front( std::pair<cairo_font_face_t*, CairoFontsCache::CacheId>(pFont, rId) ); + if (maLRUFonts.size() > 8) + { + cairo_font_face_destroy(maLRUFonts.back().first); + maLRUFonts.pop_back(); + } +} + +cairo_font_face_t* CairoFontsCache::FindCachedFont(const CairoFontsCache::CacheId &rId) +{ + auto aI = std::find_if(maLRUFonts.begin(), maLRUFonts.end(), + [&rId](const LRUFonts::value_type& rFont) { return rFont.second == rId; }); + if (aI != maLRUFonts.end()) + return aI->first; + return nullptr; +} + +} + +namespace +{ + bool hasRotation(int nRotation) + { + return nRotation != 0; + } + + double toRadian(Degree10 nDegree10th) + { + return toRadians(3600_deg10 - nDegree10th); + } + + cairo_t* syncCairoContext(cairo_t* cr) + { + //rhbz#1283420 tdf#117413 bodge to force a read from the underlying surface which has + //the side effect of making the mysterious xrender related problem go away + cairo_surface_t *target = cairo_get_target(cr); + if (cairo_surface_get_type(target) == CAIRO_SURFACE_TYPE_XLIB) + { + cairo_surface_t *throw_away = cairo_surface_create_similar(target, cairo_surface_get_content(target), 1, 1); + cairo_t *force_read_cr = cairo_create(throw_away); + cairo_set_source_surface(force_read_cr, target, 0, 0); + cairo_paint(force_read_cr); + cairo_destroy(force_read_cr); + cairo_surface_destroy(throw_away); + } + return cr; + } +} + +#if defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION) +extern "C" +{ + __attribute__((weak)) void __lsan_disable(); + __attribute__((weak)) void __lsan_enable(); +} +#endif + +CairoTextRender::CairoTextRender() +{ + // https://gitlab.freedesktop.org/cairo/cairo/-/merge_requests/235 + // I don't want to have CAIRO_ROUND_GLYPH_POS_ON set in the cairo surfaces + // font_options when trying subpixel rendering, but that's a private + // feature of cairo_font_options_t, so tricky to achieve. Hack this by + // getting the font options of a backend known to set this private feature + // to CAIRO_ROUND_GLYPH_POS_OFF and then set to defaults the public + // features and the result can be merged with new font options to set + // CAIRO_ROUND_GLYPH_POS_OFF in those + mpRoundGlyphPosOffOptions = cairo_font_options_create(); +#if defined(CAIRO_HAS_SVG_SURFACE) + // svg, pdf and ps backends have CAIRO_ROUND_GLYPH_POS_OFF by default + cairo_surface_t* hack = cairo_svg_surface_create(nullptr, 1, 1); +#elif defined(CAIRO_HAS_PDF_SURFACE) + cairo_surface_t* hack = cairo_pdf_surface_create(nullptr, 1, 1); +#endif + cairo_surface_get_font_options(hack, mpRoundGlyphPosOffOptions); + cairo_surface_destroy(hack); + cairo_font_options_set_antialias(mpRoundGlyphPosOffOptions, CAIRO_ANTIALIAS_DEFAULT); + cairo_font_options_set_subpixel_order(mpRoundGlyphPosOffOptions, CAIRO_SUBPIXEL_ORDER_DEFAULT); + cairo_font_options_set_hint_style(mpRoundGlyphPosOffOptions, CAIRO_HINT_STYLE_DEFAULT); + cairo_font_options_set_hint_metrics(mpRoundGlyphPosOffOptions, CAIRO_HINT_METRICS_DEFAULT); +} + +CairoTextRender::~CairoTextRender() +{ + cairo_font_options_destroy(mpRoundGlyphPosOffOptions); +} + +void CairoTextRender::DrawTextLayout(const GenericSalLayout& rLayout, const SalGraphics& rGraphics) +{ + const FreetypeFontInstance& rInstance = static_cast<FreetypeFontInstance&>(rLayout.GetFont()); + const FreetypeFont& rFont = rInstance.GetFreetypeFont(); + + const bool bResolutionIndependentLayoutEnabled = rGraphics.getTextRenderModeForResolutionIndependentLayoutEnabled(); + + std::vector<cairo_glyph_t> cairo_glyphs; + std::vector<int> glyph_extrarotation; + cairo_glyphs.reserve( 256 ); + + DevicePoint aPos; + const GlyphItem* pGlyph; + int nStart = 0; + while (rLayout.GetNextGlyph(&pGlyph, aPos, nStart)) + { + cairo_glyph_t aGlyph; + aGlyph.index = pGlyph->glyphId(); + aGlyph.x = aPos.getX(); + aGlyph.y = aPos.getY(); + + const bool bVertical = pGlyph->IsVertical(); + glyph_extrarotation.push_back(bVertical ? 1 : 0); + + // tdf#150507 like skia even when subpixel rendering pixel snap y + if (bResolutionIndependentLayoutEnabled) + { + if (!bVertical) + aGlyph.y = std::floor(aGlyph.y + 0.5); + else + aGlyph.x = std::floor(aGlyph.x + 0.5); + } + + cairo_glyphs.push_back(aGlyph); + } + + if (cairo_glyphs.empty()) + return; + + const vcl::font::FontSelectPattern& rFSD = rInstance.GetFontSelectPattern(); + int nHeight = rFSD.mnHeight; + int nWidth = rFSD.mnWidth ? rFSD.mnWidth : nHeight; + if (nWidth == 0 || nHeight == 0) + return; + + if (nHeight > SAL_MAX_UINT16) + { + // as seen with freetype 2.11.0, so cairo surface status is "fail" + // ("error occurred in libfreetype") and no further operations are + // executed, so this error then leads to later leaks + SAL_WARN("vcl", "rendering text would fail with height: " << nHeight); + return; + } + + int nRatio = nWidth * 10 / nHeight; + if (FreetypeFont::AlmostHorizontalDrainsRenderingPool(nRatio, rFSD)) + return; + + /* + * It might be ideal to cache surface and cairo context between calls and + * only destroy it when the drawable changes, but to do that we need to at + * least change the SalFrame etc impls to dtor the SalGraphics *before* the + * destruction of the windows they reference + */ + cairo_t *cr = syncCairoContext(getCairoContext()); + if (!cr) + { + SAL_WARN("vcl", "no cairo context for text"); + return; + } + +#if defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION) + if (__lsan_disable) + __lsan_disable(); +#endif + + const StyleSettings& rStyleSettings = Application::GetSettings().GetStyleSettings(); + const bool bDisableAA = !rStyleSettings.GetUseFontAAFromSystem() && !rGraphics.getAntiAlias(); + + const cairo_font_options_t* pFontOptions = GetSalInstance()->GetCairoFontOptions(); + if (pFontOptions || bDisableAA || bResolutionIndependentLayoutEnabled) + { + cairo_hint_style_t eHintStyle = pFontOptions ? cairo_font_options_get_hint_style(pFontOptions) : CAIRO_HINT_STYLE_DEFAULT; + bool bAllowedHintStyle = !bResolutionIndependentLayoutEnabled || (eHintStyle == CAIRO_HINT_STYLE_NONE || eHintStyle == CAIRO_HINT_STYLE_SLIGHT); + + if (bDisableAA || !bAllowedHintStyle || bResolutionIndependentLayoutEnabled) + { + // Disable font AA in case global AA setting is supposed to affect + // font rendering (not the default) and AA is disabled. + cairo_font_options_t* pOptions = pFontOptions ? cairo_font_options_copy(pFontOptions) : cairo_font_options_create(); + + if (bDisableAA) + cairo_font_options_set_antialias(pOptions, CAIRO_ANTIALIAS_NONE); + if (!bAllowedHintStyle) + cairo_font_options_set_hint_style(pOptions, CAIRO_HINT_STYLE_SLIGHT); + // Disable private CAIRO_ROUND_GLYPH_POS_ON by merging with font options known to have + // CAIRO_ROUND_GLYPH_POS_OFF + if (bResolutionIndependentLayoutEnabled) + cairo_font_options_merge(pOptions, mpRoundGlyphPosOffOptions); + cairo_set_font_options(cr, pOptions); + cairo_font_options_destroy(pOptions); + } + else if (pFontOptions) + cairo_set_font_options(cr, pFontOptions); + } + + double nDX, nDY; + getSurfaceOffset(nDX, nDY); + cairo_translate(cr, nDX, nDY); + + clipRegion(cr); + + cairo_set_source_rgb(cr, + mnTextColor.GetRed()/255.0, + mnTextColor.GetGreen()/255.0, + mnTextColor.GetBlue()/255.0); + + FT_Face aFace = rFont.GetFtFace(); + CairoFontsCache::CacheId aId; + aId.maFace = aFace; + aId.mpOptions = rFont.GetFontOptions(); + aId.mbEmbolden = rFont.NeedsArtificialBold(); + + cairo_matrix_t m; + + std::vector<int>::const_iterator aEnd = glyph_extrarotation.end(); + std::vector<int>::const_iterator aStart = glyph_extrarotation.begin(); + std::vector<int>::const_iterator aI = aStart; + while (aI != aEnd) + { + int nGlyphRotation = *aI; + + std::vector<int>::const_iterator aNext = nGlyphRotation?(aI+1):std::find_if(aI+1, aEnd, hasRotation); + + size_t nStartIndex = std::distance(aStart, aI); + size_t nLen = std::distance(aI, aNext); + + aId.mbVerticalMetrics = nGlyphRotation != 0.0; + cairo_font_face_t* font_face = CairoFontsCache::FindCachedFont(aId); + if (!font_face) + { + const FontConfigFontOptions *pOptions = aId.mpOptions; + FcPattern *pPattern = pOptions->GetPattern(); + font_face = cairo_ft_font_face_create_for_pattern(pPattern); + CairoFontsCache::CacheFont(font_face, aId); + } + cairo_set_font_face(cr, font_face); + + cairo_set_font_size(cr, nHeight); + + cairo_matrix_init_identity(&m); + + if (rLayout.GetOrientation()) + cairo_matrix_rotate(&m, toRadian(rLayout.GetOrientation())); + + cairo_matrix_scale(&m, nWidth, nHeight); + + if (nGlyphRotation) + { + cairo_matrix_rotate(&m, toRadian(Degree10(nGlyphRotation * 900))); + + cairo_matrix_t em_square; + cairo_matrix_init_identity(&em_square); + cairo_get_matrix(cr, &em_square); + + cairo_matrix_scale(&em_square, aFace->units_per_EM, + aFace->units_per_EM); + cairo_set_matrix(cr, &em_square); + + cairo_font_extents_t font_extents; + cairo_font_extents(cr, &font_extents); + + cairo_matrix_init_identity(&em_square); + cairo_set_matrix(cr, &em_square); + } + + if (rFont.NeedsArtificialItalic()) + { + cairo_matrix_t shear; + cairo_matrix_init_identity(&shear); + shear.xy = -shear.xx * 0x6000L / 0x10000L; + cairo_matrix_multiply(&m, &shear, &m); + } + + cairo_set_font_matrix(cr, &m); + cairo_show_glyphs(cr, &cairo_glyphs[nStartIndex], nLen); + if (cairo_status(cr) != CAIRO_STATUS_SUCCESS) + { + SAL_WARN("vcl", "rendering text failed with stretch ratio of: " << nRatio << ", " << cairo_status_to_string(cairo_status(cr))); + } + +#if OSL_DEBUG_LEVEL > 2 + //draw origin + cairo_save (cr); + cairo_rectangle (cr, cairo_glyphs[nStartIndex].x, cairo_glyphs[nStartIndex].y, 5, 5); + cairo_set_source_rgba (cr, 1, 0, 0, 0.80); + cairo_fill (cr); + cairo_restore (cr); +#endif + + aI = aNext; + } + + releaseCairoContext(cr); + +#if defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION) + if (__lsan_enable) + __lsan_enable(); +#endif +} + +void FontConfigFontOptions::cairo_font_options_substitute(FcPattern* pPattern) +{ + const cairo_font_options_t* pFontOptions = GetSalInstance()->GetCairoFontOptions(); + if( !pFontOptions ) + return; + cairo_ft_font_options_substitute(pFontOptions, pPattern); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/generic/gdi/font.cxx b/vcl/unx/generic/gdi/font.cxx new file mode 100644 index 000000000..57ff99a1c --- /dev/null +++ b/vcl/unx/generic/gdi/font.cxx @@ -0,0 +1,116 @@ +/* -*- 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 <vcl/sysdata.hxx> +#include <vcl/fontcharmap.hxx> + +#include <unx/salgdi.h> +#include <textrender.hxx> +#include <sallayout.hxx> + +void X11SalGraphics::DrawTextLayout(const GenericSalLayout& rLayout) +{ + mxTextRenderImpl->DrawTextLayout(rLayout, *this); +} + +FontCharMapRef X11SalGraphics::GetFontCharMap() const +{ + return mxTextRenderImpl->GetFontCharMap(); +} + +bool X11SalGraphics::GetFontCapabilities(vcl::FontCapabilities &rGetImplFontCapabilities) const +{ + return mxTextRenderImpl->GetFontCapabilities(rGetImplFontCapabilities); +} + +// SalGraphics +void X11SalGraphics::SetFont(LogicalFontInstance* pEntry, int nFallbackLevel) +{ + mxTextRenderImpl->SetFont(pEntry, nFallbackLevel); +} + +void +X11SalGraphics::SetTextColor( Color nColor ) +{ + mxTextRenderImpl->SetTextColor(nColor); +} + +bool X11SalGraphics::AddTempDevFont( vcl::font::PhysicalFontCollection* pFontCollection, + const OUString& rFileURL, + const OUString& rFontName ) +{ + return mxTextRenderImpl->AddTempDevFont(pFontCollection, rFileURL, rFontName); +} + +void X11SalGraphics::ClearDevFontCache() +{ + mxTextRenderImpl->ClearDevFontCache(); +} + +void X11SalGraphics::GetDevFontList( vcl::font::PhysicalFontCollection* pFontCollection ) +{ + mxTextRenderImpl->GetDevFontList(pFontCollection); +} + +void +X11SalGraphics::GetFontMetric( ImplFontMetricDataRef &rxFontMetric, int nFallbackLevel ) +{ + mxTextRenderImpl->GetFontMetric(rxFontMetric, nFallbackLevel); +} + +std::unique_ptr<GenericSalLayout> X11SalGraphics::GetTextLayout(int nFallbackLevel) +{ + return mxTextRenderImpl->GetTextLayout(nFallbackLevel); +} + +bool X11SalGraphics::CreateFontSubset( + const OUString& rToFile, + const vcl::font::PhysicalFontFace* pFont, + const sal_GlyphId* pGlyphIds, + const sal_uInt8* pEncoding, + sal_Int32* pWidths, + int nGlyphCount, + FontSubsetInfo& rInfo + ) +{ + return mxTextRenderImpl->CreateFontSubset(rToFile, pFont, + pGlyphIds, pEncoding, pWidths, nGlyphCount, rInfo); +} + +const void* X11SalGraphics::GetEmbedFontData(const vcl::font::PhysicalFontFace* pFont, tools::Long* pDataLen) +{ + return mxTextRenderImpl->GetEmbedFontData(pFont, pDataLen); +} + +void X11SalGraphics::FreeEmbedFontData( const void* pData, tools::Long nLen ) +{ + mxTextRenderImpl->FreeEmbedFontData(pData, nLen); +} + +void X11SalGraphics::GetGlyphWidths( const vcl::font::PhysicalFontFace* pFont, + bool bVertical, + std::vector< sal_Int32 >& rWidths, + Ucs2UIntMap& rUnicodeEnc ) +{ + mxTextRenderImpl->GetGlyphWidths(pFont, bVertical, rWidths, rUnicodeEnc); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/generic/gdi/freetypetextrender.cxx b/vcl/unx/generic/gdi/freetypetextrender.cxx new file mode 100644 index 000000000..ea8474e69 --- /dev/null +++ b/vcl/unx/generic/gdi/freetypetextrender.cxx @@ -0,0 +1,216 @@ +/* -*- 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 <unx/freetypetextrender.hxx> + +#include <unotools/configmgr.hxx> +#include <vcl/settings.hxx> +#include <vcl/sysdata.hxx> +#include <vcl/svapp.hxx> +#include <vcl/fontcharmap.hxx> +#include <sal/log.hxx> + +#include <unx/genpspgraphics.h> +#include <unx/geninst.h> +#include <unx/glyphcache.hxx> +#include <unx/fc_fontoptions.hxx> +#include <unx/freetype_glyphcache.hxx> +#include <font/PhysicalFontFace.hxx> +#include <impfontmetricdata.hxx> + +#include <sallayout.hxx> + +FreeTypeTextRenderImpl::FreeTypeTextRenderImpl() + : mnTextColor(Color(0x00, 0x00, 0x00)) //black +{ +} + +FreeTypeTextRenderImpl::~FreeTypeTextRenderImpl() +{ + ReleaseFonts(); +} + +void FreeTypeTextRenderImpl::SetFont(LogicalFontInstance *pEntry, int nFallbackLevel) +{ + // release all no longer needed font resources + for( int i = nFallbackLevel; i < MAX_FALLBACK; ++i ) + { + // old server side font is no longer referenced + mpFreetypeFont[i] = nullptr; + } + + // return early if there is no new font + if( !pEntry ) + return; + + FreetypeFontInstance* pFreetypeFont = static_cast<FreetypeFontInstance*>(pEntry); + mpFreetypeFont[ nFallbackLevel ] = pFreetypeFont; + + // ignore fonts with e.g. corrupted font files + if (!mpFreetypeFont[nFallbackLevel]->GetFreetypeFont().TestFont()) + mpFreetypeFont[nFallbackLevel] = nullptr; +} + +FontCharMapRef FreeTypeTextRenderImpl::GetFontCharMap() const +{ + if (!mpFreetypeFont[0]) + return nullptr; + return mpFreetypeFont[0]->GetFreetypeFont().GetFontCharMap(); +} + +bool FreeTypeTextRenderImpl::GetFontCapabilities(vcl::FontCapabilities &rGetImplFontCapabilities) const +{ + if (!mpFreetypeFont[0]) + return false; + return mpFreetypeFont[0]->GetFreetypeFont().GetFontCapabilities(rGetImplFontCapabilities); +} + +// SalGraphics +void +FreeTypeTextRenderImpl::SetTextColor( Color nColor ) +{ + if( mnTextColor != nColor ) + { + mnTextColor = nColor; + } +} + +bool FreeTypeTextRenderImpl::AddTempDevFont( vcl::font::PhysicalFontCollection* pFontCollection, + const OUString& rFileURL, + const OUString& rFontName ) +{ + return GenPspGraphics::AddTempDevFontHelper(pFontCollection, rFileURL, rFontName); +} + +void FreeTypeTextRenderImpl::ClearDevFontCache() +{ + FreetypeManager::get().ClearFontCache(); +} + +void FreeTypeTextRenderImpl::GetDevFontList( vcl::font::PhysicalFontCollection* pFontCollection ) +{ + // prepare the FreetypeManager using psprint's font infos + FreetypeManager& rFreetypeManager = FreetypeManager::get(); + + psp::PrintFontManager& rMgr = psp::PrintFontManager::get(); + ::std::vector< psp::fontID > aList; + psp::FastPrintFontInfo aInfo; + rMgr.getFontList( aList ); + for (auto const& elem : aList) + { + if( !rMgr.getFontFastInfo( elem, aInfo ) ) + continue; + + // normalize face number to the FreetypeManager + int nFaceNum = rMgr.getFontFaceNumber( aInfo.m_nID ); + int nVariantNum = rMgr.getFontFaceVariation( aInfo.m_nID ); + + // inform FreetypeManager about this font provided by the PsPrint subsystem + FontAttributes aDFA = GenPspGraphics::Info2FontAttributes( aInfo ); + aDFA.IncreaseQualityBy( 4096 ); + const OString& rFileName = rMgr.getFontFileSysPath( aInfo.m_nID ); + rFreetypeManager.AddFontFile(rFileName, nFaceNum, nVariantNum, aInfo.m_nID, aDFA); + } + + // announce glyphcache fonts + rFreetypeManager.AnnounceFonts(pFontCollection); + + // register platform specific font substitutions if available + if (!utl::ConfigManager::IsFuzzing()) + SalGenericInstance::RegisterFontSubstitutors( pFontCollection ); +} + +void FreeTypeTextRenderImpl::GetFontMetric( ImplFontMetricDataRef& rxFontMetric, int nFallbackLevel ) +{ + if( nFallbackLevel >= MAX_FALLBACK ) + return; + + if (mpFreetypeFont[nFallbackLevel]) + mpFreetypeFont[nFallbackLevel]->GetFreetypeFont().GetFontMetric(rxFontMetric); +} + +std::unique_ptr<GenericSalLayout> FreeTypeTextRenderImpl::GetTextLayout(int nFallbackLevel) +{ + assert(mpFreetypeFont[nFallbackLevel]); + if (!mpFreetypeFont[nFallbackLevel]) + return nullptr; + return std::make_unique<GenericSalLayout>(*mpFreetypeFont[nFallbackLevel]); +} + +bool FreeTypeTextRenderImpl::CreateFontSubset( + const OUString& rToFile, + const vcl::font::PhysicalFontFace* pFont, + const sal_GlyphId* pGlyphIds, + const sal_uInt8* pEncoding, + sal_Int32* pWidths, + int nGlyphCount, + FontSubsetInfo& rInfo + ) +{ + // in this context the pFont->GetFontId() is a valid PSP + // font since they are the only ones left after the PDF + // export has filtered its list of subsettable fonts (for + // which this method was created). The correct way would + // be to have the FreetypeManager search for the PhysicalFontFace pFont + psp::fontID aFont = pFont->GetFontId(); + + psp::PrintFontManager& rMgr = psp::PrintFontManager::get(); + bool bSuccess = rMgr.createFontSubset( rInfo, + aFont, + rToFile, + pGlyphIds, + pEncoding, + pWidths, + nGlyphCount ); + return bSuccess; +} + +const void* FreeTypeTextRenderImpl::GetEmbedFontData(const vcl::font::PhysicalFontFace* pFont, tools::Long* pDataLen) +{ + // in this context the pFont->GetFontId() is a valid PSP + // font since they are the only ones left after the PDF + // export has filtered its list of subsettable fonts (for + // which this method was created). The correct way would + // be to have the FreetypeManager search for the PhysicalFontFace pFont + psp::fontID aFont = pFont->GetFontId(); + return GenPspGraphics::DoGetEmbedFontData(aFont, pDataLen); +} + +void FreeTypeTextRenderImpl::FreeEmbedFontData( const void* pData, tools::Long nLen ) +{ + GenPspGraphics::DoFreeEmbedFontData( pData, nLen ); +} + +void FreeTypeTextRenderImpl::GetGlyphWidths( const vcl::font::PhysicalFontFace* pFont, + bool bVertical, + std::vector< sal_Int32 >& rWidths, + Ucs2UIntMap& rUnicodeEnc ) +{ + // in this context the pFont->GetFontId() is a valid PSP + // font since they are the only ones left after the PDF + // export has filtered its list of subsettable fonts (for + // which this method was created). The correct way would + // be to have the FreetypeManager search for the PhysicalFontFace pFont + psp::fontID aFont = pFont->GetFontId(); + GenPspGraphics::DoGetGlyphWidths( aFont, bVertical, rWidths, rUnicodeEnc ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/generic/gdi/gdiimpl.cxx b/vcl/unx/generic/gdi/gdiimpl.cxx new file mode 100644 index 000000000..d5fc4d6c1 --- /dev/null +++ b/vcl/unx/generic/gdi/gdiimpl.cxx @@ -0,0 +1,2015 @@ +/* -*- 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 <memory> +#include <numeric> + +#include <X11/Xlib.h> +#include <X11/Xutil.h> +#include <X11/extensions/Xrender.h> +#include <X11/Xproto.h> + +#include "gdiimpl.hxx" + +#include <vcl/gradient.hxx> +#include <sal/log.hxx> + +#include <unx/saldisp.hxx> +#include <unx/salbmp.h> +#include <unx/salgdi.h> +#include <unx/salvd.h> +#include <unx/x11/xlimits.hxx> +#include <salframe.hxx> +#include <unx/x11/xrender_peer.hxx> + +#include <basegfx/polygon/b2dpolygon.hxx> +#include <basegfx/polygon/b2dpolypolygon.hxx> +#include <basegfx/polygon/b2dpolypolygontools.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <basegfx/polygon/b2dpolygonclipper.hxx> +#include <basegfx/polygon/b2dlinegeometry.hxx> +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> +#include <basegfx/polygon/b2dtrapezoid.hxx> +#include <basegfx/utils/systemdependentdata.hxx> + +#undef SALGDI2_TESTTRANS + +#if (OSL_DEBUG_LEVEL > 1) && defined SALGDI2_TESTTRANS +#define DBG_TESTTRANS( _def_drawable ) \ +{ \ + XCopyArea( pXDisp, _def_drawable, aDrawable, GetCopyGC(), \ + 0, 0, \ + rPosAry.mnDestWidth, rPosAry.mnDestHeight, \ + 0, 0 ); \ +} +#else // (OSL_DEBUG_LEVEL > 1) && defined SALGDI2_TESTTRANS +#define DBG_TESTTRANS( _def_drawable ) +#endif // (OSL_DEBUG_LEVEL > 1) && defined SALGDI2_TESTTRANS + +/* From <X11/Intrinsic.h> */ +typedef unsigned long Pixel; + +class SalPolyLine +{ + std::vector<XPoint> Points_; +public: + SalPolyLine(sal_uLong nPoints, const Point *p) + : Points_(nPoints+1) + { + for (sal_uLong i = 0; i < nPoints; ++i) + { + Points_[i].x = static_cast<short>(p[i].getX()); + Points_[i].y = static_cast<short>(p[i].getY()); + } + Points_[nPoints] = Points_[0]; // close polyline + } + + const XPoint &operator[](sal_uLong n) const + { + return Points_[n]; + } + + XPoint &operator[](sal_uLong n) + { + return Points_[n]; + } +}; + +namespace +{ + void setForeBack(XGCValues& rValues, const SalColormap& rColMap, const SalBitmap& rSalBitmap) + { + rValues.foreground = rColMap.GetWhitePixel(); + rValues.background = rColMap.GetBlackPixel(); + + //fdo#33455 and fdo#80160 handle 1 bit depth pngs with palette entries + //to set fore/back colors + SalBitmap& rBitmap = const_cast<SalBitmap&>(rSalBitmap); + BitmapBuffer* pBitmapBuffer = rBitmap.AcquireBuffer(BitmapAccessMode::Read); + if (!pBitmapBuffer) + return; + + const BitmapPalette& rPalette = pBitmapBuffer->maPalette; + if (rPalette.GetEntryCount() == 2) + { + const BitmapColor aWhite(rPalette[rPalette.GetBestIndex(COL_WHITE)]); + rValues.foreground = rColMap.GetPixel(aWhite); + + const BitmapColor aBlack(rPalette[rPalette.GetBestIndex(COL_BLACK)]); + rValues.background = rColMap.GetPixel(aBlack); + } + rBitmap.ReleaseBuffer(pBitmapBuffer, BitmapAccessMode::Read); + } +} + +X11SalGraphicsImpl::X11SalGraphicsImpl(X11SalGraphics& rParent): + mrParent(rParent), + mnBrushColor( 0xFF, 0xFF, 0XFF ), + mpBrushGC(nullptr), + mnBrushPixel(0), + mbPenGC(false), + mbBrushGC(false), + mbCopyGC(false), + mbInvertGC(false), + mbInvert50GC(false), + mbStippleGC(false), + mbTrackingGC(false), + mbDitherBrush(false), + mbXORMode(false), + mpPenGC(nullptr), + mnPenColor( 0x00, 0x00, 0x00 ), + mnPenPixel(0), + mpMonoGC(nullptr), + mpCopyGC(nullptr), + mpMaskGC(nullptr), + mpInvertGC(nullptr), + mpInvert50GC(nullptr), + mpStippleGC(nullptr), + mpTrackingGC(nullptr) +{ +} + +X11SalGraphicsImpl::~X11SalGraphicsImpl() +{ +} + +void X11SalGraphicsImpl::Init() +{ + mnPenPixel = mrParent.GetPixel( mnPenColor ); + mnBrushPixel = mrParent.GetPixel( mnBrushColor ); +} + +XID X11SalGraphicsImpl::GetXRenderPicture() +{ + XRenderPeer& rRenderPeer = XRenderPeer::GetInstance(); + + if( !mrParent.m_aXRenderPicture ) + { + // check xrender support for matching visual + XRenderPictFormat* pXRenderFormat = mrParent.GetXRenderFormat(); + if( !pXRenderFormat ) + return 0; + // get the matching xrender target for drawable + mrParent.m_aXRenderPicture = rRenderPeer.CreatePicture( mrParent.GetDrawable(), pXRenderFormat, 0, nullptr ); + } + + { + // reset clip region + // TODO: avoid clip reset if already done + XRenderPictureAttributes aAttr; + aAttr.clip_mask = None; + rRenderPeer.ChangePicture( mrParent.m_aXRenderPicture, CPClipMask, &aAttr ); + } + + return mrParent.m_aXRenderPicture; +} + +static void freeGC(Display *pDisplay, GC& rGC) +{ + if( rGC ) + { + XFreeGC( pDisplay, rGC ); + rGC = None; + } +} + +void X11SalGraphicsImpl::freeResources() +{ + Display *pDisplay = mrParent.GetXDisplay(); + + freeGC( pDisplay, mpPenGC ); + freeGC( pDisplay, mpBrushGC ); + freeGC( pDisplay, mpMonoGC ); + freeGC( pDisplay, mpTrackingGC ); + freeGC( pDisplay, mpCopyGC ); + freeGC( pDisplay, mpMaskGC ); + freeGC( pDisplay, mpInvertGC ); + freeGC( pDisplay, mpInvert50GC ); + freeGC( pDisplay, mpStippleGC ); + mbTrackingGC = mbPenGC = mbBrushGC = mbCopyGC = mbInvertGC = mbInvert50GC = mbStippleGC = false; +} + +GC X11SalGraphicsImpl::CreateGC( Drawable hDrawable, unsigned long nMask ) +{ + XGCValues values; + + values.graphics_exposures = False; + values.foreground = mrParent.GetColormap().GetBlackPixel() + ^ mrParent.GetColormap().GetWhitePixel(); + values.function = GXxor; + values.line_width = 1; + values.fill_style = FillStippled; + values.stipple = mrParent.GetDisplay()->GetInvert50( mrParent.m_nXScreen ); + values.subwindow_mode = ClipByChildren; + + return XCreateGC( mrParent.GetXDisplay(), hDrawable, nMask | GCSubwindowMode, &values ); +} + +inline GC X11SalGraphicsImpl::GetCopyGC() +{ + if( mbXORMode ) return GetInvertGC(); + + if( !mpCopyGC ) + mpCopyGC = CreateGC( mrParent.GetDrawable() ); + + if( !mbCopyGC ) + { + mrParent.SetClipRegion( mpCopyGC ); + mbCopyGC = true; + } + return mpCopyGC; +} + +GC X11SalGraphicsImpl::GetTrackingGC() +{ + if( !mpTrackingGC ) + { + XGCValues values; + + values.graphics_exposures = False; + values.foreground = mrParent.GetColormap().GetBlackPixel() + ^ mrParent.GetColormap().GetWhitePixel(); + values.function = GXxor; + values.line_width = 1; + values.line_style = LineOnOffDash; + + mpTrackingGC = XCreateGC( mrParent.GetXDisplay(), mrParent.GetDrawable(), + GCGraphicsExposures | GCForeground | GCFunction + | GCLineWidth | GCLineStyle, + &values ); + const char dash_list[2] = {2, 2}; + XSetDashes( mrParent.GetXDisplay(), mpTrackingGC, 0, dash_list, 2 ); + } + + if( !mbTrackingGC ) + { + mrParent.SetClipRegion( mpTrackingGC ); + mbTrackingGC = true; + } + + return mpTrackingGC; +} + +GC X11SalGraphicsImpl::GetInvertGC() +{ + if( !mpInvertGC ) + mpInvertGC = CreateGC( mrParent.GetDrawable(), + GCGraphicsExposures + | GCForeground + | GCFunction + | GCLineWidth ); + + if( !mbInvertGC ) + { + mrParent.SetClipRegion( mpInvertGC ); + mbInvertGC = true; + } + return mpInvertGC; +} + +GC X11SalGraphicsImpl::GetInvert50GC() +{ + if( !mpInvert50GC ) + { + XGCValues values; + + values.graphics_exposures = False; + values.foreground = mrParent.GetColormap().GetWhitePixel(); + values.background = mrParent.GetColormap().GetBlackPixel(); + values.function = GXinvert; + values.line_width = 1; + values.line_style = LineSolid; + unsigned long const nValueMask = + GCGraphicsExposures + | GCForeground + | GCBackground + | GCFunction + | GCLineWidth + | GCLineStyle + | GCFillStyle + | GCStipple; + + values.fill_style = FillStippled; + values.stipple = mrParent.GetDisplay()->GetInvert50( mrParent.m_nXScreen ); + + mpInvert50GC = XCreateGC( mrParent.GetXDisplay(), mrParent.GetDrawable(), + nValueMask, + &values ); + } + + if( !mbInvert50GC ) + { + mrParent.SetClipRegion( mpInvert50GC ); + mbInvert50GC = true; + } + return mpInvert50GC; +} + +inline GC X11SalGraphicsImpl::GetStippleGC() +{ + if( !mpStippleGC ) + mpStippleGC = CreateGC( mrParent.GetDrawable(), + GCGraphicsExposures + | GCFillStyle + | GCLineWidth ); + + if( !mbStippleGC ) + { + XSetFunction( mrParent.GetXDisplay(), mpStippleGC, mbXORMode ? GXxor : GXcopy ); + mrParent.SetClipRegion( mpStippleGC ); + mbStippleGC = true; + } + + return mpStippleGC; +} + +GC X11SalGraphicsImpl::SelectBrush() +{ + Display *pDisplay = mrParent.GetXDisplay(); + + SAL_WARN_IF( mnBrushColor == SALCOLOR_NONE, "vcl", "Brush Transparent" ); + + if( !mpBrushGC ) + { + XGCValues values; + values.subwindow_mode = ClipByChildren; + values.fill_rule = EvenOddRule; // Pict import/ Gradient + values.graphics_exposures = False; + + mpBrushGC = XCreateGC( pDisplay, mrParent.GetDrawable(), + GCSubwindowMode | GCFillRule | GCGraphicsExposures, + &values ); + } + + if( !mbBrushGC ) + { + if( !mbDitherBrush ) + { + XSetFillStyle ( pDisplay, mpBrushGC, FillSolid ); + XSetForeground( pDisplay, mpBrushGC, mnBrushPixel ); + } + else + { + XSetFillStyle ( pDisplay, mpBrushGC, FillTiled ); + XSetTile ( pDisplay, mpBrushGC, mrParent.hBrush_ ); + } + XSetFunction ( pDisplay, mpBrushGC, mbXORMode ? GXxor : GXcopy ); + mrParent.SetClipRegion( mpBrushGC ); + + mbBrushGC = true; + } + + return mpBrushGC; +} + +GC X11SalGraphicsImpl::SelectPen() +{ + Display *pDisplay = mrParent.GetXDisplay(); + + if( !mpPenGC ) + { + XGCValues values; + values.subwindow_mode = ClipByChildren; + values.fill_rule = EvenOddRule; // Pict import/ Gradient + values.graphics_exposures = False; + + mpPenGC = XCreateGC( pDisplay, mrParent.GetDrawable(), + GCSubwindowMode | GCFillRule | GCGraphicsExposures, + &values ); + } + + if( !mbPenGC ) + { + if( mnPenColor != SALCOLOR_NONE ) + XSetForeground( pDisplay, mpPenGC, mnPenPixel ); + XSetFunction ( pDisplay, mpPenGC, mbXORMode ? GXxor : GXcopy ); + mrParent.SetClipRegion( mpPenGC ); + mbPenGC = true; + } + + return mpPenGC; +} + +void X11SalGraphicsImpl::DrawLines(sal_uInt32 nPoints, + const SalPolyLine &rPoints, + GC pGC, + bool bClose) +{ + // calculate how many lines XWindow can draw in one go + sal_uLong nMaxLines = (mrParent.GetDisplay()->GetMaxRequestSize() - sizeof(xPolyPointReq)) + / sizeof(xPoint); + if( nMaxLines > nPoints ) nMaxLines = nPoints; + + // print all lines that XWindows can draw + sal_uLong n; + for( n = 0; nPoints - n > nMaxLines; n += nMaxLines - 1 ) + XDrawLines( mrParent.GetXDisplay(), + mrParent.GetDrawable(), + pGC, + const_cast<XPoint*>(&rPoints[n]), + nMaxLines, + CoordModeOrigin ); + + if( n < nPoints ) + XDrawLines( mrParent.GetXDisplay(), + mrParent.GetDrawable(), + pGC, + const_cast<XPoint*>(&rPoints[n]), + nPoints - n, + CoordModeOrigin ); + if( bClose ) + { + if( rPoints[nPoints-1].x != rPoints[0].x || rPoints[nPoints-1].y != rPoints[0].y ) + drawLine( rPoints[nPoints-1].x, rPoints[nPoints-1].y, rPoints[0].x, rPoints[0].y ); + } +} + +void X11SalGraphicsImpl::copyBits( const SalTwoRect& rPosAry, + SalGraphics *pSSrcGraphics ) +{ + X11SalGraphics* pSrcGraphics = pSSrcGraphics + ? static_cast<X11SalGraphics*>(pSSrcGraphics) + : &mrParent; + + if( rPosAry.mnSrcWidth <= 0 + || rPosAry.mnSrcHeight <= 0 + || rPosAry.mnDestWidth <= 0 + || rPosAry.mnDestHeight <= 0 ) + { + return; + } + + int n; + if( pSrcGraphics == &mrParent ) + { + n = 2; + } + else if( pSrcGraphics->bWindow_ ) + { + // window or compatible virtual device + if( pSrcGraphics->GetDisplay() == mrParent.GetDisplay() && + pSrcGraphics->m_nXScreen == mrParent.m_nXScreen && + pSrcGraphics->GetVisual().GetDepth() == mrParent.GetVisual().GetDepth() + ) + n = 2; // same Display + else + n = 1; // printer or other display + } + else if( pSrcGraphics->bVirDev_ ) + { + n = 1; // window or compatible virtual device + } + else + n = 0; + + if( n == 2 + && rPosAry.mnSrcWidth == rPosAry.mnDestWidth + && rPosAry.mnSrcHeight == rPosAry.mnDestHeight + ) + { + // #i60699# Need to generate graphics exposures (to repaint + // obscured areas beneath overlapping windows), src and dest + // are the same window. + const bool bNeedGraphicsExposures( pSrcGraphics == &mrParent && + !mrParent.bVirDev_ && + pSrcGraphics->bWindow_ ); + + GC pCopyGC = GetCopyGC(); + + if( bNeedGraphicsExposures ) + XSetGraphicsExposures( mrParent.GetXDisplay(), + pCopyGC, + True ); + + XCopyArea( mrParent.GetXDisplay(), + pSrcGraphics->GetDrawable(), // source + mrParent.GetDrawable(), // destination + pCopyGC, // destination clipping + rPosAry.mnSrcX, rPosAry.mnSrcY, + rPosAry.mnSrcWidth, rPosAry.mnSrcHeight, + rPosAry.mnDestX, rPosAry.mnDestY ); + + if( bNeedGraphicsExposures ) + { + mrParent.YieldGraphicsExpose(); + + if( pCopyGC ) + XSetGraphicsExposures( mrParent.GetXDisplay(), + pCopyGC, + False ); + } + } + else if( n ) + { + // #i60699# No chance to handle graphics exposures - we copy + // to a temp bitmap first, into which no repaints are + // technically possible. + std::shared_ptr<SalBitmap> xDDB(pSrcGraphics->getBitmap( rPosAry.mnSrcX, + rPosAry.mnSrcY, + rPosAry.mnSrcWidth, + rPosAry.mnSrcHeight )); + + if( !xDDB ) + { + SAL_WARN( "vcl", "SalGraphics::CopyBits !pSrcGraphics->GetBitmap()" ); + return; + } + + SalTwoRect aPosAry( rPosAry ); + + aPosAry.mnSrcX = 0; + aPosAry.mnSrcY = 0; + drawBitmap( aPosAry, *xDDB ); + } + else { + SAL_WARN( "vcl", "X11SalGraphicsImpl::CopyBits from Printer not yet implemented" ); + } +} + +void X11SalGraphicsImpl::copyArea ( tools::Long nDestX, tools::Long nDestY, + tools::Long nSrcX, tools::Long nSrcY, + tools::Long nSrcWidth, tools::Long nSrcHeight, + bool /*bWindowInvalidate*/) +{ + SalTwoRect aPosAry(nSrcX, nSrcY, nSrcWidth, nSrcHeight, nDestX, nDestY, nSrcWidth, nSrcHeight); + copyBits(aPosAry, nullptr); +} + +void X11SalGraphicsImpl::drawBitmap( const SalTwoRect& rPosAry, const SalBitmap& rSalBitmap ) +{ + const SalDisplay* pSalDisp = mrParent.GetDisplay(); + Display* pXDisp = pSalDisp->GetDisplay(); + const Drawable aDrawable( mrParent.GetDrawable() ); + const SalColormap& rColMap = pSalDisp->GetColormap( mrParent.m_nXScreen ); + const tools::Long nDepth = mrParent.GetDisplay()->GetVisual( mrParent.m_nXScreen ).GetDepth(); + GC aGC( GetCopyGC() ); + XGCValues aOldVal, aNewVal; + int nValues = GCForeground | GCBackground; + + if( rSalBitmap.GetBitCount() == 1 ) + { + // set foreground/background values for 1Bit bitmaps + XGetGCValues( pXDisp, aGC, nValues, &aOldVal ); + setForeBack(aNewVal, rColMap, rSalBitmap); + XChangeGC( pXDisp, aGC, nValues, &aNewVal ); + } + + static_cast<const X11SalBitmap&>(rSalBitmap).ImplDraw( aDrawable, mrParent.m_nXScreen, nDepth, rPosAry, aGC ); + + if( rSalBitmap.GetBitCount() == 1 ) + XChangeGC( pXDisp, aGC, nValues, &aOldVal ); + XFlush( pXDisp ); +} + +void X11SalGraphicsImpl::drawBitmap( const SalTwoRect& rPosAry, + const SalBitmap& rSrcBitmap, + const SalBitmap& rMaskBitmap ) +{ + // decide if alpha masking or transparency masking is needed + BitmapBuffer* pAlphaBuffer = const_cast<SalBitmap&>(rMaskBitmap).AcquireBuffer( BitmapAccessMode::Read ); + if( pAlphaBuffer != nullptr ) + { + ScanlineFormat nMaskFormat = pAlphaBuffer->mnFormat; + const_cast<SalBitmap&>(rMaskBitmap).ReleaseBuffer( pAlphaBuffer, BitmapAccessMode::Read ); + if( nMaskFormat == ScanlineFormat::N8BitPal ) + drawAlphaBitmap( rPosAry, rSrcBitmap, rMaskBitmap ); + } + + drawMaskedBitmap( rPosAry, rSrcBitmap, rMaskBitmap ); +} + +void X11SalGraphicsImpl::drawMaskedBitmap( const SalTwoRect& rPosAry, + const SalBitmap& rSalBitmap, + const SalBitmap& rTransBitmap ) +{ + const SalDisplay* pSalDisp = mrParent.GetDisplay(); + Display* pXDisp = pSalDisp->GetDisplay(); + Drawable aDrawable( mrParent.GetDrawable() ); + + // figure work mode depth. If this is a VDev Drawable, use its + // bitdepth to create pixmaps for, otherwise, XCopyArea will + // refuse to work. + const sal_uInt16 nDepth( mrParent.m_pVDev ? + static_cast< X11SalVirtualDevice* >(mrParent.m_pVDev)->GetDepth() : + pSalDisp->GetVisual( mrParent.m_nXScreen ).GetDepth() ); + Pixmap aFG( limitXCreatePixmap( pXDisp, aDrawable, rPosAry.mnDestWidth, + rPosAry.mnDestHeight, nDepth ) ); + Pixmap aBG( limitXCreatePixmap( pXDisp, aDrawable, rPosAry.mnDestWidth, + rPosAry.mnDestHeight, nDepth ) ); + + if( aFG && aBG ) + { + GC aTmpGC; + XGCValues aValues; + setForeBack(aValues, pSalDisp->GetColormap(mrParent.m_nXScreen), rSalBitmap); + const int nValues = GCFunction | GCForeground | GCBackground; + SalTwoRect aTmpRect( rPosAry ); aTmpRect.mnDestX = aTmpRect.mnDestY = 0; + + // draw paint bitmap in pixmap #1 + aValues.function = GXcopy; + aTmpGC = XCreateGC( pXDisp, aFG, nValues, &aValues ); + static_cast<const X11SalBitmap&>(rSalBitmap).ImplDraw( aFG, mrParent.m_nXScreen, nDepth, aTmpRect, aTmpGC ); + DBG_TESTTRANS( aFG ); + + // draw background in pixmap #2 + XCopyArea( pXDisp, aDrawable, aBG, aTmpGC, + rPosAry.mnDestX, rPosAry.mnDestY, + rPosAry.mnDestWidth, rPosAry.mnDestHeight, + 0, 0 ); + + DBG_TESTTRANS( aBG ); + + // mask out paint bitmap in pixmap #1 (transparent areas 0) + aValues.function = GXand; + aValues.foreground = 0x00000000; + aValues.background = 0xffffffff; + XChangeGC( pXDisp, aTmpGC, nValues, &aValues ); + static_cast<const X11SalBitmap&>(rTransBitmap).ImplDraw( aFG, mrParent.m_nXScreen, 1, aTmpRect, aTmpGC ); + + DBG_TESTTRANS( aFG ); + + // #105055# For XOR mode, keep background behind bitmap intact + if( !mbXORMode ) + { + // mask out background in pixmap #2 (nontransparent areas 0) + aValues.function = GXand; + aValues.foreground = 0xffffffff; + aValues.background = 0x00000000; + XChangeGC( pXDisp, aTmpGC, nValues, &aValues ); + static_cast<const X11SalBitmap&>(rTransBitmap).ImplDraw( aBG, mrParent.m_nXScreen, 1, aTmpRect, aTmpGC ); + + DBG_TESTTRANS( aBG ); + } + + // merge pixmap #1 and pixmap #2 in pixmap #2 + aValues.function = GXxor; + aValues.foreground = 0xffffffff; + aValues.background = 0x00000000; + XChangeGC( pXDisp, aTmpGC, nValues, &aValues ); + XCopyArea( pXDisp, aFG, aBG, aTmpGC, + 0, 0, + rPosAry.mnDestWidth, rPosAry.mnDestHeight, + 0, 0 ); + DBG_TESTTRANS( aBG ); + + // #105055# Disable XOR temporarily + bool bOldXORMode( mbXORMode ); + mbXORMode = false; + + // copy pixmap #2 (result) to background + XCopyArea( pXDisp, aBG, aDrawable, GetCopyGC(), + 0, 0, + rPosAry.mnDestWidth, rPosAry.mnDestHeight, + rPosAry.mnDestX, rPosAry.mnDestY ); + + DBG_TESTTRANS( aBG ); + + mbXORMode = bOldXORMode; + + XFreeGC( pXDisp, aTmpGC ); + XFlush( pXDisp ); + } + else + drawBitmap( rPosAry, rSalBitmap ); + + if( aFG ) + XFreePixmap( pXDisp, aFG ); + + if( aBG ) + XFreePixmap( pXDisp, aBG ); +} + +bool X11SalGraphicsImpl::blendBitmap( const SalTwoRect&, + const SalBitmap& ) +{ + return false; +} + +bool X11SalGraphicsImpl::blendAlphaBitmap( const SalTwoRect&, + const SalBitmap&, const SalBitmap&, const SalBitmap& ) +{ + return false; +} + +bool X11SalGraphicsImpl::drawAlphaBitmap( const SalTwoRect& rTR, + const SalBitmap& rSrcBitmap, const SalBitmap& rAlphaBmp ) +{ + // non 8-bit alpha not implemented yet + if( rAlphaBmp.GetBitCount() != 8 ) + return false; + // #i75531# the workaround below can go when + // X11SalGraphics::drawAlphaBitmap()'s render acceleration + // can handle the bitmap depth mismatch directly + if( rSrcBitmap.GetBitCount() < rAlphaBmp.GetBitCount() ) + return false; + + // horizontal mirroring not implemented yet + if( rTR.mnDestWidth < 0 ) + return false; + + // stretched conversion is not implemented yet + if( rTR.mnDestWidth != rTR.mnSrcWidth ) + return false; + if( rTR.mnDestHeight!= rTR.mnSrcHeight ) + return false; + + // create destination picture + Picture aDstPic = GetXRenderPicture(); + if( !aDstPic ) + return false; + + const SalDisplay* pSalDisp = mrParent.GetDisplay(); + const SalVisual& rSalVis = pSalDisp->GetVisual( mrParent.m_nXScreen ); + Display* pXDisplay = pSalDisp->GetDisplay(); + + // create source Picture + int nDepth = mrParent.m_pVDev ? static_cast< X11SalVirtualDevice* >(mrParent.m_pVDev)->GetDepth() : rSalVis.GetDepth(); + const X11SalBitmap& rSrcX11Bmp = static_cast<const X11SalBitmap&>( rSrcBitmap ); + ImplSalDDB* pSrcDDB = rSrcX11Bmp.ImplGetDDB( mrParent.GetDrawable(), mrParent.m_nXScreen, nDepth, rTR ); + if( !pSrcDDB ) + return false; + + //#i75249# workaround for ImplGetDDB() giving us back a different depth than + // we requested. E.g. mask pixmaps are always compatible with the drawable + // TODO: find an appropriate picture format for these cases + // then remove the workaround below and the one for #i75531# + if( nDepth != pSrcDDB->ImplGetDepth() ) + return false; + + Pixmap aSrcPM = pSrcDDB->ImplGetPixmap(); + if( !aSrcPM ) + return false; + + // create source picture + // TODO: use scoped picture + Visual* pSrcXVisual = rSalVis.GetVisual(); + XRenderPeer& rPeer = XRenderPeer::GetInstance(); + XRenderPictFormat* pSrcVisFmt = rPeer.FindVisualFormat( pSrcXVisual ); + if( !pSrcVisFmt ) + return false; + Picture aSrcPic = rPeer.CreatePicture( aSrcPM, pSrcVisFmt, 0, nullptr ); + if( !aSrcPic ) + return false; + + // create alpha Picture + + // TODO: use SalX11Bitmap functionality and caching for the Alpha Pixmap + // problem is that they don't provide an 8bit Pixmap on a non-8bit display + BitmapBuffer* pAlphaBuffer = const_cast<SalBitmap&>(rAlphaBmp).AcquireBuffer( BitmapAccessMode::Read ); + + // an XImage needs its data top_down + // TODO: avoid wrongly oriented images in upper layers! + const int nImageSize = pAlphaBuffer->mnHeight * pAlphaBuffer->mnScanlineSize; + const char* pSrcBits = reinterpret_cast<char*>(pAlphaBuffer->mpBits); + char* pAlphaBits = new char[ nImageSize ]; + if( pAlphaBuffer->mnFormat & ScanlineFormat::TopDown ) + memcpy( pAlphaBits, pSrcBits, nImageSize ); + else + { + char* pDstBits = pAlphaBits + nImageSize; + const int nLineSize = pAlphaBuffer->mnScanlineSize; + for(; (pDstBits -= nLineSize) >= pAlphaBits; pSrcBits += nLineSize ) + memcpy( pDstBits, pSrcBits, nLineSize ); + } + + // the alpha values need to be inverted for XRender + // TODO: make upper layers use standard alpha + tools::Long* pLDst = reinterpret_cast<long*>(pAlphaBits); + for( int i = nImageSize/sizeof(long); --i >= 0; ++pLDst ) + *pLDst = ~*pLDst; + + char* pCDst = reinterpret_cast<char*>(pLDst); + for( int i = nImageSize & (sizeof(long)-1); --i >= 0; ++pCDst ) + *pCDst = ~*pCDst; + + const XRenderPictFormat* pAlphaFormat = rPeer.GetStandardFormatA8(); + XImage* pAlphaImg = XCreateImage( pXDisplay, pSrcXVisual, 8, ZPixmap, 0, + pAlphaBits, pAlphaBuffer->mnWidth, pAlphaBuffer->mnHeight, + pAlphaFormat->depth, pAlphaBuffer->mnScanlineSize ); + + Pixmap aAlphaPM = limitXCreatePixmap( pXDisplay, mrParent.GetDrawable(), + rTR.mnDestWidth, rTR.mnDestHeight, 8 ); + + XGCValues aAlphaGCV; + aAlphaGCV.function = GXcopy; + GC aAlphaGC = XCreateGC( pXDisplay, aAlphaPM, GCFunction, &aAlphaGCV ); + XPutImage( pXDisplay, aAlphaPM, aAlphaGC, pAlphaImg, + rTR.mnSrcX, rTR.mnSrcY, 0, 0, rTR.mnDestWidth, rTR.mnDestHeight ); + XFreeGC( pXDisplay, aAlphaGC ); + XFree( pAlphaImg ); + if( pAlphaBits != reinterpret_cast<char*>(pAlphaBuffer->mpBits) ) + delete[] pAlphaBits; + + const_cast<SalBitmap&>(rAlphaBmp).ReleaseBuffer( pAlphaBuffer, BitmapAccessMode::Read ); + + XRenderPictureAttributes aAttr; + aAttr.repeat = int(true); + Picture aAlphaPic = rPeer.CreatePicture( aAlphaPM, pAlphaFormat, CPRepeat, &aAttr ); + if( !aAlphaPic ) + return false; + + // set clipping + if( mrParent.mpClipRegion && !XEmptyRegion( mrParent.mpClipRegion ) ) + rPeer.SetPictureClipRegion( aDstPic, mrParent.mpClipRegion ); + + // paint source * mask over destination picture + rPeer.CompositePicture( PictOpOver, aSrcPic, aAlphaPic, aDstPic, + rTR.mnSrcX, rTR.mnSrcY, + rTR.mnDestX, rTR.mnDestY, rTR.mnDestWidth, rTR.mnDestHeight ); + + rPeer.FreePicture( aAlphaPic ); + XFreePixmap(pXDisplay, aAlphaPM); + rPeer.FreePicture( aSrcPic ); + return true; +} + +bool X11SalGraphicsImpl::drawTransformedBitmap( + const basegfx::B2DPoint&, + const basegfx::B2DPoint&, + const basegfx::B2DPoint&, + const SalBitmap&, + const SalBitmap*, + double) +{ + // here direct support for transformed bitmaps can be implemented + return false; +} + +bool X11SalGraphicsImpl::hasFastDrawTransformedBitmap() const +{ + return false; +} + +bool X11SalGraphicsImpl::drawAlphaRect( tools::Long nX, tools::Long nY, tools::Long nWidth, + tools::Long nHeight, sal_uInt8 nTransparency ) +{ + if( ! mrParent.m_pFrame && ! mrParent.m_pVDev ) + return false; + + if( mbPenGC || !mbBrushGC || mbXORMode ) + return false; // can only perform solid fills without XOR. + + if( mrParent.m_pVDev && static_cast< X11SalVirtualDevice* >(mrParent.m_pVDev)->GetDepth() < 8 ) + return false; + + Picture aDstPic = GetXRenderPicture(); + if( !aDstPic ) + return false; + + const double fTransparency = (100 - nTransparency) * (1.0/100); + const XRenderColor aRenderColor = GetXRenderColor( mnBrushColor , fTransparency); + + XRenderPeer& rPeer = XRenderPeer::GetInstance(); + rPeer.FillRectangle( PictOpOver, + aDstPic, + &aRenderColor, + nX, nY, + nWidth, nHeight ); + + return true; +} + +void X11SalGraphicsImpl::drawMask( const SalTwoRect& rPosAry, + const SalBitmap &rSalBitmap, + Color nMaskColor ) +{ + const SalDisplay* pSalDisp = mrParent.GetDisplay(); + Display* pXDisp = pSalDisp->GetDisplay(); + Drawable aDrawable( mrParent.GetDrawable() ); + Pixmap aStipple( limitXCreatePixmap( pXDisp, aDrawable, + rPosAry.mnDestWidth, + rPosAry.mnDestHeight, 1 ) ); + + if( aStipple ) + { + SalTwoRect aTwoRect( rPosAry ); aTwoRect.mnDestX = aTwoRect.mnDestY = 0; + GC aTmpGC; + XGCValues aValues; + + // create a stipple bitmap first (set bits are changed to unset bits and vice versa) + aValues.function = GXcopyInverted; + aValues.foreground = 1; + aValues.background = 0; + aTmpGC = XCreateGC( pXDisp, aStipple, GCFunction | GCForeground | GCBackground, &aValues ); + static_cast<const X11SalBitmap&>(rSalBitmap).ImplDraw( aStipple, mrParent.m_nXScreen, 1, aTwoRect, aTmpGC ); + + XFreeGC( pXDisp, aTmpGC ); + + // Set stipple and draw rectangle + GC aStippleGC( GetStippleGC() ); + int nX = rPosAry.mnDestX, nY = rPosAry.mnDestY; + + XSetStipple( pXDisp, aStippleGC, aStipple ); + XSetTSOrigin( pXDisp, aStippleGC, nX, nY ); + XSetForeground( pXDisp, aStippleGC, mrParent.GetPixel( nMaskColor ) ); + XFillRectangle( pXDisp, aDrawable, aStippleGC, + nX, nY, + rPosAry.mnDestWidth, rPosAry.mnDestHeight ); + XFreePixmap( pXDisp, aStipple ); + XFlush( pXDisp ); + } + else + drawBitmap( rPosAry, rSalBitmap ); +} + +void X11SalGraphicsImpl::ResetClipRegion() +{ + if( !mrParent.mpClipRegion ) + return; + + mbPenGC = false; + mbBrushGC = false; + mbCopyGC = false; + mbInvertGC = false; + mbInvert50GC = false; + mbStippleGC = false; + mbTrackingGC = false; + + XDestroyRegion( mrParent.mpClipRegion ); + mrParent.mpClipRegion = nullptr; +} + +bool X11SalGraphicsImpl::setClipRegion( const vcl::Region& i_rClip ) +{ + if( mrParent.mpClipRegion ) + XDestroyRegion( mrParent.mpClipRegion ); + mrParent.mpClipRegion = XCreateRegion(); + + RectangleVector aRectangles; + i_rClip.GetRegionRectangles(aRectangles); + + for (auto const& rectangle : aRectangles) + { + const tools::Long nW(rectangle.GetWidth()); + + if(nW) + { + const tools::Long nH(rectangle.GetHeight()); + + if(nH) + { + XRectangle aRect; + + aRect.x = static_cast<short>(rectangle.Left()); + aRect.y = static_cast<short>(rectangle.Top()); + aRect.width = static_cast<unsigned short>(nW); + aRect.height = static_cast<unsigned short>(nH); + XUnionRectWithRegion(&aRect, mrParent.mpClipRegion, mrParent.mpClipRegion); + } + } + } + + //ImplRegionInfo aInfo; + //long nX, nY, nW, nH; + //bool bRegionRect = i_rClip.ImplGetFirstRect(aInfo, nX, nY, nW, nH ); + //while( bRegionRect ) + //{ + // if ( nW && nH ) + // { + // XRectangle aRect; + // aRect.x = (short)nX; + // aRect.y = (short)nY; + // aRect.width = (unsigned short)nW; + // aRect.height = (unsigned short)nH; + + // XUnionRectWithRegion( &aRect, mrParent.mpClipRegion, mrParent.mpClipRegion ); + // } + // bRegionRect = i_rClip.ImplGetNextRect( aInfo, nX, nY, nW, nH ); + //} + + // done, invalidate GCs + mbPenGC = false; + mbBrushGC = false; + mbCopyGC = false; + mbInvertGC = false; + mbInvert50GC = false; + mbStippleGC = false; + mbTrackingGC = false; + + if( XEmptyRegion( mrParent.mpClipRegion ) ) + { + XDestroyRegion( mrParent.mpClipRegion ); + mrParent.mpClipRegion= nullptr; + } + return true; +} + +void X11SalGraphicsImpl::SetLineColor() +{ + if( mnPenColor != SALCOLOR_NONE ) + { + mnPenColor = SALCOLOR_NONE; + mbPenGC = false; + } +} + +void X11SalGraphicsImpl::SetLineColor( Color nColor ) +{ + if( mnPenColor != nColor ) + { + mnPenColor = nColor; + mnPenPixel = mrParent.GetPixel( nColor ); + mbPenGC = false; + } +} + +void X11SalGraphicsImpl::SetFillColor() +{ + if( mnBrushColor != SALCOLOR_NONE ) + { + mbDitherBrush = false; + mnBrushColor = SALCOLOR_NONE; + mbBrushGC = false; + } +} + +void X11SalGraphicsImpl::SetFillColor( Color nColor ) +{ + if( mnBrushColor == nColor ) + return; + + mbDitherBrush = false; + mnBrushColor = nColor; + mnBrushPixel = mrParent.GetPixel( nColor ); + if( TrueColor != mrParent.GetColormap().GetVisual().GetClass() + && mrParent.GetColormap().GetColor( mnBrushPixel ) != mnBrushColor + && nColor != Color( 0x00, 0x00, 0x00 ) // black + && nColor != Color( 0x00, 0x00, 0x80 ) // blue + && nColor != Color( 0x00, 0x80, 0x00 ) // green + && nColor != Color( 0x00, 0x80, 0x80 ) // cyan + && nColor != Color( 0x80, 0x00, 0x00 ) // red + && nColor != Color( 0x80, 0x00, 0x80 ) // magenta + && nColor != Color( 0x80, 0x80, 0x00 ) // brown + && nColor != Color( 0x80, 0x80, 0x80 ) // gray + && nColor != Color( 0xC0, 0xC0, 0xC0 ) // light gray + && nColor != Color( 0x00, 0x00, 0xFF ) // light blue + && nColor != Color( 0x00, 0xFF, 0x00 ) // light green + && nColor != Color( 0x00, 0xFF, 0xFF ) // light cyan + && nColor != Color( 0xFF, 0x00, 0x00 ) // light red + && nColor != Color( 0xFF, 0x00, 0xFF ) // light magenta + && nColor != Color( 0xFF, 0xFF, 0x00 ) // light brown + && nColor != Color( 0xFF, 0xFF, 0xFF ) ) + mbDitherBrush = mrParent.GetDitherPixmap(nColor); + mbBrushGC = false; +} + +void X11SalGraphicsImpl::SetROPLineColor( SalROPColor nROPColor ) +{ + switch( nROPColor ) + { + case SalROPColor::N0 : // 0 + mnPenPixel = Pixel(0); + break; + case SalROPColor::N1 : // 1 + mnPenPixel = static_cast<Pixel>(1 << mrParent.GetVisual().GetDepth()) - 1; + break; + case SalROPColor::Invert : // 2 + mnPenPixel = static_cast<Pixel>(1 << mrParent.GetVisual().GetDepth()) - 1; + break; + } + mnPenColor = mrParent.GetColormap().GetColor( mnPenPixel ); + mbPenGC = false; +} + +void X11SalGraphicsImpl::SetROPFillColor( SalROPColor nROPColor ) +{ + switch( nROPColor ) + { + case SalROPColor::N0 : // 0 + mnBrushPixel = Pixel(0); + break; + case SalROPColor::N1 : // 1 + mnBrushPixel = static_cast<Pixel>(1 << mrParent.GetVisual().GetDepth()) - 1; + break; + case SalROPColor::Invert : // 2 + mnBrushPixel = static_cast<Pixel>(1 << mrParent.GetVisual().GetDepth()) - 1; + break; + } + mbDitherBrush = false; + mnBrushColor = mrParent.GetColormap().GetColor( mnBrushPixel ); + mbBrushGC = false; +} + +void X11SalGraphicsImpl::SetXORMode( bool bSet, bool ) +{ + if (mbXORMode != bSet) + { + mbXORMode = bSet; + mbPenGC = false; + mbBrushGC = false; + mbCopyGC = false; + mbInvertGC = false; + mbInvert50GC = false; + mbStippleGC = false; + mbTrackingGC = false; + } +} + +void X11SalGraphicsImpl::drawPixel( tools::Long nX, tools::Long nY ) +{ + if( mnPenColor != SALCOLOR_NONE ) + XDrawPoint( mrParent.GetXDisplay(), mrParent.GetDrawable(), SelectPen(), nX, nY ); +} + +void X11SalGraphicsImpl::drawPixel( tools::Long nX, tools::Long nY, Color nColor ) +{ + if( nColor == SALCOLOR_NONE ) + return; + + Display *pDisplay = mrParent.GetXDisplay(); + + if( (mnPenColor == SALCOLOR_NONE) && !mbPenGC ) + { + SetLineColor( nColor ); + XDrawPoint( pDisplay, mrParent.GetDrawable(), SelectPen(), nX, nY ); + mnPenColor = SALCOLOR_NONE; + mbPenGC = False; + } + else + { + GC pGC = SelectPen(); + + if( nColor != mnPenColor ) + XSetForeground( pDisplay, pGC, mrParent.GetPixel( nColor ) ); + + XDrawPoint( pDisplay, mrParent.GetDrawable(), pGC, nX, nY ); + + if( nColor != mnPenColor ) + XSetForeground( pDisplay, pGC, mnPenPixel ); + } +} + +void X11SalGraphicsImpl::drawLine( tools::Long nX1, tools::Long nY1, tools::Long nX2, tools::Long nY2 ) +{ + if( mnPenColor != SALCOLOR_NONE ) + { + XDrawLine( mrParent.GetXDisplay(), mrParent.GetDrawable(),SelectPen(), + nX1, nY1, nX2, nY2 ); + } +} + +void X11SalGraphicsImpl::drawRect( tools::Long nX, tools::Long nY, tools::Long nDX, tools::Long nDY ) +{ + if( mnBrushColor != SALCOLOR_NONE ) + { + XFillRectangle( mrParent.GetXDisplay(), + mrParent.GetDrawable(), + SelectBrush(), + nX, nY, nDX, nDY ); + } + // description DrawRect is wrong; thus -1 + if( mnPenColor != SALCOLOR_NONE ) + XDrawRectangle( mrParent.GetXDisplay(), + mrParent.GetDrawable(), + SelectPen(), + nX, nY, nDX-1, nDY-1 ); +} + +void X11SalGraphicsImpl::drawPolyLine( sal_uInt32 nPoints, const Point *pPtAry ) +{ + internalDrawPolyLine( nPoints, pPtAry, false ); +} + +void X11SalGraphicsImpl::internalDrawPolyLine( sal_uInt32 nPoints, const Point *pPtAry, bool bClose ) +{ + if( mnPenColor != SALCOLOR_NONE ) + { + SalPolyLine Points( nPoints, pPtAry ); + + DrawLines( nPoints, Points, SelectPen(), bClose ); + } +} + +void X11SalGraphicsImpl::drawPolygon( sal_uInt32 nPoints, const Point* pPtAry ) +{ + if( nPoints == 0 ) + return; + + if( nPoints < 3 ) + { + if( !mbXORMode ) + { + if( 1 == nPoints ) + drawPixel( pPtAry[0].getX(), pPtAry[0].getY() ); + else + drawLine( pPtAry[0].getX(), pPtAry[0].getY(), + pPtAry[1].getX(), pPtAry[1].getY() ); + } + return; + } + + SalPolyLine Points( nPoints, pPtAry ); + + nPoints++; + + /* WORKAROUND: some Xservers (Xorg, VIA chipset in this case) + * do not draw the visible part of a polygon + * if it overlaps to the left of screen 0,y. + * This happens to be the case in the gradient drawn in the + * menubar background. workaround for the special case of + * of a rectangle overlapping to the left. + */ + if (nPoints == 5 && + Points[ 0 ].x == Points[ 1 ].x && + Points[ 1 ].y == Points[ 2 ].y && + Points[ 2 ].x == Points[ 3 ].x && + Points[ 0 ].x == Points[ 4 ].x && Points[ 0 ].y == Points[ 4 ].y + ) + { + bool bLeft = false; + bool bRight = false; + for(unsigned int i = 0; i < nPoints; i++ ) + { + if( Points[i].x < 0 ) + bLeft = true; + else + bRight= true; + } + if( bLeft && ! bRight ) + return; + if( bLeft && bRight ) + { + for( unsigned int i = 0; i < nPoints; i++ ) + if( Points[i].x < 0 ) + Points[i].x = 0; + } + } + + if( mnBrushColor != SALCOLOR_NONE ) + XFillPolygon( mrParent.GetXDisplay(), + mrParent.GetDrawable(), + SelectBrush(), + &Points[0], nPoints, + Complex, CoordModeOrigin ); + + if( mnPenColor != SALCOLOR_NONE ) + DrawLines( nPoints, Points, SelectPen(), true ); +} + +void X11SalGraphicsImpl::drawPolyPolygon( sal_uInt32 nPoly, + const sal_uInt32 *pPoints, + const Point* *pPtAry ) +{ + if( mnBrushColor != SALCOLOR_NONE ) + { + sal_uInt32 i, n; + Region pXRegA = nullptr; + + for( i = 0; i < nPoly; i++ ) { + n = pPoints[i]; + SalPolyLine Points( n, pPtAry[i] ); + if( n > 2 ) + { + Region pXRegB = XPolygonRegion( &Points[0], n+1, WindingRule ); + if( !pXRegA ) + pXRegA = pXRegB; + else + { + XXorRegion( pXRegA, pXRegB, pXRegA ); + XDestroyRegion( pXRegB ); + } + } + } + + if( pXRegA ) + { + XRectangle aXRect; + XClipBox( pXRegA, &aXRect ); + + GC pGC = SelectBrush(); + mrParent.SetClipRegion( pGC, pXRegA ); // ??? twice + XDestroyRegion( pXRegA ); + mbBrushGC = false; + + XFillRectangle( mrParent.GetXDisplay(), + mrParent.GetDrawable(), + pGC, + aXRect.x, aXRect.y, aXRect.width, aXRect.height ); + } + } + + if( mnPenColor != SALCOLOR_NONE ) + for( sal_uInt32 i = 0; i < nPoly; i++ ) + internalDrawPolyLine( pPoints[i], pPtAry[i], true ); +} + +bool X11SalGraphicsImpl::drawPolyLineBezier( sal_uInt32, const Point*, const PolyFlags* ) +{ + return false; +} + +bool X11SalGraphicsImpl::drawPolygonBezier( sal_uInt32, const Point*, const PolyFlags* ) +{ + return false; +} + +bool X11SalGraphicsImpl::drawPolyPolygonBezier( sal_uInt32, const sal_uInt32*, + const Point* const*, const PolyFlags* const* ) +{ + return false; +} + +void X11SalGraphicsImpl::invert( tools::Long nX, + tools::Long nY, + tools::Long nDX, + tools::Long nDY, + SalInvert nFlags ) +{ + GC pGC; + if( SalInvert::N50 & nFlags ) + { + pGC = GetInvert50GC(); + XFillRectangle( mrParent.GetXDisplay(), mrParent.GetDrawable(), pGC, nX, nY, nDX, nDY ); + } + else + { + if ( SalInvert::TrackFrame & nFlags ) + { + pGC = GetTrackingGC(); + XDrawRectangle( mrParent.GetXDisplay(), mrParent.GetDrawable(), pGC, nX, nY, nDX, nDY ); + } + else + { + pGC = GetInvertGC(); + XFillRectangle( mrParent.GetXDisplay(), mrParent.GetDrawable(), pGC, nX, nY, nDX, nDY ); + } + } +} + +void X11SalGraphicsImpl::invert( sal_uInt32 nPoints, + const Point* pPtAry, + SalInvert nFlags ) +{ + SalPolyLine Points ( nPoints, pPtAry ); + + GC pGC; + if( SalInvert::N50 & nFlags ) + pGC = GetInvert50GC(); + else + if ( SalInvert::TrackFrame & nFlags ) + pGC = GetTrackingGC(); + else + pGC = GetInvertGC(); + + if( SalInvert::TrackFrame & nFlags ) + DrawLines ( nPoints, Points, pGC, true ); + else + XFillPolygon( mrParent.GetXDisplay(), + mrParent.GetDrawable(), + pGC, + &Points[0], nPoints, + Complex, CoordModeOrigin ); +} + +bool X11SalGraphicsImpl::drawEPS( tools::Long,tools::Long,tools::Long,tools::Long,void*,sal_uInt32 ) +{ + return false; +} + +// draw a poly-polygon +bool X11SalGraphicsImpl::drawPolyPolygon( + const basegfx::B2DHomMatrix& rObjectToDevice, + const basegfx::B2DPolyPolygon& rPolyPolygon, + double fTransparency) +{ + // nothing to do for empty polypolygons + const int nOrigPolyCount = rPolyPolygon.count(); + if( nOrigPolyCount <= 0 ) + return true; + + // nothing to do if everything is transparent + if( (mnBrushColor == SALCOLOR_NONE) + && (mnPenColor == SALCOLOR_NONE) ) + return true; + + // cannot handle pencolor!=brushcolor yet + if( (mnPenColor != SALCOLOR_NONE) + && (mnPenColor != mnBrushColor) ) + return false; + + // TODO: remove the env-variable when no longer needed + static const char* pRenderEnv = getenv( "SAL_DISABLE_RENDER_POLY" ); + if( pRenderEnv ) + return false; + + // Fallback: Transform to DeviceCoordinates + basegfx::B2DPolyPolygon aPolyPolygon(rPolyPolygon); + aPolyPolygon.transform(rObjectToDevice); + + // snap to raster if requested + const bool bSnapToRaster = !mrParent.getAntiAlias(); + if( bSnapToRaster ) + aPolyPolygon = basegfx::utils::snapPointsOfHorizontalOrVerticalEdges( aPolyPolygon ); + + // don't bother with polygons outside of visible area + const basegfx::B2DRange aViewRange( 0, 0, GetGraphicsWidth(), GetGraphicsHeight() ); + aPolyPolygon = basegfx::utils::clipPolyPolygonOnRange( aPolyPolygon, aViewRange, true, false ); + if( !aPolyPolygon.count() ) + return true; + + // tessellate the polypolygon into trapezoids + basegfx::B2DTrapezoidVector aB2DTrapVector; + basegfx::utils::trapezoidSubdivide( aB2DTrapVector, aPolyPolygon ); + const int nTrapCount = aB2DTrapVector.size(); + if( !nTrapCount ) + return true; + const bool bDrawn = drawFilledTrapezoids( aB2DTrapVector.data(), nTrapCount, fTransparency ); + return bDrawn; +} + +tools::Long X11SalGraphicsImpl::GetGraphicsHeight() const +{ + if( mrParent.m_pFrame ) + return mrParent.m_pFrame->maGeometry.nHeight; + else if( mrParent.m_pVDev ) + return static_cast< X11SalVirtualDevice* >(mrParent.m_pVDev)->GetHeight(); + else + return 0; +} + +bool X11SalGraphicsImpl::drawFilledTrapezoids( const basegfx::B2DTrapezoid* pB2DTraps, int nTrapCount, double fTransparency ) +{ + if( nTrapCount <= 0 ) + return true; + + Picture aDstPic = GetXRenderPicture(); + // check xrender support for this drawable + if( !aDstPic ) + return false; + + // convert the B2DTrapezoids into XRender-Trapezoids + std::vector<XTrapezoid> aTrapVector( nTrapCount ); + const basegfx::B2DTrapezoid* pB2DTrap = pB2DTraps; + for( int i = 0; i < nTrapCount; ++pB2DTrap, ++i ) + { + XTrapezoid& rTrap = aTrapVector[ i ] ; + + // set y-coordinates + const double fY1 = pB2DTrap->getTopY(); + rTrap.left.p1.y = rTrap.right.p1.y = rTrap.top = XDoubleToFixed( fY1 ); + const double fY2 = pB2DTrap->getBottomY(); + rTrap.left.p2.y = rTrap.right.p2.y = rTrap.bottom = XDoubleToFixed( fY2 ); + + // set x-coordinates + const double fXL1 = pB2DTrap->getTopXLeft(); + rTrap.left.p1.x = XDoubleToFixed( fXL1 ); + const double fXR1 = pB2DTrap->getTopXRight(); + rTrap.right.p1.x = XDoubleToFixed( fXR1 ); + const double fXL2 = pB2DTrap->getBottomXLeft(); + rTrap.left.p2.x = XDoubleToFixed( fXL2 ); + const double fXR2 = pB2DTrap->getBottomXRight(); + rTrap.right.p2.x = XDoubleToFixed( fXR2 ); + } + + // get xrender Picture for polygon foreground + // TODO: cache it like the target picture which uses GetXRenderPicture() + XRenderPeer& rRenderPeer = XRenderPeer::GetInstance(); + SalDisplay::RenderEntry& rEntry = mrParent.GetDisplay()->GetRenderEntries( mrParent.m_nXScreen )[ 32 ]; + if( !rEntry.m_aPicture ) + { + Display* pXDisplay = mrParent.GetXDisplay(); + + rEntry.m_aPixmap = limitXCreatePixmap( pXDisplay, mrParent.GetDrawable(), 1, 1, 32 ); + XRenderPictureAttributes aAttr; + aAttr.repeat = int(true); + + XRenderPictFormat* pXRPF = rRenderPeer.FindStandardFormat( PictStandardARGB32 ); + rEntry.m_aPicture = rRenderPeer.CreatePicture( rEntry.m_aPixmap, pXRPF, CPRepeat, &aAttr ); + } + + // set polygon foreground color and opacity + XRenderColor aRenderColor = GetXRenderColor( mnBrushColor , fTransparency ); + rRenderPeer.FillRectangle( PictOpSrc, rEntry.m_aPicture, &aRenderColor, 0, 0, 1, 1 ); + + // set clipping + // TODO: move into GetXRenderPicture? + if( mrParent.mpClipRegion && !XEmptyRegion( mrParent.mpClipRegion ) ) + rRenderPeer.SetPictureClipRegion( aDstPic, mrParent.mpClipRegion ); + + // render the trapezoids + const XRenderPictFormat* pMaskFormat = rRenderPeer.GetStandardFormatA8(); + rRenderPeer.CompositeTrapezoids( PictOpOver, + rEntry.m_aPicture, aDstPic, pMaskFormat, 0, 0, aTrapVector.data(), aTrapVector.size() ); + + return true; +} + +bool X11SalGraphicsImpl::drawFilledTriangles( + const basegfx::B2DHomMatrix& rObjectToDevice, + const basegfx::triangulator::B2DTriangleVector& rTriangles, + double fTransparency) +{ + if(rTriangles.empty()) + return true; + + Picture aDstPic = GetXRenderPicture(); + // check xrender support for this drawable + if( !aDstPic ) + { + return false; + } + + // prepare transformation for ObjectToDevice coordinate system + basegfx::B2DHomMatrix aObjectToDevice = basegfx::utils::createTranslateB2DHomMatrix(0.5, 0.5) * rObjectToDevice; + + // convert the Triangles into XRender-Triangles + std::vector<XTriangle> aTriVector(rTriangles.size()); + sal_uInt32 nIndex(0); + + for(const auto& rCandidate : rTriangles) + { + const basegfx::B2DPoint aP1(aObjectToDevice * rCandidate.getA()); + const basegfx::B2DPoint aP2(aObjectToDevice * rCandidate.getB()); + const basegfx::B2DPoint aP3(aObjectToDevice * rCandidate.getC()); + XTriangle& rTri(aTriVector[nIndex++]); + + rTri.p1.x = XDoubleToFixed(aP1.getX()); + rTri.p1.y = XDoubleToFixed(aP1.getY()); + + rTri.p2.x = XDoubleToFixed(aP2.getX()); + rTri.p2.y = XDoubleToFixed(aP2.getY()); + + rTri.p3.x = XDoubleToFixed(aP3.getX()); + rTri.p3.y = XDoubleToFixed(aP3.getY()); + } + + // get xrender Picture for polygon foreground + // TODO: cache it like the target picture which uses GetXRenderPicture() + XRenderPeer& rRenderPeer = XRenderPeer::GetInstance(); + SalDisplay::RenderEntry& rEntry = mrParent.GetDisplay()->GetRenderEntries( mrParent.m_nXScreen )[ 32 ]; + if( !rEntry.m_aPicture ) + { + Display* pXDisplay = mrParent.GetXDisplay(); + + rEntry.m_aPixmap = limitXCreatePixmap( pXDisplay, mrParent.GetDrawable(), 1, 1, 32 ); + XRenderPictureAttributes aAttr; + aAttr.repeat = int(true); + + XRenderPictFormat* pXRPF = rRenderPeer.FindStandardFormat( PictStandardARGB32 ); + rEntry.m_aPicture = rRenderPeer.CreatePicture( rEntry.m_aPixmap, pXRPF, CPRepeat, &aAttr ); + } + + // set polygon foreground color and opacity + XRenderColor aRenderColor = GetXRenderColor( mnBrushColor , fTransparency ); + rRenderPeer.FillRectangle( PictOpSrc, rEntry.m_aPicture, &aRenderColor, 0, 0, 1, 1 ); + + // set clipping + // TODO: move into GetXRenderPicture? + if( mrParent.mpClipRegion && !XEmptyRegion( mrParent.mpClipRegion ) ) + rRenderPeer.SetPictureClipRegion( aDstPic, mrParent.mpClipRegion ); + + // render the trapezoids + const XRenderPictFormat* pMaskFormat = rRenderPeer.GetStandardFormatA8(); + rRenderPeer.CompositeTriangles( PictOpOver, + rEntry.m_aPicture, aDstPic, pMaskFormat, 0, 0, aTriVector.data(), aTriVector.size() ); + + return true; +} + +namespace { + +class SystemDependentData_Triangulation : public basegfx::SystemDependentData +{ +private: + // the triangulation itself + basegfx::triangulator::B2DTriangleVector maTriangles; + + // all other values the triangulation is based on and + // need to be compared with to check for data validity + double mfLineWidth; + basegfx::B2DLineJoin meJoin; + css::drawing::LineCap meCap; + double mfMiterMinimumAngle; + std::vector< double > maStroke; + +public: + SystemDependentData_Triangulation( + basegfx::SystemDependentDataManager& rSystemDependentDataManager, + basegfx::triangulator::B2DTriangleVector&& rTriangles, + double fLineWidth, + basegfx::B2DLineJoin eJoin, + css::drawing::LineCap eCap, + double fMiterMinimumAngle, + const std::vector< double >* pStroke); // MM01 + + // read access + const basegfx::triangulator::B2DTriangleVector& getTriangles() const { return maTriangles; } + double getLineWidth() const { return mfLineWidth; } + const basegfx::B2DLineJoin& getJoin() const { return meJoin; } + const css::drawing::LineCap& getCap() const { return meCap; } + double getMiterMinimumAngle() const { return mfMiterMinimumAngle; } + const std::vector< double >& getStroke() const { return maStroke; } + + virtual sal_Int64 estimateUsageInBytes() const override; +}; + +} + +SystemDependentData_Triangulation::SystemDependentData_Triangulation( + basegfx::SystemDependentDataManager& rSystemDependentDataManager, + basegfx::triangulator::B2DTriangleVector&& rTriangles, + double fLineWidth, + basegfx::B2DLineJoin eJoin, + css::drawing::LineCap eCap, + double fMiterMinimumAngle, + const std::vector< double >* pStroke) +: basegfx::SystemDependentData(rSystemDependentDataManager), + maTriangles(std::move(rTriangles)), + mfLineWidth(fLineWidth), + meJoin(eJoin), + meCap(eCap), + mfMiterMinimumAngle(fMiterMinimumAngle) +{ + if(nullptr != pStroke) + { + maStroke = *pStroke; + } +} + +sal_Int64 SystemDependentData_Triangulation::estimateUsageInBytes() const +{ + sal_Int64 nRetval(0); + + if(!maTriangles.empty()) + { + nRetval = maTriangles.size() * sizeof(basegfx::triangulator::B2DTriangle); + } + + return nRetval; +} + +bool X11SalGraphicsImpl::drawPolyLine( + const basegfx::B2DHomMatrix& rObjectToDevice, + const basegfx::B2DPolygon& rPolygon, + double fTransparency, + double fLineWidth, + const std::vector< double >* pStroke, // MM01 + basegfx::B2DLineJoin eLineJoin, + css::drawing::LineCap eLineCap, + double fMiterMinimumAngle, + bool bPixelSnapHairline) +{ + // short circuit if there is nothing to do + if(0 == rPolygon.count() || fTransparency < 0.0 || fTransparency >= 1.0) + { + return true; + } + + // need to check/handle LineWidth when ObjectToDevice transformation is used + const bool bObjectToDeviceIsIdentity(rObjectToDevice.isIdentity()); + basegfx::B2DHomMatrix aObjectToDeviceInv; + + // tdf#124848 calculate-back logical LineWidth for a hairline. + // This implementation does not hand over the transformation to + // the graphic sub-system, but the triangulation data is prepared + // view-independent based on the logic LineWidth, so we need to + // know it + if(fLineWidth == 0) + { + fLineWidth = 1.0; + + if(!bObjectToDeviceIsIdentity) + { + if(aObjectToDeviceInv.isIdentity()) + { + aObjectToDeviceInv = rObjectToDevice; + aObjectToDeviceInv.invert(); + } + + fLineWidth = (aObjectToDeviceInv * basegfx::B2DVector(fLineWidth, 0)).getLength(); + } + } + + // try to access buffered data + std::shared_ptr<SystemDependentData_Triangulation> pSystemDependentData_Triangulation( + rPolygon.getSystemDependentData<SystemDependentData_Triangulation>()); + + // MM01 need to do line dashing as fallback stuff here now + const double fDotDashLength(nullptr != pStroke ? std::accumulate(pStroke->begin(), pStroke->end(), 0.0) : 0.0); + const bool bStrokeUsed(0.0 != fDotDashLength); + assert(!bStrokeUsed || (bStrokeUsed && pStroke)); + + if(pSystemDependentData_Triangulation) + { + // MM01 - check on stroke change. Used against not used, or if oth used, + // equal or different? Triangulation geometry creation depends heavily + // on stroke, independent of being transformation independent + const bool bStrokeWasUsed(!pSystemDependentData_Triangulation->getStroke().empty()); + + if(bStrokeWasUsed != bStrokeUsed + || (bStrokeUsed && *pStroke != pSystemDependentData_Triangulation->getStroke())) + { + // data invalid, forget + pSystemDependentData_Triangulation.reset(); + } + } + + if(pSystemDependentData_Triangulation) + { + // check data validity (I) + if(pSystemDependentData_Triangulation->getJoin() != eLineJoin + || pSystemDependentData_Triangulation->getCap() != eLineCap + || pSystemDependentData_Triangulation->getMiterMinimumAngle() != fMiterMinimumAngle) + { + // data invalid, forget + pSystemDependentData_Triangulation.reset(); + } + } + + if(pSystemDependentData_Triangulation) + { + // check data validity (II) + if(pSystemDependentData_Triangulation->getLineWidth() != fLineWidth) + { + // sometimes small inconsistencies, use a percentage tolerance + const double fFactor(basegfx::fTools::equalZero(fLineWidth) + ? 0.0 + : fabs(1.0 - (pSystemDependentData_Triangulation->getLineWidth() / fLineWidth))); + // compare with 5.0% tolerance + if(basegfx::fTools::more(fFactor, 0.05)) + { + // data invalid, forget + pSystemDependentData_Triangulation.reset(); + } + } + } + + if(!pSystemDependentData_Triangulation) + { + // MM01 need to do line dashing as fallback stuff here now + basegfx::B2DPolyPolygon aPolyPolygonLine; + + if(bStrokeUsed) + { + // apply LineStyle + basegfx::utils::applyLineDashing( + rPolygon, // source + *pStroke, // pattern + &aPolyPolygonLine, // target for lines + nullptr, // target for gaps + fDotDashLength); // full length if available + } + else + { + // no line dashing, just copy + aPolyPolygonLine.append(rPolygon); + } + + // try to create data + if(bPixelSnapHairline) + { + // Do NOT transform, but keep device-independent. To + // do so, transform to device for snap, but back again after + if(!bObjectToDeviceIsIdentity) + { + aPolyPolygonLine.transform(rObjectToDevice); + } + + aPolyPolygonLine = basegfx::utils::snapPointsOfHorizontalOrVerticalEdges(aPolyPolygonLine); + + if(!bObjectToDeviceIsIdentity) + { + if(aObjectToDeviceInv.isIdentity()) + { + aObjectToDeviceInv = rObjectToDevice; + aObjectToDeviceInv.invert(); + } + + aPolyPolygonLine.transform(aObjectToDeviceInv); + } + } + + basegfx::triangulator::B2DTriangleVector aTriangles; + + // MM01 checked/verified for X11 (linux) + for(sal_uInt32 a(0); a < aPolyPolygonLine.count(); a++) + { + const basegfx::B2DPolygon aPolyLine(aPolyPolygonLine.getB2DPolygon(a)); + // MM01 upps - commit 51b5b93092d6231615de470c62494c24e54828a1 removed + // this *central* geometry-creating lines (!) probably due to aAreaPolyPoly + // *not* being used - that's true, but the work is inside of filling + // aTriangles data (!) + basegfx::utils::createAreaGeometry( + aPolyLine, + 0.5 * fLineWidth, + eLineJoin, + eLineCap, + basegfx::deg2rad(12.5), + 0.4, + fMiterMinimumAngle, + &aTriangles); // CAUTION! This is *needed* since it creates the data! + } + + if(!aTriangles.empty()) + { + // Add to buffering mechanism + // Add all values the triangulation is based off, too, to check for + // validity (see above) + pSystemDependentData_Triangulation = rPolygon.addOrReplaceSystemDependentData<SystemDependentData_Triangulation>( + ImplGetSystemDependentDataManager(), + std::move(aTriangles), + fLineWidth, + eLineJoin, + eLineCap, + fMiterMinimumAngle, + pStroke); + } + } + + if(!pSystemDependentData_Triangulation) + { + return false; + } + + // temporarily adjust brush color to pen color + // since the line is drawn as an area-polygon + const Color aKeepBrushColor = mnBrushColor; + mnBrushColor = mnPenColor; + + // create the area-polygon for the line + const bool bDrawnOk( + drawFilledTriangles( + rObjectToDevice, + pSystemDependentData_Triangulation->getTriangles(), + fTransparency)); + + // restore the original brush GC + mnBrushColor = aKeepBrushColor; + return bDrawnOk; +} + +Color X11SalGraphicsImpl::getPixel( tools::Long nX, tools::Long nY ) +{ + if( mrParent.bWindow_ && !mrParent.bVirDev_ ) + { + XWindowAttributes aAttrib; + + XGetWindowAttributes( mrParent.GetXDisplay(), mrParent.GetDrawable(), &aAttrib ); + if( aAttrib.map_state != IsViewable ) + { + SAL_WARN( "vcl", "X11SalGraphics::GetPixel drawable not viewable" ); + return 0; + } + } + + XImage *pXImage = XGetImage( mrParent.GetXDisplay(), + mrParent.GetDrawable(), + nX, nY, + 1, 1, + AllPlanes, + ZPixmap ); + if( !pXImage ) + { + SAL_WARN( "vcl", "X11SalGraphics::GetPixel !XGetImage()" ); + return 0; + } + + XColor aXColor; + + aXColor.pixel = XGetPixel( pXImage, 0, 0 ); + XDestroyImage( pXImage ); + + return mrParent.GetColormap().GetColor( aXColor.pixel ); +} + +std::shared_ptr<SalBitmap> X11SalGraphicsImpl::getBitmap( tools::Long nX, tools::Long nY, tools::Long nDX, tools::Long nDY ) +{ + bool bFakeWindowBG = false; + + // normalize + if( nDX < 0 ) + { + nX += nDX; + nDX = -nDX; + } + if ( nDY < 0 ) + { + nY += nDY; + nDY = -nDY; + } + + if( mrParent.bWindow_ && !mrParent.bVirDev_ ) + { + XWindowAttributes aAttrib; + + XGetWindowAttributes( mrParent.GetXDisplay(), mrParent.GetDrawable(), &aAttrib ); + if( aAttrib.map_state != IsViewable ) + bFakeWindowBG = true; + else + { + tools::Long nOrgDX = nDX, nOrgDY = nDY; + + // clip to window size + if ( nX < 0 ) + { + nDX += nX; + nX = 0; + } + if ( nY < 0 ) + { + nDY += nY; + nY = 0; + } + if( nX + nDX > aAttrib.width ) + nDX = aAttrib.width - nX; + if( nY + nDY > aAttrib.height ) + nDY = aAttrib.height - nY; + + // inside ? + if( nDX <= 0 || nDY <= 0 ) + { + bFakeWindowBG = true; + nDX = nOrgDX; + nDY = nOrgDY; + } + } + } + + std::shared_ptr<X11SalBitmap> pSalBitmap = std::make_shared<X11SalBitmap>(); + sal_uInt16 nBitCount = GetBitCount(); + vcl::PixelFormat ePixelFormat = vcl::bitDepthToPixelFormat(nBitCount); + + if( &mrParent.GetDisplay()->GetColormap( mrParent.m_nXScreen ) != &mrParent.GetColormap() ) + { + ePixelFormat = vcl::PixelFormat::N1_BPP; + nBitCount = 1; + } + + if (nBitCount > 8) + ePixelFormat = vcl::PixelFormat::N24_BPP; + + if( ! bFakeWindowBG ) + pSalBitmap->ImplCreateFromDrawable( mrParent.GetDrawable(), mrParent.m_nXScreen, nBitCount, nX, nY, nDX, nDY ); + else + pSalBitmap->Create( Size( nDX, nDY ), ePixelFormat, BitmapPalette( nBitCount > 8 ? nBitCount : 0 ) ); + + return pSalBitmap; +} + +sal_uInt16 X11SalGraphicsImpl::GetBitCount() const +{ + return mrParent.GetVisual().GetDepth(); +} + +tools::Long X11SalGraphicsImpl::GetGraphicsWidth() const +{ + if( mrParent.m_pFrame ) + return mrParent.m_pFrame->maGeometry.nWidth; + else if( mrParent.m_pVDev ) + return static_cast< X11SalVirtualDevice* >(mrParent.m_pVDev)->GetWidth(); + else + return 0; +} + +bool X11SalGraphicsImpl::drawGradient(const tools::PolyPolygon& /*rPolygon*/, const Gradient& /*rGradient*/) +{ + return false; +} + +bool X11SalGraphicsImpl::implDrawGradient(basegfx::B2DPolyPolygon const & /*rPolyPolygon*/, SalGradient const & /*rGradient*/) +{ + return false; +} + +bool X11SalGraphicsImpl::supportsOperation(OutDevSupportType eType) const +{ + bool bRet = false; + switch (eType) + { + case OutDevSupportType::TransparentRect: + case OutDevSupportType::B2DDraw: + { + XRenderPeer& rPeer = XRenderPeer::GetInstance(); + const SalDisplay* pSalDisp = mrParent.GetDisplay(); + const SalVisual& rSalVis = pSalDisp->GetVisual(mrParent.GetScreenNumber()); + + Visual* pDstXVisual = rSalVis.GetVisual(); + XRenderPictFormat* pDstVisFmt = rPeer.FindVisualFormat(pDstXVisual); + if (pDstVisFmt) + bRet = true; + } + break; + default: + break; + } + return bRet; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/generic/gdi/gdiimpl.hxx b/vcl/unx/generic/gdi/gdiimpl.hxx new file mode 100644 index 000000000..48211b13d --- /dev/null +++ b/vcl/unx/generic/gdi/gdiimpl.hxx @@ -0,0 +1,297 @@ +/* -*- 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 . + */ + +#pragma once + +#include <X11/Xlib.h> + +#include <unx/x11/x11gdiimpl.h> + +#include <salgdiimpl.hxx> + +#include <basegfx/polygon/b2dtrapezoid.hxx> +#include <basegfx/polygon/b2dpolygontriangulator.hxx> +#include <ControlCacheKey.hxx> + +/* From <X11/Intrinsic.h> */ +typedef unsigned long Pixel; + +class SalGraphics; +class SalBitmap; +class SalPolyLine; +class X11SalGraphics; +class Gradient; + +class X11SalGraphicsImpl : public SalGraphicsImpl, public X11GraphicsImpl +{ +private: + X11SalGraphics& mrParent; + + Color mnBrushColor; + GC mpBrushGC; // Brush attributes + Pixel mnBrushPixel; + + bool mbPenGC : 1; // is Pen GC valid + bool mbBrushGC : 1; // is Brush GC valid + bool mbCopyGC : 1; // is Copy GC valid + bool mbInvertGC : 1; // is Invert GC valid + bool mbInvert50GC : 1; // is Invert50 GC valid + bool mbStippleGC : 1; // is Stipple GC valid + bool mbTrackingGC : 1; // is Tracking GC valid + bool mbDitherBrush : 1; // is solid or tile + + bool mbXORMode : 1; // is ROP XOR Mode set + + GC mpPenGC; // Pen attributes + Color mnPenColor; + Pixel mnPenPixel; + + + GC mpMonoGC; + GC mpCopyGC; + GC mpMaskGC; + GC mpInvertGC; + GC mpInvert50GC; + GC mpStippleGC; + GC mpTrackingGC; + + GC CreateGC( Drawable hDrawable, + unsigned long nMask = GCGraphicsExposures ); + + GC SelectBrush(); + GC SelectPen(); + inline GC GetCopyGC(); + inline GC GetStippleGC(); + GC GetTrackingGC(); + GC GetInvertGC(); + GC GetInvert50GC(); + + void DrawLines( sal_uInt32 nPoints, + const SalPolyLine &rPoints, + GC pGC, + bool bClose + ); + + XID GetXRenderPicture(); + bool drawFilledTrapezoids( const basegfx::B2DTrapezoid*, int nTrapCount, double fTransparency ); + bool drawFilledTriangles( + const basegfx::B2DHomMatrix& rObjectToDevice, + const basegfx::triangulator::B2DTriangleVector& rTriangles, + double fTransparency); + + tools::Long GetGraphicsHeight() const; + + void drawMaskedBitmap( const SalTwoRect& rPosAry, + const SalBitmap& rSalBitmap, + const SalBitmap& rTransparentBitmap ); + + void internalDrawPolyLine( sal_uInt32 nPoints, const Point* pPtAry, bool bClose ); + +public: + + explicit X11SalGraphicsImpl(X11SalGraphics& rParent); + + virtual void freeResources() override; + + virtual ~X11SalGraphicsImpl() override; + + virtual OUString getRenderBackendName() const override { return "gen"; } + + virtual bool setClipRegion( const vcl::Region& ) override; + // + // get the depth of the device + virtual sal_uInt16 GetBitCount() const override; + + // get the width of the device + virtual tools::Long GetGraphicsWidth() const override; + + // set the clip region to empty + virtual void ResetClipRegion() override; + + // set the line color to transparent (= don't draw lines) + + virtual void SetLineColor() override; + + // set the line color to a specific color + virtual void SetLineColor( Color nColor ) override; + + // set the fill color to transparent (= don't fill) + virtual void SetFillColor() override; + + // set the fill color to a specific color, shapes will be + // filled accordingly + virtual void SetFillColor( Color nColor ) override; + + // enable/disable XOR drawing + virtual void SetXORMode( bool bSet, bool bInvertOnly ) override; + + // set line color for raster operations + virtual void SetROPLineColor( SalROPColor nROPColor ) override; + + // set fill color for raster operations + virtual void SetROPFillColor( SalROPColor nROPColor ) override; + + // draw --> LineColor and FillColor and RasterOp and ClipRegion + virtual void drawPixel( tools::Long nX, tools::Long nY ) override; + virtual void drawPixel( tools::Long nX, tools::Long nY, Color nColor ) override; + + virtual void drawLine( tools::Long nX1, tools::Long nY1, tools::Long nX2, tools::Long nY2 ) override; + + virtual void drawRect( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight ) override; + + virtual void drawPolyLine( sal_uInt32 nPoints, const Point* pPtAry ) override; + + virtual void drawPolygon( sal_uInt32 nPoints, const Point* pPtAry ) override; + + virtual void drawPolyPolygon( sal_uInt32 nPoly, const sal_uInt32* pPoints, const Point** pPtAry ) override; + + virtual bool drawPolyPolygon( + const basegfx::B2DHomMatrix& rObjectToDevice, + const basegfx::B2DPolyPolygon&, + double fTransparency) override; + + virtual bool drawPolyLine( + const basegfx::B2DHomMatrix& rObjectToDevice, + const basegfx::B2DPolygon&, + double fTransparency, + double fLineWidth, + const std::vector< double >* pStroke, // MM01 + basegfx::B2DLineJoin, + css::drawing::LineCap, + double fMiterMinimumAngle, + bool bPixelSnapHairline) override; + + virtual bool drawPolyLineBezier( + sal_uInt32 nPoints, + const Point* pPtAry, + const PolyFlags* pFlgAry ) override; + + virtual bool drawPolygonBezier( + sal_uInt32 nPoints, + const Point* pPtAry, + const PolyFlags* pFlgAry ) override; + + virtual bool drawPolyPolygonBezier( + sal_uInt32 nPoly, + const sal_uInt32* pPoints, + const Point* const* pPtAry, + const PolyFlags* const* pFlgAry ) override; + + // CopyArea --> No RasterOp, but ClipRegion + virtual void copyArea( + tools::Long nDestX, tools::Long nDestY, + tools::Long nSrcX, tools::Long nSrcY, + tools::Long nSrcWidth, tools::Long nSrcHeight, + bool bWindowInvalidate ) override; + + // CopyBits and DrawBitmap --> RasterOp and ClipRegion + // CopyBits() --> pSrcGraphics == NULL, then CopyBits on same Graphics + virtual void copyBits( const SalTwoRect& rPosAry, SalGraphics* pSrcGraphics ) override; + + virtual void drawBitmap( const SalTwoRect& rPosAry, const SalBitmap& rSalBitmap ) override; + + virtual void drawBitmap( + const SalTwoRect& rPosAry, + const SalBitmap& rSalBitmap, + const SalBitmap& rMaskBitmap ) override; + + virtual void drawMask( + const SalTwoRect& rPosAry, + const SalBitmap& rSalBitmap, + Color nMaskColor ) override; + + virtual std::shared_ptr<SalBitmap> getBitmap( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight ) override; + + virtual Color getPixel( tools::Long nX, tools::Long nY ) override; + + // invert --> ClipRegion (only Windows or VirDevs) + virtual void invert( + tools::Long nX, tools::Long nY, + tools::Long nWidth, tools::Long nHeight, + SalInvert nFlags) override; + + virtual void invert( sal_uInt32 nPoints, const Point* pPtAry, SalInvert nFlags ) override; + + virtual bool drawEPS( + tools::Long nX, tools::Long nY, + tools::Long nWidth, tools::Long nHeight, + void* pPtr, + sal_uInt32 nSize ) override; + + /** Blend bitmap with color channels */ + virtual bool blendBitmap( + const SalTwoRect&, + const SalBitmap& rBitmap ) override; + + /** Render bitmap by blending using the mask and alpha channel */ + virtual bool blendAlphaBitmap( + const SalTwoRect&, + const SalBitmap& rSrcBitmap, + const SalBitmap& rMaskBitmap, + const SalBitmap& rAlphaBitmap ) override; + + /** Render bitmap with alpha channel + + @param rSourceBitmap + Source bitmap to blit + + @param rAlphaBitmap + Alpha channel to use for blitting + + @return true, if the operation succeeded, and false + otherwise. In this case, clients should try to emulate alpha + compositing themselves + */ + virtual bool drawAlphaBitmap( + const SalTwoRect&, + const SalBitmap& rSourceBitmap, + const SalBitmap& rAlphaBitmap ) override; + + /** draw transformed bitmap (maybe with alpha) where Null, X, Y define the coordinate system */ + virtual bool drawTransformedBitmap( + const basegfx::B2DPoint& rNull, + const basegfx::B2DPoint& rX, + const basegfx::B2DPoint& rY, + const SalBitmap& rSourceBitmap, + const SalBitmap* pAlphaBitmap, + double fAlpha) override; + + virtual bool hasFastDrawTransformedBitmap() const override; + + /** Render solid rectangle with given transparency + + @param nTransparency + Transparency value (0-255) to use. 0 blits and opaque, 255 a + fully transparent rectangle + */ + virtual bool drawAlphaRect( + tools::Long nX, tools::Long nY, + tools::Long nWidth, tools::Long nHeight, + sal_uInt8 nTransparency ) override; + + virtual bool drawGradient(const tools::PolyPolygon& rPolygon, const Gradient& rGradient) override; + virtual bool implDrawGradient(basegfx::B2DPolyPolygon const & rPolyPolygon, SalGradient const & rGradient) override; + + virtual bool supportsOperation(OutDevSupportType eType) const override; + +public: + void Init() override; +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/generic/gdi/salbmp.cxx b/vcl/unx/generic/gdi/salbmp.cxx new file mode 100644 index 000000000..804b50184 --- /dev/null +++ b/vcl/unx/generic/gdi/salbmp.cxx @@ -0,0 +1,1001 @@ +/* -*- 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 <string.h> + +#ifdef FREEBSD +#include <sys/types.h> +#endif + +#include <X11/Xlib.h> +#include <X11/Xutil.h> + +#include <osl/endian.h> +#include <sal/log.hxx> + +#include <tools/helpers.hxx> +#include <tools/debug.hxx> +#include <vcl/bitmap.hxx> +#include <com/sun/star/beans/XFastPropertySet.hpp> + +#include <unx/saldisp.hxx> +#include <unx/salbmp.h> +#include <unx/salinst.h> +#include <unx/x11/xlimits.hxx> + +#include <o3tl/safeint.hxx> + +#include <config_features.h> + +#if defined HAVE_VALGRIND_HEADERS +#include <valgrind/valgrind.h> +#endif + +#include <memory> + +ImplSalBitmapCache* X11SalBitmap::mpCache = nullptr; +unsigned int X11SalBitmap::mnCacheInstCount = 0; + +X11SalBitmap::X11SalBitmap() + : mbGrey( false ) +{ +} + +X11SalBitmap::~X11SalBitmap() +{ + Destroy(); +} + +void X11SalBitmap::ImplCreateCache() +{ + if( !mnCacheInstCount++ ) + mpCache = new ImplSalBitmapCache; +} + +void X11SalBitmap::ImplDestroyCache() +{ + SAL_WARN_IF( !mnCacheInstCount, "vcl", "X11SalBitmap::ImplDestroyCache(): underflow" ); + + if( mnCacheInstCount && !--mnCacheInstCount ) + { + delete mpCache; + mpCache = nullptr; + } +} + +void X11SalBitmap::ImplRemovedFromCache() +{ + mpDDB.reset(); +} + +#if defined HAVE_VALGRIND_HEADERS +namespace +{ + void blankExtraSpace(BitmapBuffer* pDIB) + { + size_t nExtraSpaceInScanLine = pDIB->mnScanlineSize - pDIB->mnWidth * pDIB->mnBitCount / 8; + if (nExtraSpaceInScanLine) + { + for (tools::Long i = 0; i < pDIB->mnHeight; ++i) + { + sal_uInt8 *pRow = pDIB->mpBits + (i * pDIB->mnScanlineSize); + memset(pRow + (pDIB->mnScanlineSize - nExtraSpaceInScanLine), 0, nExtraSpaceInScanLine); + } + } + } +} +#endif + +std::unique_ptr<BitmapBuffer> X11SalBitmap::ImplCreateDIB( + const Size& rSize, + vcl::PixelFormat ePixelFormat, + const BitmapPalette& rPal) +{ + std::unique_ptr<BitmapBuffer> pDIB; + + if( !rSize.Width() || !rSize.Height() ) + return nullptr; + + try + { + pDIB.reset(new BitmapBuffer); + } + catch (const std::bad_alloc&) + { + return nullptr; + } + + pDIB->mnFormat = ScanlineFormat::NONE; + + switch(ePixelFormat) + { + case vcl::PixelFormat::N1_BPP: + pDIB->mnFormat |= ScanlineFormat::N1BitMsbPal; + break; + case vcl::PixelFormat::N8_BPP: + pDIB->mnFormat |= ScanlineFormat::N8BitPal; + break; + case vcl::PixelFormat::N24_BPP: + pDIB->mnFormat |= ScanlineFormat::N24BitTcBgr; + break; + case vcl::PixelFormat::N32_BPP: + default: + SAL_WARN("vcl.gdi", "32-bit images not supported, converting to 24-bit"); + ePixelFormat = vcl::PixelFormat::N24_BPP; + pDIB->mnFormat |= ScanlineFormat::N24BitTcBgr; + break; + } + + sal_uInt16 nColors = 0; + if (ePixelFormat <= vcl::PixelFormat::N8_BPP) + nColors = vcl::numberOfColors(ePixelFormat); + + pDIB->mnWidth = rSize.Width(); + pDIB->mnHeight = rSize.Height(); + tools::Long nScanlineBase; + bool bFail = o3tl::checked_multiply<tools::Long>(pDIB->mnWidth, vcl::pixelFormatBitCount(ePixelFormat), nScanlineBase); + if (bFail) + { + SAL_WARN("vcl.gdi", "checked multiply failed"); + return nullptr; + } + pDIB->mnScanlineSize = AlignedWidth4Bytes(nScanlineBase); + if (pDIB->mnScanlineSize < nScanlineBase/8) + { + SAL_WARN("vcl.gdi", "scanline calculation wraparound"); + return nullptr; + } + pDIB->mnBitCount = vcl::pixelFormatBitCount(ePixelFormat); + + if( nColors ) + { + pDIB->maPalette = rPal; + pDIB->maPalette.SetEntryCount( nColors ); + } + + try + { + pDIB->mpBits = new sal_uInt8[ pDIB->mnScanlineSize * pDIB->mnHeight ]; +#if defined HAVE_VALGRIND_HEADERS + if (RUNNING_ON_VALGRIND) + blankExtraSpace(pDIB.get()); +#endif + } + catch (const std::bad_alloc&) + { + return nullptr; + } + + return pDIB; +} + +std::unique_ptr<BitmapBuffer> X11SalBitmap::ImplCreateDIB( + Drawable aDrawable, + SalX11Screen nScreen, + tools::Long nDrawableDepth, + tools::Long nX, + tools::Long nY, + tools::Long nWidth, + tools::Long nHeight, + bool bGrey +) { + std::unique_ptr<BitmapBuffer> pDIB; + + if( aDrawable && nWidth && nHeight && nDrawableDepth ) + { + SalDisplay* pSalDisp = vcl_sal::getSalDisplay(GetGenericUnixSalData()); + Display* pXDisp = pSalDisp->GetDisplay(); + + // do not die on XError here + // alternatively one could check the coordinates for being offscreen + // but this call can actually work on servers with backing store + // defaults even if the rectangle is offscreen + // so better catch the XError + GetGenericUnixSalData()->ErrorTrapPush(); + XImage* pImage = XGetImage( pXDisp, aDrawable, nX, nY, nWidth, nHeight, AllPlanes, ZPixmap ); + bool bWasError = GetGenericUnixSalData()->ErrorTrapPop( false ); + + if( ! bWasError && pImage && pImage->data ) + { + const SalTwoRect aTwoRect = { 0, 0, nWidth, nHeight, 0, 0, nWidth, nHeight }; + BitmapBuffer aSrcBuf; + std::optional<BitmapPalette> pDstPal; + + aSrcBuf.mnFormat = ScanlineFormat::TopDown; + aSrcBuf.mnWidth = nWidth; + aSrcBuf.mnHeight = nHeight; + aSrcBuf.mnBitCount = pImage->bits_per_pixel; + aSrcBuf.mnScanlineSize = pImage->bytes_per_line; + aSrcBuf.mpBits = reinterpret_cast<sal_uInt8*>(pImage->data); + + pImage->red_mask = pSalDisp->GetVisual( nScreen ).red_mask; + pImage->green_mask = pSalDisp->GetVisual( nScreen ).green_mask; + pImage->blue_mask = pSalDisp->GetVisual( nScreen ).blue_mask; + + switch( aSrcBuf.mnBitCount ) + { + case 1: + { + aSrcBuf.mnFormat |= ( LSBFirst == pImage->bitmap_bit_order + ? ScanlineFormat::N1BitLsbPal + : ScanlineFormat::N1BitMsbPal + ); + } + break; + + case 8: + { + aSrcBuf.mnFormat |= ScanlineFormat::N8BitPal; + } + break; + + case 24: + { + if( ( LSBFirst == pImage->byte_order ) && ( pImage->red_mask == 0xFF ) ) + aSrcBuf.mnFormat |= ScanlineFormat::N24BitTcRgb; + else + aSrcBuf.mnFormat |= ScanlineFormat::N24BitTcBgr; + } + break; + + case 32: + { + if( LSBFirst == pImage->byte_order ) + aSrcBuf.mnFormat |= ( pSalDisp->GetVisual(nScreen).red_mask == 0xFF + ? ScanlineFormat::N32BitTcRgba + : ScanlineFormat::N32BitTcBgra + ); + else + aSrcBuf.mnFormat |= ( pSalDisp->GetVisual(nScreen).red_mask == 0xFF + ? ScanlineFormat::N32BitTcAbgr + : ScanlineFormat::N32BitTcArgb + ); + } + break; + + default: assert(false); + } + + BitmapPalette& rPal = aSrcBuf.maPalette; + + if( aSrcBuf.mnBitCount == 1 ) + { + rPal.SetEntryCount( 2 ); + rPal[ 0 ] = COL_BLACK; + rPal[ 1 ] = COL_WHITE; + pDstPal = rPal; + } + else if( pImage->depth == 8 && bGrey ) + { + rPal.SetEntryCount( 256 ); + + for( sal_uInt16 i = 0; i < 256; i++ ) + { + BitmapColor& rBmpCol = rPal[ i ]; + + rBmpCol.SetRed( i ); + rBmpCol.SetGreen( i ); + rBmpCol.SetBlue( i ); + } + + pDstPal = rPal; + } + else if( aSrcBuf.mnBitCount <= 8 ) + { + const SalColormap& rColMap = pSalDisp->GetColormap( nScreen ); + const sal_uInt16 nCols = std::min(static_cast<sal_uLong>(rColMap.GetUsed()), + sal_uLong(1) << nDrawableDepth); + + rPal.SetEntryCount( nCols ); + + for( sal_uInt16 i = 0; i < nCols; i++ ) + { + const Color nColor( rColMap.GetColor( i ) ); + BitmapColor& rBmpCol = rPal[ i ]; + + rBmpCol.SetRed( nColor.GetRed() ); + rBmpCol.SetGreen( nColor.GetGreen() ); + rBmpCol.SetBlue( nColor.GetBlue() ); + } + pDstPal = rPal; + } + + pDIB = StretchAndConvert( aSrcBuf, aTwoRect, aSrcBuf.mnFormat, + pDstPal, &aSrcBuf.maColorMask ); + XDestroyImage( pImage ); + } + } + + return pDIB; +} + +XImage* X11SalBitmap::ImplCreateXImage( + SalDisplay const *pSalDisp, + SalX11Screen nScreen, + tools::Long nDepth, + const SalTwoRect& rTwoRect +) const +{ + XImage* pImage = nullptr; + + if( !mpDIB && mpDDB ) + { + const_cast<X11SalBitmap*>(this)->mpDIB = + ImplCreateDIB( mpDDB->ImplGetPixmap(), + mpDDB->ImplGetScreen(), + mpDDB->ImplGetDepth(), + 0, 0, + mpDDB->ImplGetWidth(), + mpDDB->ImplGetHeight(), + mbGrey ); + } + + if( mpDIB && mpDIB->mnWidth && mpDIB->mnHeight ) + { + Display* pXDisp = pSalDisp->GetDisplay(); + tools::Long nWidth = rTwoRect.mnDestWidth; + tools::Long nHeight = rTwoRect.mnDestHeight; + + if( 1 == GetBitCount() ) + nDepth = 1; + + pImage = XCreateImage( pXDisp, pSalDisp->GetVisual( nScreen ).GetVisual(), + nDepth, ( 1 == nDepth ) ? XYBitmap :ZPixmap, 0, nullptr, + nWidth, nHeight, 32, 0 ); + + if( pImage ) + { + std::unique_ptr<BitmapBuffer> pDstBuf; + ScanlineFormat nDstFormat = ScanlineFormat::TopDown; + std::optional<BitmapPalette> xPal; + std::unique_ptr<ColorMask> xMask; + + switch( pImage->bits_per_pixel ) + { + case 1: + nDstFormat |= ( LSBFirst == pImage->bitmap_bit_order + ? ScanlineFormat::N1BitLsbPal + : ScanlineFormat::N1BitMsbPal + ); + break; + + case 8: + nDstFormat |= ScanlineFormat::N8BitPal; + break; + + case 24: + { + if( ( LSBFirst == pImage->byte_order ) && ( pImage->red_mask == 0xFF ) ) + nDstFormat |= ScanlineFormat::N24BitTcRgb; + else + nDstFormat |= ScanlineFormat::N24BitTcBgr; + } + break; + + case 32: + { + if( LSBFirst == pImage->byte_order ) + nDstFormat |= ( pImage->red_mask == 0xFF + ? ScanlineFormat::N32BitTcRgba + : ScanlineFormat::N32BitTcBgra + ); + else + nDstFormat |= ( pImage->red_mask == 0xFF + ? ScanlineFormat::N32BitTcAbgr + : ScanlineFormat::N32BitTcArgb + ); + } + break; + + default: assert(false); + } + + if( pImage->depth == 1 ) + { + xPal.emplace(2); + (*xPal)[ 0 ] = COL_BLACK; + (*xPal)[ 1 ] = COL_WHITE; + } + else if( pImage->depth == 8 && mbGrey ) + { + xPal.emplace(256); + + for( sal_uInt16 i = 0; i < 256; i++ ) + { + BitmapColor& rBmpCol = (*xPal)[ i ]; + + rBmpCol.SetRed( i ); + rBmpCol.SetGreen( i ); + rBmpCol.SetBlue( i ); + } + + } + else if( pImage->depth <= 8 ) + { + const SalColormap& rColMap = pSalDisp->GetColormap( nScreen ); + const sal_uInt16 nCols = std::min( static_cast<sal_uLong>(rColMap.GetUsed()) + , static_cast<sal_uLong>(1 << pImage->depth) + ); + + xPal.emplace(nCols); + + for( sal_uInt16 i = 0; i < nCols; i++ ) + { + const Color nColor( rColMap.GetColor( i ) ); + BitmapColor& rBmpCol = (*xPal)[ i ]; + + rBmpCol.SetRed( nColor.GetRed() ); + rBmpCol.SetGreen( nColor.GetGreen() ); + rBmpCol.SetBlue( nColor.GetBlue() ); + } + } + + pDstBuf = StretchAndConvert( *mpDIB, rTwoRect, nDstFormat, xPal, xMask.get() ); + xPal.reset(); + xMask.reset(); + + if( pDstBuf && pDstBuf->mpBits ) + { +#if defined HAVE_VALGRIND_HEADERS + if (RUNNING_ON_VALGRIND) + blankExtraSpace(pDstBuf.get()); +#endif + // set data in buffer as data member in pImage + pImage->data = reinterpret_cast<char*>(pDstBuf->mpBits); + } + else + { + XDestroyImage( pImage ); + pImage = nullptr; + } + + // note that pDstBuf it deleted here, but that doesn't destroy allocated data in buffer + } + } + + return pImage; +} + +bool X11SalBitmap::ImplCreateFromDrawable( + Drawable aDrawable, + SalX11Screen nScreen, + tools::Long nDrawableDepth, + tools::Long nX, + tools::Long nY, + tools::Long nWidth, + tools::Long nHeight +) { + Destroy(); + + if( aDrawable && nWidth && nHeight && nDrawableDepth ) + mpDDB.reset(new ImplSalDDB( aDrawable, nScreen, nDrawableDepth, nX, nY, nWidth, nHeight )); + + return( mpDDB != nullptr ); +} + +ImplSalDDB* X11SalBitmap::ImplGetDDB( + Drawable aDrawable, + SalX11Screen nXScreen, + tools::Long nDrawableDepth, + const SalTwoRect& rTwoRect +) const +{ + if( !mpDDB || !mpDDB->ImplMatches( nXScreen, nDrawableDepth, rTwoRect ) ) + { + if( mpDDB ) + { + // do we already have a DIB? if not, create aDIB from current DDB first + if( !mpDIB ) + { + const_cast<X11SalBitmap*>(this)->mpDIB = ImplCreateDIB( mpDDB->ImplGetPixmap(), + mpDDB->ImplGetScreen(), + mpDDB->ImplGetDepth(), + 0, 0, + mpDDB->ImplGetWidth(), + mpDDB->ImplGetHeight(), + mbGrey ); + } + + mpDDB.reset(); + } + + if( mpCache ) + mpCache->ImplRemove( this ); + + SalTwoRect aTwoRect( rTwoRect ); + if( aTwoRect.mnSrcX < 0 ) + { + aTwoRect.mnSrcWidth += aTwoRect.mnSrcX; + aTwoRect.mnSrcX = 0; + } + if( aTwoRect.mnSrcY < 0 ) + { + aTwoRect.mnSrcHeight += aTwoRect.mnSrcY; + aTwoRect.mnSrcY = 0; + } + + // create new DDB from DIB + const Size aSize( GetSize() ); + if( aTwoRect.mnSrcWidth == aTwoRect.mnDestWidth && + aTwoRect.mnSrcHeight == aTwoRect.mnDestHeight ) + { + aTwoRect.mnSrcX = aTwoRect.mnSrcY = aTwoRect.mnDestX = aTwoRect.mnDestY = 0; + aTwoRect.mnSrcWidth = aTwoRect.mnDestWidth = aSize.Width(); + aTwoRect.mnSrcHeight = aTwoRect.mnDestHeight = aSize.Height(); + } + else if( aTwoRect.mnSrcWidth+aTwoRect.mnSrcX > aSize.Width() || + aTwoRect.mnSrcHeight+aTwoRect.mnSrcY > aSize.Height() ) + { + // #i47823# this should not happen at all, but does nonetheless + // because BitmapEx allows for mask bitmaps of different size + // than image bitmap (broken) + if( aTwoRect.mnSrcX >= aSize.Width() || + aTwoRect.mnSrcY >= aSize.Height() ) + return nullptr; // this would be a really mad case + + if( aTwoRect.mnSrcWidth+aTwoRect.mnSrcX > aSize.Width() ) + { + aTwoRect.mnSrcWidth = aSize.Width()-aTwoRect.mnSrcX; + if( aTwoRect.mnSrcWidth < 1 ) + { + aTwoRect.mnSrcX = 0; + aTwoRect.mnSrcWidth = aSize.Width(); + } + } + if( aTwoRect.mnSrcHeight+aTwoRect.mnSrcY > aSize.Height() ) + { + aTwoRect.mnSrcHeight = aSize.Height() - aTwoRect.mnSrcY; + if( aTwoRect.mnSrcHeight < 1 ) + { + aTwoRect.mnSrcY = 0; + aTwoRect.mnSrcHeight = aSize.Height(); + } + } + } + + XImage* pImage = ImplCreateXImage( vcl_sal::getSalDisplay(GetGenericUnixSalData()), nXScreen, + nDrawableDepth, aTwoRect ); + + if( pImage ) + { + mpDDB.reset(new ImplSalDDB( pImage, aDrawable, nXScreen, aTwoRect )); + delete[] pImage->data; + pImage->data = nullptr; + XDestroyImage( pImage ); + + if( mpCache ) + mpCache->ImplAdd( const_cast<X11SalBitmap*>(this) ); + } + } + + return mpDDB.get(); +} + +void X11SalBitmap::ImplDraw( + Drawable aDrawable, + SalX11Screen nXScreen, + tools::Long nDrawableDepth, + const SalTwoRect& rTwoRect, + const GC& rGC +) const +{ + ImplGetDDB( aDrawable, nXScreen, nDrawableDepth, rTwoRect ); + if( mpDDB ) + mpDDB->ImplDraw( aDrawable, rTwoRect, rGC ); +} + +bool X11SalBitmap::Create( const Size& rSize, vcl::PixelFormat ePixelFormat, const BitmapPalette& rPal ) +{ + Destroy(); + mpDIB = ImplCreateDIB( rSize, ePixelFormat, rPal ); + + return( mpDIB != nullptr ); +} + +bool X11SalBitmap::Create( const SalBitmap& rSSalBmp ) +{ + Destroy(); + + auto pX11Bmp = dynamic_cast<const X11SalBitmap*>( &rSSalBmp ); + if (!pX11Bmp) + return false; + + const X11SalBitmap& rSalBmp = *pX11Bmp; + + if( rSalBmp.mpDIB ) + { + // TODO: reference counting... + mpDIB.reset(new BitmapBuffer( *rSalBmp.mpDIB )); + // TODO: get rid of this when BitmapBuffer gets copy constructor + try + { + mpDIB->mpBits = new sal_uInt8[ mpDIB->mnScanlineSize * mpDIB->mnHeight ]; +#if defined HAVE_VALGRIND_HEADERS + if (RUNNING_ON_VALGRIND) + blankExtraSpace(mpDIB.get()); +#endif + } + catch (const std::bad_alloc&) + { + mpDIB.reset(); + } + + if( mpDIB ) + memcpy( mpDIB->mpBits, rSalBmp.mpDIB->mpBits, mpDIB->mnScanlineSize * mpDIB->mnHeight ); + } + else if( rSalBmp.mpDDB ) + ImplCreateFromDrawable( rSalBmp.mpDDB->ImplGetPixmap(), + rSalBmp.mpDDB->ImplGetScreen(), + rSalBmp.mpDDB->ImplGetDepth(), + 0, 0, rSalBmp.mpDDB->ImplGetWidth(), rSalBmp.mpDDB->ImplGetHeight() ); + + return( ( !rSalBmp.mpDIB && !rSalBmp.mpDDB ) || + ( rSalBmp.mpDIB && ( mpDIB != nullptr ) ) || + ( rSalBmp.mpDDB && ( mpDDB != nullptr ) ) ); +} + +bool X11SalBitmap::Create( const SalBitmap&, SalGraphics* ) +{ + return false; +} + +bool X11SalBitmap::Create(const SalBitmap&, vcl::PixelFormat /*eNewPixelFormat*/) +{ + return false; +} + +bool X11SalBitmap::Create( + const css::uno::Reference< css::rendering::XBitmapCanvas >& rBitmapCanvas, + Size& rSize, + bool bMask +) { + css::uno::Reference< css::beans::XFastPropertySet > xFastPropertySet( rBitmapCanvas, css::uno::UNO_QUERY ); + + if( xFastPropertySet ) { + css::uno::Sequence< css::uno::Any > args; + + if( xFastPropertySet->getFastPropertyValue(bMask ? 2 : 1) >>= args ) { + sal_Int64 pixmapHandle = {}; // spurious -Werror=maybe-uninitialized + sal_Int32 depth; + if( ( args[1] >>= pixmapHandle ) && ( args[2] >>= depth ) ) { + + mbGrey = bMask; + bool bSuccess = ImplCreateFromDrawable( + pixmapHandle, + // FIXME: this seems multi-screen broken to me + SalX11Screen( 0 ), + depth, + 0, + 0, + rSize.Width(), + rSize.Height() + ); + bool bFreePixmap = false; + if( bSuccess && (args[0] >>= bFreePixmap) && bFreePixmap ) + XFreePixmap( vcl_sal::getSalDisplay(GetGenericUnixSalData())->GetDisplay(), pixmapHandle ); + + return bSuccess; + } + } + } + + return false; +} + +void X11SalBitmap::Destroy() +{ + if( mpDIB ) + { + delete[] mpDIB->mpBits; + mpDIB.reset(); + } + + mpDDB.reset(); + + if( mpCache ) + mpCache->ImplRemove( this ); +} + +Size X11SalBitmap::GetSize() const +{ + Size aSize; + + if( mpDIB ) + { + aSize.setWidth( mpDIB->mnWidth ); + aSize.setHeight( mpDIB->mnHeight ); + } + else if( mpDDB ) + { + aSize.setWidth( mpDDB->ImplGetWidth() ); + aSize.setHeight( mpDDB->ImplGetHeight() ); + } + + return aSize; +} + +sal_uInt16 X11SalBitmap::GetBitCount() const +{ + sal_uInt16 nBitCount; + + if( mpDIB ) + nBitCount = mpDIB->mnBitCount; + else if( mpDDB ) + nBitCount = mpDDB->ImplGetDepth(); + else + nBitCount = 0; + + return nBitCount; +} + +BitmapBuffer* X11SalBitmap::AcquireBuffer( BitmapAccessMode /*nMode*/ ) +{ + if( !mpDIB && mpDDB ) + { + mpDIB = ImplCreateDIB( + mpDDB->ImplGetPixmap(), + mpDDB->ImplGetScreen(), + mpDDB->ImplGetDepth(), + 0, 0, + mpDDB->ImplGetWidth(), + mpDDB->ImplGetHeight(), + mbGrey + ); + } + + return mpDIB.get(); +} + +void X11SalBitmap::ReleaseBuffer( BitmapBuffer*, BitmapAccessMode nMode ) +{ + if( nMode == BitmapAccessMode::Write ) + { + mpDDB.reset(); + + if( mpCache ) + mpCache->ImplRemove( this ); + InvalidateChecksum(); + } +} + +bool X11SalBitmap::GetSystemData( BitmapSystemData& rData ) +{ + if( mpDDB ) + { + // Rename/retype pDummy to your likings (though X11 Pixmap is + // prolly not a good idea, since it's accessed from + // non-platform aware code in vcl/bitmap.hxx) + rData.aPixmap = reinterpret_cast<void*>(mpDDB->ImplGetPixmap()); + rData.mnWidth = mpDDB->ImplGetWidth (); + rData.mnHeight = mpDDB->ImplGetHeight (); + return true; + } + + return false; +} + +bool X11SalBitmap::ScalingSupported() const +{ + return false; +} + +bool X11SalBitmap::Scale( const double& /*rScaleX*/, const double& /*rScaleY*/, BmpScaleFlag /*nScaleFlag*/ ) +{ + return false; +} + +bool X11SalBitmap::Replace( const Color& /*rSearchColor*/, const Color& /*rReplaceColor*/, sal_uInt8 /*nTol*/ ) +{ + return false; +} + + +ImplSalDDB::ImplSalDDB( XImage* pImage, Drawable aDrawable, + SalX11Screen nXScreen, const SalTwoRect& rTwoRect ) + : maPixmap ( 0 ) + , maTwoRect ( rTwoRect ) + , mnDepth ( pImage->depth ) + , mnXScreen ( nXScreen ) +{ + SalDisplay* pSalDisp = vcl_sal::getSalDisplay(GetGenericUnixSalData()); + Display* pXDisp = pSalDisp->GetDisplay(); + + maPixmap = limitXCreatePixmap( pXDisp, aDrawable, ImplGetWidth(), ImplGetHeight(), ImplGetDepth() ); + if (!maPixmap) + return; + + XGCValues aValues; + GC aGC; + int nValues = GCFunction; + + aValues.function = GXcopy; + + if( 1 == mnDepth ) + { + nValues |= ( GCForeground | GCBackground ); + aValues.foreground = 1; + aValues.background = 0; + } + + aGC = XCreateGC( pXDisp, maPixmap, nValues, &aValues ); + XPutImage( pXDisp, maPixmap, aGC, pImage, 0, 0, 0, 0, maTwoRect.mnDestWidth, maTwoRect.mnDestHeight ); + XFreeGC( pXDisp, aGC ); +} + +ImplSalDDB::ImplSalDDB( + Drawable aDrawable, + SalX11Screen nXScreen, + tools::Long nDrawableDepth, + tools::Long nX, + tools::Long nY, + tools::Long nWidth, + tools::Long nHeight +) : maTwoRect(0, 0, nWidth, nHeight, 0, 0, nWidth, nHeight) + , mnDepth( nDrawableDepth ) + , mnXScreen( nXScreen ) +{ + SalDisplay* pSalDisp = vcl_sal::getSalDisplay(GetGenericUnixSalData()); + Display* pXDisp = pSalDisp->GetDisplay(); + + if( (maPixmap = limitXCreatePixmap( pXDisp, aDrawable, nWidth, nHeight, nDrawableDepth )) ) + { + XGCValues aValues; + GC aGC; + int nValues = GCFunction; + + aValues.function = GXcopy; + + if( 1 == mnDepth ) + { + nValues |= ( GCForeground | GCBackground ); + aValues.foreground = 1; + aValues.background = 0; + } + + aGC = XCreateGC( pXDisp, maPixmap, nValues, &aValues ); + ImplDraw( aDrawable, nDrawableDepth, maPixmap, + nX, nY, nWidth, nHeight, 0, 0, aGC ); + XFreeGC( pXDisp, aGC ); + } + else + { + maTwoRect.mnSrcWidth = maTwoRect.mnDestWidth = 0; + maTwoRect.mnSrcHeight = maTwoRect.mnDestHeight = 0; + } +} + +ImplSalDDB::~ImplSalDDB() +{ + if( maPixmap && ImplGetSVData() ) + XFreePixmap( vcl_sal::getSalDisplay(GetGenericUnixSalData())->GetDisplay(), maPixmap ); +} + +bool ImplSalDDB::ImplMatches( SalX11Screen nXScreen, tools::Long nDepth, const SalTwoRect& rTwoRect ) const +{ + bool bRet = false; + + if( ( maPixmap != 0 ) && ( ( mnDepth == nDepth ) || ( 1 == mnDepth ) ) && nXScreen == mnXScreen) + { + if ( rTwoRect.mnSrcX == maTwoRect.mnSrcX + && rTwoRect.mnSrcY == maTwoRect.mnSrcY + && rTwoRect.mnSrcWidth == maTwoRect.mnSrcWidth + && rTwoRect.mnSrcHeight == maTwoRect.mnSrcHeight + && rTwoRect.mnDestWidth == maTwoRect.mnDestWidth + && rTwoRect.mnDestHeight == maTwoRect.mnDestHeight + ) + { + // absolutely identically + bRet = true; + } + else if( rTwoRect.mnSrcWidth == rTwoRect.mnDestWidth + && rTwoRect.mnSrcHeight == rTwoRect.mnDestHeight + && maTwoRect.mnSrcWidth == maTwoRect.mnDestWidth + && maTwoRect.mnSrcHeight == maTwoRect.mnDestHeight + && rTwoRect.mnSrcX >= maTwoRect.mnSrcX + && rTwoRect.mnSrcY >= maTwoRect.mnSrcY + && ( rTwoRect.mnSrcX + rTwoRect.mnSrcWidth ) <= ( maTwoRect.mnSrcX + maTwoRect.mnSrcWidth ) + && ( rTwoRect.mnSrcY + rTwoRect.mnSrcHeight ) <= ( maTwoRect.mnSrcY + maTwoRect.mnSrcHeight ) + ) + { + bRet = true; + } + } + + return bRet; +} + +void ImplSalDDB::ImplDraw( + Drawable aDrawable, + const SalTwoRect& rTwoRect, + const GC& rGC +) const +{ + ImplDraw( maPixmap, mnDepth, aDrawable, + rTwoRect.mnSrcX - maTwoRect.mnSrcX, rTwoRect.mnSrcY - maTwoRect.mnSrcY, + rTwoRect.mnDestWidth, rTwoRect.mnDestHeight, + rTwoRect.mnDestX, rTwoRect.mnDestY, rGC ); +} + +void ImplSalDDB::ImplDraw( + Drawable aSrcDrawable, + tools::Long nSrcDrawableDepth, + Drawable aDstDrawable, + tools::Long nSrcX, + tools::Long nSrcY, + tools::Long nDestWidth, + tools::Long nDestHeight, + tools::Long nDestX, + tools::Long nDestY, + const GC& rGC +) { + SalDisplay* pSalDisp = vcl_sal::getSalDisplay(GetGenericUnixSalData()); + Display* pXDisp = pSalDisp->GetDisplay(); + + if( 1 == nSrcDrawableDepth ) + { + XCopyPlane( pXDisp, aSrcDrawable, aDstDrawable, rGC, + nSrcX, nSrcY, nDestWidth, nDestHeight, nDestX, nDestY, 1 ); + } + else + { + XCopyArea( pXDisp, aSrcDrawable, aDstDrawable, rGC, + nSrcX, nSrcY, nDestWidth, nDestHeight, nDestX, nDestY ); + } +} + + +ImplSalBitmapCache::ImplSalBitmapCache() +{ +} + +ImplSalBitmapCache::~ImplSalBitmapCache() +{ + ImplClear(); +} + +void ImplSalBitmapCache::ImplAdd( X11SalBitmap* pBmp ) +{ + for(auto pObj : maBmpList) + { + if( pObj == pBmp ) + return; + } + maBmpList.push_back( pBmp ); +} + +void ImplSalBitmapCache::ImplRemove( X11SalBitmap const * pBmp ) +{ + auto it = std::find(maBmpList.begin(), maBmpList.end(), pBmp); + if( it != maBmpList.end() ) + { + (*it)->ImplRemovedFromCache(); + maBmpList.erase( it ); + } +} + +void ImplSalBitmapCache::ImplClear() +{ + for(auto pObj : maBmpList) + { + pObj->ImplRemovedFromCache(); + } + maBmpList.clear(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/generic/gdi/salgdi.cxx b/vcl/unx/generic/gdi/salgdi.cxx new file mode 100644 index 000000000..d6eecfecb --- /dev/null +++ b/vcl/unx/generic/gdi/salgdi.cxx @@ -0,0 +1,482 @@ +/* -*- 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 <config_features.h> +#include <vcl/skia/SkiaHelper.hxx> +#if HAVE_FEATURE_SKIA +#include <skia/x11/gdiimpl.hxx> +#include <skia/x11/textrender.hxx> +#endif + +#include <X11/Xlib.h> +#include <X11/Xutil.h> +#include <X11/extensions/Xrender.h> + + +#include <basegfx/polygon/b2dpolygon.hxx> +#include <basegfx/polygon/b2dpolypolygon.hxx> +#include <basegfx/polygon/b2dpolypolygontools.hxx> +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/curve/b2dcubicbezier.hxx> + +#include <headless/svpgdi.hxx> + +#include <vcl/sysdata.hxx> +#include <vcl/virdev.hxx> +#include <sal/log.hxx> + +#include <unx/salunx.h> +#include <unx/saldisp.hxx> +#include <unx/salgdi.h> +#include <unx/x11/xlimits.hxx> + +#include <salframe.hxx> +#include <salgdiimpl.hxx> +#include <textrender.hxx> +#include <salvd.hxx> +#include "gdiimpl.hxx" + +#include <unx/x11/x11cairotextrender.hxx> +#include <unx/x11/xrender_peer.hxx> +#include "cairo_xlib_cairo.hxx" +#include <cairo-xlib.h> + +#if ENABLE_CAIRO_CANVAS +#include "X11CairoSalGraphicsImpl.hxx" +#endif + + +// X11Common + +X11Common::X11Common() + : m_hDrawable(None) + , m_pColormap(nullptr) + , m_pExternalSurface(nullptr) +{} + +cairo_t* X11Common::getCairoContext() +{ + if (m_pExternalSurface) + return cairo_create(m_pExternalSurface); + + cairo_surface_t* surface = cairo_xlib_surface_create(GetXDisplay(), m_hDrawable, GetVisual().visual, SAL_MAX_INT16, SAL_MAX_INT16); + + cairo_t *cr = cairo_create(surface); + cairo_surface_destroy(surface); + + return cr; +} + +void X11Common::releaseCairoContext(cairo_t* cr) +{ + cairo_destroy(cr); +} + +bool X11Common::SupportsCairo() const +{ + static bool bSupportsCairo = [this] { + Display *pDisplay = GetXDisplay(); + int nDummy; + return XQueryExtension(pDisplay, "RENDER", &nDummy, &nDummy, &nDummy); + }(); + return bSupportsCairo; +} + +// X11SalGraphics + +X11SalGraphics::X11SalGraphics(): + m_pFrame(nullptr), + m_pVDev(nullptr), + m_nXScreen( 0 ), + m_pXRenderFormat(nullptr), + m_aXRenderPicture(0), + mpClipRegion(nullptr), + hBrush_(None), + bWindow_(false), + bVirDev_(false) +{ +#if HAVE_FEATURE_SKIA + if (SkiaHelper::isVCLSkiaEnabled()) + { + mxImpl.reset(new X11SkiaSalGraphicsImpl(*this)); + mxTextRenderImpl.reset(new SkiaTextRender); + } + else +#endif + { + mxTextRenderImpl.reset(new X11CairoTextRender(*this)); +#if ENABLE_CAIRO_CANVAS + mxImpl.reset(new X11CairoSalGraphicsImpl(*this, maX11Common)); +#else + mxImpl.reset(new X11SalGraphicsImpl(*this)); +#endif + } +} + +X11SalGraphics::~X11SalGraphics() COVERITY_NOEXCEPT_FALSE +{ + DeInit(); + ReleaseFonts(); + freeResources(); +} + +void X11SalGraphics::freeResources() +{ + Display *pDisplay = GetXDisplay(); + + if( mpClipRegion ) + { + XDestroyRegion( mpClipRegion ); + mpClipRegion = nullptr; + } + + mxImpl->freeResources(); + + if( hBrush_ ) + { + XFreePixmap( pDisplay, hBrush_ ); + hBrush_ = None; + } + if( m_pDeleteColormap ) + { + m_pDeleteColormap.reset(); + maX11Common.m_pColormap = nullptr; + } + if( m_aXRenderPicture ) + { + XRenderPeer::GetInstance().FreePicture( m_aXRenderPicture ); + m_aXRenderPicture = 0; + } +} + +SalGraphicsImpl* X11SalGraphics::GetImpl() const +{ + return mxImpl.get(); +} + +void X11SalGraphics::SetDrawable(Drawable aDrawable, cairo_surface_t* pExternalSurface, SalX11Screen nXScreen) +{ + maX11Common.m_pExternalSurface = pExternalSurface; + + // shortcut if nothing changed + if( maX11Common.m_hDrawable == aDrawable ) + return; + + // free screen specific resources if needed + if( nXScreen != m_nXScreen ) + { + freeResources(); + maX11Common.m_pColormap = &vcl_sal::getSalDisplay(GetGenericUnixSalData())->GetColormap( nXScreen ); + m_nXScreen = nXScreen; + } + + maX11Common.m_hDrawable = aDrawable; + SetXRenderFormat( nullptr ); + if( m_aXRenderPicture ) + { + XRenderPeer::GetInstance().FreePicture( m_aXRenderPicture ); + m_aXRenderPicture = 0; + } +} + +void X11SalGraphics::Init( SalFrame *pFrame, Drawable aTarget, + SalX11Screen nXScreen ) +{ + maX11Common.m_pColormap = &vcl_sal::getSalDisplay(GetGenericUnixSalData())->GetColormap(nXScreen); + m_nXScreen = nXScreen; + + m_pFrame = pFrame; + m_pVDev = nullptr; + + bWindow_ = true; + bVirDev_ = false; + + SetDrawable(aTarget, nullptr, nXScreen); + mxImpl->Init(); +} + +void X11SalGraphics::DeInit() +{ + mxImpl->DeInit(); + SetDrawable(None, nullptr, m_nXScreen); +} + +void X11SalGraphics::SetClipRegion( GC pGC, Region pXReg ) const +{ + Display *pDisplay = GetXDisplay(); + + int n = 0; + Region Regions[3]; + + if( mpClipRegion ) + Regions[n++] = mpClipRegion; + + if( pXReg && !XEmptyRegion( pXReg ) ) + Regions[n++] = pXReg; + + if( 0 == n ) + XSetClipMask( pDisplay, pGC, None ); + else if( 1 == n ) + XSetRegion( pDisplay, pGC, Regions[0] ); + else + { + Region pTmpRegion = XCreateRegion(); + XIntersectRegion( Regions[0], Regions[1], pTmpRegion ); + + XSetRegion( pDisplay, pGC, pTmpRegion ); + XDestroyRegion( pTmpRegion ); + } +} + +// Calculate a dither-pixmap and make a brush of it +#define P_DELTA 51 +#define DMAP( v, m ) ((v % P_DELTA) > m ? (v / P_DELTA) + 1 : (v / P_DELTA)) + +bool X11SalGraphics::GetDitherPixmap( Color nColor ) +{ + static const short nOrdDither8Bit[ 8 ][ 8 ] = + { + { 0, 38, 9, 48, 2, 40, 12, 50}, + {25, 12, 35, 22, 28, 15, 37, 24}, + { 6, 44, 3, 41, 8, 47, 5, 44}, + {32, 19, 28, 16, 34, 21, 31, 18}, + { 1, 40, 11, 49, 0, 39, 10, 48}, + {27, 14, 36, 24, 26, 13, 36, 23}, + { 8, 46, 4, 43, 7, 45, 4, 42}, + {33, 20, 30, 17, 32, 20, 29, 16} + }; + + // test for correct depth (8bit) + if( GetColormap().GetVisual().GetDepth() != 8 ) + return false; + + char pBits[64]; + char *pBitsPtr = pBits; + + // Set the palette-entries for the dithering tile + sal_uInt8 nColorRed = nColor.GetRed(); + sal_uInt8 nColorGreen = nColor.GetGreen(); + sal_uInt8 nColorBlue = nColor.GetBlue(); + + for(auto & nY : nOrdDither8Bit) + { + for( int nX = 0; nX < 8; nX++ ) + { + short nMagic = nY[nX]; + sal_uInt8 nR = P_DELTA * DMAP( nColorRed, nMagic ); + sal_uInt8 nG = P_DELTA * DMAP( nColorGreen, nMagic ); + sal_uInt8 nB = P_DELTA * DMAP( nColorBlue, nMagic ); + + *pBitsPtr++ = GetColormap().GetPixel( Color( nR, nG, nB ) ); + } + } + + // create the tile as ximage and an according pixmap -> caching + XImage *pImage = XCreateImage( GetXDisplay(), + GetColormap().GetXVisual(), + 8, + ZPixmap, + 0, // offset + pBits, // data + 8, 8, // width & height + 8, // bitmap_pad + 0 ); // (default) bytes_per_line + + if( !hBrush_ ) + hBrush_ = limitXCreatePixmap( GetXDisplay(), GetDrawable(), 8, 8, 8 ); + + // put the ximage to the pixmap + XPutImage( GetXDisplay(), + hBrush_, + GetDisplay()->GetCopyGC( m_nXScreen ), + pImage, + 0, 0, // Source + 0, 0, // Destination + 8, 8 ); // width & height + + // destroy image-frame but not palette-data + pImage->data = nullptr; + XDestroyImage( pImage ); + + return true; +} + +void X11SalGraphics::GetResolution( sal_Int32 &rDPIX, sal_Int32 &rDPIY ) // const +{ + char* pForceDpi; + if ((pForceDpi = getenv("SAL_FORCEDPI"))) + { + OString sForceDPI(pForceDpi); + rDPIX = rDPIY = sForceDPI.toInt32(); + return; + } + + const SalDisplay *pDisplay = GetDisplay(); + if (!pDisplay) + { + SAL_WARN( "vcl", "Null display"); + rDPIX = rDPIY = 96; + return; + } + + Pair dpi = pDisplay->GetResolution(); + rDPIX = dpi.A(); + rDPIY = dpi.B(); + + if ( rDPIY > 200 ) + { + rDPIX = Divide( rDPIX * 200, rDPIY ); + rDPIY = 200; + } + + // #i12705# equalize x- and y-resolution if they are close enough + if( rDPIX == rDPIY ) + return; + + // different x- and y- resolutions are usually artifacts of + // a wrongly calculated screen size. +#ifdef DEBUG + SAL_INFO("vcl.gdi", "Forcing Resolution from " + << std::hex << rDPIX + << std::dec << rDPIX + << " to " + << std::hex << rDPIY + << std::dec << rDPIY); +#endif + rDPIX = rDPIY; // y-resolution is more trustworthy +} + +XRenderPictFormat* X11SalGraphics::GetXRenderFormat() const +{ + if( m_pXRenderFormat == nullptr ) + m_pXRenderFormat = XRenderPeer::GetInstance().FindVisualFormat( GetVisual().visual ); + return m_pXRenderFormat; +} + +SystemGraphicsData X11SalGraphics::GetGraphicsData() const +{ + SystemGraphicsData aRes; + + aRes.nSize = sizeof(aRes); + aRes.pDisplay = GetXDisplay(); + aRes.hDrawable = maX11Common.m_hDrawable; + aRes.pVisual = GetVisual().visual; + aRes.nScreen = m_nXScreen.getXScreen(); + aRes.pXRenderFormat = m_pXRenderFormat; + return aRes; +} + +void X11SalGraphics::Flush() +{ + if( X11GraphicsImpl* x11Impl = dynamic_cast< X11GraphicsImpl* >( mxImpl.get())) + x11Impl->Flush(); +} + +#if ENABLE_CAIRO_CANVAS + +bool X11SalGraphics::SupportsCairo() const +{ + return maX11Common.SupportsCairo(); +} + +cairo::SurfaceSharedPtr X11SalGraphics::CreateSurface(const cairo::CairoSurfaceSharedPtr& rSurface) const +{ + return std::make_shared<cairo::X11Surface>(rSurface); +} + +namespace +{ + cairo::X11SysData getSysData( const vcl::Window& rWindow ) + { + const SystemEnvData* pSysData = rWindow.GetSystemData(); + + if( !pSysData ) + return cairo::X11SysData(); + else + return cairo::X11SysData(*pSysData, rWindow.ImplGetFrame()); + } + + cairo::X11SysData getSysData( const VirtualDevice& rVirDev ) + { + return cairo::X11SysData( rVirDev.GetSystemGfxData() ); + } +} + +cairo::SurfaceSharedPtr X11SalGraphics::CreateSurface( const OutputDevice& rRefDevice, + int x, int y, int width, int height ) const +{ + if( rRefDevice.GetOutDevType() == OUTDEV_WINDOW ) + return std::make_shared<cairo::X11Surface>(getSysData(*rRefDevice.GetOwnerWindow()), + x,y,width,height); + if( rRefDevice.IsVirtual() ) + return std::make_shared<cairo::X11Surface>(getSysData(static_cast<const VirtualDevice&>(rRefDevice)), + x,y,width,height); + return cairo::SurfaceSharedPtr(); +} + +cairo::SurfaceSharedPtr X11SalGraphics::CreateBitmapSurface( const OutputDevice& rRefDevice, + const BitmapSystemData& rData, + const Size& rSize ) const +{ + SAL_INFO("vcl", "requested size: " << rSize.Width() << " x " << rSize.Height() + << " available size: " << rData.mnWidth << " x " + << rData.mnHeight); + if ( rData.mnWidth == rSize.Width() && rData.mnHeight == rSize.Height() ) + { + if( rRefDevice.GetOutDevType() == OUTDEV_WINDOW ) + return std::make_shared<cairo::X11Surface>(getSysData(*rRefDevice.GetOwnerWindow()), rData ); + else if( rRefDevice.IsVirtual() ) + return std::make_shared<cairo::X11Surface>(getSysData(static_cast<const VirtualDevice&>(rRefDevice)), rData ); + } + + return cairo::SurfaceSharedPtr(); +} + +css::uno::Any X11SalGraphics::GetNativeSurfaceHandle(cairo::SurfaceSharedPtr& rSurface, const basegfx::B2ISize& /*rSize*/) const +{ + cairo::X11Surface& rXlibSurface=dynamic_cast<cairo::X11Surface&>(*rSurface); + css::uno::Sequence< css::uno::Any > args{ + css::uno::Any(false), // do not call XFreePixmap on it + css::uno::Any(sal_Int64(rXlibSurface.getPixmap()->mhDrawable)), + css::uno::Any(sal_Int32( rXlibSurface.getDepth() )) + }; + return css::uno::Any(args); +} + +#endif // ENABLE_CAIRO_CANVAS + +SalGeometryProvider *X11SalGraphics::GetGeometryProvider() const +{ + if (m_pFrame) + return static_cast< SalGeometryProvider * >(m_pFrame); + else + return static_cast< SalGeometryProvider * >(m_pVDev); +} + +cairo_t* X11SalGraphics::getCairoContext() +{ + return maX11Common.getCairoContext(); +} + +void X11SalGraphics::releaseCairoContext(cairo_t* cr) +{ + X11Common::releaseCairoContext(cr); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/generic/gdi/salgdi2.cxx b/vcl/unx/generic/gdi/salgdi2.cxx new file mode 100644 index 000000000..f26048ae1 --- /dev/null +++ b/vcl/unx/generic/gdi/salgdi2.cxx @@ -0,0 +1,89 @@ +/* -*- 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 <salgdiimpl.hxx> + +#include <vcl/sysdata.hxx> + +#include <unx/saldisp.hxx> +#include <unx/salgdi.h> +#include <unx/x11/xrender_peer.hxx> +#include <salframe.hxx> + +extern "C" +{ + static Bool GraphicsExposePredicate( Display*, XEvent* pEvent, const XPointer pFrameWindow ) + { + Bool bRet = False; + if( (pEvent->type == GraphicsExpose || pEvent->type == NoExpose) && + pEvent->xnoexpose.drawable == reinterpret_cast<Drawable>(pFrameWindow) ) + { + bRet = True; + } + return bRet; + } +} + +void X11SalGraphics::YieldGraphicsExpose() +{ + // get frame if necessary + SalFrame* pFrame = m_pFrame; + Display* pDisplay = GetXDisplay(); + ::Window aWindow = GetDrawable(); + if( ! pFrame ) + { + for (auto pSalFrame : vcl_sal::getSalDisplay(GetGenericUnixSalData())->getFrames() ) + { + const SystemEnvData* pEnvData = pSalFrame->GetSystemData(); + if( Drawable(pEnvData->GetWindowHandle(pSalFrame)) == aWindow ) + { + pFrame = pSalFrame; + break; + } + } + if( ! pFrame ) + return; + } + + XEvent aEvent; + while( XCheckTypedWindowEvent( pDisplay, aWindow, Expose, &aEvent ) ) + { + SalPaintEvent aPEvt( aEvent.xexpose.x, aEvent.xexpose.y, aEvent.xexpose.width+1, aEvent.xexpose.height+1 ); + pFrame->CallCallback( SalEvent::Paint, &aPEvt ); + } + + do + { + if( ! GetDisplay()->XIfEventWithTimeout( &aEvent, reinterpret_cast<XPointer>(aWindow), GraphicsExposePredicate ) ) + // this should not happen at all; still sometimes it happens + break; + + if( aEvent.type == NoExpose ) + break; + + if( pFrame ) + { + SalPaintEvent aPEvt( aEvent.xgraphicsexpose.x, aEvent.xgraphicsexpose.y, aEvent.xgraphicsexpose.width+1, aEvent.xgraphicsexpose.height+1 ); + pFrame->CallCallback( SalEvent::Paint, &aPEvt ); + } + } while( aEvent.xgraphicsexpose.count != 0 ); +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/generic/gdi/salvd.cxx b/vcl/unx/generic/gdi/salvd.cxx new file mode 100644 index 000000000..f5e4449c6 --- /dev/null +++ b/vcl/unx/generic/gdi/salvd.cxx @@ -0,0 +1,222 @@ +/* -*- 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 <vcl/sysdata.hxx> + +#include <X11/Xlib.h> +#include <X11/extensions/Xrender.h> + +#include <unx/saldisp.hxx> +#include <unx/salinst.h> +#include <unx/salgdi.h> +#include <unx/salvd.h> +#include <unx/x11/xlimits.hxx> + +#include <config_features.h> +#include <vcl/skia/SkiaHelper.hxx> +#if HAVE_FEATURE_SKIA +#include <skia/x11/salvd.hxx> +#endif + +std::unique_ptr<SalVirtualDevice> X11SalInstance::CreateX11VirtualDevice(const SalGraphics& rGraphics, + tools::Long &nDX, tools::Long &nDY, DeviceFormat eFormat, const SystemGraphicsData *pData, + std::unique_ptr<X11SalGraphics> pNewGraphics) +{ + assert(pNewGraphics); +#if HAVE_FEATURE_SKIA + if (SkiaHelper::isVCLSkiaEnabled()) + return std::unique_ptr<SalVirtualDevice>(new X11SkiaSalVirtualDevice(rGraphics, nDX, nDY, pData, std::move(pNewGraphics))); + else +#endif + return std::unique_ptr<SalVirtualDevice>(new X11SalVirtualDevice(rGraphics, nDX, nDY, eFormat, pData, std::move(pNewGraphics))); +} + +std::unique_ptr<SalVirtualDevice> X11SalInstance::CreateVirtualDevice(SalGraphics& rGraphics, + tools::Long &nDX, tools::Long &nDY, DeviceFormat eFormat, const SystemGraphicsData *pData) +{ + return CreateX11VirtualDevice(rGraphics, nDX, nDY, eFormat, pData, std::make_unique<X11SalGraphics>()); +} + +void X11SalGraphics::Init( X11SalVirtualDevice *pDevice, cairo_surface_t* pPreExistingTarget, SalColormap* pColormap, + bool bDeleteColormap ) +{ + SalDisplay *pDisplay = pDevice->GetDisplay(); + m_nXScreen = pDevice->GetXScreenNumber(); + + int nVisualDepth = pDisplay->GetColormap( m_nXScreen ).GetVisual().GetDepth(); + int nDeviceDepth = pDevice->GetDepth(); + + if( pColormap ) + { + maX11Common.m_pColormap = pColormap; + if( bDeleteColormap ) + m_pDeleteColormap.reset(pColormap); + } + else if( nDeviceDepth == nVisualDepth ) + maX11Common.m_pColormap = &pDisplay->GetColormap( m_nXScreen ); + else if( nDeviceDepth == 1 ) + { + m_pDeleteColormap.reset(new SalColormap()); + maX11Common.m_pColormap = m_pDeleteColormap.get(); + } + + m_pVDev = pDevice; + m_pFrame = nullptr; + + bWindow_ = pDisplay->IsDisplay(); + bVirDev_ = true; + + SetDrawable(pDevice->GetDrawable(), pPreExistingTarget, m_nXScreen); + mxImpl->Init(); +} + +X11SalVirtualDevice::X11SalVirtualDevice(const SalGraphics& rGraphics, tools::Long &nDX, tools::Long &nDY, + DeviceFormat /*eFormat*/, const SystemGraphicsData *pData, + std::unique_ptr<X11SalGraphics> pNewGraphics) : + pGraphics_(std::move(pNewGraphics)), + m_nXScreen(0), + bGraphics_(false) +{ + SalColormap* pColormap = nullptr; + bool bDeleteColormap = false; + + sal_uInt16 nBitCount = rGraphics.GetBitCount(); + pDisplay_ = vcl_sal::getSalDisplay(GetGenericUnixSalData()); + nDepth_ = nBitCount; + + if( pData && pData->hDrawable != None ) + { + ::Window aRoot; + int x, y; + unsigned int w = 0, h = 0, bw, d; + Display* pDisp = pDisplay_->GetDisplay(); + XGetGeometry( pDisp, pData->hDrawable, + &aRoot, &x, &y, &w, &h, &bw, &d ); + int nScreen = 0; + while( nScreen < ScreenCount( pDisp ) ) + { + if( RootWindow( pDisp, nScreen ) == aRoot ) + break; + nScreen++; + } + nDX_ = static_cast<tools::Long>(w); + nDY_ = static_cast<tools::Long>(h); + nDX = nDX_; + nDY = nDY_; + m_nXScreen = SalX11Screen( nScreen ); + hDrawable_ = pData->hDrawable; + bExternPixmap_ = true; + } + else + { + nDX_ = nDX; + nDY_ = nDY; + m_nXScreen = static_cast<const X11SalGraphics&>(rGraphics).GetScreenNumber(); + hDrawable_ = limitXCreatePixmap( GetXDisplay(), + pDisplay_->GetDrawable( m_nXScreen ), + nDX_, nDY_, + GetDepth() ); + bExternPixmap_ = false; + } + + XRenderPictFormat* pXRenderFormat = pData ? static_cast<XRenderPictFormat*>(pData->pXRenderFormat) : nullptr; + if( pXRenderFormat ) + { + pGraphics_->SetXRenderFormat( pXRenderFormat ); + if( pXRenderFormat->colormap ) + pColormap = new SalColormap( pDisplay_, pXRenderFormat->colormap, m_nXScreen ); + else + pColormap = new SalColormap( nBitCount ); + bDeleteColormap = true; + } + else if( nBitCount != pDisplay_->GetVisual( m_nXScreen ).GetDepth() ) + { + pColormap = new SalColormap( nBitCount ); + bDeleteColormap = true; + } + + pGraphics_->SetLayout( SalLayoutFlags::NONE ); // by default no! mirroring for VirtualDevices, can be enabled with EnableRTL() + + // tdf#127529 see SvpSalInstance::CreateVirtualDevice for the rare case of a non-null pPreExistingTarget + cairo_surface_t* pPreExistingTarget = pData ? static_cast<cairo_surface_t*>(pData->pSurface) : nullptr; + + pGraphics_->Init( this, pPreExistingTarget, pColormap, bDeleteColormap ); +} + +X11SalVirtualDevice::~X11SalVirtualDevice() +{ + pGraphics_.reset(); + + if( GetDrawable() && !bExternPixmap_ ) + XFreePixmap( GetXDisplay(), GetDrawable() ); +} + +SalGraphics* X11SalVirtualDevice::AcquireGraphics() +{ + if( bGraphics_ ) + return nullptr; + + if( pGraphics_ ) + bGraphics_ = true; + + return pGraphics_.get(); +} + +void X11SalVirtualDevice::ReleaseGraphics( SalGraphics* ) +{ bGraphics_ = false; } + +bool X11SalVirtualDevice::SetSize( tools::Long nDX, tools::Long nDY ) +{ + if( bExternPixmap_ ) + return false; + + if( !nDX ) nDX = 1; + if( !nDY ) nDY = 1; + + Pixmap h = limitXCreatePixmap( GetXDisplay(), + pDisplay_->GetDrawable( m_nXScreen ), + nDX, nDY, nDepth_ ); + + if( !h ) + { + if( !GetDrawable() ) + { + hDrawable_ = limitXCreatePixmap( GetXDisplay(), + pDisplay_->GetDrawable( m_nXScreen ), + 1, 1, nDepth_ ); + nDX_ = 1; + nDY_ = 1; + } + return false; + } + + if( GetDrawable() ) + XFreePixmap( GetXDisplay(), GetDrawable() ); + hDrawable_ = h; + + nDX_ = nDX; + nDY_ = nDY; + + if( pGraphics_ ) + pGraphics_->Init( this ); + + return true; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/generic/gdi/x11cairotextrender.cxx b/vcl/unx/generic/gdi/x11cairotextrender.cxx new file mode 100644 index 000000000..6bbbbc1bf --- /dev/null +++ b/vcl/unx/generic/gdi/x11cairotextrender.cxx @@ -0,0 +1,66 @@ +/* -*- 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 <unx/x11/x11cairotextrender.hxx> + +#include <unx/glyphcache.hxx> +#include <X11/Xregion.h> +#include <cairo.h> +#include <salframe.hxx> +#include <salvd.hxx> + +X11CairoTextRender::X11CairoTextRender(X11SalGraphics& rParent) + : mrParent(rParent) +{ +} + +cairo_t* X11CairoTextRender::getCairoContext() +{ + return mrParent.getCairoContext(); +} + +void X11CairoTextRender::getSurfaceOffset( double& nDX, double& nDY ) +{ + nDX = 0; + nDY = 0; +} + +void X11CairoTextRender::clipRegion(cairo_t* cr) +{ + Region pClipRegion = mrParent.mpClipRegion; + if( pClipRegion && !XEmptyRegion( pClipRegion ) ) + { + for (tools::Long i = 0; i < pClipRegion->numRects; ++i) + { + cairo_rectangle(cr, + pClipRegion->rects[i].x1, + pClipRegion->rects[i].y1, + pClipRegion->rects[i].x2 - pClipRegion->rects[i].x1, + pClipRegion->rects[i].y2 - pClipRegion->rects[i].y1); + } + cairo_clip(cr); + } +} + +void X11CairoTextRender::releaseCairoContext(cairo_t* cr) +{ + X11SalGraphics::releaseCairoContext(cr); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/generic/gdi/xrender_peer.cxx b/vcl/unx/generic/gdi/xrender_peer.cxx new file mode 100644 index 000000000..961f4cd3a --- /dev/null +++ b/vcl/unx/generic/gdi/xrender_peer.cxx @@ -0,0 +1,48 @@ +/* -*- 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 <unx/saldisp.hxx> + +#include <unx/x11/xrender_peer.hxx> + +XRenderPeer::XRenderPeer() + : mpDisplay( vcl_sal::getSalDisplay(GetGenericUnixSalData())->GetDisplay() ) + , mpStandardFormatA8( nullptr ) +{ + InitRenderLib(); +} + +XRenderPeer& XRenderPeer::GetInstance() +{ + static XRenderPeer aPeer; + return aPeer; +} + +void XRenderPeer::InitRenderLib() +{ + int nDummy; + // needed to initialize libXrender internals + XRenderQueryExtension( mpDisplay, &nDummy, &nDummy ); + + // the 8bit alpha mask format must be there + XRenderPictFormat aPictFormat={0,0,8,{0,0,0,0,0,0,0,0xFF},0}; + mpStandardFormatA8 = XRenderFindFormat( mpDisplay, PictFormatAlphaMask|PictFormatDepth, &aPictFormat, 0 ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/generic/glyphs/freetype_glyphcache.cxx b/vcl/unx/generic/glyphs/freetype_glyphcache.cxx new file mode 100644 index 000000000..bb7d3e10e --- /dev/null +++ b/vcl/unx/generic/glyphs/freetype_glyphcache.cxx @@ -0,0 +1,975 @@ +/* -*- 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 <o3tl/safeint.hxx> +#include <vcl/fontcharmap.hxx> + +#include <unx/freetype_glyphcache.hxx> + +#include <fontinstance.hxx> +#include <fontattributes.hxx> + +#include <unotools/fontdefs.hxx> + +#include <tools/poly.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> +#include <basegfx/polygon/b2dpolypolygon.hxx> + +#include <sal/log.hxx> +#include <osl/module.h> + +#include <langboost.hxx> +#include <font/PhysicalFontCollection.hxx> +#include <sft.hxx> + +#include <ft2build.h> +#include FT_FREETYPE_H +#include FT_GLYPH_H +#include FT_MULTIPLE_MASTERS_H +#include FT_OUTLINE_H +#include FT_SIZES_H +#include FT_SYNTHESIS_H +#include FT_TRUETYPE_TABLES_H + +#include <vector> + +// TODO: move file mapping stuff to OSL +#include <unistd.h> +#include <dlfcn.h> +#include <fcntl.h> +#include <sys/stat.h> +#include <sys/mman.h> +#include <unx/fontmanager.hxx> +#include <impfontcharmap.hxx> + +static FT_Library aLibFT = nullptr; + +// TODO: remove when the priorities are selected by UI +// if (AH==0) => disable autohinting +// if (AA==0) => disable antialiasing +// if (EB==0) => disable embedded bitmaps +// if (AA prio <= AH prio) => antialias + autohint +// if (AH<AA) => do not autohint when antialiasing +// if (EB<AH) => do not autohint for monochrome +static int nDefaultPrioEmbedded = 2; +static int nDefaultPrioAntiAlias = 1; + +FreetypeFontFile::FreetypeFontFile( const OString& rNativeFileName ) +: maNativeFileName( rNativeFileName ), + mpFileMap( nullptr ), + mnFileSize( 0 ), + mnRefCount( 0 ), + mnLangBoost( 0 ) +{ + // boost font preference if UI language is mentioned in filename + int nPos = maNativeFileName.lastIndexOf( '_' ); + if( nPos == -1 || maNativeFileName[nPos+1] == '.' ) + mnLangBoost += 0x1000; // no langinfo => good + else + { + static const char* pLangBoost = nullptr; + static bool bOnce = true; + if( bOnce ) + { + bOnce = false; + pLangBoost = vcl::getLangBoost(); + } + + if( pLangBoost && !strncasecmp( pLangBoost, &maNativeFileName.getStr()[nPos+1], 3 ) ) + mnLangBoost += 0x2000; // matching langinfo => better + } +} + +bool FreetypeFontFile::Map() +{ + if (mnRefCount++ == 0) + { + const char* pFileName = maNativeFileName.getStr(); + int nFile = open( pFileName, O_RDONLY ); + if( nFile < 0 ) + { + SAL_WARN("vcl.unx.freetype", "open('" << maNativeFileName << "') failed: " << strerror(errno)); + return false; + } + + struct stat aStat; + int nRet = fstat( nFile, &aStat ); + if (nRet < 0) + { + SAL_WARN("vcl.unx.freetype", "fstat on '" << maNativeFileName << "' failed: " << strerror(errno)); + close (nFile); + return false; + } + mnFileSize = aStat.st_size; + mpFileMap = static_cast<unsigned char*>( + mmap( nullptr, mnFileSize, PROT_READ, MAP_SHARED, nFile, 0 )); + if( mpFileMap == MAP_FAILED ) + { + SAL_WARN("vcl.unx.freetype", "mmap of '" << maNativeFileName << "' failed: " << strerror(errno)); + mpFileMap = nullptr; + } + close( nFile ); + } + + return (mpFileMap != nullptr); +} + +void FreetypeFontFile::Unmap() +{ + if (--mnRefCount != 0) + return; + assert(mnRefCount >= 0 && "how did this go negative\n"); + if (mpFileMap) + { + munmap(mpFileMap, mnFileSize); + mpFileMap = nullptr; + } +} + +FreetypeFontInfo::FreetypeFontInfo( const FontAttributes& rDevFontAttributes, + FreetypeFontFile* const pFontFile, int nFaceNum, int nFaceVariation, sal_IntPtr nFontId) +: + maFaceFT( nullptr ), + mpFontFile(pFontFile), + mnFaceNum( nFaceNum ), + mnFaceVariation( nFaceVariation ), + mnRefCount( 0 ), + mnFontId( nFontId ), + maDevFontAttributes( rDevFontAttributes ) +{ + // prefer font with low ID + maDevFontAttributes.IncreaseQualityBy( 10000 - nFontId ); + // prefer font with matching file names + maDevFontAttributes.IncreaseQualityBy( mpFontFile->GetLangBoost() ); +} + +FreetypeFontInfo::~FreetypeFontInfo() +{ +} + +namespace +{ + void dlFT_Done_MM_Var(FT_Library library, FT_MM_Var *amaster) + { +#if !HAVE_DLAPI + FT_Done_MM_Var(library, amaster); +#else + static auto func = reinterpret_cast<void(*)(FT_Library, FT_MM_Var*)>( + osl_getAsciiFunctionSymbol(nullptr, "FT_Done_MM_Var")); + if (func) + func(library, amaster); + else + free(amaster); +#endif + } +} + +FT_FaceRec_* FreetypeFontInfo::GetFaceFT() +{ + if (!maFaceFT && mpFontFile->Map()) + { + FT_Error rc = FT_New_Memory_Face( aLibFT, + mpFontFile->GetBuffer(), + mpFontFile->GetFileSize(), mnFaceNum, &maFaceFT ); + if( (rc != FT_Err_Ok) || (maFaceFT->num_glyphs <= 0) ) + maFaceFT = nullptr; + + if (maFaceFT && mnFaceVariation) + { + FT_MM_Var *pFtMMVar; + if (FT_Get_MM_Var(maFaceFT, &pFtMMVar) == 0) + { + if (o3tl::make_unsigned(mnFaceVariation) <= pFtMMVar->num_namedstyles) + { + FT_Var_Named_Style *instance = &pFtMMVar->namedstyle[mnFaceVariation - 1]; + FT_Set_Var_Design_Coordinates(maFaceFT, pFtMMVar->num_axis, instance->coords); + } + dlFT_Done_MM_Var(aLibFT, pFtMMVar); + } + } + } + + ++mnRefCount; + return maFaceFT; +} + +void FreetypeFont::SetFontVariationsOnHBFont(hb_font_t* pHbFace) const +{ + sal_uInt32 nFaceVariation = mxFontInfo->GetFontFaceVariation(); + if (!(maFaceFT && nFaceVariation)) + return; + + FT_MM_Var *pFtMMVar; + if (FT_Get_MM_Var(maFaceFT, &pFtMMVar) != 0) + return; + + if (nFaceVariation <= pFtMMVar->num_namedstyles) + { + FT_Var_Named_Style *instance = &pFtMMVar->namedstyle[nFaceVariation - 1]; + std::vector<hb_variation_t> aVariations(pFtMMVar->num_axis); + for (FT_UInt i = 0; i < pFtMMVar->num_axis; ++i) + { + aVariations[i].tag = pFtMMVar->axis[i].tag; + aVariations[i].value = instance->coords[i] / 65536.0; + } + hb_font_set_variations(pHbFace, aVariations.data(), aVariations.size()); + } + dlFT_Done_MM_Var(aLibFT, pFtMMVar); +} + +void FreetypeFontInfo::ReleaseFaceFT() +{ + if (--mnRefCount == 0) + { + if (maFaceFT) + { + FT_Done_Face(maFaceFT); + maFaceFT = nullptr; + } + mpFontFile->Unmap(); + } + assert(mnRefCount >= 0 && "how did this go negative\n"); +} + +static unsigned GetUInt( const unsigned char* p ) { return((p[0]<<24)+(p[1]<<16)+(p[2]<<8)+p[3]);} +static unsigned GetUShort( const unsigned char* p ){ return((p[0]<<8)+p[1]);} + +const sal_uInt32 T_true = 0x74727565; /* 'true' */ +const sal_uInt32 T_ttcf = 0x74746366; /* 'ttcf' */ +const sal_uInt32 T_otto = 0x4f54544f; /* 'OTTO' */ + +const unsigned char* FreetypeFontInfo::GetTable( const char* pTag, sal_uLong* pLength ) const +{ + const unsigned char* pBuffer = mpFontFile->GetBuffer(); + int nFileSize = mpFontFile->GetFileSize(); + if( !pBuffer || nFileSize<1024 ) + return nullptr; + + // we currently handle TTF, TTC and OTF headers + unsigned nFormat = GetUInt( pBuffer ); + + const unsigned char* p = pBuffer + 12; + if( nFormat == ::T_ttcf ) // TTC_MAGIC + p += GetUInt( p + 4 * mnFaceNum ); + else if( nFormat != 0x00010000 && nFormat != ::T_true && nFormat != ::T_otto) // TTF_MAGIC and Apple TTF Magic and PS-OpenType font + return nullptr; + + // walk table directory until match + int nTables = GetUShort( p - 8 ); + if( nTables >= 64 ) // something fishy? + return nullptr; + for( int i = 0; i < nTables; ++i, p+=16 ) + { + if( p[0]==pTag[0] && p[1]==pTag[1] && p[2]==pTag[2] && p[3]==pTag[3] ) + { + sal_uLong nLength = GetUInt( p + 12 ); + if( pLength != nullptr ) + *pLength = nLength; + const unsigned char* pTable = pBuffer + GetUInt( p + 8 ); + if( (pTable + nLength) <= (mpFontFile->GetBuffer() + nFileSize) ) + return pTable; + } + } + + return nullptr; +} + +void FreetypeFontInfo::AnnounceFont( vcl::font::PhysicalFontCollection* pFontCollection ) +{ + rtl::Reference<FreetypeFontFace> pFD(new FreetypeFontFace( this, maDevFontAttributes )); + pFontCollection->Add( pFD.get() ); +} + +void FreetypeManager::InitFreetype() +{ + /*FT_Error rcFT =*/ FT_Init_FreeType( &aLibFT ); + + // TODO: remove when the priorities are selected by UI + char* pEnv; + pEnv = ::getenv( "SAL_EMBEDDED_BITMAP_PRIORITY" ); + if( pEnv ) + nDefaultPrioEmbedded = pEnv[0] - '0'; + pEnv = ::getenv( "SAL_ANTIALIASED_TEXT_PRIORITY" ); + if( pEnv ) + nDefaultPrioAntiAlias = pEnv[0] - '0'; +} + +namespace +{ + bool DoesAlmostHorizontalDrainRenderingPool() + { + FT_Int nMajor, nMinor, nPatch; + FT_Library_Version(aLibFT, &nMajor, &nMinor, &nPatch); + if (nMajor > 2) + return false; + if (nMajor == 2 && nMinor <= 8) + return true; + return false; + } +} + +bool FreetypeFont::AlmostHorizontalDrainsRenderingPool(int nRatio, const vcl::font::FontSelectPattern& rFSD) +{ + static bool bAlmostHorizontalDrainsRenderingPool = DoesAlmostHorizontalDrainRenderingPool(); + if (nRatio > 100 && rFSD.maTargetName == "OpenSymbol" && bAlmostHorizontalDrainsRenderingPool) + { + // tdf#127189 FreeType <= 2.8 will fail to render stretched horizontal + // brace glyphs in starmath at a fairly low stretch ratio. The failure + // will set CAIRO_STATUS_FREETYPE_ERROR on the surface which cannot be + // cleared, so all further painting to the surface fails. + + // This appears fixed in >= freetype 2.9 + + // Restrict this bodge to a stretch ratio > ~10 of the OpenSymbol font + // where it has been seen in practice. + SAL_WARN("vcl", "rendering text would fail with stretch ratio of: " << nRatio << ", with FreeType <= 2.8"); + return true; + } + return false; +} + +FT_Face FreetypeFont::GetFtFace() const +{ + FT_Activate_Size( maSizeFT ); + + return maFaceFT; +} + +void FreetypeManager::AddFontFile(const OString& rNormalizedName, + int nFaceNum, int nVariantNum, sal_IntPtr nFontId, const FontAttributes& rDevFontAttr) +{ + if( rNormalizedName.isEmpty() ) + return; + + if( m_aFontInfoList.find( nFontId ) != m_aFontInfoList.end() ) + return; + + FreetypeFontInfo* pFontInfo = new FreetypeFontInfo( rDevFontAttr, + FindFontFile(rNormalizedName), nFaceNum, nVariantNum, nFontId); + m_aFontInfoList[ nFontId ].reset(pFontInfo); + if( m_nMaxFontId < nFontId ) + m_nMaxFontId = nFontId; +} + +void FreetypeManager::AnnounceFonts( vcl::font::PhysicalFontCollection* pToAdd ) const +{ + for (auto const& font : m_aFontInfoList) + { + FreetypeFontInfo* pFreetypeFontInfo = font.second.get(); + pFreetypeFontInfo->AnnounceFont( pToAdd ); + } +} + +FreetypeFont* FreetypeManager::CreateFont(FreetypeFontInstance* pFontInstance) +{ + // find a FontInfo matching to the font id + if (!pFontInstance) + return nullptr; + + const vcl::font::PhysicalFontFace* pFontFace = pFontInstance->GetFontFace(); + if (!pFontFace) + return nullptr; + + sal_IntPtr nFontId = pFontFace->GetFontId(); + FontInfoList::iterator it = m_aFontInfoList.find(nFontId); + + if (it == m_aFontInfoList.end()) + return nullptr; + + return new FreetypeFont(*pFontInstance, it->second); +} + +FreetypeFontFace::FreetypeFontFace( FreetypeFontInfo* pFI, const FontAttributes& rDFA ) +: vcl::font::PhysicalFontFace( rDFA ), + mpFreetypeFontInfo( pFI ) +{ +} + +rtl::Reference<LogicalFontInstance> FreetypeFontFace::CreateFontInstance(const vcl::font::FontSelectPattern& rFSD) const +{ + return new FreetypeFontInstance(*this, rFSD); +} + +// FreetypeFont + +FreetypeFont::FreetypeFont(FreetypeFontInstance& rFontInstance, const std::shared_ptr<FreetypeFontInfo>& rFI) +: mrFontInstance(rFontInstance), + mnCos( 0x10000), + mnSin( 0 ), + mnPrioAntiAlias(nDefaultPrioAntiAlias), + mxFontInfo(rFI), + mnLoadFlags( 0 ), + maFaceFT( nullptr ), + maSizeFT( nullptr ), + mbFaceOk( false ), + mbArtItalic( false ), + mbArtBold(false) +{ + int nPrioEmbedded = nDefaultPrioEmbedded; + + maFaceFT = mxFontInfo->GetFaceFT(); + + const vcl::font::FontSelectPattern& rFSD = rFontInstance.GetFontSelectPattern(); + + if( rFSD.mnOrientation ) + { + const double dRad = toRadians(rFSD.mnOrientation); + mnCos = static_cast<tools::Long>( 0x10000 * cos( dRad ) + 0.5 ); + mnSin = static_cast<tools::Long>( 0x10000 * sin( dRad ) + 0.5 ); + } + + // set the pixel size of the font instance + mnWidth = rFSD.mnWidth; + if( !mnWidth ) + mnWidth = rFSD.mnHeight; + if (rFSD.mnHeight == 0) + { + SAL_WARN("vcl", "FreetypeFont divide by zero"); + mfStretch = 1.0; + return; + } + mfStretch = static_cast<double>(mnWidth) / rFSD.mnHeight; + // sanity checks (e.g. #i66394#, #i66244#, #i66537#) + if (mnWidth < 0) + { + SAL_WARN("vcl", "FreetypeFont negative font width of: " << mnWidth); + return; + } + if (mfStretch > +148.0 || mfStretch < -148.0) + { + SAL_WARN("vcl", "FreetypeFont excessive stretch of: " << mfStretch); + return; + } + + if( !maFaceFT ) + return; + + FT_New_Size( maFaceFT, &maSizeFT ); + FT_Activate_Size( maSizeFT ); + /* This might fail for color bitmap fonts, but that is fine since we will + * not need any glyph data from FreeType in this case */ + /*FT_Error rc = */ FT_Set_Pixel_Sizes( maFaceFT, mnWidth, rFSD.mnHeight ); + + FT_Select_Charmap(maFaceFT, FT_ENCODING_UNICODE); + + if( mxFontInfo->IsSymbolFont() ) + { + FT_Encoding eEncoding = FT_ENCODING_MS_SYMBOL; + FT_Select_Charmap(maFaceFT, eEncoding); + } + + mbFaceOk = true; + + // TODO: query GASP table for load flags + mnLoadFlags = FT_LOAD_DEFAULT | FT_LOAD_IGNORE_TRANSFORM; + + mbArtItalic = (rFSD.GetItalic() != ITALIC_NONE && mxFontInfo->GetFontAttributes().GetItalic() == ITALIC_NONE); + mbArtBold = (rFSD.GetWeight() > WEIGHT_MEDIUM && mxFontInfo->GetFontAttributes().GetWeight() <= WEIGHT_MEDIUM); + + if( ((mnCos != 0) && (mnSin != 0)) || (nPrioEmbedded <= 0) ) + mnLoadFlags |= FT_LOAD_NO_BITMAP; +} + +namespace +{ + std::unique_ptr<FontConfigFontOptions> GetFCFontOptions(const FontAttributes& rFontAttributes, int nSize) + { + return psp::PrintFontManager::getFontOptions(rFontAttributes, nSize); + } +} + +const FontConfigFontOptions* FreetypeFont::GetFontOptions() const +{ + if (!mxFontOptions) + { + mxFontOptions = GetFCFontOptions(mxFontInfo->GetFontAttributes(), mrFontInstance.GetFontSelectPattern().mnHeight); + mxFontOptions->SyncPattern(GetFontFileName(), GetFontFaceIndex(), GetFontFaceVariation(), NeedsArtificialBold()); + } + return mxFontOptions.get(); +} + +const OString& FreetypeFont::GetFontFileName() const +{ + return mxFontInfo->GetFontFileName(); +} + +int FreetypeFont::GetFontFaceIndex() const +{ + return mxFontInfo->GetFontFaceIndex(); +} + +int FreetypeFont::GetFontFaceVariation() const +{ + return mxFontInfo->GetFontFaceVariation(); +} + +FreetypeFont::~FreetypeFont() +{ + if( maSizeFT ) + FT_Done_Size( maSizeFT ); + + mxFontInfo->ReleaseFaceFT(); +} + +void FreetypeFont::GetFontMetric(ImplFontMetricDataRef const & rxTo) const +{ + rxTo->FontAttributes::operator =(mxFontInfo->GetFontAttributes()); + + rxTo->SetOrientation(mrFontInstance.GetFontSelectPattern().mnOrientation); + + //Always consider [star]symbol as symbol fonts + if ( IsStarSymbol( rxTo->GetFamilyName() ) ) + rxTo->SetSymbolFlag( true ); + + FT_Activate_Size( maSizeFT ); + + rxTo->ImplCalcLineSpacing(&mrFontInstance); + rxTo->ImplInitBaselines(&mrFontInstance); + + rxTo->SetSlant( 0 ); + rxTo->SetWidth( mnWidth ); + + const TT_OS2* pOS2 = static_cast<const TT_OS2*>(FT_Get_Sfnt_Table( maFaceFT, ft_sfnt_os2 )); + if( pOS2 && (pOS2->version != 0xFFFF) ) + { + // map the panose info from the OS2 table to their VCL counterparts + switch( pOS2->panose[0] ) + { + case 1: rxTo->SetFamilyType( FAMILY_ROMAN ); break; + case 2: rxTo->SetFamilyType( FAMILY_SWISS ); break; + case 3: rxTo->SetFamilyType( FAMILY_MODERN ); break; + case 4: rxTo->SetFamilyType( FAMILY_SCRIPT ); break; + case 5: rxTo->SetFamilyType( FAMILY_DECORATIVE ); break; + // TODO: is it reasonable to override the attribute with DONTKNOW? + case 0: // fall through + default: rxTo->SetFamilyType( FAMILY_DONTKNOW ); break; + } + + switch( pOS2->panose[3] ) + { + case 2: // fall through + case 3: // fall through + case 4: // fall through + case 5: // fall through + case 6: // fall through + case 7: // fall through + case 8: rxTo->SetPitch( PITCH_VARIABLE ); break; + case 9: rxTo->SetPitch( PITCH_FIXED ); break; + // TODO: is it reasonable to override the attribute with DONTKNOW? + case 0: // fall through + case 1: // fall through + default: rxTo->SetPitch( PITCH_DONTKNOW ); break; + } + } + + // initialize kashida width + rxTo->SetMinKashida(mrFontInstance.GetKashidaWidth()); +} + +void FreetypeFont::ApplyGlyphTransform(bool bVertical, FT_Glyph pGlyphFT ) const +{ + // shortcut most common case + if (!mrFontInstance.GetFontSelectPattern().mnOrientation && !bVertical) + return; + + const FT_Size_Metrics& rMetrics = maFaceFT->size->metrics; + FT_Vector aVector; + FT_Matrix aMatrix; + + bool bStretched = false; + + if (!bVertical) + { + // straight + aVector.x = 0; + aVector.y = 0; + aMatrix.xx = +mnCos; + aMatrix.yy = +mnCos; + aMatrix.xy = -mnSin; + aMatrix.yx = +mnSin; + } + else + { + // left + bStretched = (mfStretch != 1.0); + aVector.x = static_cast<FT_Pos>(+rMetrics.descender * mfStretch); + aVector.y = -rMetrics.ascender; + aMatrix.xx = static_cast<FT_Pos>(-mnSin / mfStretch); + aMatrix.yy = static_cast<FT_Pos>(-mnSin * mfStretch); + aMatrix.xy = static_cast<FT_Pos>(-mnCos * mfStretch); + aMatrix.yx = static_cast<FT_Pos>(+mnCos / mfStretch); + } + + if( pGlyphFT->format != FT_GLYPH_FORMAT_BITMAP ) + { + FT_Glyph_Transform( pGlyphFT, nullptr, &aVector ); + + // orthogonal transforms are better handled by bitmap operations + if( bStretched ) + { + // apply non-orthogonal or stretch transformations + FT_Glyph_Transform( pGlyphFT, &aMatrix, nullptr ); + } + } + else + { + // FT<=2005 ignores transforms for bitmaps, so do it manually + FT_BitmapGlyph pBmpGlyphFT = reinterpret_cast<FT_BitmapGlyph>(pGlyphFT); + pBmpGlyphFT->left += (aVector.x + 32) >> 6; + pBmpGlyphFT->top += (aVector.y + 32) >> 6; + } +} + +bool FreetypeFont::GetGlyphBoundRect(sal_GlyphId nID, tools::Rectangle& rRect, bool bVertical) const +{ + FT_Activate_Size( maSizeFT ); + + FT_Error rc = FT_Load_Glyph(maFaceFT, nID, mnLoadFlags); + + if (rc != FT_Err_Ok) + return false; + + if (mbArtBold) + FT_GlyphSlot_Embolden(maFaceFT->glyph); + + FT_Glyph pGlyphFT; + rc = FT_Get_Glyph(maFaceFT->glyph, &pGlyphFT); + if (rc != FT_Err_Ok) + return false; + + ApplyGlyphTransform(bVertical, pGlyphFT); + + FT_BBox aBbox; + FT_Glyph_Get_CBox( pGlyphFT, FT_GLYPH_BBOX_PIXELS, &aBbox ); + FT_Done_Glyph( pGlyphFT ); + + tools::Rectangle aRect(aBbox.xMin, -aBbox.yMax, aBbox.xMax, -aBbox.yMin); + if (mnCos != 0x10000 && mnSin != 0) + { + const double nCos = mnCos / 65536.0; + const double nSin = mnSin / 65536.0; + rRect.SetLeft( nCos*aRect.Left() + nSin*aRect.Top() ); + rRect.SetTop( -nSin*aRect.Left() - nCos*aRect.Top() ); + rRect.SetRight( nCos*aRect.Right() + nSin*aRect.Bottom() ); + rRect.SetBottom( -nSin*aRect.Right() - nCos*aRect.Bottom() ); + } + else + rRect = aRect; + return true; +} + +bool FreetypeFont::GetAntialiasAdvice() const +{ + // TODO: also use GASP info + return !mrFontInstance.GetFontSelectPattern().mbNonAntialiased && (mnPrioAntiAlias > 0); +} + +// determine unicode ranges in font + +const FontCharMapRef & FreetypeFont::GetFontCharMap() const +{ + return mxFontInfo->GetFontCharMap(); +} + +bool FreetypeFont::GetFontCapabilities(vcl::FontCapabilities &rFontCapabilities) const +{ + return mxFontInfo->GetFontCapabilities(rFontCapabilities); +} + +const FontCharMapRef & FreetypeFontInfo::GetFontCharMap() const +{ + // check if the charmap is already cached + if( mxFontCharMap.is() ) + return mxFontCharMap; + + // get the charmap and cache it + CmapResult aCmapResult; + aCmapResult.mbSymbolic = IsSymbolFont(); + + sal_uLong nLength = 0; + const unsigned char* pCmap = GetTable("cmap", &nLength); + if (pCmap && (nLength > 0) && ParseCMAP(pCmap, nLength, aCmapResult)) + { + FontCharMapRef xFontCharMap( new FontCharMap ( aCmapResult ) ); + mxFontCharMap = xFontCharMap; + } + else + { + FontCharMapRef xFontCharMap( new FontCharMap() ); + mxFontCharMap = xFontCharMap; + } + // mxFontCharMap on either branch now has a refcount of 1 + return mxFontCharMap; +} + +bool FreetypeFontInfo::GetFontCapabilities(vcl::FontCapabilities &rFontCapabilities) const +{ + bool bRet = false; + + sal_uLong nLength = 0; + + // load OS/2 table + const FT_Byte* pOS2 = GetTable("OS/2", &nLength); + if (pOS2) + { + bRet = vcl::getTTCoverage( + rFontCapabilities.oUnicodeRange, + rFontCapabilities.oCodePageRange, + pOS2, nLength); + } + + return bRet; +} + +// outline stuff + +namespace { + +class PolyArgs +{ +public: + PolyArgs( tools::PolyPolygon& rPolyPoly, sal_uInt16 nMaxPoints ); + + void AddPoint( tools::Long nX, tools::Long nY, PolyFlags); + void ClosePolygon(); + + tools::Long GetPosX() const { return maPosition.x;} + tools::Long GetPosY() const { return maPosition.y;} + +private: + tools::PolyPolygon& mrPolyPoly; + + std::unique_ptr<Point[]> + mpPointAry; + std::unique_ptr<PolyFlags[]> + mpFlagAry; + + FT_Vector maPosition; + sal_uInt16 mnMaxPoints; + sal_uInt16 mnPoints; + sal_uInt16 mnPoly; + bool bHasOffline; + + PolyArgs(const PolyArgs&) = delete; + PolyArgs& operator=(const PolyArgs&) = delete; +}; + +} + +PolyArgs::PolyArgs( tools::PolyPolygon& rPolyPoly, sal_uInt16 nMaxPoints ) +: mrPolyPoly(rPolyPoly), + mnMaxPoints(nMaxPoints), + mnPoints(0), + mnPoly(0), + bHasOffline(false) +{ + mpPointAry.reset( new Point[ mnMaxPoints ] ); + mpFlagAry.reset( new PolyFlags [ mnMaxPoints ] ); + maPosition.x = maPosition.y = 0; +} + +void PolyArgs::AddPoint( tools::Long nX, tools::Long nY, PolyFlags aFlag ) +{ + SAL_WARN_IF( (mnPoints >= mnMaxPoints), "vcl", "FTGlyphOutline: AddPoint overflow!" ); + if( mnPoints >= mnMaxPoints ) + return; + + maPosition.x = nX; + maPosition.y = nY; + mpPointAry[ mnPoints ] = Point( nX, nY ); + mpFlagAry[ mnPoints++ ]= aFlag; + bHasOffline |= (aFlag != PolyFlags::Normal); +} + +void PolyArgs::ClosePolygon() +{ + if( !mnPoly++ ) + return; + + // freetype seems to always close the polygon with an ON_CURVE point + // PolyPoly wants to close the polygon itself => remove last point + SAL_WARN_IF( (mnPoints < 2), "vcl", "FTGlyphOutline: PolyFinishNum failed!" ); + --mnPoints; + SAL_WARN_IF( (mpPointAry[0]!=mpPointAry[mnPoints]), "vcl", "FTGlyphOutline: PolyFinishEq failed!" ); + SAL_WARN_IF( (mpFlagAry[0]!=PolyFlags::Normal), "vcl", "FTGlyphOutline: PolyFinishFE failed!" ); + SAL_WARN_IF( (mpFlagAry[mnPoints]!=PolyFlags::Normal), "vcl", "FTGlyphOutline: PolyFinishFS failed!" ); + + tools::Polygon aPoly( mnPoints, mpPointAry.get(), (bHasOffline ? mpFlagAry.get() : nullptr) ); + + // #i35928# + // This may be an invalid polygon, e.g. the last point is a control point. + // So close the polygon (and add the first point again) if the last point + // is a control point or different from first. + // #i48298# + // Now really duplicating the first point, to close or correct the + // polygon. Also no longer duplicating the flags, but enforcing + // PolyFlags::Normal for the newly added last point. + const sal_uInt16 nPolySize(aPoly.GetSize()); + if(nPolySize) + { + if((aPoly.HasFlags() && PolyFlags::Control == aPoly.GetFlags(nPolySize - 1)) + || (aPoly.GetPoint(nPolySize - 1) != aPoly.GetPoint(0))) + { + aPoly.SetSize(nPolySize + 1); + aPoly.SetPoint(aPoly.GetPoint(0), nPolySize); + + if(aPoly.HasFlags()) + { + aPoly.SetFlags(nPolySize, PolyFlags::Normal); + } + } + } + + mrPolyPoly.Insert( aPoly ); + mnPoints = 0; + bHasOffline = false; +} + +extern "C" { + +// TODO: wait till all compilers accept that calling conventions +// for functions are the same independent of implementation constness, +// then uncomment the const-tokens in the function interfaces below +static int FT_move_to( const FT_Vector* p0, void* vpPolyArgs ) +{ + PolyArgs& rA = *static_cast<PolyArgs*>(vpPolyArgs); + + // move_to implies a new polygon => finish old polygon first + rA.ClosePolygon(); + + rA.AddPoint( p0->x, p0->y, PolyFlags::Normal ); + return 0; +} + +static int FT_line_to( const FT_Vector* p1, void* vpPolyArgs ) +{ + PolyArgs& rA = *static_cast<PolyArgs*>(vpPolyArgs); + rA.AddPoint( p1->x, p1->y, PolyFlags::Normal ); + return 0; +} + +static int FT_conic_to( const FT_Vector* p1, const FT_Vector* p2, void* vpPolyArgs ) +{ + PolyArgs& rA = *static_cast<PolyArgs*>(vpPolyArgs); + + // VCL's Polygon only knows cubic beziers + const tools::Long nX1 = (2 * rA.GetPosX() + 4 * p1->x + 3) / 6; + const tools::Long nY1 = (2 * rA.GetPosY() + 4 * p1->y + 3) / 6; + rA.AddPoint( nX1, nY1, PolyFlags::Control ); + + const tools::Long nX2 = (2 * p2->x + 4 * p1->x + 3) / 6; + const tools::Long nY2 = (2 * p2->y + 4 * p1->y + 3) / 6; + rA.AddPoint( nX2, nY2, PolyFlags::Control ); + + rA.AddPoint( p2->x, p2->y, PolyFlags::Normal ); + return 0; +} + +static int FT_cubic_to( const FT_Vector* p1, const FT_Vector* p2, const FT_Vector* p3, void* vpPolyArgs ) +{ + PolyArgs& rA = *static_cast<PolyArgs*>(vpPolyArgs); + rA.AddPoint( p1->x, p1->y, PolyFlags::Control ); + rA.AddPoint( p2->x, p2->y, PolyFlags::Control ); + rA.AddPoint( p3->x, p3->y, PolyFlags::Normal ); + return 0; +} + +} // extern "C" + +bool FreetypeFont::GetGlyphOutline(sal_GlyphId nId, basegfx::B2DPolyPolygon& rB2DPolyPoly, bool bIsVertical) const +{ + if( maSizeFT ) + FT_Activate_Size( maSizeFT ); + + rB2DPolyPoly.clear(); + + FT_Int nLoadFlags = FT_LOAD_DEFAULT | FT_LOAD_IGNORE_TRANSFORM; + +#ifdef FT_LOAD_TARGET_LIGHT + // enable "light hinting" if available + nLoadFlags |= FT_LOAD_TARGET_LIGHT; +#endif + + FT_Error rc = FT_Load_Glyph(maFaceFT, nId, nLoadFlags); + if( rc != FT_Err_Ok ) + return false; + + if (mbArtBold) + FT_GlyphSlot_Embolden(maFaceFT->glyph); + + FT_Glyph pGlyphFT; + rc = FT_Get_Glyph( maFaceFT->glyph, &pGlyphFT ); + if( rc != FT_Err_Ok ) + return false; + + if( pGlyphFT->format != FT_GLYPH_FORMAT_OUTLINE ) + { + FT_Done_Glyph( pGlyphFT ); + return false; + } + + if( mbArtItalic ) + { + FT_Matrix aMatrix; + aMatrix.xx = aMatrix.yy = 0x10000L; + aMatrix.xy = 0x6000L; + aMatrix.yx = 0; + FT_Glyph_Transform( pGlyphFT, &aMatrix, nullptr ); + } + + FT_Outline& rOutline = reinterpret_cast<FT_OutlineGlyphRec*>(pGlyphFT)->outline; + if( !rOutline.n_points ) // blank glyphs are ok + { + FT_Done_Glyph( pGlyphFT ); + return true; + } + + tools::Long nMaxPoints = 1 + rOutline.n_points * 3; + tools::PolyPolygon aToolPolyPolygon; + PolyArgs aPolyArg( aToolPolyPolygon, nMaxPoints ); + + ApplyGlyphTransform(bIsVertical, pGlyphFT); + + FT_Outline_Funcs aFuncs; + aFuncs.move_to = &FT_move_to; + aFuncs.line_to = &FT_line_to; + aFuncs.conic_to = &FT_conic_to; + aFuncs.cubic_to = &FT_cubic_to; + aFuncs.shift = 0; + aFuncs.delta = 0; + FT_Outline_Decompose( &rOutline, &aFuncs, static_cast<void*>(&aPolyArg) ); + aPolyArg.ClosePolygon(); // close last polygon + FT_Done_Glyph( pGlyphFT ); + + // convert to basegfx polypolygon + // TODO: get rid of the intermediate tools polypolygon + rB2DPolyPoly = aToolPolyPolygon.getB2DPolyPolygon(); + rB2DPolyPoly.transform(basegfx::utils::createScaleB2DHomMatrix( +0x1p-6, -0x1p-6 )); + + return true; +} + +const unsigned char* FreetypeFont::GetTable(const char* pName, sal_uLong* pLength) const +{ + return mxFontInfo->GetTable( pName, pLength ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/generic/glyphs/glyphcache.cxx b/vcl/unx/generic/glyphs/glyphcache.cxx new file mode 100644 index 000000000..79db2d87b --- /dev/null +++ b/vcl/unx/generic/glyphs/glyphcache.cxx @@ -0,0 +1,121 @@ +/* -*- 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 <stdlib.h> +#include <unx/freetype_glyphcache.hxx> +#include <unx/gendata.hxx> + +#include <fontinstance.hxx> + +#include <rtl/ustring.hxx> +#include <sal/log.hxx> + +FreetypeManager::FreetypeManager() + : m_nMaxFontId(0) +{ + InitFreetype(); +} + +FreetypeManager::~FreetypeManager() +{ + ClearFontCache(); +} + +void FreetypeManager::ClearFontCache() +{ + m_aFontInfoList.clear(); +} + +FreetypeManager& FreetypeManager::get() +{ + GenericUnixSalData* const pSalData(GetGenericUnixSalData()); + assert(pSalData); + return *pSalData->GetFreetypeManager(); +} + +FreetypeFontFile* FreetypeManager::FindFontFile(const OString& rNativeFileName) +{ + // font file already known? (e.g. for ttc, synthetic, aliased fonts) + const char* pFileName = rNativeFileName.getStr(); + FontFileList::const_iterator it = m_aFontFileList.find(pFileName); + if (it != m_aFontFileList.end()) + return it->second.get(); + + // no => create new one + FreetypeFontFile* pFontFile = new FreetypeFontFile(rNativeFileName); + pFileName = pFontFile->maNativeFileName.getStr(); + m_aFontFileList[pFileName].reset(pFontFile); + return pFontFile; +} + +FreetypeFontInstance::FreetypeFontInstance(const vcl::font::PhysicalFontFace& rPFF, const vcl::font::FontSelectPattern& rFSP) + : LogicalFontInstance(rPFF, rFSP) + , mxFreetypeFont(FreetypeManager::get().CreateFont(this)) +{ +} + +FreetypeFontInstance::~FreetypeFontInstance() +{ +} + +static hb_blob_t* getFontTable(hb_face_t* /*face*/, hb_tag_t nTableTag, void* pUserData) +{ + char pTagName[5]; + LogicalFontInstance::DecodeOpenTypeTag( nTableTag, pTagName ); + + sal_uLong nLength = 0; + FreetypeFontInstance* pFontInstance = static_cast<FreetypeFontInstance*>( pUserData ); + FreetypeFont& rFont = pFontInstance->GetFreetypeFont(); + const char* pBuffer = reinterpret_cast<const char*>( + rFont.GetTable(pTagName, &nLength) ); + + hb_blob_t* pBlob = nullptr; + if (pBuffer != nullptr) + pBlob = hb_blob_create(pBuffer, nLength, HB_MEMORY_MODE_READONLY, nullptr, nullptr); + + return pBlob; +} + +hb_font_t* FreetypeFontInstance::ImplInitHbFont() +{ + hb_font_t* pRet = InitHbFont(hb_face_create_for_tables(getFontTable, this, nullptr)); + assert(mxFreetypeFont); + mxFreetypeFont->SetFontVariationsOnHBFont(pRet); + return pRet; +} + +bool FreetypeFontInstance::ImplGetGlyphBoundRect(sal_GlyphId nId, tools::Rectangle& rRect, bool bVertical) const +{ + assert(mxFreetypeFont); + if (!mxFreetypeFont) + return false; + return mxFreetypeFont->GetGlyphBoundRect(nId, rRect, bVertical); +} + +bool FreetypeFontInstance::GetGlyphOutline(sal_GlyphId nId, basegfx::B2DPolyPolygon& rPoly, bool bVertical) const +{ + assert(mxFreetypeFont); + if (!mxFreetypeFont) + return false; + return mxFreetypeFont->GetGlyphOutline(nId, rPoly, bVertical); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/generic/print/GenPspGfxBackend.cxx b/vcl/unx/generic/print/GenPspGfxBackend.cxx new file mode 100644 index 000000000..7b461ff4f --- /dev/null +++ b/vcl/unx/generic/print/GenPspGfxBackend.cxx @@ -0,0 +1,412 @@ +/* -*- 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 <unx/GenPspGfxBackend.hxx> +#include <unx/printergfx.hxx> +#include <vcl/BitmapReadAccess.hxx> +#include <salbmp.hxx> + +// ----- Implementation of PrinterBmp by means of SalBitmap/BitmapBuffer --------------- + +namespace +{ +class SalPrinterBmp : public psp::PrinterBmp +{ +private: + BitmapBuffer* mpBmpBuffer; + + FncGetPixel mpFncGetPixel; + Scanline mpScanAccess; + sal_PtrDiff mnScanOffset; + +public: + explicit SalPrinterBmp(BitmapBuffer* pBitmap); + + virtual sal_uInt32 GetPaletteColor(sal_uInt32 nIdx) const override; + virtual sal_uInt32 GetPaletteEntryCount() const override; + virtual sal_uInt32 GetPixelRGB(sal_uInt32 nRow, sal_uInt32 nColumn) const override; + virtual sal_uInt8 GetPixelGray(sal_uInt32 nRow, sal_uInt32 nColumn) const override; + virtual sal_uInt8 GetPixelIdx(sal_uInt32 nRow, sal_uInt32 nColumn) const override; + virtual sal_uInt32 GetDepth() const override; +}; +} + +SalPrinterBmp::SalPrinterBmp(BitmapBuffer* pBuffer) + : mpBmpBuffer(pBuffer) +{ + assert(mpBmpBuffer && "SalPrinterBmp::SalPrinterBmp () can't acquire Bitmap"); + + // calibrate scanline buffer + if (mpBmpBuffer->mnFormat & ScanlineFormat::TopDown) + { + mpScanAccess = mpBmpBuffer->mpBits; + mnScanOffset = mpBmpBuffer->mnScanlineSize; + } + else + { + mpScanAccess + = mpBmpBuffer->mpBits + (mpBmpBuffer->mnHeight - 1) * mpBmpBuffer->mnScanlineSize; + mnScanOffset = -mpBmpBuffer->mnScanlineSize; + } + + // request read access to the pixels + mpFncGetPixel = BitmapReadAccess::GetPixelFunction(mpBmpBuffer->mnFormat); +} + +sal_uInt32 SalPrinterBmp::GetDepth() const +{ + sal_uInt32 nDepth; + + switch (mpBmpBuffer->mnBitCount) + { + case 1: + nDepth = 1; + break; + + case 4: + case 8: + nDepth = 8; + break; + + case 24: + case 32: + nDepth = 24; + break; + + default: + nDepth = 1; + assert(false && "Error: unsupported bitmap depth in SalPrinterBmp::GetDepth()"); + break; + } + + return nDepth; +} + +sal_uInt32 SalPrinterBmp::GetPaletteEntryCount() const +{ + return mpBmpBuffer->maPalette.GetEntryCount(); +} + +sal_uInt32 SalPrinterBmp::GetPaletteColor(sal_uInt32 nIdx) const +{ + BitmapColor aColor(mpBmpBuffer->maPalette[nIdx]); + + return ((aColor.GetBlue()) & 0x000000ff) | ((aColor.GetGreen() << 8) & 0x0000ff00) + | ((aColor.GetRed() << 16) & 0x00ff0000); +} + +sal_uInt32 SalPrinterBmp::GetPixelRGB(sal_uInt32 nRow, sal_uInt32 nColumn) const +{ + Scanline pScan = mpScanAccess + nRow * mnScanOffset; + BitmapColor aColor = mpFncGetPixel(pScan, nColumn, mpBmpBuffer->maColorMask); + + if (!!mpBmpBuffer->maPalette) + GetPaletteColor(aColor.GetIndex()); + + return ((aColor.GetBlue()) & 0x000000ff) | ((aColor.GetGreen() << 8) & 0x0000ff00) + | ((aColor.GetRed() << 16) & 0x00ff0000); +} + +sal_uInt8 SalPrinterBmp::GetPixelGray(sal_uInt32 nRow, sal_uInt32 nColumn) const +{ + Scanline pScan = mpScanAccess + nRow * mnScanOffset; + BitmapColor aColor = mpFncGetPixel(pScan, nColumn, mpBmpBuffer->maColorMask); + + if (!!mpBmpBuffer->maPalette) + aColor = mpBmpBuffer->maPalette[aColor.GetIndex()]; + + return (aColor.GetBlue() * 28UL + aColor.GetGreen() * 151UL + aColor.GetRed() * 77UL) >> 8; +} + +sal_uInt8 SalPrinterBmp::GetPixelIdx(sal_uInt32 nRow, sal_uInt32 nColumn) const +{ + Scanline pScan = mpScanAccess + nRow * mnScanOffset; + BitmapColor aColor = mpFncGetPixel(pScan, nColumn, mpBmpBuffer->maColorMask); + + if (!!mpBmpBuffer->maPalette) + return aColor.GetIndex(); + else + return 0; +} + +GenPspGfxBackend::GenPspGfxBackend(psp::PrinterGfx* pPrinterGfx) + : m_pPrinterGfx(pPrinterGfx) +{ +} + +GenPspGfxBackend::~GenPspGfxBackend() {} + +void GenPspGfxBackend::Init() {} +void GenPspGfxBackend::freeResources() {} + +bool GenPspGfxBackend::setClipRegion(vcl::Region const& rRegion) +{ + // TODO: support polygonal clipregions here + RectangleVector aRectangles; + rRegion.GetRegionRectangles(aRectangles); + m_pPrinterGfx->BeginSetClipRegion(); + + for (auto const& rectangle : aRectangles) + { + const tools::Long nWidth(rectangle.GetWidth()); + const tools::Long nHeight(rectangle.GetHeight()); + + if (nWidth && nHeight) + { + m_pPrinterGfx->UnionClipRegion(rectangle.Left(), rectangle.Top(), nWidth, nHeight); + } + } + + m_pPrinterGfx->EndSetClipRegion(); + + return true; +} + +void GenPspGfxBackend::ResetClipRegion() { m_pPrinterGfx->ResetClipRegion(); } + +sal_uInt16 GenPspGfxBackend::GetBitCount() const { return m_pPrinterGfx->GetBitCount(); } + +tools::Long GenPspGfxBackend::GetGraphicsWidth() const { return 0; } + +void GenPspGfxBackend::SetLineColor() { m_pPrinterGfx->SetLineColor(); } + +void GenPspGfxBackend::SetLineColor(Color nColor) +{ + psp::PrinterColor aColor(nColor.GetRed(), nColor.GetGreen(), nColor.GetBlue()); + m_pPrinterGfx->SetLineColor(aColor); +} + +void GenPspGfxBackend::SetFillColor() { m_pPrinterGfx->SetFillColor(); } + +void GenPspGfxBackend::SetFillColor(Color nColor) +{ + psp::PrinterColor aColor(nColor.GetRed(), nColor.GetGreen(), nColor.GetBlue()); + m_pPrinterGfx->SetFillColor(aColor); +} + +void GenPspGfxBackend::SetXORMode(bool bSet, bool /*bInvertOnly*/) +{ + SAL_WARN_IF(bSet, "vcl", "Error: PrinterGfx::SetXORMode() not implemented"); +} + +void GenPspGfxBackend::SetROPLineColor(SalROPColor /*nROPColor*/) +{ + SAL_WARN("vcl", "Error: PrinterGfx::SetROPLineColor() not implemented"); +} + +void GenPspGfxBackend::SetROPFillColor(SalROPColor /*nROPColor*/) +{ + SAL_WARN("vcl", "Error: PrinterGfx::SetROPFillColor() not implemented"); +} + +void GenPspGfxBackend::drawPixel(tools::Long nX, tools::Long nY) +{ + m_pPrinterGfx->DrawPixel(Point(nX, nY)); +} +void GenPspGfxBackend::drawPixel(tools::Long nX, tools::Long nY, Color nColor) +{ + psp::PrinterColor aColor(nColor.GetRed(), nColor.GetGreen(), nColor.GetBlue()); + m_pPrinterGfx->DrawPixel(Point(nX, nY), aColor); +} + +void GenPspGfxBackend::drawLine(tools::Long nX1, tools::Long nY1, tools::Long nX2, tools::Long nY2) +{ + m_pPrinterGfx->DrawLine(Point(nX1, nY1), Point(nX2, nY2)); +} +void GenPspGfxBackend::drawRect(tools::Long nX, tools::Long nY, tools::Long nWidth, + tools::Long nHeight) +{ + m_pPrinterGfx->DrawRect(tools::Rectangle(Point(nX, nY), Size(nWidth, nHeight))); +} + +void GenPspGfxBackend::drawPolyLine(sal_uInt32 nPoints, const Point* pPointArray) +{ + m_pPrinterGfx->DrawPolyLine(nPoints, pPointArray); +} + +void GenPspGfxBackend::drawPolygon(sal_uInt32 nPoints, const Point* pPointArray) +{ + // Point must be equal to Point! see include/vcl/salgtype.hxx + m_pPrinterGfx->DrawPolygon(nPoints, pPointArray); +} + +void GenPspGfxBackend::drawPolyPolygon(sal_uInt32 nPoly, const sal_uInt32* pPoints, + const Point** pPointArray) +{ + m_pPrinterGfx->DrawPolyPolygon(nPoly, pPoints, pPointArray); +} + +bool GenPspGfxBackend::drawPolyPolygon(const basegfx::B2DHomMatrix& /*rObjectToDevice*/, + const basegfx::B2DPolyPolygon&, double /*fTransparency*/) +{ + // TODO: implement and advertise OutDevSupportType::B2DDraw support + return false; +} + +bool GenPspGfxBackend::drawPolyLine(const basegfx::B2DHomMatrix& /*rObjectToDevice*/, + const basegfx::B2DPolygon& /*rPolygon*/, + double /*fTransparency*/, double /*fLineWidth*/, + const std::vector<double>* /*pStroke*/, basegfx::B2DLineJoin, + css::drawing::LineCap, double /*fMiterMinimumAngle*/, + bool /*bPixelSnapHairline*/) +{ + // TODO: a PS printer can draw B2DPolyLines almost directly + return false; +} + +bool GenPspGfxBackend::drawPolyLineBezier(sal_uInt32 nPoints, const Point* pPointArray, + const PolyFlags* pFlagArray) +{ + m_pPrinterGfx->DrawPolyLineBezier(nPoints, pPointArray, pFlagArray); + return true; +} + +bool GenPspGfxBackend::drawPolygonBezier(sal_uInt32 nPoints, const Point* pPointArray, + const PolyFlags* pFlagArray) +{ + m_pPrinterGfx->DrawPolygonBezier(nPoints, pPointArray, pFlagArray); + return true; +} + +bool GenPspGfxBackend::drawPolyPolygonBezier(sal_uInt32 nPoly, const sal_uInt32* pPoints, + const Point* const* pPointArray, + const PolyFlags* const* pFlagArray) +{ + // Point must be equal to Point! see include/vcl/salgtype.hxx + m_pPrinterGfx->DrawPolyPolygonBezier(nPoly, pPoints, pPointArray, pFlagArray); + return true; +} + +void GenPspGfxBackend::copyArea(tools::Long /*nDestX*/, tools::Long /*nDestY*/, + tools::Long /*nSrcX*/, tools::Long /*nSrcY*/, + tools::Long /*nSrcWidth*/, tools::Long /*nSrcHeight*/, + bool /*bWindowInvalidate*/) +{ + OSL_FAIL("Error: PrinterGfx::CopyArea() not implemented"); +} + +void GenPspGfxBackend::copyBits(const SalTwoRect& /*rPosAry*/, SalGraphics* /*pSrcGraphics*/) +{ + OSL_FAIL("Error: PrinterGfx::CopyBits() not implemented"); +} + +void GenPspGfxBackend::drawBitmap(const SalTwoRect& rPosAry, const SalBitmap& rSalBitmap) +{ + tools::Rectangle aSrc(Point(rPosAry.mnSrcX, rPosAry.mnSrcY), + Size(rPosAry.mnSrcWidth, rPosAry.mnSrcHeight)); + + tools::Rectangle aDst(Point(rPosAry.mnDestX, rPosAry.mnDestY), + Size(rPosAry.mnDestWidth, rPosAry.mnDestHeight)); + + BitmapBuffer* pBuffer + = const_cast<SalBitmap&>(rSalBitmap).AcquireBuffer(BitmapAccessMode::Read); + + SalPrinterBmp aBmp(pBuffer); + m_pPrinterGfx->DrawBitmap(aDst, aSrc, aBmp); + + const_cast<SalBitmap&>(rSalBitmap).ReleaseBuffer(pBuffer, BitmapAccessMode::Read); +} + +void GenPspGfxBackend::drawBitmap(const SalTwoRect& /*rPosAry*/, const SalBitmap& /*rSalBitmap*/, + const SalBitmap& /*rMaskBitmap*/) +{ + OSL_FAIL("Error: no PrinterGfx::DrawBitmap() for transparent bitmap"); +} + +void GenPspGfxBackend::drawMask(const SalTwoRect& /*rPosAry*/, const SalBitmap& /*rSalBitmap*/, + Color /*nMaskColor*/) +{ + OSL_FAIL("Error: PrinterGfx::DrawMask() not implemented"); +} + +std::shared_ptr<SalBitmap> GenPspGfxBackend::getBitmap(tools::Long /*nX*/, tools::Long /*nY*/, + tools::Long /*nWidth*/, + tools::Long /*nHeight*/) +{ + SAL_INFO("vcl", "Warning: PrinterGfx::GetBitmap() not implemented"); + return nullptr; +} + +Color GenPspGfxBackend::getPixel(tools::Long /*nX*/, tools::Long /*nY*/) +{ + OSL_FAIL("Warning: PrinterGfx::GetPixel() not implemented"); + return Color(); +} + +void GenPspGfxBackend::invert(tools::Long /*nX*/, tools::Long /*nY*/, tools::Long /*nWidth*/, + tools::Long /*nHeight*/, SalInvert /*nFlags*/) +{ + OSL_FAIL("Warning: PrinterGfx::Invert() not implemented"); +} + +void GenPspGfxBackend::invert(sal_uInt32 /*nPoints*/, const Point* /*pPtAry*/, SalInvert /*nFlags*/) +{ + SAL_WARN("vcl", "Error: PrinterGfx::Invert() not implemented"); +} + +bool GenPspGfxBackend::drawEPS(tools::Long nX, tools::Long nY, tools::Long nWidth, + tools::Long nHeight, void* pPtr, sal_uInt32 nSize) +{ + return m_pPrinterGfx->DrawEPS(tools::Rectangle(Point(nX, nY), Size(nWidth, nHeight)), pPtr, + nSize); +} + +bool GenPspGfxBackend::blendBitmap(const SalTwoRect& /*rPosAry*/, const SalBitmap& /*rBitmap*/) +{ + return false; +} + +bool GenPspGfxBackend::blendAlphaBitmap(const SalTwoRect& /*rPosAry*/, + const SalBitmap& /*rSrcBitmap*/, + const SalBitmap& /*rMaskBitmap*/, + const SalBitmap& /*rAlphaBitmap*/) +{ + return false; +} + +bool GenPspGfxBackend::drawAlphaBitmap(const SalTwoRect& /*rPosAry*/, + const SalBitmap& /*rSourceBitmap*/, + const SalBitmap& /*rAlphaBitmap*/) +{ + return false; +} + +bool GenPspGfxBackend::drawTransformedBitmap(const basegfx::B2DPoint& /*rNull*/, + const basegfx::B2DPoint& /*rX*/, + const basegfx::B2DPoint& /*rY*/, + const SalBitmap& /*rSourceBitmap*/, + const SalBitmap* /*pAlphaBitmap*/, double /*fAlpha*/) +{ + return false; +} + +bool GenPspGfxBackend::hasFastDrawTransformedBitmap() const { return false; } + +bool GenPspGfxBackend::drawAlphaRect(tools::Long /*nX*/, tools::Long /*nY*/, tools::Long /*nWidth*/, + tools::Long /*nHeight*/, sal_uInt8 /*nTransparency*/) +{ + return false; +} + +bool GenPspGfxBackend::drawGradient(const tools::PolyPolygon& /*rPolygon*/, + const Gradient& /*rGradient*/) +{ + return false; +} + +bool GenPspGfxBackend::implDrawGradient(basegfx::B2DPolyPolygon const& /*rPolyPolygon*/, + SalGradient const& /*rGradient*/) +{ + return false; +} + +bool GenPspGfxBackend::supportsOperation(OutDevSupportType /*eType*/) const { return false; } + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/generic/print/bitmap_gfx.cxx b/vcl/unx/generic/print/bitmap_gfx.cxx new file mode 100644 index 000000000..2d8649706 --- /dev/null +++ b/vcl/unx/generic/print/bitmap_gfx.cxx @@ -0,0 +1,674 @@ +/* -*- 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 <array> +#include <memory> +#include "psputil.hxx" + +#include <unx/printergfx.hxx> + +namespace psp { + +const sal_uInt32 nLineLength = 80; +const sal_uInt32 nBufferSize = 16384; + +/* + * + * Bitmap compression / Hex encoding / Ascii85 Encoding + * + */ + +PrinterBmp::~PrinterBmp() +{ +} + +/* HexEncoder */ + +namespace { + +class HexEncoder +{ +private: + + osl::File* mpFile; + sal_uInt32 mnColumn; + sal_uInt32 mnOffset; + OStringBuffer mpFileBuffer; + +public: + + explicit HexEncoder (osl::File* pFile); + ~HexEncoder (); + void WriteAscii (sal_uInt8 nByte); + void EncodeByte (sal_uInt8 nByte); + void FlushLine (); +}; + +} + +HexEncoder::HexEncoder (osl::File* pFile) : + mpFile (pFile), + mnColumn (0), + mnOffset (0) +{} + +HexEncoder::~HexEncoder () +{ + FlushLine (); + if (mnColumn > 0) + WritePS (mpFile, "\n"); +} + +void +HexEncoder::WriteAscii (sal_uInt8 nByte) +{ + sal_uInt32 nOff = psp::getHexValueOf (nByte, mpFileBuffer); + mnColumn += nOff; + mnOffset += nOff; + + if (mnColumn >= nLineLength) + { + mnOffset += psp::appendStr ("\n", mpFileBuffer); + mnColumn = 0; + } + if (mnOffset >= nBufferSize) + FlushLine (); +} + +void +HexEncoder::EncodeByte (sal_uInt8 nByte) +{ + WriteAscii (nByte); +} + +void +HexEncoder::FlushLine () +{ + if (mnOffset > 0) + { + WritePS (mpFile, mpFileBuffer.makeStringAndClear()); + mnOffset = 0; + } +} + +/* Ascii85 encoder, is abi compatible with HexEncoder but writes a ~> to + indicate end of data EOD */ + +namespace { + +class Ascii85Encoder +{ +private: + + osl::File* mpFile; + sal_uInt32 mnByte; + sal_uInt8 mpByteBuffer[4]; + + sal_uInt32 mnColumn; + sal_uInt32 mnOffset; + OStringBuffer mpFileBuffer; + + inline void PutByte (sal_uInt8 nByte); + inline void PutEOD (); + void ConvertToAscii85 (); + void FlushLine (); + +public: + + explicit Ascii85Encoder (osl::File* pFile); + virtual ~Ascii85Encoder (); + virtual void EncodeByte (sal_uInt8 nByte); + void WriteAscii (sal_uInt8 nByte); +}; + +} + +Ascii85Encoder::Ascii85Encoder (osl::File* pFile) : + mpFile (pFile), + mnByte (0), + mnColumn (0), + mnOffset (0) +{} + +inline void +Ascii85Encoder::PutByte (sal_uInt8 nByte) +{ + mpByteBuffer [mnByte++] = nByte; +} + +inline void +Ascii85Encoder::PutEOD () +{ + WritePS (mpFile, "~>\n"); +} + +void +Ascii85Encoder::ConvertToAscii85 () +{ + // Add (4 - mnByte) zero padding bytes: + if (mnByte < 4) + std::memset (mpByteBuffer + mnByte, 0, (4 - mnByte) * sizeof(sal_uInt8)); + + sal_uInt32 nByteValue = mpByteBuffer[0] * 256 * 256 * 256 + + mpByteBuffer[1] * 256 * 256 + + mpByteBuffer[2] * 256 + + mpByteBuffer[3]; + + if (nByteValue == 0 && mnByte == 4) + { + /* special case of 4 Bytes in row */ + mpFileBuffer.append('z'); + + mnOffset += 1; + mnColumn += 1; + } + else + { + /* real ascii85 encoding */ + + // Of the up to 5 characters to be generated, do not generate the last (4 - mnByte) ones + // that correspond to the (4 - mnByte) zero padding bytes added to the input: + + auto const pos = mpFileBuffer.getLength(); + if (mnByte == 4) { + mpFileBuffer.insert(pos, char((nByteValue % 85) + 33)); + } + nByteValue /= 85; + if (mnByte >= 3) { + mpFileBuffer.insert(pos, char((nByteValue % 85) + 33)); + } + nByteValue /= 85; + if (mnByte >= 2) { + mpFileBuffer.insert(pos, char((nByteValue % 85) + 33)); + } + nByteValue /= 85; + mpFileBuffer.insert(pos, char((nByteValue % 85) + 33)); + nByteValue /= 85; + mpFileBuffer.insert(pos, char((nByteValue % 85) + 33)); + + mnColumn += (mnByte + 1); + mnOffset += (mnByte + 1); + + /* insert a newline if necessary */ + if (mnColumn > nLineLength) + { + sal_uInt32 nEolOff = mnColumn - nLineLength; + auto const posNl = pos + (mnByte + 1) - nEolOff; + + mpFileBuffer.insert(posNl, '\n'); + + mnOffset++; + mnColumn = nEolOff; + } + } + + mnByte = 0; +} + +void +Ascii85Encoder::WriteAscii (sal_uInt8 nByte) +{ + PutByte (nByte); + if (mnByte == 4) + ConvertToAscii85 (); + + if (mnColumn >= nLineLength) + { + mnOffset += psp::appendStr ("\n", mpFileBuffer); + mnColumn = 0; + } + if (mnOffset >= nBufferSize) + FlushLine (); +} + +void +Ascii85Encoder::EncodeByte (sal_uInt8 nByte) +{ + WriteAscii (nByte); +} + +void +Ascii85Encoder::FlushLine () +{ + if (mnOffset > 0) + { + WritePS (mpFile, mpFileBuffer.makeStringAndClear()); + mnOffset = 0; + } +} + +Ascii85Encoder::~Ascii85Encoder () +{ + if (mnByte > 0) + ConvertToAscii85 (); + if (mnOffset > 0) + FlushLine (); + PutEOD (); +} + +/* LZW encoder */ + +namespace { + +class LZWEncoder : public Ascii85Encoder +{ +private: + + struct LZWCTreeNode + { + LZWCTreeNode* mpBrother; // next node with same parent + LZWCTreeNode* mpFirstChild; // first son + sal_uInt16 mnCode; // code for the string + sal_uInt16 mnValue; // pixelvalue + }; + + std::array<LZWCTreeNode, 4096> + maTable; // LZW compression data + LZWCTreeNode* mpPrefix; // the compression is as same as the TIFF compression + static constexpr sal_uInt16 gnDataSize = 8; + static constexpr sal_uInt16 gnClearCode = 1 << gnDataSize; + static constexpr sal_uInt16 gnEOICode = gnClearCode + 1; + sal_uInt16 mnTableSize; + sal_uInt16 mnCodeSize; + sal_uInt32 mnOffset; + sal_uInt32 mdwShift; + + void WriteBits (sal_uInt16 nCode, sal_uInt16 nCodeLen); + +public: + + explicit LZWEncoder (osl::File* pOutputFile); + virtual ~LZWEncoder () override; + + virtual void EncodeByte (sal_uInt8 nByte) override; +}; + +} + +LZWEncoder::LZWEncoder(osl::File* pOutputFile) : + Ascii85Encoder (pOutputFile), + maTable{{}}, + mpPrefix(nullptr), + mnTableSize(gnEOICode + 1), + mnCodeSize(gnDataSize + 1), + mnOffset(32), // free bits in dwShift + mdwShift(0) +{ + for (sal_uInt32 i = 0; i < 4096; i++) + { + maTable[i].mpBrother = nullptr; + maTable[i].mpFirstChild = nullptr; + maTable[i].mnCode = i; + maTable[i].mnValue = static_cast<sal_uInt8>(maTable[i].mnCode); + } + + WriteBits( gnClearCode, mnCodeSize ); +} + +LZWEncoder::~LZWEncoder() +{ + if (mpPrefix) + WriteBits (mpPrefix->mnCode, mnCodeSize); + + WriteBits (gnEOICode, mnCodeSize); +} + +void +LZWEncoder::WriteBits (sal_uInt16 nCode, sal_uInt16 nCodeLen) +{ + mdwShift |= (nCode << (mnOffset - nCodeLen)); + mnOffset -= nCodeLen; + while (mnOffset < 24) + { + WriteAscii (static_cast<sal_uInt8>(mdwShift >> 24)); + mdwShift <<= 8; + mnOffset += 8; + } + if (nCode == 257 && mnOffset != 32) + WriteAscii (static_cast<sal_uInt8>(mdwShift >> 24)); +} + +void +LZWEncoder::EncodeByte (sal_uInt8 nByte ) +{ + LZWCTreeNode* p; + sal_uInt16 i; + sal_uInt8 nV; + + if (!mpPrefix) + { + mpPrefix = maTable.data() + nByte; + } + else + { + nV = nByte; + for (p = mpPrefix->mpFirstChild; p != nullptr; p = p->mpBrother) + { + if (p->mnValue == nV) + break; + } + + if (p != nullptr) + { + mpPrefix = p; + } + else + { + WriteBits (mpPrefix->mnCode, mnCodeSize); + + if (mnTableSize == 409) + { + WriteBits (gnClearCode, mnCodeSize); + + for (i = 0; i < gnClearCode; i++) + maTable[i].mpFirstChild = nullptr; + + mnCodeSize = gnDataSize + 1; + mnTableSize = gnEOICode + 1; + } + else + { + if(mnTableSize == static_cast<sal_uInt16>((1 << mnCodeSize) - 1)) + mnCodeSize++; + + p = maTable.data() + (mnTableSize++); + p->mpBrother = mpPrefix->mpFirstChild; + mpPrefix->mpFirstChild = p; + p->mnValue = nV; + p->mpFirstChild = nullptr; + } + + mpPrefix = maTable.data() + nV; + } + } +} + +/* + * + * bitmap handling routines + * + */ + +void +PrinterGfx::DrawBitmap (const tools::Rectangle& rDest, const tools::Rectangle& rSrc, + const PrinterBmp& rBitmap) +{ + double fScaleX = static_cast<double>(rDest.GetWidth()); + double fScaleY = static_cast<double>(rDest.GetHeight()); + if(rSrc.GetWidth() > 0) + { + fScaleX = static_cast<double>(rDest.GetWidth()) / static_cast<double>(rSrc.GetWidth()); + } + if(rSrc.GetHeight() > 0) + { + fScaleY = static_cast<double>(rDest.GetHeight()) / static_cast<double>(rSrc.GetHeight()); + } + PSGSave (); + PSTranslate (rDest.BottomLeft()); + PSScale (fScaleX, fScaleY); + + if (mnPSLevel >= 2) + { + if (rBitmap.GetDepth() == 1) + { + DrawPS2MonoImage (rBitmap, rSrc); + } + else + if (rBitmap.GetDepth() == 8 && mbColor) + { + // if the palette is larger than the image itself print it as a truecolor + // image to save diskspace. This is important for printing transparent + // bitmaps that are disassembled into small pieces + sal_Int32 nImageSz = rSrc.GetWidth() * rSrc.GetHeight(); + sal_Int32 nPaletteSz = rBitmap.GetPaletteEntryCount(); + if ((nImageSz < nPaletteSz) || (nImageSz < 24) ) + DrawPS2TrueColorImage (rBitmap, rSrc); + else + DrawPS2PaletteImage (rBitmap, rSrc); + } + else + if (rBitmap.GetDepth() == 24 && mbColor) + { + DrawPS2TrueColorImage (rBitmap, rSrc); + } + else + { + DrawPS2GrayImage (rBitmap, rSrc); + } + } + else + { + DrawPS1GrayImage (rBitmap, rSrc); + } + + PSGRestore (); +} + +/* + * + * Implementation: PS Level 1 + * + */ + +void +PrinterGfx::DrawPS1GrayImage (const PrinterBmp& rBitmap, const tools::Rectangle& rArea) +{ + sal_uInt32 nWidth = rArea.GetWidth(); + sal_uInt32 nHeight = rArea.GetHeight(); + + OStringBuffer pGrayImage; + + // image header + psp::getValueOf (nWidth, pGrayImage); + psp::appendStr (" ", pGrayImage); + psp::getValueOf (nHeight, pGrayImage); + psp::appendStr (" 8 ", pGrayImage); + psp::appendStr ("[ 1 0 0 1 0 ", pGrayImage); + psp::getValueOf (nHeight, pGrayImage); + psp::appendStr ("]", pGrayImage); + psp::appendStr (" {currentfile ", pGrayImage); + psp::getValueOf (nWidth, pGrayImage); + psp::appendStr (" string readhexstring pop}\n", pGrayImage); + psp::appendStr ("image\n", pGrayImage); + + WritePS (mpPageBody, pGrayImage.makeStringAndClear()); + + // image body + HexEncoder aEncoder(mpPageBody); + + for (tools::Long nRow = rArea.Top(); nRow <= rArea.Bottom(); nRow++) + { + for (tools::Long nColumn = rArea.Left(); nColumn <= rArea.Right(); nColumn++) + { + unsigned char nByte = rBitmap.GetPixelGray (nRow, nColumn); + aEncoder.EncodeByte (nByte); + } + } + + WritePS (mpPageBody, "\n"); +} + +/* + * + * Implementation: PS Level 2 + * + */ + +void +PrinterGfx::writePS2ImageHeader (const tools::Rectangle& rArea, psp::ImageType nType) +{ + OStringBuffer pImage; + + sal_Int32 nDictType = 0; + switch (nType) + { + case psp::ImageType::TrueColorImage: nDictType = 0; break; + case psp::ImageType::PaletteImage: nDictType = 1; break; + case psp::ImageType::GrayScaleImage: nDictType = 2; break; + case psp::ImageType::MonochromeImage: nDictType = 3; break; + default: break; + } + + psp::getValueOf (rArea.GetWidth(), pImage); + psp::appendStr (" ", pImage); + psp::getValueOf (rArea.GetHeight(), pImage); + psp::appendStr (" ", pImage); + psp::getValueOf (nDictType, pImage); + psp::appendStr (" ", pImage); + psp::getValueOf (sal_Int32(1), pImage); // nCompressType + psp::appendStr (" psp_imagedict image\n", pImage); + + WritePS (mpPageBody, pImage.makeStringAndClear()); +} + +void +PrinterGfx::writePS2Colorspace(const PrinterBmp& rBitmap, psp::ImageType nType) +{ + switch (nType) + { + case psp::ImageType::GrayScaleImage: + + WritePS (mpPageBody, "/DeviceGray setcolorspace\n"); + break; + + case psp::ImageType::TrueColorImage: + + WritePS (mpPageBody, "/DeviceRGB setcolorspace\n"); + break; + + case psp::ImageType::MonochromeImage: + case psp::ImageType::PaletteImage: + { + + OStringBuffer pImage; + + const sal_uInt32 nSize = rBitmap.GetPaletteEntryCount(); + + psp::appendStr ("[/Indexed /DeviceRGB ", pImage); + psp::getValueOf (nSize - 1, pImage); + psp::appendStr ("\npsp_lzwstring\n", pImage); + WritePS (mpPageBody, pImage.makeStringAndClear()); + + LZWEncoder aEncoder(mpPageBody); + for (sal_uInt32 i = 0; i < nSize; i++) + { + PrinterColor aColor = rBitmap.GetPaletteColor(i); + + aEncoder.EncodeByte (aColor.GetRed()); + aEncoder.EncodeByte (aColor.GetGreen()); + aEncoder.EncodeByte (aColor.GetBlue()); + } + + WritePS (mpPageBody, "pop ] setcolorspace\n"); + } + break; + default: break; + } +} + +void +PrinterGfx::DrawPS2GrayImage (const PrinterBmp& rBitmap, const tools::Rectangle& rArea) +{ + writePS2Colorspace(rBitmap, psp::ImageType::GrayScaleImage); + writePS2ImageHeader(rArea, psp::ImageType::GrayScaleImage); + + LZWEncoder aEncoder(mpPageBody); + + for (tools::Long nRow = rArea.Top(); nRow <= rArea.Bottom(); nRow++) + { + for (tools::Long nColumn = rArea.Left(); nColumn <= rArea.Right(); nColumn++) + { + unsigned char nByte = rBitmap.GetPixelGray (nRow, nColumn); + aEncoder.EncodeByte (nByte); + } + } +} + +void +PrinterGfx::DrawPS2MonoImage (const PrinterBmp& rBitmap, const tools::Rectangle& rArea) +{ + writePS2Colorspace(rBitmap, psp::ImageType::MonochromeImage); + writePS2ImageHeader(rArea, psp::ImageType::MonochromeImage); + + LZWEncoder aEncoder(mpPageBody); + + for (tools::Long nRow = rArea.Top(); nRow <= rArea.Bottom(); nRow++) + { + tools::Long nBitPos = 0; + unsigned char nByte = 0; + + for (tools::Long nColumn = rArea.Left(); nColumn <= rArea.Right(); nColumn++) + { + unsigned char nBit = rBitmap.GetPixelIdx (nRow, nColumn); + nByte |= nBit << (7 - nBitPos); + + if (++nBitPos == 8) + { + aEncoder.EncodeByte (nByte); + nBitPos = 0; + nByte = 0; + } + } + // keep the row byte aligned + if (nBitPos != 0) + aEncoder.EncodeByte (nByte); + } +} + +void +PrinterGfx::DrawPS2PaletteImage (const PrinterBmp& rBitmap, const tools::Rectangle& rArea) +{ + writePS2Colorspace(rBitmap, psp::ImageType::PaletteImage); + writePS2ImageHeader(rArea, psp::ImageType::PaletteImage); + + LZWEncoder aEncoder(mpPageBody); + + for (tools::Long nRow = rArea.Top(); nRow <= rArea.Bottom(); nRow++) + { + for (tools::Long nColumn = rArea.Left(); nColumn <= rArea.Right(); nColumn++) + { + unsigned char nByte = rBitmap.GetPixelIdx (nRow, nColumn); + aEncoder.EncodeByte (nByte); + } + } +} + +void +PrinterGfx::DrawPS2TrueColorImage (const PrinterBmp& rBitmap, const tools::Rectangle& rArea) +{ + writePS2Colorspace(rBitmap, psp::ImageType::TrueColorImage); + writePS2ImageHeader(rArea, psp::ImageType::TrueColorImage); + + LZWEncoder aEncoder(mpPageBody); + + for (tools::Long nRow = rArea.Top(); nRow <= rArea.Bottom(); nRow++) + { + for (tools::Long nColumn = rArea.Left(); nColumn <= rArea.Right(); nColumn++) + { + PrinterColor aColor = rBitmap.GetPixelRGB (nRow, nColumn); + aEncoder.EncodeByte (aColor.GetRed()); + aEncoder.EncodeByte (aColor.GetGreen()); + aEncoder.EncodeByte (aColor.GetBlue()); + } + } +} + +} /* namespace psp */ + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/generic/print/common_gfx.cxx b/vcl/unx/generic/print/common_gfx.cxx new file mode 100644 index 000000000..aba50ece2 --- /dev/null +++ b/vcl/unx/generic/print/common_gfx.cxx @@ -0,0 +1,1152 @@ +/* -*- 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 "psputil.hxx" +#include "glyphset.hxx" + +#include <unx/printergfx.hxx> +#include <unx/printerjob.hxx> +#include <unx/fontmanager.hxx> +#include <strhelper.hxx> +#include <printerinfomanager.hxx> + +#include <tools/color.hxx> +#include <tools/poly.hxx> +#include <tools/stream.hxx> +#include <o3tl/string_view.hxx> + +using namespace psp ; + +const sal_Int32 nMaxTextColumn = 80; + +GraphicsStatus::GraphicsStatus() : + maEncoding(RTL_TEXTENCODING_DONTKNOW), + mbArtItalic( false ), + mbArtBold( false ), + mnTextHeight( 0 ), + mnTextWidth( 0 ), + mfLineWidth( -1 ) +{ +} + +/* + * non graphics routines + */ + +void +PrinterGfx::Init (PrinterJob &rPrinterJob) +{ + mpPageBody = rPrinterJob.GetCurrentPageBody (); + mnDepth = rPrinterJob.GetDepth (); + mnPSLevel = rPrinterJob.GetPostscriptLevel (); + mbColor = rPrinterJob.IsColorPrinter (); + + mnDpi = rPrinterJob.GetResolution(); + rPrinterJob.GetScale (mfScaleX, mfScaleY); + const PrinterInfo& rInfo( PrinterInfoManager::get().getPrinterInfo( rPrinterJob.GetPrinterName() ) ); + mbUploadPS42Fonts = rInfo.m_pParser && rInfo.m_pParser->isType42Capable(); +} + +void +PrinterGfx::Init (const JobData& rData) +{ + mpPageBody = nullptr; + mnDepth = rData.m_nColorDepth; + mnPSLevel = rData.m_nPSLevel ? rData.m_nPSLevel : (rData.m_pParser ? rData.m_pParser->getLanguageLevel() : 2 ); + mbColor = rData.m_nColorDevice ? ( rData.m_nColorDevice != -1 ) : ( rData.m_pParser == nullptr || rData.m_pParser->isColorDevice() ); + int nRes = rData.m_aContext.getRenderResolution(); + mnDpi = nRes; + mfScaleX = 72.0 / static_cast<double>(mnDpi); + mfScaleY = 72.0 / static_cast<double>(mnDpi); + const PrinterInfo& rInfo( PrinterInfoManager::get().getPrinterInfo( rData.m_aPrinterName ) ); + mbUploadPS42Fonts = rInfo.m_pParser && rInfo.m_pParser->isType42Capable(); +} + + +PrinterGfx::PrinterGfx() + : mfScaleX(0.0) + , mfScaleY(0.0) + , mnDpi(0) + , mnDepth(0) + , mnPSLevel(0) + , mbColor(false) + , mbUploadPS42Fonts(false) + , mpPageBody(nullptr) + , mnFontID(0) + , mnTextAngle(0) + , mbTextVertical(false) + , mrFontMgr(PrintFontManager::get()) + , maFillColor(0xff,0,0) + , maTextColor(0,0,0) + , maLineColor(0, 0xff, 0) +{ + maVirtualStatus.mfLineWidth = 1.0; + maVirtualStatus.mnTextHeight = 12; + maVirtualStatus.mnTextWidth = 0; + + maGraphicsStack.emplace_back( ); +} + +PrinterGfx::~PrinterGfx() +{ +} + +void +PrinterGfx::Clear() +{ + mpPageBody = nullptr; + mnFontID = 0; + maVirtualStatus = GraphicsStatus(); + maVirtualStatus.mnTextHeight = 12; + maVirtualStatus.mnTextWidth = 0; + maVirtualStatus.mfLineWidth = 1.0; + mbTextVertical = false; + maLineColor = PrinterColor(); + maFillColor = PrinterColor(); + maTextColor = PrinterColor(); + mnDpi = 300; + mnDepth = 24; + mnPSLevel = 2; + mbColor = true; + mnTextAngle = 0_deg10; + + maClipRegion.clear(); + maGraphicsStack.clear(); + maGraphicsStack.emplace_back( ); +} + +/* + * clip region handling + */ + +void +PrinterGfx::ResetClipRegion() +{ + maClipRegion.clear(); + PSGRestore (); + PSGSave (); // get "clean" clippath +} + +void +PrinterGfx::BeginSetClipRegion() +{ + maClipRegion.clear(); +} + +void +PrinterGfx::UnionClipRegion (sal_Int32 nX,sal_Int32 nY,sal_Int32 nDX,sal_Int32 nDY) +{ + if( nDX && nDY ) + maClipRegion.emplace_back(Point(nX,nY ), Size(nDX,nDY)); +} + +bool +PrinterGfx::JoinVerticalClipRectangles( std::list< tools::Rectangle >::iterator& it, + Point& rOldPoint, sal_Int32& rColumn ) +{ + bool bSuccess = false; + + std::list< tools::Rectangle >::iterator tempit, nextit; + nextit = it; + ++nextit; + std::list< Point > leftside, rightside; + + tools::Rectangle aLastRect( *it ); + leftside.emplace_back( it->Left(), it->Top() ); + rightside.emplace_back( it->Right()+1, it->Top() ); + while( nextit != maClipRegion.end() ) + { + tempit = nextit; + ++tempit; + if( nextit->Top() == aLastRect.Bottom()+1 ) + { + if( + ( nextit->Left() >= aLastRect.Left() && nextit->Left() <= aLastRect.Right() ) // left endpoint touches last rectangle + || + ( nextit->Right() >= aLastRect.Left() && nextit->Right() <= aLastRect.Right() ) // right endpoint touches last rectangle + || + ( nextit->Left() <= aLastRect.Left() && nextit->Right() >= aLastRect.Right() ) // whole line touches last rectangle + ) + { + if( aLastRect.GetHeight() > 1 || + std::abs( aLastRect.Left() - nextit->Left() ) > 2 || + std::abs( aLastRect.Right() - nextit->Right() ) > 2 + ) + { + leftside.emplace_back( aLastRect.Left(), aLastRect.Bottom()+1 ); + rightside.emplace_back( aLastRect.Right()+1, aLastRect.Bottom()+1 ); + } + aLastRect = *nextit; + leftside.push_back( aLastRect.TopLeft() ); + rightside.push_back( aLastRect.TopRight() ); + maClipRegion.erase( nextit ); + } + } + nextit = tempit; + } + if( leftside.size() > 1 ) + { + // push the last coordinates + leftside.emplace_back( aLastRect.Left(), aLastRect.Bottom()+1 ); + rightside.emplace_back( aLastRect.Right()+1, aLastRect.Bottom()+1 ); + + // cool, we can concatenate rectangles + const int nDX = -65536, nDY = 65536; + int nNewDX = 0, nNewDY = 0; + + Point aLastPoint = leftside.front(); + PSBinMoveTo (aLastPoint, rOldPoint, rColumn); + leftside.pop_front(); + while( !leftside.empty() ) + { + Point aPoint (leftside.front()); + leftside.pop_front(); + // may have been the last one + if( !leftside.empty() ) + { + nNewDX = aPoint.X() - aLastPoint.X(); + nNewDY = aPoint.Y() - aLastPoint.Y(); + if( nNewDX != 0 && + static_cast<double>(nNewDY)/static_cast<double>(nNewDX) == double(nDY)/double(nDX) ) + continue; + } + PSBinLineTo (aPoint, rOldPoint, rColumn); + aLastPoint = aPoint; + } + + aLastPoint = rightside.back(); + PSBinLineTo (aLastPoint, rOldPoint, rColumn); + rightside.pop_back(); + while( !rightside.empty() ) + { + Point aPoint (rightside.back()); + rightside.pop_back(); + if( !rightside.empty() ) + { + nNewDX = aPoint.X() - aLastPoint.X(); + nNewDY = aPoint.Y() - aLastPoint.Y(); + if( nNewDX != 0 && + static_cast<double>(nNewDY)/static_cast<double>(nNewDX) == double(nDY)/double(nDX) ) + continue; + } + PSBinLineTo (aPoint, rOldPoint, rColumn); + } + + tempit = it; + ++tempit; + maClipRegion.erase( it ); + it = tempit; + bSuccess = true; + } + return bSuccess; +} + +void +PrinterGfx::EndSetClipRegion() +{ + PSGRestore (); + PSGSave (); // get "clean" clippath + + PSBinStartPath (); + Point aOldPoint (0, 0); + sal_Int32 nColumn = 0; + + std::list< tools::Rectangle >::iterator it = maClipRegion.begin(); + while( it != maClipRegion.end() ) + { + // try to concatenate adjacent rectangles + // first try in y direction, then in x direction + if( ! JoinVerticalClipRectangles( it, aOldPoint, nColumn ) ) + { + // failed, so it is a single rectangle + PSBinMoveTo (Point( it->Left()-1, it->Top()-1), aOldPoint, nColumn ); + PSBinLineTo (Point( it->Left()-1, it->Bottom()+1 ), aOldPoint, nColumn ); + PSBinLineTo (Point( it->Right()+1, it->Bottom()+1 ), aOldPoint, nColumn ); + PSBinLineTo (Point( it->Right()+1, it->Top()-1 ), aOldPoint, nColumn ); + ++it; + } + } + + PSBinEndPath (); + + WritePS (mpPageBody, "closepath clip newpath\n"); + maClipRegion.clear(); +} + +/* + * draw graphic primitives + */ + +void +PrinterGfx::DrawRect (const tools::Rectangle& rRectangle ) +{ + OStringBuffer pRect; + + psp::getValueOf (rRectangle.Left(), pRect); + psp::appendStr (" ", pRect); + psp::getValueOf (rRectangle.Top(), pRect); + psp::appendStr (" ", pRect); + psp::getValueOf (rRectangle.GetWidth(), pRect); + psp::appendStr (" ", pRect); + psp::getValueOf (rRectangle.GetHeight(), pRect); + psp::appendStr (" ", pRect); + auto const rect = pRect.makeStringAndClear(); + + if( maFillColor.Is() ) + { + PSSetColor (maFillColor); + PSSetColor (); + WritePS (mpPageBody, rect); + WritePS (mpPageBody, "rectfill\n"); + } + if( maLineColor.Is() ) + { + PSSetColor (maLineColor); + PSSetColor (); + PSSetLineWidth (); + WritePS (mpPageBody, rect); + WritePS (mpPageBody, "rectstroke\n"); + } +} + +void +PrinterGfx::DrawLine (const Point& rFrom, const Point& rTo) +{ + if( maLineColor.Is() ) + { + PSSetColor (maLineColor); + PSSetColor (); + PSSetLineWidth (); + + PSMoveTo (rFrom); + PSLineTo (rTo); + WritePS (mpPageBody, "stroke\n" ); + } +} + +void +PrinterGfx::DrawPixel (const Point& rPoint, const PrinterColor& rPixelColor) +{ + if( rPixelColor.Is() ) + { + PSSetColor (rPixelColor); + PSSetColor (); + + PSMoveTo (rPoint); + PSLineTo (Point (rPoint.X ()+1, rPoint.Y ())); + PSLineTo (Point (rPoint.X ()+1, rPoint.Y ()+1)); + PSLineTo (Point (rPoint.X (), rPoint.Y ()+1)); + WritePS (mpPageBody, "fill\n" ); + } +} + +void +PrinterGfx::DrawPolyLine (sal_uInt32 nPoints, const Point* pPath) +{ + if( maLineColor.Is() && nPoints && pPath ) + { + PSSetColor (maLineColor); + PSSetColor (); + PSSetLineWidth (); + + PSBinCurrentPath (nPoints, pPath); + + WritePS (mpPageBody, "stroke\n" ); + } +} + +void +PrinterGfx::DrawPolygon (sal_uInt32 nPoints, const Point* pPath) +{ + // premature end of operation + if (nPoints <= 0 || (pPath == nullptr) || !(maFillColor.Is() || maLineColor.Is())) + return; + + // setup closed path + Point aPoint( 0, 0 ); + sal_Int32 nColumn( 0 ); + + PSBinStartPath(); + PSBinMoveTo( pPath[0], aPoint, nColumn ); + for( unsigned int n = 1; n < nPoints; n++ ) + PSBinLineTo( pPath[n], aPoint, nColumn ); + if( pPath[0] != pPath[nPoints-1] ) + PSBinLineTo( pPath[0], aPoint, nColumn ); + PSBinEndPath(); + + // fill the polygon first, then draw the border, note that fill and + // stroke reset the currentpath + + // if fill and stroke, save the current path + if( maFillColor.Is() && maLineColor.Is()) + PSGSave(); + + if (maFillColor.Is ()) + { + PSSetColor (maFillColor); + PSSetColor (); + WritePS (mpPageBody, "eofill\n"); + } + + // restore the current path + if( maFillColor.Is() && maLineColor.Is()) + PSGRestore(); + + if (maLineColor.Is ()) + { + PSSetColor (maLineColor); + PSSetColor (); + PSSetLineWidth (); + WritePS (mpPageBody, "stroke\n"); + } +} + +void +PrinterGfx::DrawPolyPolygon (sal_uInt32 nPoly, const sal_uInt32* pSizes, const Point** pPaths ) +{ + // sanity check + if ( !nPoly || !pPaths || !(maFillColor.Is() || maLineColor.Is())) + return; + + // setup closed path + for( unsigned int i = 0; i < nPoly; i++ ) + { + Point aPoint( 0, 0 ); + sal_Int32 nColumn( 0 ); + + PSBinStartPath(); + PSBinMoveTo( pPaths[i][0], aPoint, nColumn ); + for( unsigned int n = 1; n < pSizes[i]; n++ ) + PSBinLineTo( pPaths[i][n], aPoint, nColumn ); + if( pPaths[i][0] != pPaths[i][pSizes[i]-1] ) + PSBinLineTo( pPaths[i][0], aPoint, nColumn ); + PSBinEndPath(); + } + + // if eofill and stroke, save the current path + if( maFillColor.Is() && maLineColor.Is()) + PSGSave(); + + // first draw area + if( maFillColor.Is() ) + { + PSSetColor (maFillColor); + PSSetColor (); + WritePS (mpPageBody, "eofill\n"); + } + + // restore the current path + if( maFillColor.Is() && maLineColor.Is()) + PSGRestore(); + + // now draw outlines + if( maLineColor.Is() ) + { + PSSetColor (maLineColor); + PSSetColor (); + PSSetLineWidth (); + WritePS (mpPageBody, "stroke\n"); + } +} + +/* + * Bezier Polygon Drawing methods. + */ + +void +PrinterGfx::DrawPolyLineBezier (sal_uInt32 nPoints, const Point* pPath, const PolyFlags* pFlgAry) +{ + const sal_uInt32 nBezString= 1024; + char pString[nBezString]; + + if ( nPoints <= 1 || !maLineColor.Is() || !pPath ) + return; + + PSSetColor (maLineColor); + PSSetColor (); + PSSetLineWidth (); + + snprintf(pString, nBezString, "%" SAL_PRIdINT64 " %" SAL_PRIdINT64 " moveto\n", sal_Int64(pPath[0].X()), sal_Int64(pPath[0].Y())); + WritePS(mpPageBody, pString); + + // Handle the drawing of mixed lines mixed with curves + // - a normal point followed by a normal point is a line + // - a normal point followed by 2 control points and a normal point is a curve + for (unsigned int i=1; i<nPoints;) + { + if (pFlgAry[i] != PolyFlags::Control) //If the next point is a PolyFlags::Normal, we're drawing a line + { + snprintf(pString, nBezString, "%" SAL_PRIdINT64 " %" SAL_PRIdINT64 " lineto\n", sal_Int64(pPath[i].X()), sal_Int64(pPath[i].Y())); + i++; + } + else //Otherwise we're drawing a spline + { + if (i+2 >= nPoints) + return; //Error: wrong sequence of control/normal points somehow + if ((pFlgAry[i] == PolyFlags::Control) && (pFlgAry[i+1] == PolyFlags::Control) && + (pFlgAry[i+2] != PolyFlags::Control)) + { + snprintf(pString, nBezString, "%" SAL_PRIdINT64 " %" SAL_PRIdINT64 " %" SAL_PRIdINT64 " %" SAL_PRIdINT64 " %" SAL_PRIdINT64 " %" SAL_PRIdINT64 " curveto\n", + sal_Int64(pPath[i].X()), sal_Int64(pPath[i].Y()), + sal_Int64(pPath[i+1].X()), sal_Int64(pPath[i+1].Y()), + sal_Int64(pPath[i+2].X()), sal_Int64(pPath[i+2].Y())); + } + else + { + OSL_FAIL( "PrinterGfx::DrawPolyLineBezier: Strange output" ); + } + i+=3; + } + WritePS(mpPageBody, pString); + } + + // now draw outlines + WritePS (mpPageBody, "stroke\n"); +} + +void +PrinterGfx::DrawPolygonBezier (sal_uInt32 nPoints, const Point* pPath, const PolyFlags* pFlgAry) +{ + const sal_uInt32 nBezString = 1024; + char pString[nBezString]; + // premature end of operation + if (nPoints <= 0 || (pPath == nullptr) || !(maFillColor.Is() || maLineColor.Is())) + return; + + snprintf(pString, nBezString, "%" SAL_PRIdINT64 " %" SAL_PRIdINT64 " moveto\n", sal_Int64(pPath[0].X()), sal_Int64(pPath[0].Y())); + WritePS(mpPageBody, pString); //Move to the starting point for the PolyPolygon + for (unsigned int i=1; i < nPoints;) + { + if (pFlgAry[i] != PolyFlags::Control) + { + snprintf(pString, nBezString, "%" SAL_PRIdINT64 " %" SAL_PRIdINT64 " lineto\n", + sal_Int64(pPath[i].X()), sal_Int64(pPath[i].Y())); + WritePS(mpPageBody, pString); + i++; + } + else + { + if (i+2 >= nPoints) + return; //Error: wrong sequence of control/normal points somehow + if ((pFlgAry[i] == PolyFlags::Control) && (pFlgAry[i+1] == PolyFlags::Control) && + (pFlgAry[i+2] != PolyFlags::Control)) + { + snprintf(pString, nBezString, "%" SAL_PRIdINT64 " %" SAL_PRIdINT64 " %" SAL_PRIdINT64 " %" SAL_PRIdINT64 " %" SAL_PRIdINT64 " %" SAL_PRIdINT64 " curveto\n", + sal_Int64(pPath[i].X()), sal_Int64(pPath[i].Y()), + sal_Int64(pPath[i+1].X()), sal_Int64(pPath[i+1].Y()), + sal_Int64(pPath[i+2].X()), sal_Int64(pPath[i+2].Y())); + WritePS(mpPageBody, pString); + } + else + { + OSL_FAIL( "PrinterGfx::DrawPolygonBezier: Strange output" ); + } + i+=3; + } + } + + // if fill and stroke, save the current path + if( maFillColor.Is() && maLineColor.Is()) + PSGSave(); + + if (maFillColor.Is ()) + { + PSSetColor (maFillColor); + PSSetColor (); + WritePS (mpPageBody, "eofill\n"); + } + + // restore the current path + if( maFillColor.Is() && maLineColor.Is()) + PSGRestore(); +} + +void +PrinterGfx::DrawPolyPolygonBezier (sal_uInt32 nPoly, const sal_uInt32 * pPoints, const Point* const * pPtAry, const PolyFlags* const* pFlgAry) +{ + const sal_uInt32 nBezString = 1024; + char pString[nBezString]; + if ( !nPoly || !pPtAry || !pPoints || !(maFillColor.Is() || maLineColor.Is())) + return; + + for (unsigned int i=0; i<nPoly;i++) + { + sal_uInt32 nPoints = pPoints[i]; + // sanity check + if( nPoints == 0 || pPtAry[i] == nullptr ) + continue; + + snprintf(pString, nBezString, "%" SAL_PRIdINT64 " %" SAL_PRIdINT64 " moveto\n", + sal_Int64(pPtAry[i][0].X()), sal_Int64(pPtAry[i][0].Y())); //Move to the starting point + WritePS(mpPageBody, pString); + for (unsigned int j=1; j < nPoints;) + { + // if no flag array exists for this polygon, then it must be a regular + // polygon without beziers + if ( ! pFlgAry[i] || pFlgAry[i][j] != PolyFlags::Control) + { + snprintf(pString, nBezString, "%" SAL_PRIdINT64 " %" SAL_PRIdINT64 " lineto\n", + sal_Int64(pPtAry[i][j].X()), sal_Int64(pPtAry[i][j].Y())); + WritePS(mpPageBody, pString); + j++; + } + else + { + if (j+2 >= nPoints) + break; //Error: wrong sequence of control/normal points somehow + if ((pFlgAry[i][j] == PolyFlags::Control) && (pFlgAry[i][j+1] == PolyFlags::Control) && (pFlgAry[i][j+2] != PolyFlags::Control)) + { + snprintf(pString, nBezString, "%" SAL_PRIdINT64 " %" SAL_PRIdINT64 " %" SAL_PRIdINT64 " %" SAL_PRIdINT64 " %" SAL_PRIdINT64 " %" SAL_PRIdINT64 " curveto\n", + sal_Int64(pPtAry[i][j].X()), sal_Int64(pPtAry[i][j].Y()), + sal_Int64(pPtAry[i][j+1].X()), sal_Int64(pPtAry[i][j+1].Y()), + sal_Int64(pPtAry[i][j+2].X()), sal_Int64(pPtAry[i][j+2].Y())); + WritePS(mpPageBody, pString); + } + else + { + OSL_FAIL( "PrinterGfx::DrawPolyPolygonBezier: Strange output" ); + } + j+=3; + } + } + } + + // if fill and stroke, save the current path + if( maFillColor.Is() && maLineColor.Is()) + PSGSave(); + + if (maFillColor.Is ()) + { + PSSetColor (maFillColor); + PSSetColor (); + WritePS (mpPageBody, "eofill\n"); + } + + // restore the current path + if( maFillColor.Is() && maLineColor.Is()) + PSGRestore(); +} + +/* + * postscript generating routines + */ +void +PrinterGfx::PSGSave () +{ + WritePS (mpPageBody, "gsave\n" ); + GraphicsStatus aNewState; + if( !maGraphicsStack.empty() ) + aNewState = maGraphicsStack.front(); + maGraphicsStack.push_front( aNewState ); +} + +void +PrinterGfx::PSGRestore () +{ + WritePS (mpPageBody, "grestore\n" ); + if( maGraphicsStack.empty() ) + WritePS (mpPageBody, "Error: too many grestores\n" ); + else + maGraphicsStack.pop_front(); +} + +void +PrinterGfx::PSSetLineWidth () +{ + if( currentState().mfLineWidth != maVirtualStatus.mfLineWidth ) + { + OStringBuffer pBuffer; + + currentState().mfLineWidth = maVirtualStatus.mfLineWidth; + psp::getValueOfDouble (pBuffer, maVirtualStatus.mfLineWidth, 5); + psp::appendStr (" setlinewidth\n", pBuffer); + WritePS (mpPageBody, pBuffer.makeStringAndClear()); + } +} + +void +PrinterGfx::PSSetColor () +{ + PrinterColor& rColor( maVirtualStatus.maColor ); + + if( currentState().maColor == rColor ) + return; + + currentState().maColor = rColor; + + OStringBuffer pBuffer; + + if( mbColor ) + { + psp::getValueOfDouble (pBuffer, + static_cast<double>(rColor.GetRed()) / 255.0, 5); + psp::appendStr (" ", pBuffer); + psp::getValueOfDouble (pBuffer, + static_cast<double>(rColor.GetGreen()) / 255.0, 5); + psp::appendStr (" ", pBuffer); + psp::getValueOfDouble (pBuffer, + static_cast<double>(rColor.GetBlue()) / 255.0, 5); + psp::appendStr (" setrgbcolor\n", pBuffer ); + } + else + { + Color aColor( rColor.GetRed(), rColor.GetGreen(), rColor.GetBlue() ); + sal_uInt8 nCol = aColor.GetLuminance(); + psp::getValueOfDouble( pBuffer, static_cast<double>(nCol) / 255.0, 5 ); + psp::appendStr( " setgray\n", pBuffer ); + } + + WritePS (mpPageBody, pBuffer.makeStringAndClear()); +} + +void +PrinterGfx::PSSetFont () +{ + GraphicsStatus& rCurrent( currentState() ); + if( !(maVirtualStatus.maFont != rCurrent.maFont || + maVirtualStatus.mnTextHeight != rCurrent.mnTextHeight || + maVirtualStatus.maEncoding != rCurrent.maEncoding || + maVirtualStatus.mnTextWidth != rCurrent.mnTextWidth || + maVirtualStatus.mbArtBold != rCurrent.mbArtBold || + maVirtualStatus.mbArtItalic != rCurrent.mbArtItalic) + ) + return; + + rCurrent.maFont = maVirtualStatus.maFont; + rCurrent.maEncoding = maVirtualStatus.maEncoding; + rCurrent.mnTextWidth = maVirtualStatus.mnTextWidth; + rCurrent.mnTextHeight = maVirtualStatus.mnTextHeight; + rCurrent.mbArtItalic = maVirtualStatus.mbArtItalic; + rCurrent.mbArtBold = maVirtualStatus.mbArtBold; + + sal_Int32 nTextHeight = rCurrent.mnTextHeight; + sal_Int32 nTextWidth = rCurrent.mnTextWidth ? rCurrent.mnTextWidth + : rCurrent.mnTextHeight; + + OStringBuffer pSetFont; + + // postscript based fonts need reencoding + if ( ( rCurrent.maEncoding == RTL_TEXTENCODING_MS_1252) + || ( rCurrent.maEncoding == RTL_TEXTENCODING_ISO_8859_1) + || ( rCurrent.maEncoding >= RTL_TEXTENCODING_USER_START + && rCurrent.maEncoding <= RTL_TEXTENCODING_USER_END) + ) + { + OString aReencodedFont = + psp::GlyphSet::GetReencodedFontName (rCurrent.maEncoding, + rCurrent.maFont); + + psp::appendStr ("(", pSetFont); + psp::appendStr (aReencodedFont.getStr(), + pSetFont); + psp::appendStr (") cvn findfont ", + pSetFont); + } + else + // tt based fonts mustn't reencode, the encoding is implied by the fontname + // same for symbol type1 fonts, don't try to touch them + { + psp::appendStr ("(", pSetFont); + psp::appendStr (rCurrent.maFont.getStr(), + pSetFont); + psp::appendStr (") cvn findfont ", + pSetFont); + } + + if( ! rCurrent.mbArtItalic ) + { + psp::getValueOf (nTextWidth, pSetFont); + psp::appendStr (" ", pSetFont); + psp::getValueOf (-nTextHeight, pSetFont); + psp::appendStr (" matrix scale makefont setfont\n", pSetFont); + } + else // skew 15 degrees to right + { + psp::appendStr ( " [", pSetFont); + psp::getValueOf (nTextWidth, pSetFont); + psp::appendStr (" 0 ", pSetFont); + psp::getValueOfDouble (pSetFont, 0.27*static_cast<double>(nTextWidth), 3 ); + psp::appendStr ( " ", pSetFont); + psp::getValueOf (-nTextHeight, pSetFont); + + psp::appendStr (" 0 0] makefont setfont\n", pSetFont); + } + + WritePS (mpPageBody, pSetFont.makeStringAndClear()); + +} + +void +PrinterGfx::PSRotate (Degree10 nAngle) +{ + sal_Int32 nPostScriptAngle = -nAngle.get(); + while( nPostScriptAngle < 0 ) + nPostScriptAngle += 3600; + + if (nPostScriptAngle == 0) + return; + + sal_Int32 nFullAngle = nPostScriptAngle / 10; + sal_Int32 nTenthAngle = nPostScriptAngle % 10; + + OStringBuffer pRotate; + + psp::getValueOf (nFullAngle, pRotate); + psp::appendStr (".", pRotate); + psp::getValueOf (nTenthAngle, pRotate); + psp::appendStr (" rotate\n", pRotate); + + WritePS (mpPageBody, pRotate.makeStringAndClear()); +} + +void +PrinterGfx::PSPointOp (const Point& rPoint, const char* pOperator) +{ + OStringBuffer pPSCommand; + + psp::getValueOf (rPoint.X(), pPSCommand); + psp::appendStr (" ", pPSCommand); + psp::getValueOf (rPoint.Y(), pPSCommand); + psp::appendStr (" ", pPSCommand); + psp::appendStr (pOperator, pPSCommand); + psp::appendStr ("\n", pPSCommand); + + WritePS (mpPageBody, pPSCommand.makeStringAndClear()); +} + +void +PrinterGfx::PSTranslate (const Point& rPoint) +{ + PSPointOp (rPoint, "translate"); +} + +void +PrinterGfx::PSMoveTo (const Point& rPoint) +{ + PSPointOp (rPoint, "moveto"); +} + +void +PrinterGfx::PSLineTo (const Point& rPoint) +{ + PSPointOp (rPoint, "lineto"); +} + +/* get a compressed representation of the path information */ + +#define DEBUG_BINPATH 0 + +void +PrinterGfx::PSBinLineTo (const Point& rCurrent, Point& rOld, sal_Int32& nColumn) +{ +#if (DEBUG_BINPATH == 1) + PSLineTo (rCurrent); +#else + PSBinPath (rCurrent, rOld, lineto, nColumn); +#endif +} + +void +PrinterGfx::PSBinMoveTo (const Point& rCurrent, Point& rOld, sal_Int32& nColumn) +{ +#if (DEBUG_BINPATH == 1) + PSMoveTo (rCurrent); +#else + PSBinPath (rCurrent, rOld, moveto, nColumn); +#endif +} + +void +PrinterGfx::PSBinStartPath () +{ +#if (DEBUG_BINPATH == 1) + WritePS (mpPageBody, "% PSBinStartPath\n"); +#else + WritePS (mpPageBody, "readpath\n" ); +#endif +} + +void +PrinterGfx::PSBinEndPath () +{ +#if (DEBUG_BINPATH == 1) + WritePS (mpPageBody, "% PSBinEndPath\n"); +#else + WritePS (mpPageBody, "~\n"); +#endif +} + +void +PrinterGfx::PSBinCurrentPath (sal_uInt32 nPoints, const Point* pPath) +{ + // create the path + Point aPoint (0, 0); + sal_Int32 nColumn = 0; + + PSBinStartPath (); + PSBinMoveTo (*pPath, aPoint, nColumn); + for (unsigned int i = 1; i < nPoints; i++) + PSBinLineTo (pPath[i], aPoint, nColumn); + PSBinEndPath (); +} + +void +PrinterGfx::PSBinPath (const Point& rCurrent, Point& rOld, + pspath_t eType, sal_Int32& nColumn) +{ + OStringBuffer pPath; + sal_Int32 nChar; + + // create the hex representation of the dx and dy path shift, store the field + // width as it is needed for the building the command + sal_Int32 nXPrec = getAlignedHexValueOf (rCurrent.X() - rOld.X(), pPath); + sal_Int32 nYPrec = getAlignedHexValueOf (rCurrent.Y() - rOld.Y(), pPath); + + // build the command, it is a char with bit representation 000cxxyy + // c represents the char, xx and yy repr. the field width of the dx and dy shift, + // dx and dy represent the number of bytes to read after the opcode + char cCmd = (eType == lineto ? char(0x00) : char(0x10)); + switch (nYPrec) + { + case 2: break; + case 4: cCmd |= 0x01; break; + case 6: cCmd |= 0x02; break; + case 8: cCmd |= 0x03; break; + default: OSL_FAIL("invalid x precision in binary path"); + } + switch (nXPrec) + { + case 2: break; + case 4: cCmd |= 0x04; break; + case 6: cCmd |= 0x08; break; + case 8: cCmd |= 0x0c; break; + default: OSL_FAIL("invalid y precision in binary path"); + } + cCmd += 'A'; + pPath.insert(0, cCmd); + auto const path = pPath.makeStringAndClear(); + + // write the command to file, + // line breaking at column nMaxTextColumn (80) + nChar = 1 + nXPrec + nYPrec; + if ((nColumn + nChar) > nMaxTextColumn) + { + sal_Int32 nSegment = nMaxTextColumn - nColumn; + + WritePS (mpPageBody, path.copy(0, nSegment)); + WritePS (mpPageBody, "\n", 1); + WritePS (mpPageBody, path.copy(nSegment)); + + nColumn = nChar - nSegment; + } + else + { + WritePS (mpPageBody, path); + + nColumn += nChar; + } + + rOld = rCurrent; +} + +void +PrinterGfx::PSScale (double fScaleX, double fScaleY) +{ + OStringBuffer pScale; + + psp::getValueOfDouble (pScale, fScaleX, 5); + psp::appendStr (" ", pScale); + psp::getValueOfDouble (pScale, fScaleY, 5); + psp::appendStr (" scale\n", pScale); + + WritePS (mpPageBody, pScale.makeStringAndClear()); +} + +/* psshowtext helper routines: draw an hex string for show/xshow */ +void +PrinterGfx::PSHexString (const unsigned char* pString, sal_Int16 nLen) +{ + OStringBuffer pHexString; + sal_Int32 nChar = psp::appendStr ("<", pHexString); + for (int i = 0; i < nLen; i++) + { + if (nChar >= (nMaxTextColumn - 1)) + { + psp::appendStr ("\n", pHexString); + WritePS (mpPageBody, pHexString.makeStringAndClear()); + nChar = 0; + } + nChar += psp::getHexValueOf (static_cast<sal_Int32>(pString[i]), pHexString); + } + + psp::appendStr (">\n", pHexString); + WritePS (mpPageBody, pHexString.makeStringAndClear()); +} + +void +PrinterGfx::PSShowGlyph (const unsigned char nGlyphId) +{ + PSSetColor (maTextColor); + PSSetColor (); + PSSetFont (); + // rotate the user coordinate system + if (mnTextAngle) + { + PSGSave (); + PSRotate (mnTextAngle); + } + + char pBuffer[256]; + if( maVirtualStatus.mbArtBold ) + { + sal_Int32 nLW = maVirtualStatus.mnTextWidth; + if( nLW == 0 ) + nLW = maVirtualStatus.mnTextHeight; + else + nLW = std::min(nLW, maVirtualStatus.mnTextHeight); + psp::getValueOfDouble( pBuffer, static_cast<double>(nLW) / 30.0 ); + } + + // dispatch to the drawing method + PSHexString (&nGlyphId, 1); + + if( maVirtualStatus.mbArtBold ) + { + WritePS( mpPageBody, pBuffer ); + WritePS( mpPageBody, " bshow\n" ); + } + else + WritePS (mpPageBody, "show\n"); + + // restore the user coordinate system + if (mnTextAngle) + PSGRestore (); +} + +bool +PrinterGfx::DrawEPS( const tools::Rectangle& rBoundingBox, void* pPtr, sal_uInt32 nSize ) +{ + if( nSize == 0 ) + return true; + if( ! mpPageBody ) + return false; + + bool bSuccess = false; + + // first search the BoundingBox of the EPS data + SvMemoryStream aStream( pPtr, nSize, StreamMode::READ ); + aStream.Seek( STREAM_SEEK_TO_BEGIN ); + OString aLine; + + OString aDocTitle; + double fLeft = 0, fRight = 0, fTop = 0, fBottom = 0; + bool bEndComments = false; + while( ! aStream.eof() + && ( ( fLeft == 0 && fRight == 0 && fTop == 0 && fBottom == 0 ) || + ( aDocTitle.isEmpty() && !bEndComments ) ) + ) + { + aStream.ReadLine( aLine ); + if( aLine.getLength() > 1 && aLine[0] == '%' ) + { + char cChar = aLine[1]; + if( cChar == '%' ) + { + if( aLine.matchIgnoreAsciiCase( "%%BoundingBox:" ) ) + { + aLine = WhitespaceToSpace( o3tl::getToken(aLine, 1, ':') ); + if( !aLine.isEmpty() && aLine.indexOf( "atend" ) == -1 ) + { + fLeft = StringToDouble( GetCommandLineToken( 0, aLine ) ); + fBottom = StringToDouble( GetCommandLineToken( 1, aLine ) ); + fRight = StringToDouble( GetCommandLineToken( 2, aLine ) ); + fTop = StringToDouble( GetCommandLineToken( 3, aLine ) ); + } + } + else if( aLine.matchIgnoreAsciiCase( "%%Title:" ) ) + aDocTitle = WhitespaceToSpace( aLine.subView( 8 ) ); + else if( aLine.matchIgnoreAsciiCase( "%%EndComments" ) ) + bEndComments = true; + } + else if( cChar == ' ' || cChar == '\t' || cChar == '\r' || cChar == '\n' ) + bEndComments = true; + } + else + bEndComments = true; + } + + static sal_uInt16 nEps = 0; + if( aDocTitle.isEmpty() ) + aDocTitle = OString::number(nEps++); + + if( fLeft != fRight && fTop != fBottom ) + { + double fScaleX = static_cast<double>(rBoundingBox.GetWidth())/(fRight-fLeft); + double fScaleY = -static_cast<double>(rBoundingBox.GetHeight())/(fTop-fBottom); + Point aTranslatePoint( static_cast<int>(rBoundingBox.Left()-fLeft*fScaleX), + static_cast<int>(rBoundingBox.Bottom()+1-fBottom*fScaleY) ); + // prepare EPS + WritePS( mpPageBody, + "/b4_Inc_state save def\n" + "/dict_count countdictstack def\n" + "/op_count count 1 sub def\n" + "userdict begin\n" + "/showpage {} def\n" + "0 setgray 0 setlinecap 1 setlinewidth 0 setlinejoin\n" + "10 setmiterlimit [] 0 setdash newpath\n" + "/languagelevel where\n" + "{pop languagelevel\n" + "1 ne\n" + " {false setstrokeadjust false setoverprint\n" + " } if\n" + "}if\n" ); + // set up clip path and scale + BeginSetClipRegion(); + UnionClipRegion( rBoundingBox.Left(), rBoundingBox.Top(), rBoundingBox.GetWidth(), rBoundingBox.GetHeight() ); + EndSetClipRegion(); + PSTranslate( aTranslatePoint ); + PSScale( fScaleX, fScaleY ); + + // DSC requires BeginDocument + WritePS( mpPageBody, "%%BeginDocument: " ); + WritePS( mpPageBody, aDocTitle ); + WritePS( mpPageBody, "\n" ); + + // write the EPS data + sal_uInt64 nOutLength; + mpPageBody->write( pPtr, nSize, nOutLength ); + bSuccess = nOutLength == nSize; + + // corresponding EndDocument + if( static_cast<char*>(pPtr)[ nSize-1 ] != '\n' ) + WritePS( mpPageBody, "\n" ); + WritePS( mpPageBody, "%%EndDocument\n" ); + + // clean up EPS + WritePS( mpPageBody, + "count op_count sub {pop} repeat\n" + "countdictstack dict_count sub {end} repeat\n" + "b4_Inc_state restore\n" ); + } + return bSuccess; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/generic/print/genprnpsp.cxx b/vcl/unx/generic/print/genprnpsp.cxx new file mode 100644 index 000000000..b84ba0bef --- /dev/null +++ b/vcl/unx/generic/print/genprnpsp.cxx @@ -0,0 +1,1298 @@ +/* -*- 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 . + */ + +/** + this file implements the sal printer interface (SalPrinter, SalInfoPrinter + and some printer relevant methods of SalInstance and SalGraphicsData) + + as underlying library the printer features of psprint are used. + + The query methods of a SalInfoPrinter are implemented by querying psprint + + The job methods of a SalPrinter are implemented by calling psprint + printer job functions. + */ + +#include <sal/config.h> + +#include <string_view> + +// For spawning PDF and FAX generation +#include <unistd.h> +#include <sys/wait.h> +#include <sys/stat.h> + +#include <comphelper/fileurl.hxx> +#include <o3tl/safeint.hxx> +#include <rtl/ustrbuf.hxx> +#include <rtl/ustring.hxx> +#include <sal/log.hxx> + +#include <vcl/gdimtf.hxx> +#include <vcl/idle.hxx> +#include <vcl/printer/Options.hxx> +#include <vcl/print.hxx> +#include <vcl/QueueInfo.hxx> +#include <vcl/pdfwriter.hxx> +#include <printerinfomanager.hxx> +#include <vcl/svapp.hxx> +#include <vcl/settings.hxx> +#include <vcl/weld.hxx> +#include <strings.hrc> +#include <unx/genprn.h> +#include <unx/geninst.h> +#include <unx/genpspgraphics.h> + +#include <jobset.h> +#include <print.h> +#include "prtsetup.hxx" +#include <salptype.hxx> + +#include <com/sun/star/beans/PropertyValue.hpp> + +using namespace psp; +using namespace com::sun::star; + +static bool getPdfDir( const PrinterInfo& rInfo, OUString &rDir ) +{ + sal_Int32 nIndex = 0; + while( nIndex != -1 ) + { + OUString aToken( rInfo.m_aFeatures.getToken( 0, ',', nIndex ) ); + if( aToken.startsWith( "pdf=" ) ) + { + sal_Int32 nPos = 0; + rDir = aToken.getToken( 1, '=', nPos ); + if( rDir.isEmpty() && getenv( "HOME" ) ) + rDir = OUString( getenv( "HOME" ), strlen( getenv( "HOME" ) ), osl_getThreadTextEncoding() ); + return true; + } + } + return false; +} + +namespace +{ + class QueryString : public weld::GenericDialogController + { + private: + OUString& m_rReturnValue; + + std::unique_ptr<weld::Button> m_xOKButton; + std::unique_ptr<weld::Label> m_xFixedText; + std::unique_ptr<weld::Entry> m_xEdit; + + DECL_LINK( ClickBtnHdl, weld::Button&, void ); + + public: + // parent window, Query text, initial value + QueryString(weld::Window*, OUString const &, OUString &); + }; + + /* + * QueryString + */ + QueryString::QueryString(weld::Window* pParent, OUString const & rQuery, OUString& rRet) + : GenericDialogController(pParent, "vcl/ui/querydialog.ui", "QueryDialog") + , m_rReturnValue( rRet ) + , m_xOKButton(m_xBuilder->weld_button("ok")) + , m_xFixedText(m_xBuilder->weld_label("label")) + , m_xEdit(m_xBuilder->weld_entry("entry")) + { + m_xOKButton->connect_clicked(LINK(this, QueryString, ClickBtnHdl)); + m_xFixedText->set_label(rQuery); + m_xEdit->set_text(m_rReturnValue); + m_xDialog->set_title(rQuery); + } + + IMPL_LINK(QueryString, ClickBtnHdl, weld::Button&, rButton, void) + { + if (&rButton == m_xOKButton.get()) + { + m_rReturnValue = m_xEdit->get_text(); + m_xDialog->response(RET_OK); + } + else + m_xDialog->response(RET_CANCEL); + } + + int QueryFaxNumber(OUString& rNumber) + { + QueryString aQuery(Application::GetDefDialogParent(), VclResId(SV_PRINT_QUERYFAXNUMBER_TXT), rNumber); + return aQuery.run(); + } +} + +static int PtTo10Mu( int nPoints ) { return static_cast<int>((static_cast<double>(nPoints)*35.27777778)+0.5); } + +static int TenMuToPt( int nUnits ) { return static_cast<int>((static_cast<double>(nUnits)/35.27777778)+0.5); } + +static void copyJobDataToJobSetup( ImplJobSetup* pJobSetup, JobData& rData ) +{ + pJobSetup->SetOrientation( rData.m_eOrientation == orientation::Landscape ? + Orientation::Landscape : Orientation::Portrait ); + + // copy page size + OUString aPaper; + int width, height; + + rData.m_aContext.getPageSize( aPaper, width, height ); + pJobSetup->SetPaperFormat( PaperInfo::fromPSName( + OUStringToOString( aPaper, RTL_TEXTENCODING_ISO_8859_1 ))); + + pJobSetup->SetPaperWidth( 0 ); + pJobSetup->SetPaperHeight( 0 ); + if( pJobSetup->GetPaperFormat() == PAPER_USER ) + { + // transform to 100dth mm + width = PtTo10Mu( width ); + height = PtTo10Mu( height ); + + if( rData.m_eOrientation == psp::orientation::Portrait ) + { + pJobSetup->SetPaperWidth( width ); + pJobSetup->SetPaperHeight( height ); + } + else + { + pJobSetup->SetPaperWidth( height ); + pJobSetup->SetPaperHeight( width ); + } + } + + // copy input slot + const PPDKey* pKey = nullptr; + const PPDValue* pValue = nullptr; + + pJobSetup->SetPaperBin( 0 ); + if( rData.m_pParser ) + pKey = rData.m_pParser->getKey( "InputSlot" ); + if( pKey ) + pValue = rData.m_aContext.getValue( pKey ); + if( pKey && pValue ) + { + int nPaperBin; + for( nPaperBin = 0; + pValue != pKey->getValue( nPaperBin ) && + nPaperBin < pKey->countValues(); + nPaperBin++); + pJobSetup->SetPaperBin( + nPaperBin == pKey->countValues() ? 0 : nPaperBin); + } + + // copy duplex + pKey = nullptr; + pValue = nullptr; + + pJobSetup->SetDuplexMode( DuplexMode::Unknown ); + if( rData.m_pParser ) + pKey = rData.m_pParser->getKey( "Duplex" ); + if( pKey ) + pValue = rData.m_aContext.getValue( pKey ); + if( pKey && pValue ) + { + if( pValue->m_aOption.equalsIgnoreAsciiCase( "None" ) || + pValue->m_aOption.startsWithIgnoreAsciiCase( "Simplex" ) + ) + { + pJobSetup->SetDuplexMode( DuplexMode::Off); + } + else if( pValue->m_aOption.equalsIgnoreAsciiCase( "DuplexNoTumble" ) ) + { + pJobSetup->SetDuplexMode( DuplexMode::LongEdge ); + } + else if( pValue->m_aOption.equalsIgnoreAsciiCase( "DuplexTumble" ) ) + { + pJobSetup->SetDuplexMode( DuplexMode::ShortEdge ); + } + } + + // copy the whole context + if( pJobSetup->GetDriverData() ) + std::free( const_cast<sal_uInt8*>(pJobSetup->GetDriverData()) ); + + sal_uInt32 nBytes; + void* pBuffer = nullptr; + if( rData.getStreamBuffer( pBuffer, nBytes ) ) + { + pJobSetup->SetDriverDataLen( nBytes ); + pJobSetup->SetDriverData( static_cast<sal_uInt8*>(pBuffer) ); + } + else + { + pJobSetup->SetDriverDataLen( 0 ); + pJobSetup->SetDriverData( nullptr ); + } + pJobSetup->SetPapersizeFromSetup( rData.m_bPapersizeFromSetup ); +} + +// Needs a cleaner abstraction ... +static bool passFileToCommandLine( const OUString& rFilename, const OUString& rCommandLine ) +{ + bool bSuccess = false; + + rtl_TextEncoding aEncoding = osl_getThreadTextEncoding(); + OString aCmdLine(OUStringToOString(rCommandLine, aEncoding)); + OString aFilename(OUStringToOString(rFilename, aEncoding)); + + bool bPipe = aCmdLine.indexOf( "(TMP)" ) == -1; + + // setup command line for exec + if( ! bPipe ) + aCmdLine = aCmdLine.replaceAll("(TMP)", aFilename); + +#if OSL_DEBUG_LEVEL > 1 + SAL_INFO("vcl.unx.print", (bPipe ? "piping to" : "executing") + << " commandline: \"" << aCmdLine << "\"."); + struct stat aStat; + SAL_WARN_IF(stat( aFilename.getStr(), &aStat ), + "vcl.unx.print", "stat( " << aFilename << " ) failed."); + SAL_INFO("vcl.unx.print", "Tmp file " << aFilename + << " has modes: " + << std::showbase << std::oct + << (long)aStat.st_mode); +#endif + const char* argv[4]; + if( ! ( argv[ 0 ] = getenv( "SHELL" ) ) ) + argv[ 0 ] = "/bin/sh"; + argv[ 1 ] = "-c"; + argv[ 2 ] = aCmdLine.getStr(); + argv[ 3 ] = nullptr; + + bool bHavePipes = false; + int pid, fd[2]; + + if( bPipe ) + bHavePipes = pipe( fd ) == 0; + if( ( pid = fork() ) > 0 ) + { + if( bPipe && bHavePipes ) + { + close( fd[0] ); + char aBuffer[ 2048 ]; + FILE* fp = fopen( aFilename.getStr(), "r" ); + while (fp && !feof(fp)) + { + size_t nBytesRead = fread(aBuffer, 1, sizeof( aBuffer ), fp); + if (nBytesRead ) + { + size_t nBytesWritten = write(fd[1], aBuffer, nBytesRead); + OSL_ENSURE(nBytesWritten == nBytesRead, "short write"); + if (nBytesWritten != nBytesRead) + break; + } + } + fclose( fp ); + close( fd[ 1 ] ); + } + int status = 0; + if(waitpid( pid, &status, 0 ) != -1) + { + if( ! status ) + bSuccess = true; + } + } + else if( ! pid ) + { + if( bPipe && bHavePipes ) + { + close( fd[1] ); + if( fd[0] != STDIN_FILENO ) // not probable, but who knows :) + dup2( fd[0], STDIN_FILENO ); + } + execv( argv[0], const_cast<char**>(argv) ); + fprintf( stderr, "failed to execute \"%s\"\n", aCmdLine.getStr() ); + _exit( 1 ); + } + else + fprintf( stderr, "failed to fork\n" ); + + // clean up the mess + unlink( aFilename.getStr() ); + + return bSuccess; +} + +static std::vector<OUString> getFaxNumbers() +{ + std::vector<OUString> aFaxNumbers; + + OUString aNewNr; + if (QueryFaxNumber(aNewNr)) + { + for (sal_Int32 nIndex {0}; nIndex >= 0; ) + aFaxNumbers.push_back(aNewNr.getToken( 0, ';', nIndex )); + } + + return aFaxNumbers; +} + +static bool createPdf( std::u16string_view rToFile, const OUString& rFromFile, const OUString& rCommandLine ) +{ + return passFileToCommandLine( rFromFile, rCommandLine.replaceAll("(OUTFILE)", rToFile) ); +} + +/* + * SalInstance + */ + +void SalGenericInstance::configurePspInfoPrinter(PspSalInfoPrinter *pPrinter, + SalPrinterQueueInfo const * pQueueInfo, ImplJobSetup* pJobSetup) +{ + if( !pJobSetup ) + return; + + PrinterInfoManager& rManager( PrinterInfoManager::get() ); + PrinterInfo aInfo( rManager.getPrinterInfo( pQueueInfo->maPrinterName ) ); + pPrinter->m_aJobData = aInfo; + pPrinter->m_aPrinterGfx.Init( pPrinter->m_aJobData ); + + if( pJobSetup->GetDriverData() ) + JobData::constructFromStreamBuffer( pJobSetup->GetDriverData(), + pJobSetup->GetDriverDataLen(), aInfo ); + + pJobSetup->SetSystem( JOBSETUP_SYSTEM_UNIX ); + pJobSetup->SetPrinterName( pQueueInfo->maPrinterName ); + pJobSetup->SetDriver( aInfo.m_aDriverName ); + copyJobDataToJobSetup( pJobSetup, aInfo ); +} + +SalInfoPrinter* SalGenericInstance::CreateInfoPrinter( SalPrinterQueueInfo* pQueueInfo, + ImplJobSetup* pJobSetup ) +{ + mbPrinterInit = true; + // create and initialize SalInfoPrinter + PspSalInfoPrinter* pPrinter = new PspSalInfoPrinter(); + configurePspInfoPrinter(pPrinter, pQueueInfo, pJobSetup); + return pPrinter; +} + +void SalGenericInstance::DestroyInfoPrinter( SalInfoPrinter* pPrinter ) +{ + delete pPrinter; +} + +std::unique_ptr<SalPrinter> SalGenericInstance::CreatePrinter( SalInfoPrinter* pInfoPrinter ) +{ + mbPrinterInit = true; + // create and initialize SalPrinter + PspSalPrinter* pPrinter = new PspSalPrinter( pInfoPrinter ); + pPrinter->m_aJobData = static_cast<PspSalInfoPrinter*>(pInfoPrinter)->m_aJobData; + + return std::unique_ptr<SalPrinter>(pPrinter); +} + +void SalGenericInstance::GetPrinterQueueInfo( ImplPrnQueueList* pList ) +{ + mbPrinterInit = true; + PrinterInfoManager& rManager( PrinterInfoManager::get() ); + static const char* pNoSyncDetection = getenv( "SAL_DISABLE_SYNCHRONOUS_PRINTER_DETECTION" ); + if( ! pNoSyncDetection || ! *pNoSyncDetection ) + { + // #i62663# synchronize possible asynchronouse printer detection now + rManager.checkPrintersChanged( true ); + } + ::std::vector< OUString > aPrinters; + rManager.listPrinters( aPrinters ); + + for (auto const& printer : aPrinters) + { + const PrinterInfo& rInfo( rManager.getPrinterInfo(printer) ); + // create new entry + std::unique_ptr<SalPrinterQueueInfo> pInfo(new SalPrinterQueueInfo); + pInfo->maPrinterName = printer; + pInfo->maDriver = rInfo.m_aDriverName; + pInfo->maLocation = rInfo.m_aLocation; + pInfo->maComment = rInfo.m_aComment; + + OUString sPdfDir; + if (getPdfDir(rInfo, sPdfDir)) + pInfo->maLocation = sPdfDir; + + pList->Add( std::move(pInfo) ); + } +} + +void SalGenericInstance::GetPrinterQueueState( SalPrinterQueueInfo* ) +{ + mbPrinterInit = true; +} + +OUString SalGenericInstance::GetDefaultPrinter() +{ + mbPrinterInit = true; + PrinterInfoManager& rManager( PrinterInfoManager::get() ); + return rManager.getDefaultPrinter(); +} + +PspSalInfoPrinter::PspSalInfoPrinter() +{ +} + +PspSalInfoPrinter::~PspSalInfoPrinter() +{ +} + +void PspSalInfoPrinter::InitPaperFormats( const ImplJobSetup* ) +{ + m_aPaperFormats.clear(); + m_bPapersInit = true; + + if( !m_aJobData.m_pParser ) + return; + + const PPDKey* pKey = m_aJobData.m_pParser->getKey( "PageSize" ); + if( pKey ) + { + int nValues = pKey->countValues(); + for( int i = 0; i < nValues; i++ ) + { + const PPDValue* pValue = pKey->getValue( i ); + int nWidth = 0, nHeight = 0; + m_aJobData.m_pParser->getPaperDimension( pValue->m_aOption, nWidth, nHeight ); + PaperInfo aInfo(PtTo10Mu( nWidth ), PtTo10Mu( nHeight )); + m_aPaperFormats.push_back( aInfo ); + } + } +} + +int PspSalInfoPrinter::GetLandscapeAngle( const ImplJobSetup* ) +{ + return 900; +} + +SalGraphics* PspSalInfoPrinter::AcquireGraphics() +{ + // return a valid pointer only once + // the reasoning behind this is that we could have different + // SalGraphics that can run in multiple threads + // (future plans) + SalGraphics* pRet = nullptr; + if( ! m_pGraphics ) + { + m_pGraphics = GetGenericInstance()->CreatePrintGraphics(); + m_pGraphics->Init(&m_aJobData, &m_aPrinterGfx); + pRet = m_pGraphics.get(); + } + return pRet; +} + +void PspSalInfoPrinter::ReleaseGraphics( SalGraphics* pGraphics ) +{ + if( m_pGraphics.get() == pGraphics ) + { + m_pGraphics.reset(); + } +} + +bool PspSalInfoPrinter::Setup( weld::Window* pFrame, ImplJobSetup* pJobSetup ) +{ + if( ! pFrame || ! pJobSetup ) + return false; + + PrinterInfoManager& rManager = PrinterInfoManager::get(); + + PrinterInfo aInfo( rManager.getPrinterInfo( pJobSetup->GetPrinterName() ) ); + if ( pJobSetup->GetDriverData() ) + { + SetData( JobSetFlags::ALL, pJobSetup ); + JobData::constructFromStreamBuffer( pJobSetup->GetDriverData(), pJobSetup->GetDriverDataLen(), aInfo ); + } + aInfo.m_bPapersizeFromSetup = pJobSetup->GetPapersizeFromSetup(); + aInfo.meSetupMode = pJobSetup->GetPrinterSetupMode(); + + if (SetupPrinterDriver(pFrame, aInfo)) + { + aInfo.resolveDefaultBackend(); + std::free( const_cast<sal_uInt8*>(pJobSetup->GetDriverData()) ); + pJobSetup->SetDriverData( nullptr ); + + sal_uInt32 nBytes; + void* pBuffer = nullptr; + aInfo.getStreamBuffer( pBuffer, nBytes ); + pJobSetup->SetDriverDataLen( nBytes ); + pJobSetup->SetDriverData( static_cast<sal_uInt8*>(pBuffer) ); + + // copy everything to job setup + copyJobDataToJobSetup( pJobSetup, aInfo ); + JobData::constructFromStreamBuffer( pJobSetup->GetDriverData(), pJobSetup->GetDriverDataLen(), m_aJobData ); + return true; + } + return false; +} + +// This function gets the driver data and puts it into pJobSetup +// If pJobSetup->GetDriverData() is NOT NULL, then the independent +// data should be merged into the driver data +// If pJobSetup->GetDriverData() IS NULL, then the driver defaults +// should be merged into the independent data +bool PspSalInfoPrinter::SetPrinterData( ImplJobSetup* pJobSetup ) +{ + if( pJobSetup->GetDriverData() ) + return SetData( JobSetFlags::ALL, pJobSetup ); + + copyJobDataToJobSetup( pJobSetup, m_aJobData ); + + return true; +} + +// This function merges the independent driver data +// and sets the new independent data in pJobSetup +// Only the data must be changed, where the bit +// in nGetDataFlags is set +bool PspSalInfoPrinter::SetData( + JobSetFlags nSetDataFlags, + ImplJobSetup* pJobSetup ) +{ + JobData aData; + JobData::constructFromStreamBuffer( pJobSetup->GetDriverData(), pJobSetup->GetDriverDataLen(), aData ); + + if( aData.m_pParser ) + { + const PPDKey* pKey; + const PPDValue* pValue; + + // merge papersize if necessary + if( nSetDataFlags & JobSetFlags::PAPERSIZE ) + { + OUString aPaper; + + if( pJobSetup->GetPaperFormat() == PAPER_USER ) + aPaper = aData.m_pParser->matchPaper( + TenMuToPt( pJobSetup->GetPaperWidth() ), + TenMuToPt( pJobSetup->GetPaperHeight() ) ); + else + aPaper = OStringToOUString(PaperInfo::toPSName(pJobSetup->GetPaperFormat()), RTL_TEXTENCODING_ISO_8859_1); + + pKey = aData.m_pParser->getKey( "PageSize" ); + pValue = pKey ? pKey->getValueCaseInsensitive( aPaper ) : nullptr; + + // some PPD files do not specify the standard paper names (e.g. C5 instead of EnvC5) + // try to find the correct paper anyway using the size + if( pKey && ! pValue && pJobSetup->GetPaperFormat() != PAPER_USER ) + { + PaperInfo aInfo( pJobSetup->GetPaperFormat() ); + aPaper = aData.m_pParser->matchPaper( + TenMuToPt( aInfo.getWidth() ), + TenMuToPt( aInfo.getHeight() ) ); + pValue = pKey->getValueCaseInsensitive( aPaper ); + } + + if( ! ( pKey && pValue && aData.m_aContext.setValue( pKey, pValue ) == pValue ) ) + return false; + } + + // merge paperbin if necessary + if( nSetDataFlags & JobSetFlags::PAPERBIN ) + { + pKey = aData.m_pParser->getKey( "InputSlot" ); + if( pKey ) + { + int nPaperBin = pJobSetup->GetPaperBin(); + if( nPaperBin >= pKey->countValues() ) + pValue = pKey->getDefaultValue(); + else + pValue = pKey->getValue( pJobSetup->GetPaperBin() ); + + // may fail due to constraints; + // real paper bin is copied back to jobsetup in that case + aData.m_aContext.setValue( pKey, pValue ); + } + // if printer has no InputSlot key simply ignore this setting + // (e.g. SGENPRT has no InputSlot) + } + + // merge orientation if necessary + if( nSetDataFlags & JobSetFlags::ORIENTATION ) + aData.m_eOrientation = pJobSetup->GetOrientation() == Orientation::Landscape ? orientation::Landscape : orientation::Portrait; + + // merge duplex if necessary + if( nSetDataFlags & JobSetFlags::DUPLEXMODE ) + { + pKey = aData.m_pParser->getKey( "Duplex" ); + if( pKey ) + { + pValue = nullptr; + switch( pJobSetup->GetDuplexMode() ) + { + case DuplexMode::Off: + pValue = pKey->getValue( "None" ); + if( pValue == nullptr ) + pValue = pKey->getValue( "SimplexNoTumble" ); + break; + case DuplexMode::ShortEdge: + pValue = pKey->getValue( "DuplexTumble" ); + break; + case DuplexMode::LongEdge: + pValue = pKey->getValue( "DuplexNoTumble" ); + break; + case DuplexMode::Unknown: + default: + pValue = nullptr; + break; + } + if( ! pValue ) + pValue = pKey->getDefaultValue(); + aData.m_aContext.setValue( pKey, pValue ); + } + } + aData.m_bPapersizeFromSetup = pJobSetup->GetPapersizeFromSetup(); + + m_aJobData = aData; + copyJobDataToJobSetup( pJobSetup, aData ); + return true; + } + + return false; +} + +void PspSalInfoPrinter::GetPageInfo( + const ImplJobSetup* pJobSetup, + tools::Long& rOutWidth, tools::Long& rOutHeight, + Point& rPageOffset, + Size& rPaperSize ) +{ + if( ! pJobSetup ) + return; + + JobData aData; + JobData::constructFromStreamBuffer( pJobSetup->GetDriverData(), pJobSetup->GetDriverDataLen(), aData ); + + // get the selected page size + if( !aData.m_pParser ) + return; + + + OUString aPaper; + int width, height; + int left = 0, top = 0, right = 0, bottom = 0; + int nDPI = aData.m_aContext.getRenderResolution(); + + if( aData.m_eOrientation == psp::orientation::Portrait ) + { + aData.m_aContext.getPageSize( aPaper, width, height ); + aData.m_pParser->getMargins( aPaper, left, right, top, bottom ); + } + else + { + aData.m_aContext.getPageSize( aPaper, height, width ); + aData.m_pParser->getMargins( aPaper, top, bottom, right, left ); + } + + rPaperSize.setWidth( width * nDPI / 72 ); + rPaperSize.setHeight( height * nDPI / 72 ); + rPageOffset.setX( left * nDPI / 72 ); + rPageOffset.setY( top * nDPI / 72 ); + rOutWidth = ( width - left - right ) * nDPI / 72; + rOutHeight = ( height - top - bottom ) * nDPI / 72; + +} + +sal_uInt16 PspSalInfoPrinter::GetPaperBinCount( const ImplJobSetup* pJobSetup ) +{ + if( ! pJobSetup ) + return 0; + + JobData aData; + JobData::constructFromStreamBuffer( pJobSetup->GetDriverData(), pJobSetup->GetDriverDataLen(), aData ); + + const PPDKey* pKey = aData.m_pParser ? aData.m_pParser->getKey( "InputSlot" ): nullptr; + return pKey ? pKey->countValues() : 0; +} + +OUString PspSalInfoPrinter::GetPaperBinName( const ImplJobSetup* pJobSetup, sal_uInt16 nPaperBin ) +{ + JobData aData; + JobData::constructFromStreamBuffer( pJobSetup->GetDriverData(), pJobSetup->GetDriverDataLen(), aData ); + + if( aData.m_pParser ) + { + const PPDKey* pKey = aData.m_pParser ? aData.m_pParser->getKey( "InputSlot" ): nullptr; + if( ! pKey || nPaperBin >= o3tl::make_unsigned(pKey->countValues()) ) + return aData.m_pParser->getDefaultInputSlot(); + const PPDValue* pValue = pKey->getValue( nPaperBin ); + if( pValue ) + return aData.m_pParser->translateOption( pKey->getKey(), pValue->m_aOption ); + } + + return OUString(); +} + +sal_uInt32 PspSalInfoPrinter::GetCapabilities( const ImplJobSetup* pJobSetup, PrinterCapType nType ) +{ + switch( nType ) + { + case PrinterCapType::SupportDialog: + return 1; + case PrinterCapType::Copies: + return 0xffff; + case PrinterCapType::CollateCopies: + { + // PPDs don't mention the number of possible collated copies. + // so let's guess as many as we want ? + return 0xffff; + } + case PrinterCapType::SetOrientation: + return 1; + case PrinterCapType::SetPaperSize: + return 1; + case PrinterCapType::SetPaper: + return 0; + case PrinterCapType::Fax: + { + // see if the PPD contains the fax4CUPS "Dial" option and that it's not set + // to "manually" + JobData aData = PrinterInfoManager::get().getPrinterInfo(pJobSetup->GetPrinterName()); + if( pJobSetup->GetDriverData() ) + JobData::constructFromStreamBuffer( pJobSetup->GetDriverData(), pJobSetup->GetDriverDataLen(), aData ); + const PPDKey* pKey = aData.m_pParser ? aData.m_pParser->getKey("Dial") : nullptr; + const PPDValue* pValue = pKey ? aData.m_aContext.getValue(pKey) : nullptr; + if (pValue && !pValue->m_aOption.equalsIgnoreAsciiCase("Manually")) + return 1; + return 0; + } + + case PrinterCapType::PDF: + if( PrinterInfoManager::get().checkFeatureToken( pJobSetup->GetPrinterName(), "pdf" ) ) + return 1; + else + { + // see if the PPD contains a value to set PDF device + JobData aData = PrinterInfoManager::get().getPrinterInfo( pJobSetup->GetPrinterName() ); + if( pJobSetup->GetDriverData() ) + JobData::constructFromStreamBuffer( pJobSetup->GetDriverData(), pJobSetup->GetDriverDataLen(), aData ); + return aData.m_nPDFDevice > 0 ? 1 : 0; + } + case PrinterCapType::ExternalDialog: + return PrinterInfoManager::get().checkFeatureToken( pJobSetup->GetPrinterName(), "external_dialog" ) ? 1 : 0; + case PrinterCapType::UsePullModel: + { + // see if the PPD contains a value to set PDF device + JobData aData = PrinterInfoManager::get().getPrinterInfo( pJobSetup->GetPrinterName() ); + if( pJobSetup->GetDriverData() ) + JobData::constructFromStreamBuffer( pJobSetup->GetDriverData(), pJobSetup->GetDriverDataLen(), aData ); + return aData.m_nPDFDevice > 0 ? 1 : 0; + } + default: break; + } + return 0; +} + +/* + * SalPrinter + */ +PspSalPrinter::PspSalPrinter( SalInfoPrinter* pInfoPrinter ) + : m_pInfoPrinter( pInfoPrinter ) + , m_nCopies( 1 ) + , m_bCollate( false ) + , m_bPdf( false ) + , m_bIsPDFWriterJob( false ) +{ +} + +PspSalPrinter::~PspSalPrinter() +{ +} + +static OUString getTmpName() +{ + OUString aTmp, aSys; + osl_createTempFile( nullptr, nullptr, &aTmp.pData ); + osl_getSystemPathFromFileURL( aTmp.pData, &aSys.pData ); + + return aSys; +} + +bool PspSalPrinter::StartJob( + const OUString* pFileName, + const OUString& rJobName, + const OUString& rAppName, + sal_uInt32 nCopies, + bool bCollate, + bool bDirect, + ImplJobSetup* pJobSetup ) +{ + SAL_INFO( "vcl.unx.print", "PspSalPrinter::StartJob"); + GetSalInstance()->jobStartedPrinterUpdate(); + m_bPdf = false; + if (pFileName) + m_aFileName = *pFileName; + else + m_aFileName.clear(); + m_aTmpFile.clear(); + m_nCopies = nCopies; + m_bCollate = bCollate; + + JobData::constructFromStreamBuffer( pJobSetup->GetDriverData(), pJobSetup->GetDriverDataLen(), m_aJobData ); + if( m_nCopies > 1 ) + { + // in case user did not do anything (m_nCopies=1) + // take the default from jobsetup + m_aJobData.m_nCopies = m_nCopies; + m_aJobData.setCollate( bCollate ); + } + + int nMode = 0; + // check whether this printer is configured as fax + const PrinterInfo& rInfo( PrinterInfoManager::get().getPrinterInfo( m_aJobData.m_aPrinterName ) ); + OUString sPdfDir; + if (getPdfDir(rInfo, sPdfDir)) + { + m_bPdf = true; + m_aTmpFile = getTmpName(); + nMode = S_IRUSR | S_IWUSR; + + if( m_aFileName.isEmpty() ) + m_aFileName = sPdfDir + "/" + rJobName + ".pdf"; + } + m_aPrinterGfx.Init( m_aJobData ); + + return m_aPrintJob.StartJob( ! m_aTmpFile.isEmpty() ? m_aTmpFile : m_aFileName, nMode, rJobName, rAppName, m_aJobData, &m_aPrinterGfx, bDirect ); +} + +bool PspSalPrinter::EndJob() +{ + bool bSuccess = false; + if( m_bIsPDFWriterJob ) + bSuccess = true; + else + { + bSuccess = m_aPrintJob.EndJob(); + SAL_INFO( "vcl.unx.print", "PspSalPrinter::EndJob " << bSuccess); + + if( bSuccess && m_bPdf ) + { + const PrinterInfo& rInfo( PrinterInfoManager::get().getPrinterInfo( m_aJobData.m_aPrinterName ) ); + bSuccess = createPdf( m_aFileName, m_aTmpFile, rInfo.m_aCommand ); + } + } + GetSalInstance()->jobEndedPrinterUpdate(); + return bSuccess; +} + +SalGraphics* PspSalPrinter::StartPage( ImplJobSetup* pJobSetup, bool ) +{ + SAL_INFO( "vcl.unx.print", "PspSalPrinter::StartPage"); + + JobData::constructFromStreamBuffer( pJobSetup->GetDriverData(), pJobSetup->GetDriverDataLen(), m_aJobData ); + m_xGraphics = GetGenericInstance()->CreatePrintGraphics(); + m_xGraphics->Init(&m_aJobData, &m_aPrinterGfx); + + if( m_nCopies > 1 ) + { + // in case user did not do anything (m_nCopies=1) + // take the default from jobsetup + m_aJobData.m_nCopies = m_nCopies; + m_aJobData.setCollate( m_nCopies > 1 && m_bCollate ); + } + + m_aPrintJob.StartPage( m_aJobData ); + m_aPrinterGfx.Init( m_aPrintJob ); + + return m_xGraphics.get(); +} + +void PspSalPrinter::EndPage() +{ + m_aPrintJob.EndPage(); + m_aPrinterGfx.Clear(); + SAL_INFO( "vcl.unx.print", "PspSalPrinter::EndPage"); +} + +namespace { + +struct PDFNewJobParameters +{ + Size maPageSize; + sal_uInt16 mnPaperBin; + + PDFNewJobParameters( const Size& i_rSize = Size(), + sal_uInt16 i_nPaperBin = 0xffff ) + : maPageSize( i_rSize ), mnPaperBin( i_nPaperBin ) {} + + bool operator==(const PDFNewJobParameters& rComp ) const + { + const tools::Long nRotatedWidth = rComp.maPageSize.Height(); + const tools::Long nRotatedHeight = rComp.maPageSize.Width(); + Size aCompLSSize(nRotatedWidth, nRotatedHeight); + return + (maPageSize == rComp.maPageSize || maPageSize == aCompLSSize) + && mnPaperBin == rComp.mnPaperBin + ; + } + + bool operator!=(const PDFNewJobParameters& rComp) const + { + return ! operator==(rComp); + } +}; + +struct PDFPrintFile +{ + OUString maTmpURL; + PDFNewJobParameters maParameters; + + PDFPrintFile( const OUString& i_rURL, const PDFNewJobParameters& i_rNewParameters ) + : maTmpURL( i_rURL ) + , maParameters( i_rNewParameters ) {} +}; + +} + +bool PspSalPrinter::StartJob( const OUString* i_pFileName, const OUString& i_rJobName, const OUString& i_rAppName, + ImplJobSetup* i_pSetupData, vcl::PrinterController& i_rController ) +{ + SAL_INFO( "vcl.unx.print", "StartJob with controller: pFilename = " << (i_pFileName ? *i_pFileName : "<nil>") ); + // mark for endjob + m_bIsPDFWriterJob = true; + // reset IsLastPage + i_rController.setLastPage( false ); + // is this a fax device + bool bFax = m_pInfoPrinter->GetCapabilities(i_pSetupData, PrinterCapType::Fax) == 1; + + // update job data + if( i_pSetupData ) + JobData::constructFromStreamBuffer( i_pSetupData->GetDriverData(), i_pSetupData->GetDriverDataLen(), m_aJobData ); + + OSL_ASSERT( m_aJobData.m_nPDFDevice > 0 ); + m_aJobData.m_nPDFDevice = 1; + + // possibly create one job for collated output + int nCopies = i_rController.getPrinter()->GetCopyCount(); + bool bCollate = i_rController.getPrinter()->IsCollateCopy(); + bool bSinglePrintJobs = i_rController.getPrinter()->IsSinglePrintJobs(); + + // notify start of real print job + i_rController.jobStarted(); + + // setup PDFWriter context + vcl::PDFWriter::PDFWriterContext aContext; + aContext.Version = vcl::PDFWriter::PDFVersion::PDF_1_4; + aContext.Tagged = false; + aContext.DocumentLocale = Application::GetSettings().GetLanguageTag().getLocale(); + aContext.ColorMode = i_rController.getPrinter()->GetPrinterOptions().IsConvertToGreyscales() + ? vcl::PDFWriter::DrawGreyscale : vcl::PDFWriter::DrawColor; + + // prepare doc info + aContext.DocumentInfo.Title = i_rJobName; + aContext.DocumentInfo.Creator = i_rAppName; + aContext.DocumentInfo.Producer = i_rAppName; + + // define how we handle metafiles in PDFWriter + vcl::PDFWriter::PlayMetafileContext aMtfContext; + aMtfContext.m_bOnlyLosslessCompression = true; + + std::shared_ptr<vcl::PDFWriter> xWriter; + std::vector< PDFPrintFile > aPDFFiles; + VclPtr<Printer> xPrinter( i_rController.getPrinter() ); + int nAllPages = i_rController.getFilteredPageCount(); + i_rController.createProgressDialog(); + bool bAborted = false; + PDFNewJobParameters aLastParm; + + aContext.DPIx = xPrinter->GetDPIX(); + aContext.DPIy = xPrinter->GetDPIY(); + for( int nPage = 0; nPage < nAllPages && ! bAborted; nPage++ ) + { + if( nPage == nAllPages-1 ) + i_rController.setLastPage( true ); + + // get the page's metafile + GDIMetaFile aPageFile; + vcl::PrinterController::PageSize aPageSize = i_rController.getFilteredPageFile( nPage, aPageFile ); + if( i_rController.isProgressCanceled() ) + { + bAborted = true; + if( nPage != nAllPages-1 ) + { + i_rController.createProgressDialog(); + i_rController.setLastPage( true ); + i_rController.getFilteredPageFile( nPage, aPageFile ); + } + } + else + { + xPrinter->SetMapMode( MapMode( MapUnit::Map100thMM ) ); + xPrinter->SetPaperSizeUser( aPageSize.aSize ); + PDFNewJobParameters aNewParm(xPrinter->GetPaperSize(), xPrinter->GetPaperBin()); + + // create PDF writer on demand + // either on first page + // or on paper format change - cups does not support multiple paper formats per job (yet?) + // so we need to start a new job to get a new paper format from the printer + // orientation switches (that is switch of height and width) is handled transparently by CUPS + if( ! xWriter || + (aNewParm != aLastParm && ! i_pFileName ) ) + { + if( xWriter ) + { + xWriter->Emit(); + } + // produce PDF file + OUString aPDFUrl; + if( i_pFileName ) + aPDFUrl = *i_pFileName; + else + osl_createTempFile( nullptr, nullptr, &aPDFUrl.pData ); + // normalize to file URL + if( !comphelper::isFileUrl(aPDFUrl) ) + { + // this is not a file URL, but it should + // form it into an osl friendly file URL + OUString aTmp; + osl_getFileURLFromSystemPath( aPDFUrl.pData, &aTmp.pData ); + aPDFUrl = aTmp; + } + // save current file and paper format + aLastParm = aNewParm; + aPDFFiles.emplace_back( aPDFUrl, aNewParm ); + // update context + aContext.URL = aPDFUrl; + + // create and initialize PDFWriter + xWriter = std::make_shared<vcl::PDFWriter>( aContext, uno::Reference< beans::XMaterialHolder >() ); + } + + xWriter->NewPage( TenMuToPt( aNewParm.maPageSize.Width() ), + TenMuToPt( aNewParm.maPageSize.Height() ), + vcl::PDFWriter::Orientation::Portrait ); + + xWriter->PlayMetafile( aPageFile, aMtfContext ); + } + } + + // emit the last file + if( xWriter ) + xWriter->Emit(); + + // handle collate, copy count and multiple jobs correctly + int nOuterJobs = 1; + if( bSinglePrintJobs ) + { + nOuterJobs = nCopies; + m_aJobData.m_nCopies = 1; + } + else + { + if( bCollate ) + { + if (aPDFFiles.size() == 1 && xPrinter->HasSupport(PrinterSupport::CollateCopy)) + { + m_aJobData.setCollate( true ); + m_aJobData.m_nCopies = nCopies; + } + else + { + nOuterJobs = nCopies; + m_aJobData.m_nCopies = 1; + } + } + else + { + m_aJobData.setCollate( false ); + m_aJobData.m_nCopies = nCopies; + } + } + + std::vector<OUString> aFaxNumbers; + + // check for fax numbers + if (!bAborted && bFax) + { + aFaxNumbers = getFaxNumbers(); + bAborted = aFaxNumbers.empty(); + } + + bool bSuccess(true); + // spool files + if( ! i_pFileName && ! bAborted ) + { + do + { + OUString sFaxNumber; + if (!aFaxNumbers.empty()) + { + sFaxNumber = aFaxNumbers.back(); + aFaxNumbers.pop_back(); + } + + bool bFirstJob = true; + for( int nCurJob = 0; nCurJob < nOuterJobs; nCurJob++ ) + { + for( size_t i = 0; i < aPDFFiles.size(); i++ ) + { + oslFileHandle pFile = nullptr; + osl_openFile( aPDFFiles[i].maTmpURL.pData, &pFile, osl_File_OpenFlag_Read ); + if (pFile && (osl_setFilePos(pFile, osl_Pos_Absolut, 0) == osl_File_E_None)) + { + std::vector< char > buffer( 0x10000, 0 ); + // update job data with current page size + Size aPageSize( aPDFFiles[i].maParameters.maPageSize ); + m_aJobData.setPaper( TenMuToPt( aPageSize.Width() ), TenMuToPt( aPageSize.Height() ) ); + // update job data with current paperbin + m_aJobData.setPaperBin( aPDFFiles[i].maParameters.mnPaperBin ); + + // spool current file + FILE* fp = PrinterInfoManager::get().startSpool(xPrinter->GetName(), i_rController.isDirectPrint()); + if( fp ) + { + sal_uInt64 nBytesRead = 0; + do + { + osl_readFile( pFile, buffer.data(), buffer.size(), &nBytesRead ); + if( nBytesRead > 0 ) + { + size_t nBytesWritten = fwrite(buffer.data(), 1, nBytesRead, fp); + OSL_ENSURE(nBytesRead == nBytesWritten, "short write"); + if (nBytesRead != nBytesWritten) + break; + } + } while( nBytesRead == buffer.size() ); + OUStringBuffer aBuf( i_rJobName.getLength() + 8 ); + aBuf.append( i_rJobName ); + if( i > 0 || nCurJob > 0 ) + { + aBuf.append( ' ' ); + aBuf.append( sal_Int32( i + nCurJob * aPDFFiles.size() ) ); + } + bSuccess &= + PrinterInfoManager::get().endSpool(xPrinter->GetName(), aBuf.makeStringAndClear(), fp, m_aJobData, bFirstJob, sFaxNumber); + bFirstJob = false; + } + } + osl_closeFile( pFile ); + } + } + } + while (!aFaxNumbers.empty()); + } + + // job has been spooled + i_rController.setJobState( bAborted + ? view::PrintableState_JOB_ABORTED + : (bSuccess ? view::PrintableState_JOB_SPOOLED + : view::PrintableState_JOB_SPOOLING_FAILED)); + + // clean up the temporary PDF files + if( ! i_pFileName || bAborted ) + { + for(PDFPrintFile & rPDFFile : aPDFFiles) + { + osl_removeFile( rPDFFile.maTmpURL.pData ); + SAL_INFO( "vcl.unx.print", "removed print PDF file " << rPDFFile.maTmpURL ); + } + } + + return true; +} + +namespace { + +class PrinterUpdate +{ + static Idle* pPrinterUpdateIdle; + static int nActiveJobs; + + static void doUpdate(); + DECL_STATIC_LINK( PrinterUpdate, UpdateTimerHdl, Timer*, void ); +public: + static void update(SalGenericInstance const &rInstance); + static void jobStarted() { nActiveJobs++; } + static void jobEnded(); +}; + +} + +Idle* PrinterUpdate::pPrinterUpdateIdle = nullptr; +int PrinterUpdate::nActiveJobs = 0; + +void PrinterUpdate::doUpdate() +{ + ::psp::PrinterInfoManager& rManager( ::psp::PrinterInfoManager::get() ); + SalGenericInstance *pInst = GetGenericInstance(); + if( pInst && rManager.checkPrintersChanged( false ) ) + pInst->PostPrintersChanged(); +} + +IMPL_STATIC_LINK_NOARG( PrinterUpdate, UpdateTimerHdl, Timer*, void ) +{ + if( nActiveJobs < 1 ) + { + doUpdate(); + delete pPrinterUpdateIdle; + pPrinterUpdateIdle = nullptr; + } + else + pPrinterUpdateIdle->Start(); +} + +void PrinterUpdate::update(SalGenericInstance const &rInstance) +{ + if( Application::GetSettings().GetMiscSettings().GetDisablePrinting() ) + return; + + if( ! rInstance.isPrinterInit() ) + { + // #i45389# start background printer detection + psp::PrinterInfoManager::get(); + return; + } + + if( nActiveJobs < 1 ) + doUpdate(); + else if( ! pPrinterUpdateIdle ) + { + pPrinterUpdateIdle = new Idle("PrinterUpdateTimer"); + pPrinterUpdateIdle->SetPriority( TaskPriority::LOWEST ); + pPrinterUpdateIdle->SetInvokeHandler( LINK( nullptr, PrinterUpdate, UpdateTimerHdl ) ); + pPrinterUpdateIdle->Start(); + } +} + +void SalGenericInstance::updatePrinterUpdate() +{ + PrinterUpdate::update(*this); +} + +void SalGenericInstance::jobStartedPrinterUpdate() +{ + PrinterUpdate::jobStarted(); +} + +void PrinterUpdate::jobEnded() +{ + nActiveJobs--; + if( nActiveJobs < 1 ) + { + if( pPrinterUpdateIdle ) + { + pPrinterUpdateIdle->Stop(); + delete pPrinterUpdateIdle; + pPrinterUpdateIdle = nullptr; + doUpdate(); + } + } +} + +void SalGenericInstance::jobEndedPrinterUpdate() +{ + PrinterUpdate::jobEnded(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/generic/print/genpspgraphics.cxx b/vcl/unx/generic/print/genpspgraphics.cxx new file mode 100644 index 000000000..7c4e14b27 --- /dev/null +++ b/vcl/unx/generic/print/genpspgraphics.cxx @@ -0,0 +1,521 @@ +/* -*- 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 <vector> + +#include <sal/types.h> + +#include <unistd.h> +#include <fcntl.h> +#include <sys/mman.h> +#include <sys/stat.h> + +#include <i18nlangtag/mslangid.hxx> +#include <jobdata.hxx> +#include <vcl/settings.hxx> +#include <vcl/svapp.hxx> +#include <vcl/sysdata.hxx> +#include <vcl/fontcharmap.hxx> +#include <config_cairo_canvas.h> + +#include <fontsubset.hxx> +#include <unx/freetype_glyphcache.hxx> +#include <unx/geninst.h> +#include <unx/genpspgraphics.h> +#include <unx/printergfx.hxx> +#include <langboost.hxx> +#include <fontinstance.hxx> +#include <fontattributes.hxx> +#include <impfontmetricdata.hxx> +#include <font/FontSelectPattern.hxx> +#include <font/PhysicalFontCollection.hxx> +#include <font/PhysicalFontFace.hxx> +#include <o3tl/string_view.hxx> +#include <sallayout.hxx> + +using namespace psp; + +/******************************************************* + * GenPspGraphics + *******************************************************/ + +GenPspGraphics::GenPspGraphics() + : m_pJobData( nullptr ) + , m_pPrinterGfx( nullptr ) +{ +} + +void GenPspGraphics::Init(psp::JobData* pJob, psp::PrinterGfx* pGfx) +{ + m_pBackend = std::make_unique<GenPspGfxBackend>(pGfx); + m_pJobData = pJob; + m_pPrinterGfx = pGfx; + SetLayout( SalLayoutFlags::NONE ); +} + +GenPspGraphics::~GenPspGraphics() +{ + ReleaseFonts(); +} + +void GenPspGraphics::GetResolution( sal_Int32 &rDPIX, sal_Int32 &rDPIY ) +{ + if (m_pJobData != nullptr) + { + int x = m_pJobData->m_aContext.getRenderResolution(); + + rDPIX = x; + rDPIY = x; + } +} + +namespace { + +class ImplPspFontData : public FreetypeFontFace +{ +private: + sal_IntPtr mnFontId; + +public: + explicit ImplPspFontData( const psp::FastPrintFontInfo& ); + virtual sal_IntPtr GetFontId() const override { return mnFontId; } +}; + +} + +ImplPspFontData::ImplPspFontData(const psp::FastPrintFontInfo& rInfo) +: FreetypeFontFace(nullptr, GenPspGraphics::Info2FontAttributes(rInfo)), + mnFontId( rInfo.m_nID ) +{} + +namespace { + +class PspSalLayout : public GenericSalLayout +{ +public: + PspSalLayout(psp::PrinterGfx&, LogicalFontInstance &rFontInstance); + + void InitFont() const final override; + +private: + ::psp::PrinterGfx& mrPrinterGfx; + sal_IntPtr mnFontID; + int mnFontHeight; + int mnFontWidth; + bool mbVertical; + bool mbArtItalic; + bool mbArtBold; +}; + +} + +PspSalLayout::PspSalLayout(::psp::PrinterGfx& rGfx, LogicalFontInstance &rFontInstance) +: GenericSalLayout(rFontInstance) +, mrPrinterGfx(rGfx) +{ + mnFontID = mrPrinterGfx.GetFontID(); + mnFontHeight = mrPrinterGfx.GetFontHeight(); + mnFontWidth = mrPrinterGfx.GetFontWidth(); + mbVertical = mrPrinterGfx.GetFontVertical(); + mbArtItalic = mrPrinterGfx.GetArtificialItalic(); + mbArtBold = mrPrinterGfx.GetArtificialBold(); +} + +void PspSalLayout::InitFont() const +{ + GenericSalLayout::InitFont(); + mrPrinterGfx.SetFont(mnFontID, mnFontHeight, mnFontWidth, + mnOrientation, mbVertical, mbArtItalic, mbArtBold); +} + +void GenPspGraphics::DrawTextLayout(const GenericSalLayout& rLayout) +{ + const GlyphItem* pGlyph; + DevicePoint aPos; + int nStart = 0; + while (rLayout.GetNextGlyph(&pGlyph, aPos, nStart)) + m_pPrinterGfx->DrawGlyph(Point(aPos.getX(), aPos.getY()), *pGlyph); +} + +FontCharMapRef GenPspGraphics::GetFontCharMap() const +{ + if (!m_pFreetypeFont[0]) + return nullptr; + + return m_pFreetypeFont[0]->GetFreetypeFont().GetFontCharMap(); +} + +bool GenPspGraphics::GetFontCapabilities(vcl::FontCapabilities &rFontCapabilities) const +{ + if (!m_pFreetypeFont[0]) + return false; + + return m_pFreetypeFont[0]->GetFreetypeFont().GetFontCapabilities(rFontCapabilities); +} + +void GenPspGraphics::SetFont(LogicalFontInstance *pFontInstance, int nFallbackLevel) +{ + // release all fonts that are to be overridden + for( int i = nFallbackLevel; i < MAX_FALLBACK; ++i ) + { + // old server side font is no longer referenced + m_pFreetypeFont[i] = nullptr; + } + + // return early if there is no new font + if (!pFontInstance) + return; + + sal_IntPtr nID = pFontInstance->GetFontFace()->GetFontId(); + + const vcl::font::FontSelectPattern& rEntry = pFontInstance->GetFontSelectPattern(); + + // determine which font attributes need to be emulated + bool bArtItalic = false; + bool bArtBold = false; + if( rEntry.GetItalic() == ITALIC_OBLIQUE || rEntry.GetItalic() == ITALIC_NORMAL ) + { + FontItalic eItalic = m_pPrinterGfx->GetFontMgr().getFontItalic( nID ); + if( eItalic != ITALIC_NORMAL && eItalic != ITALIC_OBLIQUE ) + bArtItalic = true; + } + FontWeight nWeight = rEntry.GetWeight(); + FontWeight nRealWeight = m_pPrinterGfx->GetFontMgr().getFontWeight( nID ); + if( nRealWeight <= WEIGHT_MEDIUM && nWeight > WEIGHT_MEDIUM ) + { + bArtBold = true; + } + + // also set the serverside font for layouting + // requesting a font provided by builtin rasterizer + FreetypeFontInstance* pFreetypeFont = static_cast<FreetypeFontInstance*>(pFontInstance); + m_pFreetypeFont[ nFallbackLevel ] = pFreetypeFont; + + // ignore fonts with e.g. corrupted font files + if (!m_pFreetypeFont[nFallbackLevel]->GetFreetypeFont().TestFont()) + m_pFreetypeFont[nFallbackLevel] = nullptr; + + // set the printer font + m_pPrinterGfx->SetFont( nID, + rEntry.mnHeight, + rEntry.mnWidth, + rEntry.mnOrientation, + rEntry.mbVertical, + bArtItalic, + bArtBold + ); +} + +void GenPspGraphics::SetTextColor( Color nColor ) +{ + psp::PrinterColor aColor (nColor.GetRed(), + nColor.GetGreen(), + nColor.GetBlue()); + m_pPrinterGfx->SetTextColor (aColor); +} + +bool GenPspGraphics::AddTempDevFont( vcl::font::PhysicalFontCollection*, const OUString&,const OUString& ) +{ + return false; +} + +bool GenPspGraphics::AddTempDevFontHelper( vcl::font::PhysicalFontCollection* pFontCollection, + std::u16string_view rFileURL, + const OUString& rFontName) +{ + // inform PSP font manager + psp::PrintFontManager& rMgr = psp::PrintFontManager::get(); + std::vector<psp::fontID> aFontIds = rMgr.addFontFile( rFileURL ); + if( aFontIds.empty() ) + return false; + + FreetypeManager& rFreetypeManager = FreetypeManager::get(); + for (auto const& elem : aFontIds) + { + // prepare font data + psp::FastPrintFontInfo aInfo; + rMgr.getFontFastInfo( elem, aInfo ); + if (!rFontName.isEmpty()) + aInfo.m_aFamilyName = rFontName; + + // inform glyph cache of new font + FontAttributes aDFA = GenPspGraphics::Info2FontAttributes( aInfo ); + aDFA.IncreaseQualityBy( 5800 ); + + int nFaceNum = rMgr.getFontFaceNumber( aInfo.m_nID ); + int nVariantNum = rMgr.getFontFaceVariation( aInfo.m_nID ); + + const OString& rFileName = rMgr.getFontFileSysPath( aInfo.m_nID ); + rFreetypeManager.AddFontFile(rFileName, nFaceNum, nVariantNum, aInfo.m_nID, aDFA); + } + + // announce new font to device's font list + rFreetypeManager.AnnounceFonts(pFontCollection); + return true; +} + +void GenPspGraphics::GetDevFontList( vcl::font::PhysicalFontCollection *pFontCollection ) +{ + ::std::vector< psp::fontID > aList; + psp::PrintFontManager& rMgr = psp::PrintFontManager::get(); + rMgr.getFontList( aList ); + + psp::FastPrintFontInfo aInfo; + for (auto const& elem : aList) + if (rMgr.getFontFastInfo (elem, aInfo)) + AnnounceFonts( pFontCollection, aInfo ); + + // register platform specific font substitutions if available + SalGenericInstance::RegisterFontSubstitutors( pFontCollection ); +} + +void GenPspGraphics::ClearDevFontCache() +{ + FreetypeManager::get().ClearFontCache(); +} + +void GenPspGraphics::GetFontMetric(ImplFontMetricDataRef& rxFontMetric, int nFallbackLevel) +{ + if (nFallbackLevel >= MAX_FALLBACK) + return; + + if (m_pFreetypeFont[nFallbackLevel]) + m_pFreetypeFont[nFallbackLevel]->GetFreetypeFont().GetFontMetric(rxFontMetric); +} + +std::unique_ptr<GenericSalLayout> GenPspGraphics::GetTextLayout(int nFallbackLevel) +{ + assert(m_pFreetypeFont[nFallbackLevel]); + if (!m_pFreetypeFont[nFallbackLevel]) + return nullptr; + return std::make_unique<PspSalLayout>(*m_pPrinterGfx, *m_pFreetypeFont[nFallbackLevel]); +} + +bool GenPspGraphics::CreateFontSubset( + const OUString& rToFile, + const vcl::font::PhysicalFontFace* pFont, + const sal_GlyphId* pGlyphIds, + const sal_uInt8* pEncoding, + sal_Int32* pWidths, + int nGlyphCount, + FontSubsetInfo& rInfo + ) +{ + // in this context the pFont->GetFontId() is a valid PSP + // font since they are the only ones left after the PDF + // export has filtered its list of subsettable fonts (for + // which this method was created). The correct way would + // be to have the FreetypeManager search for the PhysicalFontFace pFont + psp::fontID aFont = pFont->GetFontId(); + + psp::PrintFontManager& rMgr = psp::PrintFontManager::get(); + bool bSuccess = rMgr.createFontSubset( rInfo, + aFont, + rToFile, + pGlyphIds, + pEncoding, + pWidths, + nGlyphCount ); + return bSuccess; +} + +void GenPspGraphics::GetGlyphWidths( const vcl::font::PhysicalFontFace* pFont, + bool bVertical, + std::vector< sal_Int32 >& rWidths, + Ucs2UIntMap& rUnicodeEnc ) +{ + // in this context the pFont->GetFontId() is a valid PSP + // font since they are the only ones left after the PDF + // export has filtered its list of subsettable fonts (for + // which this method was created). The correct way would + // be to have the FreetypeManager search for the PhysicalFontFace pFont + psp::fontID aFont = pFont->GetFontId(); + GenPspGraphics::DoGetGlyphWidths( aFont, bVertical, rWidths, rUnicodeEnc ); +} + +void GenPspGraphics::DoGetGlyphWidths( psp::fontID aFont, + bool bVertical, + std::vector< sal_Int32 >& rWidths, + Ucs2UIntMap& rUnicodeEnc ) +{ + psp::PrintFontManager& rMgr = psp::PrintFontManager::get(); + rMgr.getGlyphWidths( aFont, bVertical, rWidths, rUnicodeEnc ); +} + +FontAttributes GenPspGraphics::Info2FontAttributes( const psp::FastPrintFontInfo& rInfo ) +{ + FontAttributes aDFA; + aDFA.SetFamilyName( rInfo.m_aFamilyName ); + aDFA.SetStyleName( rInfo.m_aStyleName ); + aDFA.SetFamilyType( rInfo.m_eFamilyStyle ); + aDFA.SetWeight( rInfo.m_eWeight ); + aDFA.SetItalic( rInfo.m_eItalic ); + aDFA.SetWidthType( rInfo.m_eWidth ); + aDFA.SetPitch( rInfo.m_ePitch ); + aDFA.SetSymbolFlag( rInfo.m_aEncoding == RTL_TEXTENCODING_SYMBOL ); + aDFA.SetQuality(512); + + // add font family name aliases + for (auto const& alias : rInfo.m_aAliases) + aDFA.AddMapName(alias); + +#if OSL_DEBUG_LEVEL > 2 + if( aDFA.GetMapNames().getLength() > 0 ) + { + SAL_INFO( "vcl.fonts", "using alias names " << aDFA.GetMapNames() << " for font family " << aDFA.GetFamilyName() ); + } +#endif + + return aDFA; +} + +namespace vcl +{ + const char* getLangBoost() + { + const char* pLangBoost; + const LanguageType eLang = Application::GetSettings().GetUILanguageTag().getLanguageType(); + if (eLang == LANGUAGE_JAPANESE) + pLangBoost = "jan"; + else if (MsLangId::isKorean(eLang)) + pLangBoost = "kor"; + else if (MsLangId::isSimplifiedChinese(eLang)) + pLangBoost = "zhs"; + else if (MsLangId::isTraditionalChinese(eLang)) + pLangBoost = "zht"; + else + pLangBoost = nullptr; + return pLangBoost; + } +} + +void GenPspGraphics::AnnounceFonts( vcl::font::PhysicalFontCollection* pFontCollection, const psp::FastPrintFontInfo& aInfo ) +{ + int nQuality = 0; + + psp::PrintFontManager& rMgr = psp::PrintFontManager::get(); + OString aFileName( rMgr.getFontFileSysPath( aInfo.m_nID ) ); + int nPos = aFileName.lastIndexOf( '_' ); + if( nPos == -1 || aFileName[nPos+1] == '.' ) + nQuality += 5; + else + { + static const char* pLangBoost = nullptr; + static bool bOnce = true; + if( bOnce ) + { + bOnce = false; + pLangBoost = vcl::getLangBoost(); + } + + if( pLangBoost ) + if( o3tl::equalsIgnoreAsciiCase(aFileName.subView( nPos+1, 3 ), pLangBoost ) ) + nQuality += 10; + } + + rtl::Reference<ImplPspFontData> pFD(new ImplPspFontData( aInfo )); + pFD->IncreaseQualityBy( nQuality ); + pFontCollection->Add( pFD.get() ); +} + +SystemGraphicsData GenPspGraphics::GetGraphicsData() const +{ + return SystemGraphicsData(); +} + +#if ENABLE_CAIRO_CANVAS + +bool GenPspGraphics::SupportsCairo() const +{ + return false; +} + +cairo::SurfaceSharedPtr GenPspGraphics::CreateSurface(const cairo::CairoSurfaceSharedPtr& /*rSurface*/) const +{ + return cairo::SurfaceSharedPtr(); +} + +cairo::SurfaceSharedPtr GenPspGraphics::CreateSurface(const OutputDevice& /*rRefDevice*/, int /*x*/, int /*y*/, int /*width*/, int /*height*/) const +{ + return cairo::SurfaceSharedPtr(); +} + +cairo::SurfaceSharedPtr GenPspGraphics::CreateBitmapSurface(const OutputDevice& /*rRefDevice*/, const BitmapSystemData& /*rData*/, const Size& /*rSize*/) const +{ + return cairo::SurfaceSharedPtr(); +} + +css::uno::Any GenPspGraphics::GetNativeSurfaceHandle(cairo::SurfaceSharedPtr& /*rSurface*/, const basegfx::B2ISize& /*rSize*/) const +{ + return css::uno::Any(); +} + +#endif // ENABLE_CAIRO_CANVAS + +void GenPspGraphics::DoFreeEmbedFontData( const void* pData, tools::Long nLen ) +{ + if( pData ) + munmap( const_cast<void *>(pData), nLen ); +} + +const void* GenPspGraphics::DoGetEmbedFontData(psp::fontID aFont, tools::Long* pDataLen) +{ + + psp::PrintFontManager& rMgr = psp::PrintFontManager::get(); + + OString aSysPath = rMgr.getFontFileSysPath( aFont ); + + int fd = open( aSysPath.getStr(), O_RDONLY ); + if( fd < 0 ) + return nullptr; + struct stat aStat; + if( fstat( fd, &aStat ) ) + { + close( fd ); + return nullptr; + } + void* pFile = mmap( nullptr, aStat.st_size, PROT_READ, MAP_SHARED, fd, 0 ); + close( fd ); + if( pFile == MAP_FAILED ) + return nullptr; + *pDataLen = aStat.st_size; + + return pFile; +} + +void GenPspGraphics::FreeEmbedFontData( const void* pData, tools::Long nLen ) +{ + DoFreeEmbedFontData( pData, nLen ); +} + +const void* GenPspGraphics::GetEmbedFontData(const vcl::font::PhysicalFontFace* pFont, tools::Long* pDataLen) +{ + // in this context the pFont->GetFontId() is a valid PSP + // font since they are the only ones left after the PDF + // export has filtered its list of subsettable fonts (for + // which this method was created). The correct way would + // be to have the FreetypeManager search for the PhysicalFontFace pFont + psp::fontID aFont = pFont->GetFontId(); + return DoGetEmbedFontData(aFont, pDataLen); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/generic/print/glyphset.cxx b/vcl/unx/generic/print/glyphset.cxx new file mode 100644 index 000000000..6b0475a62 --- /dev/null +++ b/vcl/unx/generic/print/glyphset.cxx @@ -0,0 +1,301 @@ +/* -*- 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 "glyphset.hxx" + +#include <sft.hxx> + +#include <unx/printergfx.hxx> +#include <fontsubset.hxx> +#include <unx/fontmanager.hxx> + +#include <tools/gen.hxx> + +#include <osl/thread.h> + +#include <rtl/ustring.hxx> +#include <rtl/strbuf.hxx> + +#include <unotools/tempfile.hxx> + +#include <algorithm> + +using namespace vcl; +using namespace psp; + +GlyphSet::GlyphSet (sal_Int32 nFontID, bool bVertical) + : mnFontID (nFontID), + mbVertical (bVertical) +{ + PrintFontManager &rMgr = PrintFontManager::get(); + maBaseName = OUStringToOString (rMgr.getPSName(mnFontID), + RTL_TEXTENCODING_ASCII_US); +} + +void +GlyphSet::GetGlyphID ( + sal_GlyphId nGlyph, + unsigned char* nOutGlyphID, + sal_Int32* nOutGlyphSetID + ) +{ + if (!LookupGlyphID(nGlyph, nOutGlyphID, nOutGlyphSetID)) + AddGlyphID(nGlyph, nOutGlyphID, nOutGlyphSetID); +} + +bool +GlyphSet::LookupGlyphID ( + sal_GlyphId nGlyph, + unsigned char* nOutGlyphID, + sal_Int32* nOutGlyphSetID + ) +{ + sal_Int32 nGlyphSetID = 1; + + // loop through all the font subsets + for (auto const& glyph : maGlyphList) + { + // check every subset if it contains the queried unicode char + glyph_map_t::const_iterator aGlyph = glyph.find (nGlyph); + if (aGlyph != glyph.end()) + { + // success: found the glyph id, return the mapped glyphid and the glyphsetid + *nOutGlyphSetID = nGlyphSetID; + *nOutGlyphID = aGlyph->second; + return true; + } + ++nGlyphSetID; + } + + *nOutGlyphSetID = -1; + *nOutGlyphID = 0; + return false; +} + +void +GlyphSet::AddNotdef (glyph_map_t &rGlyphMap) +{ + if (rGlyphMap.empty()) + rGlyphMap[0] = 0; +} + +void +GlyphSet::AddGlyphID ( + sal_GlyphId nGlyph, + unsigned char* nOutGlyphID, + sal_Int32* nOutGlyphSetID + ) +{ + // create an empty glyphmap that is reserved for unencoded symbol glyphs, + // and a second map that takes any other + if (maGlyphList.empty()) + { + glyph_map_t aMap, aMapp; + + maGlyphList.push_back (aMap); + maGlyphList.push_back (aMapp); + } + // if the last map is full, create a new one + if (maGlyphList.back().size() == 255) + { + glyph_map_t aMap; + maGlyphList.push_back (aMap); + } + + glyph_map_t& aGlyphSet = maGlyphList.back(); + AddNotdef (aGlyphSet); + + int nSize = aGlyphSet.size(); + + aGlyphSet [nGlyph] = nSize; + *nOutGlyphSetID = maGlyphList.size(); + *nOutGlyphID = aGlyphSet [nGlyph]; +} + +OString +GlyphSet::GetGlyphSetName (sal_Int32 nGlyphSetID) +{ + OStringBuffer aSetName( maBaseName.getLength() + 32 ); + aSetName.append( maBaseName ); + aSetName.append( "FID" ); + aSetName.append( mnFontID ); + aSetName.append( mbVertical ? "VGSet" : "HGSet" ); + aSetName.append( nGlyphSetID ); + return aSetName.makeStringAndClear(); +} + +OString +GlyphSet::GetReencodedFontName (rtl_TextEncoding nEnc, std::string_view rFontName) +{ + if ( nEnc == RTL_TEXTENCODING_MS_1252 + || nEnc == RTL_TEXTENCODING_ISO_8859_1) + { + return OString::Concat(rFontName) + "-iso1252"; + } + else + if (nEnc >= RTL_TEXTENCODING_USER_START && nEnc <= RTL_TEXTENCODING_USER_END) + { + return OString::Concat(rFontName) + + "-enc" + + OString::number(nEnc - RTL_TEXTENCODING_USER_START); + } + else + { + return OString(); + } +} + +void GlyphSet::DrawGlyph(PrinterGfx& rGfx, + const Point& rPoint, + const sal_GlyphId nGlyphId) +{ + unsigned char nGlyphID; + sal_Int32 nGlyphSetID; + + // convert to font glyph id and font subset + GetGlyphID (nGlyphId, &nGlyphID, &nGlyphSetID); + + OString aGlyphSetName = GetGlyphSetName(nGlyphSetID); + + rGfx.PSSetFont (aGlyphSetName, RTL_TEXTENCODING_DONTKNOW); + rGfx.PSMoveTo (rPoint); + rGfx.PSShowGlyph(nGlyphID); +} + +namespace { + +struct EncEntry +{ + unsigned char aEnc; + tools::Long aGID; + + EncEntry() : aEnc( 0 ), aGID( 0 ) {} + + bool operator<( const EncEntry& rRight ) const + { return aEnc < rRight.aEnc; } +}; + +} + +static void CreatePSUploadableFont( TrueTypeFont* pSrcFont, FILE* pTmpFile, + const char* pGlyphSetName, int nGlyphCount, + /*const*/ const sal_uInt16* pRequestedGlyphs, /*const*/ const unsigned char* pEncoding, + bool bAllowType42 ) +{ + // match the font-subset to the printer capabilities + // TODO: allow CFF for capable printers + FontType nTargetMask = FontType::TYPE1_PFA | FontType::TYPE3_FONT; + if( bAllowType42 ) + nTargetMask |= FontType::TYPE42_FONT; + + std::vector< EncEntry > aSorted( nGlyphCount, EncEntry() ); + for( int i = 0; i < nGlyphCount; i++ ) + { + aSorted[i].aEnc = pEncoding[i]; + aSorted[i].aGID = pRequestedGlyphs[i]; + } + + std::stable_sort( aSorted.begin(), aSorted.end() ); + + std::vector< unsigned char > aEncoding( nGlyphCount ); + std::vector< sal_GlyphId > aRequestedGlyphs( nGlyphCount ); + + for( int i = 0; i < nGlyphCount; i++ ) + { + aEncoding[i] = aSorted[i].aEnc; + aRequestedGlyphs[i] = aSorted[i].aGID; + } + + FontSubsetInfo aInfo; + aInfo.LoadFont( pSrcFont ); + + aInfo.CreateFontSubset( nTargetMask, pTmpFile, pGlyphSetName, + aRequestedGlyphs.data(), aEncoding.data(), nGlyphCount ); +} + +void +GlyphSet::PSUploadFont (osl::File& rOutFile, PrinterGfx &rGfx, bool bAllowType42, std::vector< OString >& rSuppliedFonts ) +{ + TrueTypeFont *pTTFont; + OString aTTFileName (rGfx.GetFontMgr().getFontFileSysPath(mnFontID)); + int nFace = rGfx.GetFontMgr().getFontFaceNumber(mnFontID); + SFErrCodes nSuccess = OpenTTFontFile(aTTFileName.getStr(), nFace, &pTTFont); + if (nSuccess != SFErrCodes::Ok) + return; + + utl::TempFile aTmpFile; + aTmpFile.EnableKillingFile(); + FILE* pTmpFile = fopen(OUStringToOString(aTmpFile.GetFileName(), osl_getThreadTextEncoding()).getStr(), "w+b"); + if (pTmpFile == nullptr) + return; + + // encoding vector maps character encoding to the ordinal number + // of the glyph in the output file + unsigned char pEncoding[256]; + sal_uInt16 pTTGlyphMapping[256]; + + // loop through all the font glyph subsets + sal_Int32 nGlyphSetID = 1; + for (auto const& glyph : maGlyphList) + { + if (glyph.empty()) + { + ++nGlyphSetID; + continue; + } + + // loop through all the glyphs in the subset + sal_Int32 n = 0; + for (auto const& elem : glyph) + { + pTTGlyphMapping [n] = elem.first; + pEncoding [n] = elem.second; + n++; + } + + // create the current subset + OString aGlyphSetName = GetGlyphSetName(nGlyphSetID); + fprintf( pTmpFile, "%%%%BeginResource: font %s\n", aGlyphSetName.getStr() ); + CreatePSUploadableFont( pTTFont, pTmpFile, aGlyphSetName.getStr(), glyph.size(), + pTTGlyphMapping, pEncoding, bAllowType42 ); + fprintf( pTmpFile, "%%%%EndResource\n" ); + rSuppliedFonts.push_back( aGlyphSetName ); + ++nGlyphSetID; + } + + // copy the file into the page header + rewind(pTmpFile); + fflush(pTmpFile); + + unsigned char pBuffer[0x2000]; + sal_uInt64 nIn; + sal_uInt64 nOut; + do + { + nIn = fread(pBuffer, 1, sizeof(pBuffer), pTmpFile); + rOutFile.write (pBuffer, nIn, nOut); + } + while ((nIn == nOut) && !feof(pTmpFile)); + + // cleanup + CloseTTFont (pTTFont); + fclose (pTmpFile); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/generic/print/glyphset.hxx b/vcl/unx/generic/print/glyphset.hxx new file mode 100644 index 000000000..db7fe72ef --- /dev/null +++ b/vcl/unx/generic/print/glyphset.hxx @@ -0,0 +1,81 @@ +/* -*- 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 . + */ + +#pragma once + +#include <osl/file.hxx> + +#include <rtl/string.hxx> + +#include <glyphid.hxx> + +#include <string_view> +#include <vector> +#include <unordered_map> + +class Point; + +namespace psp { + +class PrinterGfx; +class PrintFontManager; + +class GlyphSet +{ +private: + + sal_Int32 mnFontID; + bool mbVertical; + OString maBaseName; + + typedef std::unordered_map< sal_GlyphId, sal_uInt8 > glyph_map_t; + std::vector< glyph_map_t > maGlyphList; + + OString GetGlyphSetName (sal_Int32 nGlyphSetID); + + void GetGlyphID (sal_GlyphId nGlyphId, + unsigned char* nOutGlyphID, sal_Int32* nOutGlyphSetID); + bool LookupGlyphID (sal_GlyphId nGlyphId, + unsigned char* nOutGlyphID, sal_Int32* nOutGlyphSetID); + void AddGlyphID (sal_GlyphId nGlyphId, + unsigned char* nOutGlyphID, + sal_Int32* nOutGlyphSetID); + static void AddNotdef (glyph_map_t &rGlyphMap); + +public: + + GlyphSet (sal_Int32 nFontID, bool bVertical); + /* FIXME delete the glyphlist in ~GlyphSet ??? */ + + sal_Int32 GetFontID () const { return mnFontID;} + static OString + GetReencodedFontName (rtl_TextEncoding nEnc, + std::string_view rFontName); + + bool IsVertical () const { return mbVertical;} + + void DrawGlyph (PrinterGfx& rGfx, + const Point& rPoint, + const sal_GlyphId nGlyphId); + void PSUploadFont (osl::File& rOutFile, PrinterGfx &rGfx, bool bAsType42, std::vector< OString >& rSuppliedFonts ); +}; + +} /* namespace psp */ + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/generic/print/printerjob.cxx b/vcl/unx/generic/print/printerjob.cxx new file mode 100644 index 000000000..233bd2195 --- /dev/null +++ b/vcl/unx/generic/print/printerjob.cxx @@ -0,0 +1,973 @@ +/* -*- 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 <stdio.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <unistd.h> + +#include "psputil.hxx" + +#include <unx/printerjob.hxx> +#include <unx/printergfx.hxx> +#include <ppdparser.hxx> +#include <strhelper.hxx> +#include <printerinfomanager.hxx> + +#include <rtl/ustring.hxx> +#include <rtl/strbuf.hxx> +#include <rtl/ustrbuf.hxx> + +#include <osl/thread.h> +#include <osl/security.hxx> + +#include <algorithm> +#include <cstddef> +#include <deque> +#include <vector> + +using namespace psp; + +#define nBLOCKSIZE 0x2000 + +namespace psp +{ + +static bool +AppendPS (FILE* pDst, osl::File* pSrc, unsigned char* pBuffer) +{ + assert(pBuffer); + if ((pDst == nullptr) || (pSrc == nullptr)) + return false; + + if (pSrc->setPos(osl_Pos_Absolut, 0) != osl::FileBase::E_None) + return false; + + sal_uInt64 nIn = 0; + sal_uInt64 nOut = 0; + do + { + pSrc->read (pBuffer, nBLOCKSIZE, nIn); + if (nIn > 0) + nOut = fwrite (pBuffer, 1, sal::static_int_cast<sal_uInt32>(nIn), pDst); + } + while ((nIn > 0) && (nIn == nOut)); + + return true; +} + +} // namespace psp + +/* + * private convenience routines for file handling + */ + +std::unique_ptr<osl::File> +PrinterJob::CreateSpoolFile (std::u16string_view rName, std::u16string_view rExtension) const +{ + OUString aFile = OUString::Concat(rName) + rExtension; + OUString aFileURL; + osl::File::RC nError = osl::File::getFileURLFromSystemPath( aFile, aFileURL ); + if (nError != osl::File::E_None) + return nullptr; + aFileURL = maSpoolDirName + "/" + aFileURL; + + std::unique_ptr<osl::File> pFile( new osl::File (aFileURL) ); + nError = pFile->open (osl_File_OpenFlag_Read | osl_File_OpenFlag_Write | osl_File_OpenFlag_Create); + if (nError != osl::File::E_None) + { + return nullptr; + } + + osl::File::setAttributes (aFileURL, + osl_File_Attribute_OwnWrite | osl_File_Attribute_OwnRead); + return pFile; +} + +/* + * public methods of PrinterJob: for use in PrinterGfx + */ + +void +PrinterJob::GetScale (double &rXScale, double &rYScale) const +{ + rXScale = mfXScale; + rYScale = mfYScale; +} + +sal_uInt16 +PrinterJob::GetDepth () const +{ + sal_Int32 nLevel = GetPostscriptLevel(); + bool bColor = IsColorPrinter (); + + return nLevel > 1 && bColor ? 24 : 8; +} + +sal_uInt16 +PrinterJob::GetPostscriptLevel (const JobData *pJobData) const +{ + sal_uInt16 nPSLevel = 2; + + if( pJobData == nullptr ) + pJobData = &m_aLastJobData; + + if( pJobData->m_nPSLevel ) + nPSLevel = pJobData->m_nPSLevel; + else + if( pJobData->m_pParser ) + nPSLevel = pJobData->m_pParser->getLanguageLevel(); + + return nPSLevel; +} + +bool +PrinterJob::IsColorPrinter () const +{ + bool bColor = false; + + if( m_aLastJobData.m_nColorDevice ) + bColor = m_aLastJobData.m_nColorDevice != -1; + else if( m_aLastJobData.m_pParser ) + bColor = m_aLastJobData.m_pParser->isColorDevice(); + + return bColor; +} + +osl::File* +PrinterJob::GetCurrentPageBody () +{ + return maPageVector.back().get(); +} + +/* + * public methods of PrinterJob: the actual job / spool handling + */ +PrinterJob::PrinterJob() + : mnFileMode(0) + , m_pGraphics(nullptr) + , mnResolution(96) + , mnWidthPt(0) + , mnHeightPt(0) + , mnMaxWidthPt(0) + , mnMaxHeightPt(0) + , mnLandscapes(0) + , mnPortraits(0) + , mnLMarginPt(0) + , mnRMarginPt(0) + , mnTMarginPt(0) + , mnBMarginPt(0) + , mfXScale(1) + , mfYScale(1) + , m_bQuickJob(false) +{ +} + +/* remove all our temporary files, uses external program "rm", since + osl functionality is inadequate */ +static void +removeSpoolDir (const OUString& rSpoolDir) +{ + OUString aSysPath; + if( osl::File::E_None != osl::File::getSystemPathFromFileURL( rSpoolDir, aSysPath ) ) + { + // Conversion did not work, as this is quite a dangerous action, + // we should abort here... + OSL_FAIL( "psprint: couldn't remove spool directory" ); + return; + } + OString aSysPathByte = + OUStringToOString (aSysPath, osl_getThreadTextEncoding()); + if (system (OString("rm -rf " + aSysPathByte).getStr()) == -1) + OSL_FAIL( "psprint: couldn't remove spool directory" ); +} + +/* creates a spool directory with a "pidgin random" value based on + current system time */ +static OUString +createSpoolDir () +{ + TimeValue aCur; + osl_getSystemTime( &aCur ); + sal_Int32 nRand = aCur.Seconds ^ (aCur.Nanosec/1000); + + OUString aTmpDir; + osl_getTempDirURL( &aTmpDir.pData ); + + do + { + OUString aDir = aTmpDir + "/psp" + OUString::number(nRand); + if( osl::Directory::create( aDir ) == osl::FileBase::E_None ) + { + osl::File::setAttributes( aDir, + osl_File_Attribute_OwnWrite + | osl_File_Attribute_OwnRead + | osl_File_Attribute_OwnExe ); + return aDir; + } + nRand++; + } while( nRand ); + return OUString(); +} + +PrinterJob::~PrinterJob () +{ + maPageVector.clear(); + maHeaderVector.clear(); + + // mpJobHeader->remove(); + mpJobHeader.reset(); + // mpJobTrailer->remove(); + mpJobTrailer.reset(); + + // XXX should really call osl::remove routines + if( !maSpoolDirName.isEmpty() ) + removeSpoolDir (maSpoolDirName); + + // osl::Directory::remove (maSpoolDirName); +} + +static void WriteLocalTimePS( osl::File *rFile ) +{ + TimeValue aStartTime, tLocal; + oslDateTime date_time; + if (osl_getSystemTime( &aStartTime ) && + osl_getLocalTimeFromSystemTime( &aStartTime, &tLocal ) && + osl_getDateTimeFromTimeValue( &tLocal, &date_time )) + { + char ar[ 256 ]; + snprintf( + ar, sizeof (ar), + "%04d-%02d-%02d %02d:%02d:%02d ", + date_time.Year, date_time.Month, date_time.Day, + date_time.Hours, date_time.Minutes, date_time.Seconds ); + WritePS( rFile, ar ); + } + else + WritePS( rFile, "Unknown-Time" ); +} + +static bool isAscii( const OUString& rStr ) +{ + sal_Int32 nLen = rStr.getLength(); + for( sal_Int32 i = 0; i < nLen; i++ ) + if( rStr[i] > 127 ) + return false; + return true; +} + +bool +PrinterJob::StartJob ( + const OUString& rFileName, + int nMode, + const OUString& rJobName, + std::u16string_view rAppName, + const JobData& rSetupData, + PrinterGfx* pGraphics, + bool bIsQuickJob + ) +{ + m_bQuickJob = bIsQuickJob; + mnMaxWidthPt = mnMaxHeightPt = 0; + mnLandscapes = mnPortraits = 0; + m_pGraphics = pGraphics; + InitPaperSize (rSetupData); + + // create file container for document header and trailer + maFileName = rFileName; + mnFileMode = nMode; + maSpoolDirName = createSpoolDir (); + maJobTitle = rJobName; + + OUString aExt(".ps"); + mpJobHeader = CreateSpoolFile (u"psp_head", aExt); + mpJobTrailer = CreateSpoolFile (u"psp_tail", aExt); + if( ! (mpJobHeader && mpJobTrailer) ) // existing files are removed in destructor + return false; + + // write document header according to Document Structuring Conventions (DSC) + WritePS (mpJobHeader.get(), + "%!PS-Adobe-3.0\n" + "%%BoundingBox: (atend)\n" ); + + // Creator (this application) + OUString aFilterWS = WhitespaceToSpace( rAppName, false ); + WritePS (mpJobHeader.get(), "%%Creator: ("); + WritePS (mpJobHeader.get(), aFilterWS); + WritePS (mpJobHeader.get(), ")\n"); + + // For (user name) + osl::Security aSecurity; + OUString aUserName; + if( aSecurity.getUserName( aUserName ) ) + { + WritePS (mpJobHeader.get(), "%%For: ("); + WritePS (mpJobHeader.get(), aUserName); + WritePS (mpJobHeader.get(), ")\n"); + } + + // Creation Date (locale independent local time) + WritePS (mpJobHeader.get(), "%%CreationDate: ("); + WriteLocalTimePS (mpJobHeader.get()); + WritePS (mpJobHeader.get(), ")\n"); + + // Document Title + /* #i74335# + * The title should be clean ascii; rJobName however may + * contain any Unicode character. So implement the following + * algorithm: + * use rJobName, if it contains only ascii + * use the filename, if it contains only ascii + * else omit %%Title + */ + aFilterWS = WhitespaceToSpace( rJobName, false ); + OUString aTitle( aFilterWS ); + if( ! isAscii( aTitle ) ) + { + aTitle = WhitespaceToSpace( rFileName.subView(rFileName.lastIndexOf('/')+1), false ); + if( ! isAscii( aTitle ) ) + aTitle.clear(); + } + + maJobTitle = aFilterWS; + if( !aTitle.isEmpty() ) + { + WritePS (mpJobHeader.get(), "%%Title: ("); + WritePS (mpJobHeader.get(), aTitle); + WritePS (mpJobHeader.get(), ")\n"); + } + + // Language Level + OStringBuffer pLevel; + getValueOf(GetPostscriptLevel(&rSetupData), pLevel); + pLevel.append('\n'); + WritePS (mpJobHeader.get(), "%%LanguageLevel: "); + WritePS (mpJobHeader.get(), pLevel.makeStringAndClear()); + + // Other + WritePS (mpJobHeader.get(), "%%DocumentData: Clean7Bit\n"); + WritePS (mpJobHeader.get(), "%%Pages: (atend)\n"); + WritePS (mpJobHeader.get(), "%%Orientation: (atend)\n"); + WritePS (mpJobHeader.get(), "%%PageOrder: Ascend\n"); + WritePS (mpJobHeader.get(), "%%EndComments\n"); + + // write Prolog + writeProlog (mpJobHeader.get(), rSetupData); + + // mark last job setup as not set + m_aLastJobData.m_pParser = nullptr; + m_aLastJobData.m_aContext.setParser( nullptr ); + + return true; +} + +bool +PrinterJob::EndJob() +{ + // no pages ? that really means no print job + if( maPageVector.empty() ) + return false; + + // write document setup (done here because it + // includes the accumulated fonts + if( mpJobHeader ) + writeSetup( mpJobHeader.get(), m_aDocumentJobData ); + m_pGraphics->OnEndJob(); + if( ! (mpJobHeader && mpJobTrailer) ) + return false; + + // write document trailer according to Document Structuring Conventions (DSC) + OStringBuffer aTrailer(512); + aTrailer.append( "%%Trailer\n" ); + aTrailer.append( "%%BoundingBox: 0 0 " ); + aTrailer.append( static_cast<sal_Int32>(mnMaxWidthPt) ); + aTrailer.append( " " ); + aTrailer.append( static_cast<sal_Int32>(mnMaxHeightPt) ); + if( mnLandscapes > mnPortraits ) + aTrailer.append("\n%%Orientation: Landscape"); + else + aTrailer.append("\n%%Orientation: Portrait"); + aTrailer.append( "\n%%Pages: " ); + aTrailer.append( static_cast<sal_Int32>(maPageVector.size()) ); + aTrailer.append( "\n%%EOF\n" ); + WritePS (mpJobTrailer.get(), aTrailer.getStr()); + + /* + * spool the set of files to their final destination, this is U**X dependent + */ + + FILE* pDestFILE = nullptr; + + /* create a destination either as file or as a pipe */ + bool bSpoolToFile = !maFileName.isEmpty(); + if (bSpoolToFile) + { + const OString aFileName = OUStringToOString (maFileName, + osl_getThreadTextEncoding()); + if( mnFileMode ) + { + int nFile = open( aFileName.getStr(), O_CREAT | O_EXCL | O_RDWR, mnFileMode ); + if( nFile != -1 ) + { + pDestFILE = fdopen( nFile, "w" ); + if( pDestFILE == nullptr ) + { + close( nFile ); + unlink( aFileName.getStr() ); + return false; + } + } + else + { + (void)chmod( aFileName.getStr(), mnFileMode ); + } + } + if (pDestFILE == nullptr) + pDestFILE = fopen (aFileName.getStr(), "w"); + + if (pDestFILE == nullptr) + return false; + } + else + { + PrinterInfoManager& rPrinterInfoManager = PrinterInfoManager::get (); + pDestFILE = rPrinterInfoManager.startSpool( m_aLastJobData.m_aPrinterName, m_bQuickJob ); + if (pDestFILE == nullptr) + return false; + } + + /* spool the document parts to the destination */ + + unsigned char pBuffer[ nBLOCKSIZE ]; + + AppendPS (pDestFILE, mpJobHeader.get(), pBuffer); + mpJobHeader->close(); + + bool bSuccess = true; + std::vector< std::unique_ptr<osl::File> >::iterator pPageBody; + std::vector< std::unique_ptr<osl::File> >::iterator pPageHead; + for (pPageBody = maPageVector.begin(), pPageHead = maHeaderVector.begin(); + pPageBody != maPageVector.end() && pPageHead != maHeaderVector.end(); + ++pPageBody, ++pPageHead) + { + if( *pPageHead ) + { + osl::File::RC nError = (*pPageHead)->open(osl_File_OpenFlag_Read); + if (nError == osl::File::E_None) + { + AppendPS (pDestFILE, pPageHead->get(), pBuffer); + (*pPageHead)->close(); + } + } + else + bSuccess = false; + if( *pPageBody ) + { + osl::File::RC nError = (*pPageBody)->open(osl_File_OpenFlag_Read); + if (nError == osl::File::E_None) + { + AppendPS (pDestFILE, pPageBody->get(), pBuffer); + (*pPageBody)->close(); + } + } + else + bSuccess = false; + } + + AppendPS (pDestFILE, mpJobTrailer.get(), pBuffer); + mpJobTrailer->close(); + + /* well done */ + + if (bSpoolToFile) + fclose (pDestFILE); + else + { + PrinterInfoManager& rPrinterInfoManager = PrinterInfoManager::get(); + if (!rPrinterInfoManager.endSpool( m_aLastJobData.m_aPrinterName, + maJobTitle, pDestFILE, m_aDocumentJobData, true, OUString())) + { + bSuccess = false; + } + } + + return bSuccess; +} + +void +PrinterJob::InitPaperSize (const JobData& rJobSetup) +{ + int nRes = rJobSetup.m_aContext.getRenderResolution (); + + OUString aPaper; + int nWidth, nHeight; + rJobSetup.m_aContext.getPageSize (aPaper, nWidth, nHeight); + + int nLeft = 0, nRight = 0, nUpper = 0, nLower = 0; + const PPDParser* pParser = rJobSetup.m_aContext.getParser(); + if (pParser != nullptr) + pParser->getMargins (aPaper, nLeft, nRight, nUpper, nLower); + + mnResolution = nRes; + + mnWidthPt = nWidth; + mnHeightPt = nHeight; + + if( mnWidthPt > mnMaxWidthPt ) + mnMaxWidthPt = mnWidthPt; + if( mnHeightPt > mnMaxHeightPt ) + mnMaxHeightPt = mnHeightPt; + + mnLMarginPt = nLeft; + mnRMarginPt = nRight; + mnTMarginPt = nUpper; + mnBMarginPt = nLower; + + mfXScale = 72.0 / static_cast<double>(mnResolution); + mfYScale = -1.0 * 72.0 / static_cast<double>(mnResolution); +} + +void +PrinterJob::StartPage (const JobData& rJobSetup) +{ + InitPaperSize (rJobSetup); + + OUString aPageNo = OUString::number (static_cast<sal_Int32>(maPageVector.size())+1); // sequential page number must start with 1 + OUString aExt = aPageNo + ".ps"; + + maHeaderVector.push_back( CreateSpoolFile ( u"psp_pghead", aExt) ); + maPageVector.push_back( CreateSpoolFile ( u"psp_pgbody", aExt) ); + + osl::File* pPageHeader = maHeaderVector.back().get(); + osl::File* pPageBody = maPageVector.back().get(); + + if( ! (pPageHeader && pPageBody) ) + return; + + // write page header according to Document Structuring Conventions (DSC) + WritePS (pPageHeader, "%%Page: "); + WritePS (pPageHeader, aPageNo); + WritePS (pPageHeader, " "); + WritePS (pPageHeader, aPageNo); + WritePS (pPageHeader, "\n"); + + if( rJobSetup.m_eOrientation == orientation::Landscape ) + { + WritePS (pPageHeader, "%%PageOrientation: Landscape\n"); + mnLandscapes++; + } + else + { + WritePS (pPageHeader, "%%PageOrientation: Portrait\n"); + mnPortraits++; + } + + OStringBuffer pBBox; + + psp::appendStr ("%%PageBoundingBox: ", pBBox); + psp::getValueOf (mnLMarginPt, pBBox); + psp::appendStr (" ", pBBox); + psp::getValueOf (mnBMarginPt, pBBox); + psp::appendStr (" ", pBBox); + psp::getValueOf (mnWidthPt - mnRMarginPt, pBBox); + psp::appendStr (" ", pBBox); + psp::getValueOf (mnHeightPt - mnTMarginPt, pBBox); + psp::appendStr ("\n", pBBox); + + WritePS (pPageHeader, pBBox.makeStringAndClear()); + + /* #i7262# #i65491# write setup only before first page + * (to %%Begin(End)Setup, instead of %%Begin(End)PageSetup) + * don't do this in StartJob since the jobsetup there may be + * different. + */ + bool bWriteFeatures = true; + if( 1 == maPageVector.size() ) + { + m_aDocumentJobData = rJobSetup; + bWriteFeatures = false; + } + + if ( writePageSetup( pPageHeader, rJobSetup, bWriteFeatures ) ) + { + m_aLastJobData = rJobSetup; + } +} + +void +PrinterJob::EndPage () +{ + osl::File* pPageHeader = maHeaderVector.back().get(); + osl::File* pPageBody = maPageVector.back().get(); + + if( ! (pPageBody && pPageHeader) ) + return; + + // copy page to paper and write page trailer according to DSC + + OStringBuffer pTrailer; + psp::appendStr ("grestore grestore\n", pTrailer); + psp::appendStr ("showpage\n", pTrailer); + psp::appendStr ("%%PageTrailer\n\n", pTrailer); + WritePS (pPageBody, pTrailer.makeStringAndClear()); + + // this page is done for now, close it to avoid having too many open fd's + + pPageHeader->close(); + pPageBody->close(); +} + +namespace { + +struct less_ppd_key +{ + bool operator()(const PPDKey* left, const PPDKey* right) + { return left->getOrderDependency() < right->getOrderDependency(); } +}; + +} + +static bool writeFeature( osl::File* pFile, const PPDKey* pKey, const PPDValue* pValue, bool bUseIncluseFeature ) +{ + if( ! pKey || ! pValue ) + return true; + + OStringBuffer aFeature(256); + aFeature.append( "[{\n" ); + if( bUseIncluseFeature ) + aFeature.append( "%%IncludeFeature:" ); + else + aFeature.append( "%%BeginFeature:" ); + aFeature.append( " *" ); + aFeature.append( OUStringToOString( pKey->getKey(), RTL_TEXTENCODING_ASCII_US ) ); + aFeature.append( ' ' ); + aFeature.append( OUStringToOString( pValue->m_aOption, RTL_TEXTENCODING_ASCII_US ) ); + if( !bUseIncluseFeature ) + { + aFeature.append( '\n' ); + aFeature.append( OUStringToOString( pValue->m_aValue, RTL_TEXTENCODING_ASCII_US ) ); + aFeature.append( "\n%%EndFeature" ); + } + aFeature.append( "\n} stopped cleartomark\n" ); + sal_uInt64 nWritten = 0; + return !(pFile->write( aFeature.getStr(), aFeature.getLength(), nWritten ) + || nWritten != static_cast<sal_uInt64>(aFeature.getLength())); +} + +bool PrinterJob::writeFeatureList( osl::File* pFile, const JobData& rJob, bool bDocumentSetup ) const +{ + bool bSuccess = true; + + // emit features ordered to OrderDependency + // ignore features that are set to default + + // sanity check + if( rJob.m_pParser == rJob.m_aContext.getParser() && + rJob.m_pParser && + ( m_aLastJobData.m_pParser == rJob.m_pParser || m_aLastJobData.m_pParser == nullptr ) + ) + { + std::size_t i; + std::size_t nKeys = rJob.m_aContext.countValuesModified(); + ::std::vector< const PPDKey* > aKeys( nKeys ); + for( i = 0; i < nKeys; i++ ) + aKeys[i] = rJob.m_aContext.getModifiedKey( i ); + ::std::sort( aKeys.begin(), aKeys.end(), less_ppd_key() ); + + for( i = 0; i < nKeys && bSuccess; i++ ) + { + const PPDKey* pKey = aKeys[i]; + bool bEmit = false; + if( bDocumentSetup ) + { + if( pKey->getSetupType() == PPDKey::SetupType::DocumentSetup ) + bEmit = true; + } + if( pKey->getSetupType() == PPDKey::SetupType::PageSetup || + pKey->getSetupType() == PPDKey::SetupType::AnySetup ) + bEmit = true; + if( bEmit ) + { + const PPDValue* pValue = rJob.m_aContext.getValue( pKey ); + if( pValue + && pValue->m_eType == eInvocation + && ( m_aLastJobData.m_pParser == nullptr + || m_aLastJobData.m_aContext.getValue( pKey ) != pValue + || bDocumentSetup + ) + ) + { + // try to avoid PS level 2 feature commands if level is set to 1 + if( GetPostscriptLevel( &rJob ) == 1 ) + { + bool bHavePS2 = + ( pValue->m_aValue.indexOf( "<<" ) != -1 ) + || + ( pValue->m_aValue.indexOf( ">>" ) != -1 ); + if( bHavePS2 ) + continue; + } + bSuccess = writeFeature( pFile, pKey, pValue, PrinterInfoManager::get().getUseIncludeFeature() ); + } + } + } + } + else + bSuccess = false; + + return bSuccess; +} + +bool PrinterJob::writePageSetup( osl::File* pFile, const JobData& rJob, bool bWriteFeatures ) +{ + bool bSuccess = true; + + WritePS (pFile, "%%BeginPageSetup\n%\n"); + if ( bWriteFeatures ) + bSuccess = writeFeatureList( pFile, rJob, false ); + WritePS (pFile, "%%EndPageSetup\n"); + + OStringBuffer pTranslate; + + if( rJob.m_eOrientation == orientation::Portrait ) + { + psp::appendStr ("gsave\n[", pTranslate); + psp::getValueOfDouble ( pTranslate, mfXScale, 5); + psp::appendStr (" 0 0 ", pTranslate); + psp::getValueOfDouble ( pTranslate, mfYScale, 5); + psp::appendStr (" ", pTranslate); + psp::getValueOf (mnRMarginPt, pTranslate); + psp::appendStr (" ", pTranslate); + psp::getValueOf (mnHeightPt-mnTMarginPt, + pTranslate); + psp::appendStr ("] concat\ngsave\n", + pTranslate); + } + else + { + psp::appendStr ("gsave\n", pTranslate); + psp::appendStr ("[ 0 ", pTranslate); + psp::getValueOfDouble ( pTranslate, -mfYScale, 5); + psp::appendStr (" ", pTranslate); + psp::getValueOfDouble ( pTranslate, mfXScale, 5); + psp::appendStr (" 0 ", pTranslate ); + psp::getValueOfDouble ( pTranslate, mnLMarginPt, 5 ); + psp::appendStr (" ", pTranslate); + psp::getValueOf (mnBMarginPt, pTranslate ); + psp::appendStr ("] concat\ngsave\n", + pTranslate); + } + + WritePS (pFile, pTranslate.makeStringAndClear()); + + return bSuccess; +} + +void PrinterJob::writeJobPatch( osl::File* pFile, const JobData& rJobData ) +{ + if( ! PrinterInfoManager::get().getUseJobPatch() ) + return; + + const PPDKey* pKey = nullptr; + + if( rJobData.m_pParser ) + pKey = rJobData.m_pParser->getKey( "JobPatchFile" ); + if( ! pKey ) + return; + + // order the patch files + // according to PPD spec the JobPatchFile options must be int + // and should be emitted in order + std::deque< sal_Int32 > patch_order; + int nValueCount = pKey->countValues(); + for( int i = 0; i < nValueCount; i++ ) + { + const PPDValue* pVal = pKey->getValue( i ); + patch_order.push_back( pVal->m_aOption.toInt32() ); + if( patch_order.back() == 0 && pVal->m_aOption != "0" ) + { + WritePS( pFile, "% Warning: left out JobPatchFile option \"" ); + OString aOption = OUStringToOString( pVal->m_aOption, RTL_TEXTENCODING_ASCII_US ); + WritePS( pFile, aOption.getStr() ); + WritePS( pFile, + "\"\n% as it violates the PPD spec;\n" + "% JobPatchFile options need to be numbered for ordering.\n" ); + } + } + + std::sort(patch_order.begin(), patch_order.end()); + patch_order.erase(std::unique(patch_order.begin(), patch_order.end()), patch_order.end()); + + for (auto const& elem : patch_order) + { + // note: this discards patch files not adhering to the "int" scheme + // as there won't be a value for them + writeFeature( pFile, pKey, pKey->getValue( OUString::number(elem) ), false ); + } +} + +void PrinterJob::writeProlog (osl::File* pFile, const JobData& rJobData ) +{ + WritePS( pFile, "%%BeginProlog\n" ); + + // JobPatchFile feature needs to be emitted at begin of prolog + writeJobPatch( pFile, rJobData ); + + static const char pProlog[] = { + "%%BeginResource: procset PSPrint-Prolog 1.0 0\n" + "/ISO1252Encoding [\n" + "/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef\n" + "/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef\n" + "/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef\n" + "/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef\n" + "/space /exclam /quotedbl /numbersign /dollar /percent /ampersand /quotesingle\n" + "/parenleft /parenright /asterisk /plus /comma /hyphen /period /slash\n" + "/zero /one /two /three /four /five /six /seven\n" + "/eight /nine /colon /semicolon /less /equal /greater /question\n" + "/at /A /B /C /D /E /F /G\n" + "/H /I /J /K /L /M /N /O\n" + "/P /Q /R /S /T /U /V /W\n" + "/X /Y /Z /bracketleft /backslash /bracketright /asciicircum /underscore\n" + "/grave /a /b /c /d /e /f /g\n" + "/h /i /j /k /l /m /n /o\n" + "/p /q /r /s /t /u /v /w\n" + "/x /y /z /braceleft /bar /braceright /asciitilde /unused\n" + "/Euro /unused /quotesinglbase /florin /quotedblbase /ellipsis /dagger /daggerdbl\n" + "/circumflex /perthousand /Scaron /guilsinglleft /OE /unused /Zcaron /unused\n" + "/unused /quoteleft /quoteright /quotedblleft /quotedblright /bullet /endash /emdash\n" + "/tilde /trademark /scaron /guilsinglright /oe /unused /zcaron /Ydieresis\n" + "/space /exclamdown /cent /sterling /currency /yen /brokenbar /section\n" + "/dieresis /copyright /ordfeminine /guillemotleft /logicalnot /hyphen /registered /macron\n" + "/degree /plusminus /twosuperior /threesuperior /acute /mu /paragraph /periodcentered\n" + "/cedilla /onesuperior /ordmasculine /guillemotright /onequarter /onehalf /threequarters /questiondown\n" + "/Agrave /Aacute /Acircumflex /Atilde /Adieresis /Aring /AE /Ccedilla\n" + "/Egrave /Eacute /Ecircumflex /Edieresis /Igrave /Iacute /Icircumflex /Idieresis\n" + "/Eth /Ntilde /Ograve /Oacute /Ocircumflex /Otilde /Odieresis /multiply\n" + "/Oslash /Ugrave /Uacute /Ucircumflex /Udieresis /Yacute /Thorn /germandbls\n" + "/agrave /aacute /acircumflex /atilde /adieresis /aring /ae /ccedilla\n" + "/egrave /eacute /ecircumflex /edieresis /igrave /iacute /icircumflex /idieresis\n" + "/eth /ntilde /ograve /oacute /ocircumflex /otilde /odieresis /divide\n" + "/oslash /ugrave /uacute /ucircumflex /udieresis /yacute /thorn /ydieresis] def\n" + "\n" + "/psp_definefont { exch dup findfont dup length dict begin { 1 index /FID ne\n" + "{ def } { pop pop } ifelse } forall /Encoding 3 -1 roll def\n" + "currentdict end exch pop definefont pop } def\n" + "\n" + "/pathdict dup 8 dict def load begin\n" + "/rcmd { { currentfile 1 string readstring pop 0 get dup 32 gt { exit }\n" + "{ pop } ifelse } loop dup 126 eq { pop exit } if 65 sub dup 16#3 and 1\n" + "add exch dup 16#C and -2 bitshift 16#3 and 1 add exch 16#10 and 16#10\n" + "eq 3 1 roll exch } def\n" + "/rhex { dup 1 sub exch currentfile exch string readhexstring pop dup 0\n" + "get dup 16#80 and 16#80 eq dup 3 1 roll { 16#7f and } if 2 index 0 3\n" + "-1 roll put 3 1 roll 0 0 1 5 -1 roll { 2 index exch get add 256 mul }\n" + "for 256 div exch pop exch { neg } if } def\n" + "/xcmd { rcmd exch rhex exch rhex exch 5 -1 roll add exch 4 -1 roll add\n" + "1 index 1 index 5 -1 roll { moveto } { lineto } ifelse } def end\n" + "/readpath { 0 0 pathdict begin { xcmd } loop end pop pop } def\n" + "\n" + "systemdict /languagelevel known not {\n" + "/xshow { exch dup length 0 1 3 -1 roll 1 sub { dup 3 index exch get\n" + "exch 2 index exch get 1 string dup 0 4 -1 roll put currentpoint 3 -1\n" + "roll show moveto 0 rmoveto } for pop pop } def\n" + "/rectangle { 4 -2 roll moveto 1 index 0 rlineto 0 exch rlineto neg 0\n" + "rlineto closepath } def\n" + "/rectfill { rectangle fill } def\n" + "/rectstroke { rectangle stroke } def } if\n" + "/bshow { currentlinewidth 3 1 roll currentpoint 3 index show moveto\n" + "setlinewidth false charpath stroke setlinewidth } def\n" + "/bxshow { currentlinewidth 4 1 roll setlinewidth exch dup length 1 sub\n" + "0 1 3 -1 roll { 1 string 2 index 2 index get 1 index exch 0 exch put dup\n" + "currentpoint 3 -1 roll show moveto currentpoint 3 -1 roll false charpath\n" + "stroke moveto 2 index exch get 0 rmoveto } for pop pop setlinewidth } def\n" + "\n" + "/psp_lzwfilter { currentfile /ASCII85Decode filter /LZWDecode filter } def\n" + "/psp_ascii85filter { currentfile /ASCII85Decode filter } def\n" + "/psp_lzwstring { psp_lzwfilter 1024 string readstring } def\n" + "/psp_ascii85string { psp_ascii85filter 1024 string readstring } def\n" + "/psp_imagedict {\n" + "/psp_bitspercomponent { 3 eq { 1 }{ 8 } ifelse } def\n" + "/psp_decodearray { [ [0 1 0 1 0 1] [0 255] [0 1] [0 255] ] exch get }\n" + "def 7 dict dup\n" + "/ImageType 1 put dup\n" + "/Width 7 -1 roll put dup\n" + "/Height 5 index put dup\n" + "/BitsPerComponent 4 index psp_bitspercomponent put dup\n" + "/Decode 5 -1 roll psp_decodearray put dup\n" + "/ImageMatrix [1 0 0 1 0 0] dup 5 8 -1 roll put put dup\n" + "/DataSource 4 -1 roll 1 eq { psp_lzwfilter } { psp_ascii85filter } ifelse put\n" + "} def\n" + "%%EndResource\n" + "%%EndProlog\n" + }; + WritePS (pFile, pProlog); +} + +bool PrinterJob::writeSetup( osl::File* pFile, const JobData& rJob ) +{ + WritePS (pFile, "%%BeginSetup\n%\n"); + + // download fonts + std::vector< OString > aFonts; + m_pGraphics->writeResources( pFile, aFonts ); + + if( !aFonts.empty() ) + { + std::vector< OString >::const_iterator it = aFonts.begin(); + OStringBuffer aLine( 256 ); + aLine.append( "%%DocumentSuppliedResources: font " ); + aLine.append( *it ); + aLine.append( "\n" ); + WritePS ( pFile, aLine.getStr() ); + while( (++it) != aFonts.end() ) + { + aLine.setLength(0); + aLine.append( "%%+ font " ); + aLine.append( *it ); + aLine.append( "\n" ); + WritePS ( pFile, aLine.getStr() ); + } + } + + bool bSuccess = true; + // in case of external print dialog the number of copies is prepended + // to the job, let us not complicate things by emitting our own copy count + bool bExternalDialog = PrinterInfoManager::get().checkFeatureToken( GetPrinterName(), "external_dialog" ); + if( ! bExternalDialog && rJob.m_nCopies > 1 ) + { + // setup code + OString aLine = "/#copies " + + OString::number(static_cast<sal_Int32>(rJob.m_nCopies)) + + " def\n"; + sal_uInt64 nWritten = 0; + bSuccess = !(pFile->write(aLine.getStr(), aLine.getLength(), nWritten) + || nWritten != static_cast<sal_uInt64>(aLine.getLength())); + + if( bSuccess && GetPostscriptLevel( &rJob ) >= 2 ) + WritePS (pFile, "<< /NumCopies null /Policies << /NumCopies 1 >> >> setpagedevice\n" ); + } + + bool bFeatureSuccess = writeFeatureList( pFile, rJob, true ); + + WritePS (pFile, "%%EndSetup\n"); + + return bSuccess && bFeatureSuccess; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/generic/print/prtsetup.cxx b/vcl/unx/generic/print/prtsetup.cxx new file mode 100644 index 000000000..56ee475e7 --- /dev/null +++ b/vcl/unx/generic/print/prtsetup.cxx @@ -0,0 +1,516 @@ +/* -*- 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 "prtsetup.hxx" +#include <svdata.hxx> +#include <strings.hrc> + +#include <officecfg/Office/Common.hxx> + +using namespace psp; + +void RTSDialog::insertAllPPDValues(weld::ComboBox& rBox, const PPDParser* pParser, const PPDKey* pKey ) +{ + if( ! pKey || ! pParser ) + return; + + const PPDValue* pValue = nullptr; + OUString aOptionText; + + for (int i = 0; i < pKey->countValues(); ++i) + { + pValue = pKey->getValue( i ); + if (pValue->m_bCustomOption) + continue; + aOptionText = pParser->translateOption( pKey->getKey(), pValue->m_aOption) ; + + OUString sId(weld::toId(pValue)); + int nCurrentPos = rBox.find_id(sId); + if( m_aJobData.m_aContext.checkConstraints( pKey, pValue ) ) + { + if (nCurrentPos == -1) + rBox.append(sId, aOptionText); + } + else + { + if (nCurrentPos != -1) + rBox.remove(nCurrentPos); + } + } + pValue = m_aJobData.m_aContext.getValue( pKey ); + if (pValue && !pValue->m_bCustomOption) + { + OUString sId(weld::toId(pValue)); + int nPos = rBox.find_id(sId); + if (nPos != -1) + rBox.set_active(nPos); + } +} + +/* + * RTSDialog + */ + +RTSDialog::RTSDialog(const PrinterInfo& rJobData, weld::Window* pParent) + : GenericDialogController(pParent, "vcl/ui/printerpropertiesdialog.ui", "PrinterPropertiesDialog") + , m_aJobData(rJobData) + , m_bDataModified(false) + , m_xTabControl(m_xBuilder->weld_notebook("tabcontrol")) + , m_xOKButton(m_xBuilder->weld_button("ok")) + , m_xCancelButton(m_xBuilder->weld_button("cancel")) + , m_xPaperPage(new RTSPaperPage(m_xTabControl->get_page("paper"), this)) + , m_xDevicePage(new RTSDevicePage(m_xTabControl->get_page("device"), this)) +{ + OUString aTitle(m_xDialog->get_title()); + m_xDialog->set_title(aTitle.replaceAll("%s", m_aJobData.m_aPrinterName)); + + m_xTabControl->connect_enter_page( LINK( this, RTSDialog, ActivatePage ) ); + m_xOKButton->connect_clicked( LINK( this, RTSDialog, ClickButton ) ); + m_xCancelButton->connect_clicked( LINK( this, RTSDialog, ClickButton ) ); + ActivatePage(m_xTabControl->get_current_page_ident()); +} + +RTSDialog::~RTSDialog() +{ +} + +IMPL_LINK(RTSDialog, ActivatePage, const OString&, rPage, void) +{ + if (rPage == "paper") + m_xPaperPage->update(); +} + +IMPL_LINK( RTSDialog, ClickButton, weld::Button&, rButton, void ) +{ + if (&rButton == m_xOKButton.get()) + { + // refresh the changed values + if (m_xPaperPage) + { + // orientation + m_aJobData.m_eOrientation = m_xPaperPage->getOrientation() == 0 ? + orientation::Portrait : orientation::Landscape; + // assume use of paper size from printer setup if the user + // got here via File > Printer Settings ... + if ( m_aJobData.meSetupMode == PrinterSetupMode::DocumentGlobal ) + m_aJobData.m_bPapersizeFromSetup = true; + } + if( m_xDevicePage ) + { + m_aJobData.m_nColorDepth = m_xDevicePage->getDepth(); + m_aJobData.m_nColorDevice = m_xDevicePage->getColorDevice(); + m_aJobData.m_nPSLevel = m_xDevicePage->getLevel(); + m_aJobData.m_nPDFDevice = m_xDevicePage->getPDFDevice(); + } + m_xDialog->response(RET_OK); + } + else if (&rButton == m_xCancelButton.get()) + m_xDialog->response(RET_CANCEL); +} + +/* + * RTSPaperPage + */ + +RTSPaperPage::RTSPaperPage(weld::Widget* pPage, RTSDialog* pDialog) + : m_xBuilder(Application::CreateBuilder(pPage, "vcl/ui/printerpaperpage.ui")) + , m_pParent(pDialog) + , m_xContainer(m_xBuilder->weld_widget("PrinterPaperPage")) + , m_xCbFromSetup(m_xBuilder->weld_check_button("papersizefromsetup")) + , m_xPaperText(m_xBuilder->weld_label("paperft")) + , m_xPaperBox(m_xBuilder->weld_combo_box("paperlb")) + , m_xOrientText(m_xBuilder->weld_label("orientft")) + , m_xOrientBox(m_xBuilder->weld_combo_box("orientlb")) + , m_xDuplexText(m_xBuilder->weld_label("duplexft")) + , m_xDuplexBox(m_xBuilder->weld_combo_box("duplexlb")) + , m_xSlotText(m_xBuilder->weld_label("slotft")) + , m_xSlotBox(m_xBuilder->weld_combo_box("slotlb")) +{ + //PrinterPaperPage + m_xPaperBox->connect_changed( LINK( this, RTSPaperPage, SelectHdl ) ); + m_xOrientBox->connect_changed( LINK( this, RTSPaperPage, SelectHdl ) ); + m_xDuplexBox->connect_changed( LINK( this, RTSPaperPage, SelectHdl ) ); + m_xSlotBox->connect_changed( LINK( this, RTSPaperPage, SelectHdl ) ); + m_xCbFromSetup->connect_toggled( LINK( this, RTSPaperPage, CheckBoxHdl ) ); + + update(); +} + +RTSPaperPage::~RTSPaperPage() +{ +} + +void RTSPaperPage::update() +{ + const PPDKey* pKey = nullptr; + + // orientation + m_xOrientBox->set_active(m_pParent->m_aJobData.m_eOrientation == orientation::Portrait ? 0 : 1); + + // duplex + if( m_pParent->m_aJobData.m_pParser && + (pKey = m_pParent->m_aJobData.m_pParser->getKey( "Duplex" )) ) + { + m_pParent->insertAllPPDValues( *m_xDuplexBox, m_pParent->m_aJobData.m_pParser, pKey ); + } + else + { + m_xDuplexText->set_sensitive( false ); + m_xDuplexBox->set_sensitive( false ); + } + + // paper + if( m_pParent->m_aJobData.m_pParser && + (pKey = m_pParent->m_aJobData.m_pParser->getKey( "PageSize" )) ) + { + m_pParent->insertAllPPDValues( *m_xPaperBox, m_pParent->m_aJobData.m_pParser, pKey ); + } + else + { + m_xPaperText->set_sensitive( false ); + m_xPaperBox->set_sensitive( false ); + } + + // input slots + if( m_pParent->m_aJobData.m_pParser && + (pKey = m_pParent->m_aJobData.m_pParser->getKey( "InputSlot" )) ) + { + m_pParent->insertAllPPDValues( *m_xSlotBox, m_pParent->m_aJobData.m_pParser, pKey ); + } + else + { + m_xSlotText->set_sensitive( false ); + m_xSlotBox->set_sensitive( false ); + } + + if ( m_pParent->m_aJobData.meSetupMode != PrinterSetupMode::SingleJob ) + return; + + m_xCbFromSetup->show(); + + if ( m_pParent->m_aJobData.m_bPapersizeFromSetup ) + m_xCbFromSetup->set_active(m_pParent->m_aJobData.m_bPapersizeFromSetup); + // disable those, unless user wants to use papersize from printer prefs + // as they have no influence on what's going to be printed anyway + else + { + m_xPaperText->set_sensitive( false ); + m_xPaperBox->set_sensitive( false ); + m_xOrientText->set_sensitive( false ); + m_xOrientBox->set_sensitive( false ); + } +} + +IMPL_LINK( RTSPaperPage, SelectHdl, weld::ComboBox&, rBox, void ) +{ + const PPDKey* pKey = nullptr; + if( &rBox == m_xPaperBox.get() ) + { + if( m_pParent->m_aJobData.m_pParser ) + pKey = m_pParent->m_aJobData.m_pParser->getKey( "PageSize" ); + } + else if( &rBox == m_xDuplexBox.get() ) + { + if( m_pParent->m_aJobData.m_pParser ) + pKey = m_pParent->m_aJobData.m_pParser->getKey( "Duplex" ); + } + else if( &rBox == m_xSlotBox.get() ) + { + if( m_pParent->m_aJobData.m_pParser ) + pKey = m_pParent->m_aJobData.m_pParser->getKey( "InputSlot" ); + } + else if( &rBox == m_xOrientBox.get() ) + { + m_pParent->m_aJobData.m_eOrientation = m_xOrientBox->get_active() == 0 ? orientation::Portrait : orientation::Landscape; + } + if( pKey ) + { + PPDValue* pValue = weld::fromId<PPDValue*>(rBox.get_active_id()); + m_pParent->m_aJobData.m_aContext.setValue( pKey, pValue ); + update(); + } + + m_pParent->SetDataModified( true ); +} + +IMPL_LINK_NOARG(RTSPaperPage, CheckBoxHdl, weld::Toggleable&, void) +{ + bool bFromSetup = m_xCbFromSetup->get_active(); + m_pParent->m_aJobData.m_bPapersizeFromSetup = bFromSetup; + m_xPaperText->set_sensitive(bFromSetup); + m_xPaperBox->set_sensitive(bFromSetup); + m_xOrientText->set_sensitive(bFromSetup); + m_xOrientBox->set_sensitive(bFromSetup); + m_pParent->SetDataModified(true); +} + +/* + * RTSDevicePage + */ +RTSDevicePage::RTSDevicePage(weld::Widget* pPage, RTSDialog* pParent) + : m_xBuilder(Application::CreateBuilder(pPage, "vcl/ui/printerdevicepage.ui")) + , m_pCustomValue(nullptr) + , m_pParent(pParent) + , m_xContainer(m_xBuilder->weld_widget("PrinterDevicePage")) + , m_xPPDKeyBox(m_xBuilder->weld_tree_view("options")) + , m_xPPDValueBox(m_xBuilder->weld_tree_view("values")) + , m_xCustomEdit(m_xBuilder->weld_entry("custom")) + , m_xLevelBox(m_xBuilder->weld_combo_box("level")) + , m_xSpaceBox(m_xBuilder->weld_combo_box("colorspace")) + , m_xDepthBox(m_xBuilder->weld_combo_box("colordepth")) + , m_aReselectCustomIdle("RTSDevicePage m_aReselectCustomIdle") +{ + m_aReselectCustomIdle.SetInvokeHandler(LINK(this, RTSDevicePage, ImplHandleReselectHdl)); + + m_xPPDKeyBox->set_size_request(m_xPPDKeyBox->get_approximate_digit_width() * 32, + m_xPPDKeyBox->get_height_rows(12)); + + m_xCustomEdit->connect_changed(LINK(this, RTSDevicePage, ModifyHdl)); + + m_xPPDKeyBox->connect_changed( LINK( this, RTSDevicePage, SelectHdl ) ); + m_xPPDValueBox->connect_changed( LINK( this, RTSDevicePage, SelectHdl ) ); + + m_xLevelBox->connect_changed(LINK(this, RTSDevicePage, ComboChangedHdl)); + m_xSpaceBox->connect_changed(LINK(this, RTSDevicePage, ComboChangedHdl)); + m_xDepthBox->connect_changed(LINK(this, RTSDevicePage, ComboChangedHdl)); + + switch( m_pParent->m_aJobData.m_nColorDevice ) + { + case 0: + m_xSpaceBox->set_active(0); + break; + case 1: + m_xSpaceBox->set_active(1); + break; + case -1: + m_xSpaceBox->set_active(2); + break; + } + + sal_Int32 nLevelEntryData = 0; //automatic + if( m_pParent->m_aJobData.m_nPDFDevice == 2 ) //explicit PDF + nLevelEntryData = 10; + else if (m_pParent->m_aJobData.m_nPSLevel > 0) //explicit PS Level + nLevelEntryData = m_pParent->m_aJobData.m_nPSLevel+1; + else if (m_pParent->m_aJobData.m_nPDFDevice == 1) //automatically PDF + nLevelEntryData = 0; + else if (m_pParent->m_aJobData.m_nPDFDevice == -1) //explicitly PS from driver + nLevelEntryData = 1; + + bool bAutoIsPDF = officecfg::Office::Common::Print::Option::Printer::PDFAsStandardPrintJobFormat::get(); + + assert(nLevelEntryData != 0 + || "Generic Printer" == m_pParent->m_aJobData.m_aPrinterName + || int(bAutoIsPDF) == m_pParent->m_aJobData.m_nPDFDevice); + + OUString sStr = m_xLevelBox->get_text(0); + OUString sId = m_xLevelBox->get_id(0); + m_xLevelBox->insert(0, sStr.replaceAll("%s", bAutoIsPDF ? m_xLevelBox->get_text(5) : m_xLevelBox->get_text(1)), &sId, nullptr, nullptr); + m_xLevelBox->remove(1); + + for (int i = 0; i < m_xLevelBox->get_count(); ++i) + { + if (m_xLevelBox->get_id(i).toInt32() == nLevelEntryData) + { + m_xLevelBox->set_active(i); + break; + } + } + + if (m_pParent->m_aJobData.m_nColorDepth == 8) + m_xDepthBox->set_active(0); + else if (m_pParent->m_aJobData.m_nColorDepth == 24) + m_xDepthBox->set_active(1); + + // fill ppd boxes + if( !m_pParent->m_aJobData.m_pParser ) + return; + + for( int i = 0; i < m_pParent->m_aJobData.m_pParser->getKeys(); i++ ) + { + const PPDKey* pKey = m_pParent->m_aJobData.m_pParser->getKey( i ); + + // skip options already shown somewhere else + // also skip options from the "InstallableOptions" PPD group + // Options in that group define hardware features that are not + // job-specific and should better be handled in the system-wide + // printer configuration. Keyword is defined in PPD specification + // (version 4.3), section 5.4. + if( pKey->isUIKey() && + pKey->getKey() != "PageSize" && + pKey->getKey() != "InputSlot" && + pKey->getKey() != "PageRegion" && + pKey->getKey() != "Duplex" && + pKey->getGroup() != "InstallableOptions") + { + OUString aEntry( m_pParent->m_aJobData.m_pParser->translateKey( pKey->getKey() ) ); + m_xPPDKeyBox->append(weld::toId(pKey), aEntry); + } + } +} + +RTSDevicePage::~RTSDevicePage() +{ +} + +sal_uLong RTSDevicePage::getDepth() const +{ + sal_uInt16 nSelectPos = m_xDepthBox->get_active(); + if (nSelectPos == 0) + return 8; + else + return 24; +} + +sal_uLong RTSDevicePage::getColorDevice() const +{ + sal_uInt16 nSelectPos = m_xSpaceBox->get_active(); + switch (nSelectPos) + { + case 0: + return 0; + case 1: + return 1; + case 2: + return -1; + } + return 0; +} + +sal_uLong RTSDevicePage::getLevel() const +{ + auto nLevel = m_xLevelBox->get_active_id().toInt32(); + if (nLevel == 0) + return 0; //automatic + return nLevel < 10 ? nLevel-1 : 0; +} + +sal_uLong RTSDevicePage::getPDFDevice() const +{ + auto nLevel = m_xLevelBox->get_active_id().toInt32(); + if (nLevel > 9) + return 2; //explicitly PDF + else if (nLevel == 0) + return 0; //automatic + return -1; //explicitly PS +} + +IMPL_LINK(RTSDevicePage, ModifyHdl, weld::Entry&, rEdit, void) +{ + if (m_pCustomValue) + { + // tdf#123734 Custom PPD option values are a CUPS extension to PPDs and the user-set value + // needs to be prefixed with "Custom." in order to be processed properly + m_pCustomValue->m_aCustomOption = "Custom." + rEdit.get_text(); + } +} + +IMPL_LINK( RTSDevicePage, SelectHdl, weld::TreeView&, rBox, void ) +{ + if (&rBox == m_xPPDKeyBox.get()) + { + const PPDKey* pKey = weld::fromId<PPDKey*>(m_xPPDKeyBox->get_selected_id()); + FillValueBox( pKey ); + } + else if (&rBox == m_xPPDValueBox.get()) + { + const PPDKey* pKey = weld::fromId<PPDKey*>(m_xPPDKeyBox->get_selected_id()); + const PPDValue* pValue = weld::fromId<PPDValue*>(m_xPPDValueBox->get_selected_id()); + if (pKey && pValue) + { + m_pParent->m_aJobData.m_aContext.setValue( pKey, pValue ); + ValueBoxChanged(pKey); + } + } + m_pParent->SetDataModified( true ); +} + +IMPL_LINK_NOARG( RTSDevicePage, ComboChangedHdl, weld::ComboBox&, void ) +{ + m_pParent->SetDataModified( true ); +} + +void RTSDevicePage::FillValueBox( const PPDKey* pKey ) +{ + m_xPPDValueBox->clear(); + m_xCustomEdit->hide(); + + if( ! pKey ) + return; + + const PPDValue* pValue = nullptr; + for( int i = 0; i < pKey->countValues(); i++ ) + { + pValue = pKey->getValue( i ); + if( m_pParent->m_aJobData.m_aContext.checkConstraints( pKey, pValue ) && + m_pParent->m_aJobData.m_pParser ) + { + OUString aEntry; + if (pValue->m_bCustomOption) + aEntry = VclResId(SV_PRINT_CUSTOM_TXT); + else + aEntry = m_pParent->m_aJobData.m_pParser->translateOption( pKey->getKey(), pValue->m_aOption); + m_xPPDValueBox->append(weld::toId(pValue), aEntry); + } + } + pValue = m_pParent->m_aJobData.m_aContext.getValue( pKey ); + m_xPPDValueBox->select_id(weld::toId(pValue)); + + ValueBoxChanged(pKey); +} + +IMPL_LINK_NOARG(RTSDevicePage, ImplHandleReselectHdl, Timer*, void) +{ + //in case selected entry is now not visible select it again to scroll it into view + m_xPPDValueBox->select(m_xPPDValueBox->get_selected_index()); +} + +void RTSDevicePage::ValueBoxChanged( const PPDKey* pKey ) +{ + const PPDValue* pValue = m_pParent->m_aJobData.m_aContext.getValue(pKey); + if (pValue->m_bCustomOption) + { + m_pCustomValue = pValue; + m_pParent->m_aJobData.m_aContext.setValue(pKey, pValue); + // don't show the "Custom." prefix in the UI, s.a. comment in ModifyHdl + m_xCustomEdit->set_text(m_pCustomValue->m_aCustomOption.replaceFirst("Custom.", "")); + m_xCustomEdit->show(); + m_aReselectCustomIdle.Start(); + } + else + m_xCustomEdit->hide(); +} + +int SetupPrinterDriver(weld::Window* pParent, ::psp::PrinterInfo& rJobData) +{ + int nRet = 0; + RTSDialog aDialog(rJobData, pParent); + + // return 0 if cancel was pressed or if the data + // weren't modified, 1 otherwise + if (aDialog.run() != RET_CANCEL) + { + rJobData = aDialog.getSetup(); + nRet = aDialog.GetDataModified() ? 1 : 0; + } + + return nRet; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/generic/print/prtsetup.hxx b/vcl/unx/generic/print/prtsetup.hxx new file mode 100644 index 000000000..bcf86670d --- /dev/null +++ b/vcl/unx/generic/print/prtsetup.hxx @@ -0,0 +1,138 @@ +/* -*- 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 . + */ + +#pragma once + +#include <vcl/idle.hxx> +#include <vcl/weld.hxx> +#include <ppdparser.hxx> +#include <printerinfomanager.hxx> + +class RTSPaperPage; +class RTSDevicePage; + +class RTSDialog : public weld::GenericDialogController +{ + friend class RTSPaperPage; + friend class RTSDevicePage; + + ::psp::PrinterInfo m_aJobData; + + bool m_bDataModified; + + // controls + std::unique_ptr<weld::Notebook> m_xTabControl; + std::unique_ptr<weld::Button> m_xOKButton; + std::unique_ptr<weld::Button> m_xCancelButton; + + // pages + std::unique_ptr<RTSPaperPage> m_xPaperPage; + std::unique_ptr<RTSDevicePage> m_xDevicePage; + + DECL_LINK(ActivatePage, const OString&, void); + DECL_LINK(ClickButton, weld::Button&, void); + + // helper functions + void insertAllPPDValues(weld::ComboBox&, const psp::PPDParser*, const psp::PPDKey*); + +public: + RTSDialog(const ::psp::PrinterInfo& rJobData, weld::Window* pParent); + virtual ~RTSDialog() override; + + const ::psp::PrinterInfo& getSetup() const { return m_aJobData; } + + void SetDataModified(bool bModified) { m_bDataModified = bModified; } + bool GetDataModified() const { return m_bDataModified; } +}; + +class RTSPaperPage +{ +private: + std::unique_ptr<weld::Builder> m_xBuilder; + + RTSDialog* m_pParent; + + std::unique_ptr<weld::Widget> m_xContainer; + + std::unique_ptr<weld::CheckButton> m_xCbFromSetup; + + std::unique_ptr<weld::Label> m_xPaperText; + std::unique_ptr<weld::ComboBox> m_xPaperBox; + + std::unique_ptr<weld::Label> m_xOrientText; + std::unique_ptr<weld::ComboBox> m_xOrientBox; + + std::unique_ptr<weld::Label> m_xDuplexText; + std::unique_ptr<weld::ComboBox> m_xDuplexBox; + + std::unique_ptr<weld::Label> m_xSlotText; + std::unique_ptr<weld::ComboBox> m_xSlotBox; + + DECL_LINK(SelectHdl, weld::ComboBox&, void); + DECL_LINK(CheckBoxHdl, weld::Toggleable&, void); + +public: + RTSPaperPage(weld::Widget* pPage, RTSDialog* pDialog); + ~RTSPaperPage(); + + void update(); + + sal_Int32 getOrientation() const { return m_xOrientBox->get_active(); } +}; + +class RTSDevicePage +{ +private: + std::unique_ptr<weld::Builder> m_xBuilder; + + const psp::PPDValue* m_pCustomValue; + RTSDialog* m_pParent; + + std::unique_ptr<weld::Widget> m_xContainer; + std::unique_ptr<weld::TreeView> m_xPPDKeyBox; + std::unique_ptr<weld::TreeView> m_xPPDValueBox; + std::unique_ptr<weld::Entry> m_xCustomEdit; + + std::unique_ptr<weld::ComboBox> m_xLevelBox; + std::unique_ptr<weld::ComboBox> m_xSpaceBox; + std::unique_ptr<weld::ComboBox> m_xDepthBox; + + void FillValueBox(const ::psp::PPDKey*); + void ValueBoxChanged(const ::psp::PPDKey*); + + Idle m_aReselectCustomIdle; + + DECL_LINK(SelectHdl, weld::TreeView&, void); + DECL_LINK(ModifyHdl, weld::Entry&, void); + DECL_LINK(ComboChangedHdl, weld::ComboBox&, void); + DECL_LINK(ImplHandleReselectHdl, Timer*, void); + +public: + RTSDevicePage(weld::Widget* pPage, RTSDialog* pDialog); + ~RTSDevicePage(); + + sal_uLong getLevel() const; + sal_uLong getPDFDevice() const; + sal_uLong getDepth() const; + sal_uLong getColorDevice() const; +}; + +int SetupPrinterDriver(weld::Window* pParent, ::psp::PrinterInfo& rJobData); + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/generic/print/psheader.ps b/vcl/unx/generic/print/psheader.ps new file mode 100644 index 000000000..49f0f5101 --- /dev/null +++ b/vcl/unx/generic/print/psheader.ps @@ -0,0 +1,363 @@ +% +% 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 . +% + +% This is an "unobsfucated version of postscript header" in printerjob.cxx. It +% was probably kept separate for the comments, but it is not used in itself +% and probably was not kept in sync with the actual header. + +% +% +% readpath +% +% The intention of readpath is to save disk space since the vcl clip region routines +% produce a huge amount of lineto/moveto commands +% +% The principal idea is to maintain the current point on stack and to provide only deltas +% in the command. These deltas are added to the current point. The new point is used for +% the lineto and moveto command and saved on stack for the next command. +% +% pathdict implements binary/hex representation of lineto and moveto commands. +% The command consists of a 1byte opcode to switch between lineto and moveto and the size +% of the following delta-x and delta-y values. The opcode is read with /rcmd, the two +% coordinates are read with /rhex. The whole command is executed with /xcmd +% +% + +/pathdict dup 8 dict def load +begin + + % the command is of the bit format cxxyy + % with c=0 meaning lineto + % c=1 meaning moveto + % xx is a 2bit value for the number of bytes for x position + % yy is the same for y, values are off by one: 00 means 1; 11 means 4 ! + % the command has been added to 'A' to be always in the ascii character + % range. the command is followed by 2*xx + 2*yy hexchars. + % '~' denotes the special case of EOD + /rcmd { + { + currentfile 1 string readstring % s bool + pop % s + 0 get % s[0] + % --- check whether s[0] is CR, LF ... + dup 32 gt % s > ' ' ? then read on + { exit } + { pop } + ifelse + } + loop + + dup 126 eq { pop exit } if % -- Exit loop if cmd is '~' + 65 sub % cmd=s[0]-'A' + % -- Separate yy bits + dup 16#3 and 1 add % cmd yy + % -- Separate xx bits + exch % yy cmd + dup 16#C and -2 bitshift + 16#3 and 1 add exch % yy xx cmd + % -- Separate command bit + 16#10 and 16#10 eq % yy xx bool + 3 1 roll exch % bool xx yy + } def + + % length rhex -- reads a signed hex value of given length + % the left most bit of char 0 is considered as the sign (0 means '+', 1 means '-') + % the rest of the bits is considered to be the abs value. Please note that this + % does not match the C binary representation of integers + /rhex { + dup 1 sub exch % l-1 l + currentfile exch string readhexstring % l-1 substring[l] bool + pop + dup 0 get dup % l-1 s s[0] s[0] + % -- Extract the sign + 16#80 and 16#80 eq dup % l-1 s s[0] sign=- sign=- + % -- Mask out the sign bit and put value back + 3 1 roll % l-1 s sign=- s[0] sign=- + { 16#7f and } if % l-1 s sign=- +s[0] + 2 index 0 % l-1 s sign=- +s[0] s 0 + 3 -1 roll put % l-1 s sign=- s 0 +s[0] + % -- Read loop: add to prev sum, mul with 256 + 3 1 roll 0 % sign=- l-1 s Sum=0 + 0 1 5 -1 roll % sign=- s Sum=0 0 1 l-1 + { % sign=- s Sum idx + 2 index exch % sign=- s Sum s idx + get % sign=- s Sum s[idx] + add 256 mul % sign=- s Sum=(s[idx]+Sum)*256 + } + for + % -- mul was once too often, weave in the sign + 256 div % sign=- s Sum/256 + exch pop % sign=- Sum/256 + exch { neg } if % (sign=- ? -Sum : Sum) + } def + + % execute a single command, the former x and y position is already on stack + % only offsets are read from cmdstring + /xcmd { % x y + rcmd % x y bool wx wy + exch rhex % x y bool wy Dx + exch rhex % x y bool Dx Dy + exch 5 -1 roll % y bool Dy Dx x + add exch % y bool X Dy + 4 -1 roll add % bool X Y + 1 index 1 index % bool X Y X Y + 5 -1 roll % X Y X Y bool + { moveto } + { lineto } + ifelse % X Y + } def +end + +/readpath +{ + 0 0 % push initial-x initial-y + pathdict begin + { xcmd } loop + end + pop pop % pop final-x final-y +} def + +% +% +% if languagelevel is not in the systemdict then its level 1 interpreter: +% provide compatibility routines +% +% + +systemdict /languagelevel known not +{ + % string numarray xxshow - + % does only work for single byte fonts + /xshow { + exch dup % a s s + length 0 1 % a s l(s) 1 1 + 3 -1 roll 1 sub % a s 0 1 l(s)-1 + { % a s idx + dup % a s idx idx + % -- extract the delta offset + 3 index exch get % a s idx a[idx] + % -- extract the character + exch % a s a[idx] idx + 2 index exch get % a s a[idx] s[idx] + % -- create a tmp string for show + 1 string dup 0 % a s a[idx] s[idx] s1 s1 0 + 4 -1 roll % a s a[idx] s1 s1 0 s[idx] + put % a s a[idx] s1 + % -- store the current point + currentpoint 3 -1 roll % a s a[idx] x y s1 + % -- draw the character + show % a s a[idx] x y + % -- move to the offset + moveto 0 rmoveto % a s + } + for + pop pop % - + } def + + % x y width height rectfill + % x y width height rectshow + % in contrast to the languagelevel 2 operator + % they use and change the currentpath + /rectangle { + 4 -2 roll % width height x y + moveto % width height + 1 index 0 rlineto % width height % rmoveto(width, 0) + 0 exch rlineto % width % rmoveto(0, height) + neg 0 rlineto % - % rmoveto(-width, 0) + closepath + } def + + /rectfill { rectangle fill } def + /rectstroke { rectangle stroke } def +} +if + +% -- small test program +% 75 75 moveto /Times-Roman findfont 12 scalefont setfont +% <292a2b2c2d2e2f30313233343536373839> +% [5 5 6 6 6 6 6 6 6 6 6 6 7 7 7 7 5] xshow <21>[0] xshow +% showpage + +% +% +% shortcuts for image header with compression +% +% + +/psp_lzwfilter { + currentfile /ASCII85Decode filter /LZWDecode filter +} def +/psp_ascii85filter { + currentfile /ASCII85Decode filter +} def +/psp_lzwstring { + psp_lzwfilter 1024 string readstring +} def +/psp_ascii85string { + psp_ascii85filter 1024 string readstring +} def +/psp_imagedict { + /psp_bitspercomponent { + 3 eq + { 1 } + { 8 } + ifelse + } def + /psp_decodearray { + [ [0 1 0 1 0 1] [0 255] [0 1] [0 255] ] exch get + } def + + 7 dict dup + /ImageType 1 put dup + /Width 7 -1 roll put dup + /Height 5 index put dup + /BitsPerComponent 4 index + psp_bitspercomponent put dup + /Decode 5 -1 roll + psp_decodearray put dup + /ImageMatrix [1 0 0 1 0 0] dup + 5 8 -1 roll put put dup + /DataSource 4 -1 roll + 1 eq + { psp_lzwfilter } + { psp_ascii85filter } + ifelse put +} def + + +% +% +% font encoding and reencoding +% +% + +/ISO1252Encoding [ + /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef + /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef + /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef + /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef + /space /exclam /quotedbl /numbersign /dollar /percent /ampersand /quotesingle + /parenleft /parenright /asterisk /plus /comma /hyphen /period /slash + /zero /one /two /three /four /five /six /seven + /eight /nine /colon /semicolon /less /equal /greater /question + /at /A /B /C /D /E /F /G + /H /I /J /K /L /M /N /O + /P /Q /R /S /T /U /V /W + /X /Y /Z /bracketleft /backslash /bracketright /asciicircum /underscore + /grave /a /b /c /d /e /f /g + /h /i /j /k /l /m /n /o + /p /q /r /s /t /u /v /w + /x /y /z /braceleft /bar /braceright /asciitilde /unused + /Euro /unused /quotesinglbase /florin /quotedblbase /ellipsis /dagger /daggerdbl + /circumflex /perthousand /Scaron /guilsinglleft /OE /unused /Zcaron /unused + /unused /quoteleft /quoteright /quotedblleft /quotedblright /bullet /endash /emdash + /tilde /trademark /scaron /guilsinglright /oe /unused /zcaron /Ydieresis + /space /exclamdown /cent /sterling /currency /yen /brokenbar /section + /dieresis /copyright /ordfeminine /guillemotleft /logicalnot /hyphen /registered /macron + /degree /plusminus /twosuperior /threesuperior /acute /mu /paragraph /periodcentered + /cedilla /onesuperior /ordmasculine /guillemotright /onequarter /onehalf /threequarters /questiondown + /Agrave /Aacute /Acircumflex /Atilde /Adieresis /Aring /AE /Ccedilla + /Egrave /Eacute /Ecircumflex /Edieresis /Igrave /Iacute /Icircumflex /Idieresis + /Eth /Ntilde /Ograve /Oacute /Ocircumflex /Otilde /Odieresis /multiply + /Oslash /Ugrave /Uacute /Ucircumflex /Udieresis /Yacute /Thorn /germandbls + /agrave /aacute /acircumflex /atilde /adieresis /aring /ae /ccedilla + /egrave /eacute /ecircumflex /edieresis /igrave /iacute /icircumflex /idieresis + /eth /ntilde /ograve /oacute /ocircumflex /otilde /odieresis /divide + /oslash /ugrave /uacute /ucircumflex /udieresis /yacute /thorn /ydieresis +] def + +% /fontname /encoding psp_findfont +/psp_findfont { + exch dup % encoding fontname fontname + findfont % encoding fontname + dup length dict + begin + { + 1 index /FID ne + { def } + { pop pop } + ifelse + } forall + /Encoding 3 -1 roll def + currentdict + end + /psp_reencodedfont exch definefont +} def + +% bshow shows a text in artificial bold +% this is achieved by first showing the text +% then stroking its outline over it with +% the linewidth set to the second parameter +% usage: (string) num bshow + +/bshow { + currentlinewidth % save current linewidth + 3 1 roll % move it to the last stack position + currentpoint % save the current point + 3 index % copy the string to show + show % show it + moveto % move to the original coordinates again + setlinewidth % set the linewidth + false charpath % create the outline path of the shown string + stroke % and stroke it + setlinewidth % reset the stored linewidth +} def + +% bxshow shows a text with a delta array in artificial bold +% that is it does what bshow does for show +% usage: (string) [deltaarray] num bxshow + +/bxshow { + currentlinewidth % save linewidth + 4 1 roll % move it to the last stack position + setlinewidth % set the new linewidth + exch % exchange string and delta array + dup + length % get length of string + 1 sub % prepare parameters for {} for + 0 1 + 3 -1 roll + { + 1 string % create a string object length 1 + 2 index % get the text + 2 index % get charpos (for index variable) + get % have char value at charpos + 1 index % prepare string for put + exch + 0 + exch + put % put into string of length 1 + dup % duplicate the it + currentpoint % save current position + 3 -1 roll % prepare show + show % show the character + moveto % move back to beginning + currentpoint % save current position + 3 -1 roll % prepare outline path of character + false charpath + stroke % stroke it + moveto % move back + % now move to next point + 2 index % get advance array + exch % get charpos + get % get advance element + 0 rmoveto % advance current position + } for + pop pop % remove string and delta array + setlinewidth % restore linewidth +} def diff --git a/vcl/unx/generic/print/psputil.cxx b/vcl/unx/generic/print/psputil.cxx new file mode 100644 index 000000000..b4837138a --- /dev/null +++ b/vcl/unx/generic/print/psputil.cxx @@ -0,0 +1,184 @@ +/* -*- 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 <string.h> +#include "psputil.hxx" + +namespace psp { + +/* + * string convenience routines + */ + +sal_Int32 +getHexValueOf (sal_Int32 nValue, OStringBuffer& pBuffer) +{ + const static char pHex [0x10] = { + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; + + pBuffer.append(pHex [(nValue & 0xF0) >> 4]); + pBuffer.append(pHex [(nValue & 0x0F) ]); + + return 2; +} + +sal_Int32 +getAlignedHexValueOf (sal_Int32 nValue, OStringBuffer& pBuffer) +{ + // get sign + bool bNegative = nValue < 0; + nValue = bNegative ? -nValue : nValue; + + // get required buffer size, must be a multiple of two + sal_Int32 nPrecision; + if (nValue < 0x80) + nPrecision = 2; + else + if (nValue < 0x8000) + nPrecision = 4; + else + if (nValue < 0x800000) + nPrecision = 6; + else + nPrecision = 8; + + // convert the int into its hex representation, write it into the buffer + sal_Int32 nRet = nPrecision; + auto const start = pBuffer.getLength(); + while (nPrecision) + { + OStringBuffer scratch; + nPrecision -= getHexValueOf (nValue % 256, scratch ); + pBuffer.insert(start, scratch); + nValue /= 256; + } + + // set sign bit + if (bNegative) + { + switch (pBuffer[start]) + { + case '0' : pBuffer[start] = '8'; break; + case '1' : pBuffer[start] = '9'; break; + case '2' : pBuffer[start] = 'A'; break; + case '3' : pBuffer[start] = 'B'; break; + case '4' : pBuffer[start] = 'C'; break; + case '5' : pBuffer[start] = 'D'; break; + case '6' : pBuffer[start] = 'E'; break; + case '7' : pBuffer[start] = 'F'; break; + default: OSL_FAIL("Already a signed value"); + } + } + + // report precision + return nRet; +} + +sal_Int32 +getValueOf (sal_Int32 nValue, OStringBuffer& pBuffer) +{ + sal_Int32 nChar = 0; + if (nValue < 0) + { + pBuffer.append('-'); + ++nChar; + nValue *= -1; + } + else + if (nValue == 0) + { + pBuffer.append('0'); + ++nChar; + return nChar; + } + + char pInvBuffer [32]; + sal_Int32 nInvChar = 0; + while (nValue > 0) + { + pInvBuffer [nInvChar++] = '0' + nValue % 10; + nValue /= 10; + } + while (nInvChar > 0) + { + pBuffer.append(pInvBuffer [--nInvChar]); + ++nChar; + } + + return nChar; +} + +sal_Int32 +appendStr (const char* pSrc, OStringBuffer& pDst) +{ + sal_Int32 nBytes = strlen (pSrc); + pDst.append(pSrc, nBytes); + + return nBytes; +} + +/* + * copy strings to file + */ + +bool +WritePS (osl::File* pFile, const char* pString) +{ + sal_uInt64 nInLength = rtl_str_getLength (pString); + sal_uInt64 nOutLength = 0; + + if (nInLength > 0 && pFile) + pFile->write (pString, nInLength, nOutLength); + + return nInLength == nOutLength; +} + +bool +WritePS (osl::File* pFile, const char* pString, sal_uInt64 nInLength) +{ + sal_uInt64 nOutLength = 0; + + if (nInLength > 0 && pFile) + pFile->write (pString, nInLength, nOutLength); + + return nInLength == nOutLength; +} + +bool +WritePS (osl::File* pFile, const OString &rString) +{ + sal_uInt64 nInLength = rString.getLength(); + sal_uInt64 nOutLength = 0; + + if (nInLength > 0 && pFile) + pFile->write (rString.getStr(), nInLength, nOutLength); + + return nInLength == nOutLength; +} + +bool +WritePS (osl::File* pFile, std::u16string_view rString) +{ + return WritePS (pFile, OUStringToOString(rString, RTL_TEXTENCODING_ASCII_US)); +} + +} /* namespace psp */ + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/generic/print/psputil.hxx b/vcl/unx/generic/print/psputil.hxx new file mode 100644 index 000000000..e5ae18071 --- /dev/null +++ b/vcl/unx/generic/print/psputil.hxx @@ -0,0 +1,55 @@ +/* -*- 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 . + */ + +#pragma once + +#include <sal/config.h> + +#include <string_view> + +#include <osl/file.hxx> + +#include <rtl/math.hxx> +#include <rtl/ustring.hxx> +#include <rtl/strbuf.hxx> +#include <rtl/string.hxx> + +namespace psp { + +/* + * string convenience routines + */ +sal_Int32 getHexValueOf (sal_Int32 nValue, OStringBuffer& pBuffer); +sal_Int32 getAlignedHexValueOf (sal_Int32 nValue, OStringBuffer& pBuffer); +sal_Int32 getValueOf (sal_Int32 nValue, OStringBuffer& pBuffer); +sal_Int32 appendStr (const char* pSrc, OStringBuffer& pDst); + +inline void getValueOfDouble( OStringBuffer& pBuffer, double f, int nPrecision = 0) +{ + pBuffer.append(rtl::math::doubleToString( f, rtl_math_StringFormat_G, nPrecision, '.', true )); +} + +bool WritePS (osl::File* pFile, const char* pString); +bool WritePS (osl::File* pFile, const char* pString, sal_uInt64 nInLength); +bool WritePS (osl::File* pFile, const OString &rString); +bool WritePS (osl::File* pFile, std::u16string_view rString); + +} /* namespace psp */ + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/generic/print/text_gfx.cxx b/vcl/unx/generic/print/text_gfx.cxx new file mode 100644 index 000000000..d847004ed --- /dev/null +++ b/vcl/unx/generic/print/text_gfx.cxx @@ -0,0 +1,158 @@ +/* -*- 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 "psputil.hxx" +#include "glyphset.hxx" + +#include <unx/printergfx.hxx> +#include <unx/fontmanager.hxx> + +using namespace psp ; + +/* + * implement text handling printer routines, + */ + +void PrinterGfx::SetFont( + sal_Int32 nFontID, + sal_Int32 nHeight, + sal_Int32 nWidth, + Degree10 nAngle, + bool bVertical, + bool bArtItalic, + bool bArtBold + ) +{ + // font and encoding will be set by drawText again immediately + // before PSShowText + mnFontID = nFontID; + maVirtualStatus.maFont.clear(); + maVirtualStatus.maEncoding = RTL_TEXTENCODING_DONTKNOW; + maVirtualStatus.mnTextHeight = nHeight; + maVirtualStatus.mnTextWidth = nWidth; + maVirtualStatus.mbArtItalic = bArtItalic; + maVirtualStatus.mbArtBold = bArtBold; + mnTextAngle = nAngle; + mbTextVertical = bVertical; +} + +void PrinterGfx::drawGlyph(const Point& rPoint, + sal_GlyphId aGlyphId) +{ + + // draw the string + // search for a glyph set matching the set font + bool bGlyphFound = false; + for (auto & elem : maPS3Font) + if ( (elem.GetFontID() == mnFontID) + && (elem.IsVertical() == mbTextVertical)) + { + elem.DrawGlyph (*this, rPoint, aGlyphId); + bGlyphFound = true; + break; + } + + // not found ? create a new one + if (!bGlyphFound) + { + maPS3Font.emplace_back(mnFontID, mbTextVertical); + maPS3Font.back().DrawGlyph (*this, rPoint, aGlyphId); + } +} + +void PrinterGfx::DrawGlyph(const Point& rPoint, + const GlyphItem& rGlyph) +{ + // move and rotate the user coordinate system + // avoid the gsave/grestore for the simple cases since it allows + // reuse of the current font if it hasn't changed + Degree10 nCurrentTextAngle = mnTextAngle; + Point aPoint( rPoint ); + + if (nCurrentTextAngle) + { + PSGSave (); + PSTranslate (rPoint); + PSRotate (nCurrentTextAngle); + mnTextAngle = 0_deg10; + aPoint = Point( 0, 0 ); + } + + if (mbTextVertical && rGlyph.IsVertical()) + { + sal_Int32 nTextHeight = maVirtualStatus.mnTextHeight; + sal_Int32 nTextWidth = maVirtualStatus.mnTextWidth ? maVirtualStatus.mnTextWidth : maVirtualStatus.mnTextHeight; + sal_Int32 nAscend = mrFontMgr.getFontAscend( mnFontID ); + sal_Int32 nDescend = mrFontMgr.getFontDescend( mnFontID ); + + nDescend = nDescend * nTextHeight / 1000; + nAscend = nAscend * nTextHeight / 1000; + + Point aRotPoint( -nDescend*nTextWidth/nTextHeight, nAscend*nTextWidth/nTextHeight ); + + // transform matrix to new individual direction + PSGSave (); + GraphicsStatus aSaveStatus = maVirtualStatus; + // switch font aspect + maVirtualStatus.mnTextWidth = nTextHeight; + maVirtualStatus.mnTextHeight = nTextWidth; + if( aPoint.X() || aPoint.Y() ) + PSTranslate( aPoint ); + PSRotate (900_deg10); + // draw the rotated glyph + drawGlyph(aRotPoint, rGlyph.glyphId()); + + // restore previous state + maVirtualStatus = aSaveStatus; + PSGRestore(); + } + else + drawGlyph(aPoint, rGlyph.glyphId()); + + // restore the user coordinate system + if (nCurrentTextAngle) + { + PSGRestore (); + mnTextAngle = nCurrentTextAngle; + } +} + +/* + * spool the converted truetype fonts to the page header after the page body is + * complete + * for Type1 fonts spool additional reencoding vectors that are necessary to access the + * whole font + */ + +void +PrinterGfx::OnEndJob () +{ + maPS3Font.clear(); +} + +void +PrinterGfx::writeResources( osl::File* pFile, std::vector< OString >& rSuppliedFonts ) +{ + // write glyphsets and reencodings + for (auto & PS3Font : maPS3Font) + { + PS3Font.PSUploadFont (*pFile, *this, mbUploadPS42Fonts, rSuppliedFonts ); + } +} +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/generic/printer/configuration/README b/vcl/unx/generic/printer/configuration/README new file mode 100644 index 000000000..c39237a53 --- /dev/null +++ b/vcl/unx/generic/printer/configuration/README @@ -0,0 +1,5 @@ +Contains ppds for use by vcl when not using CUPS + +This is used for the print-to-file functionality. These two PPDs +describe the range of paper sizes and postscript options necessary for +printing to postscript without a configured printer. diff --git a/vcl/unx/generic/printer/configuration/ppds/SGENPRT.PS b/vcl/unx/generic/printer/configuration/ppds/SGENPRT.PS new file mode 100644 index 000000000..177e2c4e0 --- /dev/null +++ b/vcl/unx/generic/printer/configuration/ppds/SGENPRT.PS @@ -0,0 +1,582 @@ +*PPD-Adobe: "4.0" +*% +*% 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 . +*% + +*% The user must print with a PostScript(R) emulator to non PostScript(R) +*% printers if the system has no specific printer support. This file +*% allows the user to print to most printers without any modification. +*% Standard paper sizes and resolutions are defined. There are some +*% additional definitions for screen or online documents in this file. +*% To print to a PostScript(R) printer, use the specific PPD file. + +*% ===== General ===== + +*FormatVersion: "4.0" +*FileVersion: "1.0" +*LanguageEncoding: ISOLatin1 +*LanguageVersion: English +*PSVersion: "(1) 1" +*Product: "(Generic Printer)" +*ModelName: "Generic Printer" +*NickName: "Generic Printer" +*PCFileName: "SGENPRT.PPD" + + +*% ===== Basic Capabilities and Defaults ===== + +*ColorDevice: True +*DefaultColorSpace: RGB +*LanguageLevel: "2" +*TTRasterizer: Type42 + +*% --- For None Color or old PostScript(R) printers use following lines --- +*% *ColorDevice: False +*% *DefaultColorSpace: Gray +*% *LanguageLevel: "1" + +*FreeVM: "8388608" +*VariablePaperSize: True +*FileSystem: False +*Throughput: "8" +*Password: "0" +*ExitServer: " + count 0 eq % is the password on the stack? + { true } + { dup % potential password + statusdict /checkpassword get exec not + } ifelse + { % if no password or not valid + (WARNING : Cannot perform the exitserver command.) = + (Password supplied is not valid.) = + (Please contact the author of this software.) = flush + quit + } if + serverdict /exitserver get exec +" +*End +*Reset: " + count 0 eq % is the password on the stack? + { true } + { dup % potential password + statusdict /checkpassword get exec not + } ifelse + { % if no password or not valid + (WARNING : Cannot reset printer.) = + (Password supplied is not valid.) = + (Please contact the author of this software.) = flush + quit + } if + serverdict /exitserver get exec + systemdict /quit get exec + (WARNING : Printer Reset Failed.) = flush +" +*End + + +*DefaultResolution: 300dpi + +*ResScreenFreq 72dpi: "60.0" +*ResScreenFreq 144dpi: "60.0" +*ResScreenFreq 300dpi: "60.0" +*ResScreenFreq 360dpi: "60.0" +*ResScreenFreq 600dpi: "60.0" +*ResScreenFreq 720dpi: "60.0" +*ResScreenFreq 1200dpi: "60.0" +*ResScreenFreq 1440dpi: "60.0" +*ResScreenFreq 2400dpi: "60.0" +*ResScreenAngle 72dpi: "45.0" +*ResScreenAngle 144dpi: "45.0" +*ResScreenAngle 300dpi: "45.0" +*ResScreenAngle 360dpi: "45.0" +*ResScreenAngle 600dpi: "45.0" +*ResScreenAngle 720dpi: "45.0" +*ResScreenAngle 1200dpi: "45.0" +*ResScreenAngle 1440dpi: "45.0" +*ResScreenAngle 2400dpi: "45.0" + + +*% ===== Halftone ===== + +*ContoneOnly: False +*DefaultHalftoneType: 1 +*ScreenFreq: "60.0" +*ScreenAngle: "45.0" +*DefaultScreenProc: Dot +*ScreenProc Dot: " + { abs exch abs 2 copy add 1 gt {1 sub dup mul exch 1 sub + dup mul add 1 sub } { dup mul exch dup mul add 1 exch sub } + ifelse } bind +" +*End +*ScreenProc Line: "{ exch pop abs neg } bind" +*ScreenProc Ellipse: " + { abs exch abs 2 copy mul exch 4 mul add 3 sub dup 0 + lt { pop dup mul exch .75 div dup mul add 4 div 1 exch sub } + { dup 1 gt { pop 1 exch sub dup mul exch 1 exch sub .75 div + dup mul add 4 div 1 sub } + { .5 exch sub exch pop exch pop } ifelse } ifelse } bind +" +*End +*ScreenProc Cross: "{ abs exch abs 2 copy gt { exch } if pop neg } bind" + +*DefaultTransfer: Null +*Transfer Null: "{ } bind" +*Transfer Null.Inverse: "{ 1 exch sub } bind" + + +*% ===== Paper ===== + +*OpenUI *PageSize: PickOne +*OrderDependency: 30 AnySetup *PageSize +*DefaultPageSize: Letter +*PageSize A0: "<</PageSize [2384 3370] /ImagingBBox null>> setpagedevice" +*PageSize A1: "<</PageSize [1684 2384] /ImagingBBox null>> setpagedevice" +*PageSize A2: "<</PageSize [1191 1684] /ImagingBBox null>> setpagedevice" +*PageSize A3: "<</PageSize [842 1191] /ImagingBBox null>> setpagedevice" +*PageSize A4: "<</PageSize [595 842] /ImagingBBox null>> setpagedevice" +*PageSize A5: "<</PageSize [420 595] /ImagingBBox null>> setpagedevice" +*PageSize A6: "<</PageSize [297 420] /ImagingBBox null>> setpagedevice" +*PageSize B4: "<</PageSize [728 1032] /ImagingBBox null>> setpagedevice" +*PageSize B5: "<</PageSize [516 729] /ImagingBBox null>> setpagedevice" +*PageSize B6: "<</PageSize [363 516] /ImagingBBox null>> setpagedevice" +*PageSize Legal/US Legal: "<</PageSize [612 1008] /ImagingBBox null>> setpagedevice" +*PageSize Letter/US Letter: "<</PageSize [612 792] /ImagingBBox null>> setpagedevice" +*PageSize Executive: "<</PageSize [522 756] /ImagingBBox null>> setpagedevice" +*PageSize Statement: "<</PageSize [396 612] /ImagingBBox null>> setpagedevice" +*PageSize Tabloid/US Tabloid: "<</PageSize [792 1224] /ImagingBBox null>> setpagedevice" +*PageSize Ledger/Ledger Landscape: "<</PageSize [1224 792] /ImagingBBox null>> setpagedevice" +*PageSize AnsiC/US C: "<</PageSize [1224 1584] /ImagingBBox null>> setpagedevice" +*PageSize AnsiD/US D: "<</PageSize [1584 2448] /ImagingBBox null>> setpagedevice" +*PageSize AnsiE/US E: "<</PageSize [2448 3168] /ImagingBBox null>> setpagedevice" +*PageSize ARCHA/ARCH A: "<</PageSize [648 864] /ImagingBBox null>> setpagedevice" +*PageSize ARCHB/ARCH B: "<</PageSize [864 1296] /ImagingBBox null>> setpagedevice" +*PageSize ARCHC/ARCH C: "<</PageSize [1296 1728] /ImagingBBox null>> setpagedevice" +*PageSize ARCHD/ARCH D: "<</PageSize [1728 2592] /ImagingBBox null>> setpagedevice" +*PageSize ARCHE/ARCH E: "<</PageSize [2592 3456] /ImagingBBox null>> setpagedevice" +*PageSize EnvMonarch/Monarch Envelope: "<</PageSize [279 540] /ImagingBBox null>> setpagedevice" +*PageSize EnvDL/DL Envelope: "<</PageSize [312 624] /ImagingBBox null>> setpagedevice" +*PageSize EnvC4/C4 Envelope: "<</PageSize [649 918] /ImagingBBox null>> setpagedevice" +*PageSize EnvC5/C5 Envelope: "<</PageSize [459 649] /ImagingBBox null>> setpagedevice" +*PageSize EnvC6/C6 Envelope: "<</PageSize [323 459] /ImagingBBox null>> setpagedevice" +*PageSize Env10/C10 Envelope: "<</PageSize [297 684] /ImagingBBox null>> setpagedevice" +*PageSize EnvC65/C65 Envelope: "<</PageSize [324 648] /ImagingBBox null>> setpagedevice" +*PageSize Folio: "<</PageSize [595 935] /ImagingBBox null>> setpagedevice" +*?PageSize: " + save + currentpagedevice /PageSize get aload pop + 2 copy gt {exch} if + (Unknown) + 32 dict + dup [2384 3370] (A0) put + dup [1684 2384] (A1) put + dup [1191 1684] (A2) put + dup [842 1191] (A3) put + dup [595 842] (A4) put + dup [420 595] (A5) put + dup [297 420] (A6) put + dup [728 1032] (B4) put + dup [516 729] (B5) put + dup [363 516] (B6) put + dup [612 1008] (Legal) put + dup [612 792] (Letter) put + dup [522 756] (Executive) put + dup [396 612] (Statement) put + dup [792 1224] (Tabloid) put + dup [1224 792] (Ledger) put + dup [1224 1584] (AnsiC) put + dup [1584 2448] (AnsiD) put + dup [2448 3168] (AnsiE) put + dup [648 864] (ARCHA) put + dup [864 1296] (ARCHB) put + dup [1296 1728] (ARCHC) put + dup [1728 2592] (ARCHD) put + dup [2592 3456] (ARCHE) put + dup [279 540] (EnvMonarch) put + dup [312 624] (EnvDL) put + dup [649 918] (EnvC4) put + dup [459 649] (EnvC5) put + dup [323 459] (EnvC6) put + dup [297 684] (Env10) put + dup [324 648] (EnvC65) put + dup [595 935] (Folio) put + { exch aload pop 4 index sub abs 5 le exch + 5 index sub abs 5 le and + { exch pop exit } { pop } ifelse + } bind forall + = flush pop pop + restore +" +*End +*CloseUI: *PageSize + +*OpenUI *PageRegion: PickOne +*OrderDependency: 40 AnySetup *PageRegion +*DefaultPageRegion: Letter +*PageRegion A0: "<</PageSize [2384 3370] /ImagingBBox null>> setpagedevice" +*PageRegion A1: "<</PageSize [1684 2384] /ImagingBBox null>> setpagedevice" +*PageRegion A2: "<</PageSize [1191 1684] /ImagingBBox null>> setpagedevice" +*PageRegion A3: "<</PageSize [842 1191] /ImagingBBox null>> setpagedevice" +*PageRegion A4: "<</PageSize [595 842] /ImagingBBox null>> setpagedevice" +*PageRegion A5: "<</PageSize [420 595] /ImagingBBox null>> setpagedevice" +*PageRegion A6: "<</PageSize [297 420] /ImagingBBox null>> setpagedevice" +*PageRegion B4: "<</PageSize [728 1032] /ImagingBBox null>> setpagedevice" +*PageRegion B5: "<</PageSize [516 729] /ImagingBBox null>> setpagedevice" +*PageRegion B6: "<</PageSize [363 516] /ImagingBBox null>> setpagedevice" +*PageRegion Legal/US Legal: "<</PageSize [612 1008] /ImagingBBox null>> setpagedevice" +*PageRegion Letter/US Letter: "<</PageSize [612 792] /ImagingBBox null>> setpagedevice" +*PageRegion Executive: "<</PageSize [522 756] /ImagingBBox null>> setpagedevice" +*PageRegion Statement: "<</PageSize [396 612] /ImagingBBox null>> setpagedevice" +*PageRegion Tabloid/US Tabloid: "<</PageSize [792 1224] /ImagingBBox null>> setpagedevice" +*PageRegion Ledger/Ledger Landscape: "<</PageSize [1224 792] /ImagingBBox null>> setpagedevice" +*PageRegion AnsiC/US C: "<</PageSize [1224 1584] /ImagingBBox null>> setpagedevice" +*PageRegion AnsiD/US D: "<</PageSize [1584 2448] /ImagingBBox null>> setpagedevice" +*PageRegion AnsiE/US E: "<</PageSize [2448 3168] /ImagingBBox null>> setpagedevice" +*PageRegion ARCHA/ARCH A: "<</PageSize [648 864] /ImagingBBox null>> setpagedevice" +*PageRegion ARCHB/ARCH B: "<</PageSize [864 1296] /ImagingBBox null>> setpagedevice" +*PageRegion ARCHC/ARCH C: "<</PageSize [1296 1728] /ImagingBBox null>> setpagedevice" +*PageRegion ARCHD/ARCH D: "<</PageSize [1728 2592] /ImagingBBox null>> setpagedevice" +*PageRegion ARCHE/ARCH E: "<</PageSize [2592 3456] /ImagingBBox null>> setpagedevice" +*PageRegion EnvMonarch/Monarch Envelope: "<</PageSize [279 540] /ImagingBBox null>> setpagedevice" +*PageRegion EnvDL/DL Envelope: "<</PageSize [312 624] /ImagingBBox null>> setpagedevice" +*PageRegion EnvC4/C4 Envelope: "<</PageSize [649 918] /ImagingBBox null>> setpagedevice" +*PageRegion EnvC5/C5 Envelope: "<</PageSize [459 649] /ImagingBBox null>> setpagedevice" +*PageRegion EnvC6/C6 Envelope: "<</PageSize [323 459] /ImagingBBox null>> setpagedevice" +*PageRegion Env10/C10 Envelope: "<</PageSize [297 684] /ImagingBBox null>> setpagedevice" +*PageRegion EnvC65/C65 Envelope: "<</PageSize [324 648] /ImagingBBox null>> setpagedevice" +*PageRegion Folio: "<</PageSize [595 935] /ImagingBBox null>> setpagedevice" +*CloseUI: *PageRegion + +*DefaultImageableArea: Letter +*ImageableArea A0: "0 0 2384 3370" +*ImageableArea A1: "0 0 1684 2384" +*ImageableArea A2: "0 0 1191 1684" +*ImageableArea A3: "18 18 824 1173" +*ImageableArea A4: "18 18 577 824" +*ImageableArea A5: "18 18 402 577" +*ImageableArea A6: "18 18 279 402" +*ImageableArea B4: "18 18 710 1014" +*ImageableArea B5: "18 18 498 711" +*ImageableArea B6: "18 18 345 498" +*ImageableArea Legal: "18 18 594 990" +*ImageableArea Letter: "18 18 594 774" +*ImageableArea Executive: "18 18 504 738" +*ImageableArea Statement: "18 18 378 594" +*ImageableArea Tabloid: "18 18 774 1206" +*ImageableArea Ledger: "18 18 1206 774" +*ImageableArea AnsiC: "0 0 1224 1584" +*ImageableArea AnsiD: "0 0 1584 2448" +*ImageableArea AnsiE: "0 0 2448 3168" +*ImageableArea ARCHA: "0 0 648 864" +*ImageableArea ARCHB: "0 0 864 1296" +*ImageableArea ARCHC: "0 0 1296 1728" +*ImageableArea ARCHD: "0 0 1728 2592" +*ImageableArea ARCHE: "0 0 2592 3456" +*ImageableArea EnvMonarch: "0 0 279 540" +*ImageableArea EnvDL: "0 0 312 624" +*ImageableArea EnvC4: "0 0 649 918" +*ImageableArea EnvC5: "0 0 459 649" +*ImageableArea EnvC6: "0 0 323 459" +*ImageableArea Env10: "0 0 297 684" +*ImageableArea EnvC65: "0 0 324 648" +*ImageableArea Folio: "0 0 595 935" + +*DefaultPaperDimension: Letter +*PaperDimension A0: "2384 3370" +*PaperDimension A1: "1684 2384" +*PaperDimension A2: "1191 1684" +*PaperDimension A3: "842 1191" +*PaperDimension A4: "595 842" +*PaperDimension A5: "420 595" +*PaperDimension A6: "297 420" +*PaperDimension B4: "728 1032" +*PaperDimension B5: "516 729" +*PaperDimension B6: "363 516" +*PaperDimension Legal: "612 1008" +*PaperDimension Letter: "612 792" +*PaperDimension Executive: "522 756" +*PaperDimension Statement: "396 612" +*PaperDimension Tabloid: "792 1224" +*PaperDimension Ledger: "1224 792" +*PaperDimension AnsiC: "1224 1584" +*PaperDimension AnsiD: "1584 2448" +*PaperDimension AnsiE: "2448 3168" +*PaperDimension ARCHA: "648 864" +*PaperDimension ARCHB: "864 1296" +*PaperDimension ARCHC: "1296 1728" +*PaperDimension ARCHD: "1728 2592" +*PaperDimension ARCHE: "2592 3456" +*PaperDimension EnvMonarch: "279 540" +*PaperDimension EnvDL: "312 624" +*PaperDimension EnvC4: "649 918" +*PaperDimension EnvC5: "459 649" +*PaperDimension EnvC6: "323 459" +*PaperDimension Env10: "297 684" +*PaperDimension EnvC65: "324 648" +*PaperDimension Folio: "595 935" + +*% ===== Duplex ===== +*OpenUI *Duplex/Duplex: PickOne +*OrderDependency: 30 AnySetup *Duplex +*DefaultDuplex: Simplex +*Duplex Simplex: "" +*Duplex None/Off: " +<</Duplex false /Tumble false + /Policies << /Duplex 1 /Tumble 1 >> +>> setpagedevice" +*Duplex DuplexNoTumble/Long edge:" +<</Duplex true /Tumble false + /Policies << /Duplex 1 /Tumble 1 >> +>> setpagedevice" +*Duplex DuplexTumble/Short edge:" +<</Duplex true /Tumble true + /Policies << /Duplex 1 /Tumble 1 >> +>> setpagedevice" +*End +*CloseUI: *Duplex + +*% ===== ManualFeed === +*OpenUI *ManualFeed/Manual Feed: Boolean +*OrderDependency: 15 AnySetup *ManualFeed +*DefaultManualFeed: False +*ManualFeed False: " +<< /ManualFeed false /Policies << /ManualFeed 1 >> >> setpagedevice" +*ManualFeed True: " +<< /ManualFeed true /Policies << /ManualFeed 1 >> >> setpagedevice" +*End +*CloseUI: *ManualFeed + +*% ===== Fonts ===== + +*DefaultFont: Courier +*Font AvantGarde-Book: Standard "(001.002)" Standard ROM +*Font AvantGarde-BookOblique: Standard "(001.000)" Standard ROM +*Font AvantGarde-Demi: Standard "(001.000)" Standard ROM +*Font AvantGarde-DemiOblique: Standard "(001.000)" Standard ROM +*Font Bookman-Demi: Standard "(001.000)" Standard ROM +*Font Bookman-DemiItalic: Standard "(001.000)" Standard ROM +*Font Bookman-Light: Standard "(001.000)" Standard ROM +*Font Bookman-LightItalic: Standard "(001.000)" Standard ROM +*Font Courier: Standard "(001.000)" Standard ROM +*Font Courier-Bold: Standard "(001.000)" Standard ROM +*Font Courier-BoldOblique: Standard "(001.000)" Standard ROM +*Font Courier-Oblique: Standard "(001.000)" Standard ROM +*Font Helvetica: Standard "(001.000)" Standard ROM +*Font Helvetica-Bold: Standard "(001.000)" Standard ROM +*Font Helvetica-BoldOblique: Standard "(001.000)" Standard ROM +*Font Helvetica-Narrow: Standard "(001.000)" Standard ROM +*Font Helvetica-Narrow-Bold: Standard "(001.000)" Standard ROM +*Font Helvetica-Narrow-BoldOblique: Standard "(001.000)" Standard ROM +*Font Helvetica-Narrow-Oblique: Standard "(001.000)" Standard ROM +*Font Helvetica-Oblique: Standard "(001.000)" Standard ROM +*Font NewCenturySchlbk-Bold: Standard "(001.000)" Standard ROM +*Font NewCenturySchlbk-BoldItalic: Standard "(001.000)" Standard ROM +*Font NewCenturySchlbk-Italic: Standard "(001.000)" Standard ROM +*Font NewCenturySchlbk-Roman: Standard "(001.000)" Standard ROM +*Font Palatino-Bold: Standard "(001.000)" Standard ROM +*Font Palatino-BoldItalic: Standard "(001.000)" Standard ROM +*Font Palatino-Italic: Standard "(001.000)" Standard ROM +*Font Palatino-Roman: Standard "(001.000)" Standard ROM +*Font Symbol: Special "(001.001)" Special ROM +*Font Times-Bold: Standard "(001.000)" Standard ROM +*Font Times-BoldItalic: Standard "(001.000)" Standard ROM +*Font Times-Italic: Standard "(001.000)" Standard ROM +*Font Times-Roman: Standard "(001.000)" Standard ROM +*Font ZapfChancery-MediumItalic: Standard "(001.000)" Standard ROM +*Font ZapfDingbats: Special "(001.000)" Special ROM +*?FontQuery: " + save + { + count 1 gt + { + exch dup 127 string cvs (/) print print (:) print + /Font resourcestatus {pop pop (Yes)} {(No)} ifelse = + } + { exit } ifelse + } bind loop + (*) = flush + restore +" +*End + +*?FontList: " + save + (*) {cvn ==} 128 string /Font resourceforall + (*) = flush + restore +" +*End + + +*% === Printer Messages === + +*Message: "%%[ exitserver: permanent state may be changed ]%%" +*Message: "%%[ Flushing: rest of job (to end-of-file) will be ignored ]%%" +*Message: "\FontName\ not found, using Courier" + +*% Status (format: %%[ status: <one of these> %%] ) +*Status: "idle" +*Status: "busy" +*Status: "waiting" +*Status: "printing" +*Status: "PrinterError: timeout, clearing printer" +*Status: "PrinterError: paper entry misfeed" +*Status: "PrinterError: warming up" +*Status: "PrinterError: service call" +*Status: "PrinterError: no toner cartridge" +*Status: "PrinterError: no paper tray" +*Status: "PrinterError: cover open" +*Status: "PrinterError: resetting printer" +*Status: "PrinterError: out of paper" +*Status: "PrinterError: timeout" +*Status: "PrinterError: manual feed timeout" + +*% Input Sources (format: %%[ status: <stat>; source: <one of these>]%% ) + +*% Printer Error (format: %%[ PrinterError: <one of these>]%%) +*PrinterError: "timeout, clearing printer" +*PrinterError: "paper entry misfeed" +*PrinterError: "warming up" +*PrinterError: "service call" +*PrinterError: "no toner cartridge" +*PrinterError: "no paper tray" +*PrinterError: "cover open" +*PrinterError: "resetting printer" +*PrinterError: "out of paper" +*PrinterError: "timeout" +*PrinterError: "manual feed timeout" + + +*% ===== Color Separation ===== + +*DefaultColorSep: ProcessBlack.60lpi.300dpi/60 lpi / 300 dpi +*InkName: ProcessBlack/Process Black +*InkName: CustomColor/Custom Color +*InkName: ProcessCyan/Process Cyan +*InkName: ProcessMagenta/Process Magenta +*InkName: ProcessYellow/Process Yellow + +*% --- For 60 lpi / 72 dpi --- +*ColorSepScreenAngle ProcessBlack.60lpi.72dpi/60 lpi / 72 dpi: "45" +*ColorSepScreenAngle CustomColor.60lpi.72dpi/60 lpi / 72 dpi: "45" +*ColorSepScreenAngle ProcessCyan.60lpi.72dpi/60 lpi / 72 dpi: "15" +*ColorSepScreenAngle ProcessMagenta.60lpi.72dpi/60 lpi / 72 dpi: "75" +*ColorSepScreenAngle ProcessYellow.60lpi.72dpi/60 lpi / 72 dpi: "0" +*ColorSepScreenFreq ProcessBlack.60lpi.72dpi/60 lpi / 72 dpi: "60" +*ColorSepScreenFreq CustomColor.60lpi.72dpi/60 lpi / 72 dpi: "60" +*ColorSepScreenFreq ProcessCyan.60lpi.72dpi/60 lpi / 72 dpi: "60" +*ColorSepScreenFreq ProcessMagenta.60lpi.72dpi/60 lpi / 72 dpi: "60" +*ColorSepScreenFreq ProcessYellow.60lpi.72dpi/60 lpi / 72 dpi: "60" + +*% --- For 60 lpi / 144 dpi --- +*ColorSepScreenAngle ProcessBlack.60lpi.144dpi/60 lpi / 144 dpi: "45" +*ColorSepScreenAngle CustomColor.60lpi.144dpi/60 lpi / 144 dpi: "45" +*ColorSepScreenAngle ProcessCyan.60lpi.144dpi/60 lpi / 144 dpi: "15" +*ColorSepScreenAngle ProcessMagenta.60lpi.144dpi/60 lpi / 144 dpi: "75" +*ColorSepScreenAngle ProcessYellow.60lpi.144dpi/60 lpi / 144 dpi: "0" +*ColorSepScreenFreq ProcessBlack.60lpi.144dpi/60 lpi / 144 dpi: "60" +*ColorSepScreenFreq CustomColor.60lpi.144dpi/60 lpi / 144 dpi: "60" +*ColorSepScreenFreq ProcessCyan.60lpi.144dpi/60 lpi / 144 dpi: "60" +*ColorSepScreenFreq ProcessMagenta.60lpi.144dpi/60 lpi / 144 dpi: "60" +*ColorSepScreenFreq ProcessYellow.60lpi.144dpi/60 lpi / 144 dpi: "60" + +*% --- For 60 lpi / 300 dpi --- +*ColorSepScreenAngle ProcessBlack.60lpi.300dpi/60 lpi / 300 dpi: "45" +*ColorSepScreenAngle CustomColor.60lpi.300dpi/60 lpi / 300 dpi: "45" +*ColorSepScreenAngle ProcessCyan.60lpi.300dpi/60 lpi / 300 dpi: "15" +*ColorSepScreenAngle ProcessMagenta.60lpi.300dpi/60 lpi / 300 dpi: "75" +*ColorSepScreenAngle ProcessYellow.60lpi.300dpi/60 lpi / 300 dpi: "0" +*ColorSepScreenFreq ProcessBlack.60lpi.300dpi/60 lpi / 300 dpi: "60" +*ColorSepScreenFreq CustomColor.60lpi.300dpi/60 lpi / 300 dpi: "60" +*ColorSepScreenFreq ProcessCyan.60lpi.300dpi/60 lpi / 300 dpi: "60" +*ColorSepScreenFreq ProcessMagenta.60lpi.300dpi/60 lpi / 300 dpi: "60" +*ColorSepScreenFreq ProcessYellow.60lpi.300dpi/60 lpi / 300 dpi: "60" + +*% --- For 60 lpi / 360 dpi --- +*ColorSepScreenAngle ProcessBlack.60lpi.360dpi/60 lpi / 360 dpi: "45" +*ColorSepScreenAngle CustomColor.60lpi.360dpi/60 lpi / 360 dpi: "45" +*ColorSepScreenAngle ProcessCyan.60lpi.360dpi/60 lpi / 360 dpi: "15" +*ColorSepScreenAngle ProcessMagenta.60lpi.360dpi/60 lpi / 360 dpi: "75" +*ColorSepScreenAngle ProcessYellow.60lpi.360dpi/60 lpi / 360 dpi: "0" +*ColorSepScreenFreq ProcessBlack.60lpi.360dpi/60 lpi / 360 dpi: "60" +*ColorSepScreenFreq CustomColor.60lpi.360dpi/60 lpi / 360 dpi: "60" +*ColorSepScreenFreq ProcessCyan.60lpi.360dpi/60 lpi / 360 dpi: "60" +*ColorSepScreenFreq ProcessMagenta.60lpi.360dpi/60 lpi / 360 dpi: "60" +*ColorSepScreenFreq ProcessYellow.60lpi.360dpi/60 lpi / 360 dpi: "60" + +*% --- For 71 lpi / 600 dpi --- +*ColorSepScreenAngle ProcessBlack.71lpi.600dpi/71 lpi / 600 dpi: "45.0" +*ColorSepScreenAngle CustomColor.71lpi.600dpi/71 lpi / 600 dpi: "45.0" +*ColorSepScreenAngle ProcessCyan.71lpi.600dpi/71 lpi / 600 dpi: "71.5651" +*ColorSepScreenAngle ProcessMagenta.71lpi.600dpi/71 lpi / 600 dpi: "18.4349" +*ColorSepScreenAngle ProcessYellow.71lpi.600dpi/71 lpi / 600 dpi: "0.0" +*ColorSepScreenFreq ProcessBlack.71lpi.600dpi/71 lpi / 600 dpi: "70.7107" +*ColorSepScreenFreq CustomColor.71lpi.600dpi/71 lpi / 600 dpi: "70.7107" +*ColorSepScreenFreq ProcessCyan.71lpi.600dpi/71 lpi / 600 dpi: "63.2456" +*ColorSepScreenFreq ProcessMagenta.71lpi.600dpi/71 lpi / 600 dpi: "63.2456" +*ColorSepScreenFreq ProcessYellow.71lpi.600dpi/71 lpi / 600 dpi: "66.6667" + +*% --- For 71 lpi / 720 dpi --- +*ColorSepScreenAngle ProcessBlack.71lpi.720dpi/71 lpi / 720 dpi: "45.0" +*ColorSepScreenAngle CustomColor.71lpi.720dpi/71 lpi / 720 dpi: "45.0" +*ColorSepScreenAngle ProcessCyan.71lpi.720dpi/71 lpi / 720 dpi: "71.5651" +*ColorSepScreenAngle ProcessMagenta.71lpi.720dpi/71 lpi / 720 dpi: "18.4349" +*ColorSepScreenAngle ProcessYellow.71lpi.720dpi/71 lpi / 720 dpi: "0.0" +*ColorSepScreenFreq ProcessBlack.71lpi.720dpi/71 lpi / 720 dpi: "70.7107" +*ColorSepScreenFreq CustomColor.71lpi.720dpi/71 lpi / 720 dpi: "70.7107" +*ColorSepScreenFreq ProcessCyan.71lpi.720dpi/71 lpi / 720 dpi: "63.2456" +*ColorSepScreenFreq ProcessMagenta.71lpi.720dpi/71 lpi / 720 dpi: "63.2456" +*ColorSepScreenFreq ProcessYellow.71lpi.720dpi/71 lpi / 720 dpi: "66.6667" + +*% --- For 100 lpi / 1200 dpi --- +*ColorSepScreenAngle ProcessBlack.100lpi.1200dpi/100 lpi / 1200 dpi: "45.0" +*ColorSepScreenAngle CustomColor.100lpi.1200dpi/100 lpi / 1200 dpi: "45.0" +*ColorSepScreenAngle ProcessCyan.100lpi.1200dpi/100 lpi / 1200 dpi: "15.0" +*ColorSepScreenAngle ProcessMagenta.100lpi.1200dpi/100 lpi / 1200 dpi: "75.0" +*ColorSepScreenAngle ProcessYellow.100lpi.1200dpi/100 lpi / 1200 dpi: "0.0" +*ColorSepScreenFreq ProcessBlack.100lpi.1200dpi/100 lpi / 1200 dpi: "100.0" +*ColorSepScreenFreq CustomColor.100lpi.1200dpi/100 lpi / 1200 dpi: "100.0" +*ColorSepScreenFreq ProcessCyan.100lpi.1200dpi/100 lpi / 1200 dpi: "100.0" +*ColorSepScreenFreq ProcessMagenta.100lpi.1200dpi/100 lpi / 1200 dpi: "100.0" +*ColorSepScreenFreq ProcessYellow.100lpi.1200dpi/100 lpi / 1200 dpi: "100.0" + +*% --- For 100 lpi / 1440 dpi --- +*ColorSepScreenAngle ProcessBlack.100lpi.1440dpi/100 lpi / 1440 dpi: "45.0" +*ColorSepScreenAngle CustomColor.100lpi.1440dpi/100 lpi / 1440 dpi: "45.0" +*ColorSepScreenAngle ProcessCyan.100lpi.1440dpi/100 lpi / 1440 dpi: "15.0" +*ColorSepScreenAngle ProcessMagenta.100lpi.1440dpi/100 lpi / 1440 dpi: "75.0" +*ColorSepScreenAngle ProcessYellow.100lpi.1440dpi/100 lpi / 1440 dpi: "0.0" +*ColorSepScreenFreq ProcessBlack.100lpi.1440dpi/100 lpi / 1440 dpi: "100.0" +*ColorSepScreenFreq CustomColor.100lpi.1440dpi/100 lpi / 1440 dpi: "100.0" +*ColorSepScreenFreq ProcessCyan.100lpi.1440dpi/100 lpi / 1440 dpi: "100.0" +*ColorSepScreenFreq ProcessMagenta.100lpi.1440dpi/100 lpi / 1440 dpi: "100.0" +*ColorSepScreenFreq ProcessYellow.100lpi.1440dpi/100 lpi / 1440 dpi: "100.0" + +*% --- For 175 lpi / 2400 dpi --- +*ColorSepScreenAngle ProcessBlack.175lpi.2400dpi/175 lpi / 2400 dpi: "45.0" +*ColorSepScreenAngle CustomColor.175lpi.2400dpi/175 lpi / 2400 dpi: "45.0" +*ColorSepScreenAngle ProcessCyan.175lpi.2400dpi/175 lpi / 2400 dpi: "15.0" +*ColorSepScreenAngle ProcessMagenta.175lpi.2400dpi/175 lpi / 2400 dpi: "75.0" +*ColorSepScreenAngle ProcessYellow.175lpi.2400dpi/175 lpi / 2400 dpi: "0.0" +*ColorSepScreenFreq ProcessBlack.175lpi.2400dpi/175 lpi / 2400 dpi: "175.0" +*ColorSepScreenFreq CustomColor.175lpi.2400dpi/175 lpi / 2400 dpi: "175.0" +*ColorSepScreenFreq ProcessCyan.175lpi.2400dpi/175 lpi / 2400 dpi: "175.0" +*ColorSepScreenFreq ProcessMagenta.175lpi.2400dpi/175 lpi / 2400 dpi: "175.0" +*ColorSepScreenFreq ProcessYellow.175lpi.2400dpi/175 lpi / 2400 dpi: "175.0" + +*% Last Edit Date: March 24 2000 +*% end of PPD file diff --git a/vcl/unx/generic/printer/configuration/psprint.conf b/vcl/unx/generic/printer/configuration/psprint.conf new file mode 100644 index 000000000..1b56e084e --- /dev/null +++ b/vcl/unx/generic/printer/configuration/psprint.conf @@ -0,0 +1,99 @@ +; +; 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 . +; +[__Global_Printer_Defaults__] +; Copies: the default number of copies produced +; if key is absent the default is 1 +; Copies=1 + +; Orientation: the default orientation of pages +; possible Values: Portrait, Landscape +; if key is absent the default is Portrait +; Orientation=Portrait + +; Scale: the default scaling of output in percent +; if key is absent the default is 100 +; Scale=100 + +; MarginAdjust: the default adjustment to driver margins in 1/100 mm +; MarginAdjust contains corrections for the driver defined margins +; the values are comma separated +; the order is: left,right,top,bottom +; if key is absent the default is 0,0,0,0 +; MarginAdjust=0,0,0,0 + +; ColorDepth: the default colordepth of the device in bits +; possible values: 1, 8, 24 +; if key is absent the default is 24 +; ColorDepth=24 + +; ColorDevice: the default setting whether the device is color capable +; possible values: 0: driver setting, -1: grey scale, 1: color +; if key is absent the default is 0 +; ColorDepth=0 + +; PSLevel: the default setting of the PostScript level of the output +; possible values: 0: driver setting, 1: level 1, 2: level2 +; if key is absent the default is 0 +; PSLevel=0 + +; PPD_PageSize: the default page size to use. If a specific printer does +; not support this page size its default is used instead. +; possible values: A0, A1, A2, A3, A4, A5, A6, B4, B5, B6, +; Legal, Letter, Executive, Statement, Tabloid, +; Ledger, AnsiC, AnsiD, ARCHA, ARCHB, ARCHC, +; ARCHD, ARCHE, EnvMonarch, EnvC4, EnvC5, EnvC6, +; Env10, EnvC65, Folio +; if key is absent the default value is driver specific +; PPD_PageSize=A4 + + +[Generic Printer] +; for every printer a group with at least the keys +; "Printer" and "Command" is required + +; Printer: contains the base name of the PPD and the Printer name separated by / +Printer=SGENPRT/Generic Printer + +; DefaultPrinter: marks the default printer +DefaultPrinter=1 + +; Location: a user readable string that will be shown in the print dialog +Location= + +; Comment: a user readable string that will be shown in the print dialog +Comment= + +; Command: a command line that accepts PostScript as standard input (pipe) +; note: a shell will be started for the command +Command= + +; QuickCommand: a command line that accepts PostScript as standard input (pipe) +; this command line will be used instead of the command line given in the +; "Command" key, if the user presses the direct print button. In this case +; no print dialog should be shown, neither from the printing application nor +; from the command line (example "kprinter --nodialog --stdin") +; note: a shell will be started for the command +;QuickCommand= + +; Features: a string containing additional comma separated properties of a printer +; currently valid properties: +; fax for a Fax printer queue +; pdf=<dir> for a PDF printer where <dir> is the base directory for output files +; external_dialog to notify that the print command of a printer will show a dialog +; and therefore the application should not show its own dialog. +;Features= diff --git a/vcl/unx/generic/printer/cpdmgr.cxx b/vcl/unx/generic/printer/cpdmgr.cxx new file mode 100644 index 000000000..e8b22111d --- /dev/null +++ b/vcl/unx/generic/printer/cpdmgr.cxx @@ -0,0 +1,757 @@ +/* -*- 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 <cstddef> +#include <unistd.h> + +#include <unx/cpdmgr.hxx> + +#include <osl/file.h> +#include <osl/thread.h> + +#include <rtl/ustrbuf.hxx> +#include <sal/log.hxx> + +#include <config_dbus.h> +#include <config_gio.h> + +using namespace psp; +using namespace osl; + +#if ENABLE_DBUS && ENABLE_GIO +// Function to execute when name is acquired on the bus +void CPDManager::onNameAcquired (GDBusConnection *connection, + const gchar *, + gpointer user_data) +{ + gchar* contents; + // Get Interface for introspection + if (!g_file_get_contents (FRONTEND_INTERFACE, &contents, nullptr, nullptr)) + return; + + GDBusNodeInfo *introspection_data = g_dbus_node_info_new_for_xml (contents, nullptr); + + g_dbus_connection_register_object (connection, + "/org/libreoffice/PrintDialog", + introspection_data->interfaces[0], + nullptr, + nullptr, /* user_data */ + nullptr, /* user_data_free_func */ + nullptr); /* GError** */ + g_free(contents); + g_dbus_node_info_unref(introspection_data); + + CPDManager* current = static_cast<CPDManager*>(user_data); + std::vector<std::pair<std::string, gchar*>> backends = current->getTempBackends(); + for (auto const& backend : backends) + { + // Get Interface for introspection + if (g_file_get_contents(BACKEND_INTERFACE, &contents, nullptr, nullptr)) + { + introspection_data = g_dbus_node_info_new_for_xml (contents, nullptr); + GDBusProxy *proxy = g_dbus_proxy_new_sync (connection, + G_DBUS_PROXY_FLAGS_NONE, + introspection_data->interfaces[0], + backend.first.c_str(), + backend.second, + "org.openprinting.PrintBackend", + nullptr, + nullptr); + g_assert (proxy != nullptr); + g_dbus_proxy_call(proxy, "ActivateBackend", + nullptr, + G_DBUS_CALL_FLAGS_NONE, + -1, nullptr, nullptr, nullptr); + + g_free(contents); + g_object_unref(proxy); + g_dbus_node_info_unref(introspection_data); + } + g_free(backend.second); + } +} + +void CPDManager::onNameLost (GDBusConnection *, + const gchar *name, + gpointer) +{ + g_message("Name Lost: %s", name); +} + +void CPDManager::printerAdded (GDBusConnection *connection, + const gchar *sender_name, + const gchar *object_path, + const gchar *interface_name, + const gchar *, + GVariant *parameters, + gpointer user_data) +{ + CPDManager* current = static_cast<CPDManager*>(user_data); + GDBusProxy *proxy; + proxy = current->getProxy(sender_name); + if (proxy == nullptr) { + gchar* contents; + + // Get Interface for introspection + if (g_file_get_contents ("/usr/share/dbus-1/interfaces/org.openprinting.Backend.xml", &contents, nullptr, nullptr)) { + GDBusNodeInfo *introspection_data = g_dbus_node_info_new_for_xml (contents, nullptr); + proxy = g_dbus_proxy_new_sync (connection, + G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS, + introspection_data->interfaces[0], + sender_name, + object_path, + interface_name, + nullptr, + nullptr); + + g_free(contents); + g_dbus_node_info_unref(introspection_data); + std::pair<std::string, GDBusProxy *> new_backend (sender_name, proxy); + current->addBackend(std::move(new_backend)); + } + } + CPDPrinter *pDest = static_cast<CPDPrinter *>(malloc(sizeof(CPDPrinter))); + pDest->backend = proxy; + g_variant_get (parameters, "(sssssbss)", &(pDest->id), &(pDest->name), &(pDest->info), &(pDest->location), &(pDest->make_and_model), &(pDest->is_accepting_jobs), &(pDest->printer_state), &(pDest->backend_name)); + std::stringstream printerName; + printerName << pDest->name << ", " << pDest->backend_name; + std::stringstream uniqueName; + uniqueName << pDest->id << ", " << pDest->backend_name; + rtl_TextEncoding aEncoding = osl_getThreadTextEncoding(); + OUString aPrinterName = OStringToOUString( printerName.str().c_str(), aEncoding ); + OUString aUniqueName = OStringToOUString( uniqueName.str().c_str(), aEncoding ); + current->addNewPrinter(aPrinterName, aUniqueName, pDest); +} + +void CPDManager::printerRemoved (GDBusConnection *, + const gchar *, + const gchar *, + const gchar *, + const gchar *, + GVariant *parameters, + gpointer user_data) +{ + // TODO: Remove every data linked to this particular printer. + CPDManager* pManager = static_cast<CPDManager*>(user_data); + char* id; + char* backend_name; + g_variant_get (parameters, "(ss)", &id, &backend_name); + std::stringstream uniqueName; + uniqueName << id << ", " << backend_name; + rtl_TextEncoding aEncoding = osl_getThreadTextEncoding(); + OUString aUniqueName = OStringToOUString( uniqueName.str().c_str(), aEncoding ); + std::unordered_map<OUString, CPDPrinter *>::iterator it = pManager->m_aCPDDestMap.find( aUniqueName ); + if (it == pManager->m_aCPDDestMap.end()) { + SAL_WARN("vcl.unx.print", "CPD trying to remove non-existent printer from list"); + return; + } + pManager->m_aCPDDestMap.erase(it); + std::unordered_map<OUString, Printer>::iterator printersIt = pManager->m_aPrinters.find( aUniqueName ); + if (printersIt == pManager->m_aPrinters.end()) { + SAL_WARN("vcl.unx.print", "CPD trying to remove non-existent printer from m_aPrinters"); + return; + } + pManager->m_aPrinters.erase(printersIt); +} + +GDBusProxy* CPDManager::getProxy(const std::string& target) +{ + std::unordered_map<std::string, GDBusProxy *>::const_iterator it = m_pBackends.find(target); + if (it == m_pBackends.end()) { + return nullptr; + } + return it->second; +} + +void CPDManager::addBackend(std::pair<std::string, GDBusProxy *> pair) { + m_pBackends.insert(pair); +} + +void CPDManager::addTempBackend(const std::pair<std::string, gchar*>& pair) +{ + m_tBackends.push_back(pair); +} + +std::vector<std::pair<std::string, gchar*>> const & CPDManager::getTempBackends() const { + return m_tBackends; +} + +void CPDManager::addNewPrinter(const OUString& aPrinterName, const OUString& aUniqueName, CPDPrinter *pDest) { + m_aCPDDestMap[aUniqueName] = pDest; + bool bSetToGlobalDefaults = m_aPrinters.find( aUniqueName ) == m_aPrinters.end(); + Printer aPrinter = m_aPrinters[ aUniqueName ]; + if( bSetToGlobalDefaults ) + aPrinter.m_aInfo = m_aGlobalDefaults; + aPrinter.m_aInfo.m_aPrinterName = aPrinterName; + + // TODO: I don't know how this should work when we have multiple + // sources with multiple possible defaults for each + // if( pDest->is_default ) + // m_aDefaultPrinter = aPrinterName; + + rtl_TextEncoding aEncoding = osl_getThreadTextEncoding(); + aPrinter.m_aInfo.m_aComment = OStringToOUString(pDest->info, aEncoding); + aPrinter.m_aInfo.m_aLocation = OStringToOUString(pDest->location, aEncoding); + // note: the parser that goes with the PrinterInfo + // is created implicitly by the JobData::operator=() + // when it detects the NULL ptr m_pParser. + // if we wanted to fill in the parser here this + // would mean we'd have to send a dbus message for each and + // every printer - which would be really bad runtime + // behaviour + aPrinter.m_aInfo.m_pParser = nullptr; + aPrinter.m_aInfo.m_aContext.setParser( nullptr ); + std::unordered_map< OUString, PPDContext >::const_iterator c_it = m_aDefaultContexts.find( aUniqueName ); + if( c_it != m_aDefaultContexts.end() ) + { + aPrinter.m_aInfo.m_pParser = c_it->second.getParser(); + aPrinter.m_aInfo.m_aContext = c_it->second; + } + aPrinter.m_aInfo.setDefaultBackend(true); + aPrinter.m_aInfo.m_aDriverName = "CPD:" + aUniqueName; + m_aPrinters[ aUniqueName ] = aPrinter; +} +#endif + +/* + * CPDManager class + */ + +CPDManager* CPDManager::tryLoadCPD() +{ + CPDManager* pManager = nullptr; +#if ENABLE_DBUS && ENABLE_GIO + static const char* pEnv = getenv("SAL_DISABLE_CPD"); + + if (!pEnv || !*pEnv) { + // interface description XML files are needed in 'onNameAcquired()' + if (!g_file_test(FRONTEND_INTERFACE, G_FILE_TEST_IS_REGULAR) || + !g_file_test(BACKEND_INTERFACE, G_FILE_TEST_IS_REGULAR)) { + return nullptr; + } + + GDir *dir; + const gchar *filename; + dir = g_dir_open(BACKEND_DIR, 0, nullptr); + if (dir != nullptr) { + while ((filename = g_dir_read_name(dir))) { + if (pManager == nullptr) { + pManager = new CPDManager(); + } + gchar* contents; + std::stringstream filepath; + filepath << BACKEND_DIR << '/' << filename; + if (g_file_get_contents(filepath.str().c_str(), &contents, nullptr, nullptr)) + { + std::pair<std::string, gchar*> new_tbackend (filename, contents); + pManager->addTempBackend(new_tbackend); + } + } + g_dir_close(dir); + } + } +#endif + return pManager; +} + +CPDManager::CPDManager() : + PrinterInfoManager( PrinterInfoManager::Type::CPD ) +{ +#if ENABLE_DBUS && ENABLE_GIO + // Get Destinations number and pointers + GError *error = nullptr; + m_pConnection = g_bus_get_sync (G_BUS_TYPE_SESSION, nullptr, &error); + g_assert_no_error (error); +#endif +} + +CPDManager::~CPDManager() +{ +#if ENABLE_DBUS && ENABLE_GIO + g_dbus_connection_emit_signal (m_pConnection, + nullptr, + "/org/libreoffice/PrintDialog", + "org.openprinting.PrintFrontend", + "StopListing", + nullptr, + nullptr); + g_dbus_connection_flush_sync (m_pConnection, + nullptr, + nullptr); + g_dbus_connection_close_sync (m_pConnection, + nullptr, + nullptr); + for (auto const& backend : m_pBackends) + { + g_object_unref(backend.second); + } + for (auto const& backend : m_aCPDDestMap) + { + free(backend.second); + } +#endif +} + + +const PPDParser* CPDManager::createCPDParser( const OUString& rPrinter ) +{ + const PPDParser* pNewParser = nullptr; +#if ENABLE_DBUS && ENABLE_GIO + OUString aPrinter; + + if( rPrinter.startsWith("CPD:") ) + aPrinter = rPrinter.copy( 4 ); + else + aPrinter = rPrinter; + + std::unordered_map< OUString, CPDPrinter * >::iterator dest_it = + m_aCPDDestMap.find( aPrinter ); + + if( dest_it != m_aCPDDestMap.end() ) + { + CPDPrinter* pDest = dest_it->second; + GVariant* ret = nullptr; + GError* error = nullptr; + ret = g_dbus_proxy_call_sync (pDest->backend, "GetAllOptions", + g_variant_new("(s)", (pDest->id)), + G_DBUS_CALL_FLAGS_NONE, + -1, nullptr, &error); + if (ret != nullptr && error == nullptr) + { + // TODO: These keys need to be redefined to preserve usage across libreoffice + // InputSlot - media-col.media-source? + // Font - not needed now as it is required only for ps and we are using pdf + // Dial? - for FAX (need to look up PWG spec) + + int num_attribute; + GVariantIter *iter_attr, *iter_supported_values; + g_variant_get (ret, "(ia(ssia(s)))", &num_attribute, &iter_attr); + rtl_TextEncoding aEncoding = osl_getThreadTextEncoding(); + PPDKey *pKey = nullptr; + OUString aValueName; + PPDValue* pValue; + std::vector<PPDKey*> keys; + std::vector<OUString> default_values; + for (int i = 0; i < num_attribute; i++) { + char *name, *default_value; + int num_supported_values; + g_variant_iter_loop(iter_attr, "(ssia(s))", + &name, &default_value, + &num_supported_values, &iter_supported_values); + OUString aOptionName = OStringToOUString( name, aEncoding ); + OUString aDefaultValue = OStringToOUString( default_value, aEncoding ); + if (aOptionName == "sides") { + // Duplex key is used throughout for checking Duplex Support + aOptionName = OUString("Duplex"); + } else if (aOptionName == "printer-resolution") { + // Resolution key is used in places + aOptionName = OUString("Resolution"); + } else if (aOptionName == "media") { + // PageSize key is used in many places + aOptionName = OUString("PageSize"); + } + default_values.push_back(aDefaultValue); + pKey = new PPDKey( aOptionName ); + + // If number of values are 0, this is not settable via UI + if (num_supported_values > 0 && aDefaultValue != "NA") + pKey->m_bUIOption = true; + + bool bDefaultFound = false; + + for (int j = 0; j < num_supported_values; j++) { + char* value; + g_variant_iter_loop(iter_supported_values, "(s)", &value); + aValueName = OStringToOUString( value, aEncoding ); + if (aOptionName == "Duplex") { + // Duplex key matches against very specific Values + if (aValueName == "one-sided") { + aValueName = OUString("None"); + } else if (aValueName == "two-sided-long-edge") { + aValueName = OUString("DuplexNoTumble"); + } else if (aValueName == "two-sided-short-edge") { + aValueName = OUString("DuplexTumble"); + } + } + + pValue = pKey->insertValue( aValueName, eQuoted ); + if( ! pValue ) + continue; + pValue->m_aValue = aValueName; + + if (aValueName.equals(aDefaultValue)) { + pKey->m_pDefaultValue = pValue; + bDefaultFound = true; + } + + } + // This could be done to ensure default values also appear as options: + if (!bDefaultFound && pKey->m_bUIOption) { + // pValue = pKey->insertValue( aDefaultValue, eQuoted ); + // if( pValue ) + // pValue->m_aValue = aDefaultValue; + } + keys.emplace_back(pKey); + } + + pKey = new PPDKey("ModelName"); + aValueName = OStringToOUString( "", aEncoding ); + pValue = pKey->insertValue( aValueName, eQuoted ); + if( pValue ) + pValue->m_aValue = aValueName; + pKey->m_pDefaultValue = pValue; + keys.emplace_back(pKey); + + pKey = new PPDKey("NickName"); + aValueName = OStringToOUString( pDest->name, aEncoding ); + pValue = pKey->insertValue( aValueName, eQuoted ); + if( pValue ) + pValue->m_aValue = aValueName; + pKey->m_pDefaultValue = pValue; + keys.emplace_back(pKey); + + pNewParser = new PPDParser(aPrinter, keys); + PrinterInfo& rInfo = m_aPrinters[ aPrinter ].m_aInfo; + PPDContext& rContext = m_aDefaultContexts[ aPrinter ]; + rContext.setParser( pNewParser ); + setDefaultPaper( rContext ); + std::vector<OUString>::iterator defit = default_values.begin(); + for (auto const& key : keys) + { + const PPDValue* p1Value = key->getValue( *defit ); + if( p1Value ) + { + if( p1Value != key->getDefaultValue() ) + { + rContext.setValue( key, p1Value, true ); + SAL_INFO("vcl.unx.print", "key " << pKey->getKey() << " is set to " << *defit); + } + else + SAL_INFO("vcl.unx.print", "key " << pKey->getKey() << " is defaulted to " << *defit); + } + ++defit; + } + + rInfo.m_pParser = pNewParser; + rInfo.m_aContext = rContext; + g_variant_unref(ret); + } + else + { + g_clear_error(&error); + SAL_INFO("vcl.unx.print", "CPD GetAllOptions failed, falling back to generic driver"); + } + } + else + SAL_INFO("vcl.unx.print", "no dest found for printer " << aPrinter); + + if( ! pNewParser ) + { + // get the default PPD + pNewParser = PPDParser::getParser( "SGENPRT" ); + SAL_WARN("vcl.unx.print", "Parsing default SGENPRT PPD" ); + + PrinterInfo& rInfo = m_aPrinters[ aPrinter ].m_aInfo; + + rInfo.m_pParser = pNewParser; + rInfo.m_aContext.setParser( pNewParser ); + } +#else + (void)rPrinter; +#endif + return pNewParser; +} + + +void CPDManager::initialize() +{ + // get normal printers, clear printer list + PrinterInfoManager::initialize(); +#if ENABLE_DBUS && ENABLE_GIO + g_bus_own_name_on_connection (m_pConnection, + "org.libreoffice.print-dialog", + G_BUS_NAME_OWNER_FLAGS_NONE, + onNameAcquired, + onNameLost, + this, + nullptr); + + g_dbus_connection_signal_subscribe (m_pConnection, // DBus Connection + nullptr, // Sender Name + "org.openprinting.PrintBackend", // Sender Interface + "PrinterAdded", // Signal Name + nullptr, // Object Path + nullptr, // arg0 behaviour + G_DBUS_SIGNAL_FLAGS_NONE, // Signal Flags + printerAdded, // Callback Function + this, + nullptr); + g_dbus_connection_signal_subscribe (m_pConnection, // DBus Connection + nullptr, // Sender Name + "org.openprinting.PrintBackend", // Sender Interface + "PrinterRemoved", // Signal Name + nullptr, // Object Path + nullptr, // arg0 behaviour + G_DBUS_SIGNAL_FLAGS_NONE, // Signal Flags + printerRemoved, // Callback Function + this, + nullptr); + + // remove everything that is not a CUPS printer and not + // a special purpose printer (PDF, Fax) + std::unordered_map< OUString, Printer >::iterator it = m_aPrinters.begin(); + while (it != m_aPrinters.end()) + { + if( m_aCPDDestMap.find( it->first ) != m_aCPDDestMap.end() ) + { + ++it; + continue; + } + + if( !it->second.m_aInfo.m_aFeatures.isEmpty() ) + { + ++it; + continue; + } + it = m_aPrinters.erase(it); + } +#endif +} + +void CPDManager::setupJobContextData( JobData& rData ) +{ +#if ENABLE_DBUS && ENABLE_GIO + std::unordered_map<OUString, CPDPrinter *>::iterator dest_it = + m_aCPDDestMap.find( rData.m_aPrinterName ); + + if( dest_it == m_aCPDDestMap.end() ) + return PrinterInfoManager::setupJobContextData( rData ); + + std::unordered_map< OUString, Printer >::iterator p_it = + m_aPrinters.find( rData.m_aPrinterName ); + if( p_it == m_aPrinters.end() ) // huh ? + { + SAL_WARN("vcl.unx.print", "CPD printer list in disorder, " + "no dest for printer " << rData.m_aPrinterName); + return; + } + + if( p_it->second.m_aInfo.m_pParser == nullptr ) + { + // in turn calls createCPDParser + // which updates the printer info + p_it->second.m_aInfo.m_pParser = PPDParser::getParser( p_it->second.m_aInfo.m_aDriverName ); + } + if( p_it->second.m_aInfo.m_aContext.getParser() == nullptr ) + { + OUString aPrinter; + if( p_it->second.m_aInfo.m_aDriverName.startsWith("CPD:") ) + aPrinter = p_it->second.m_aInfo.m_aDriverName.copy( 4 ); + else + aPrinter = p_it->second.m_aInfo.m_aDriverName; + + p_it->second.m_aInfo.m_aContext = m_aDefaultContexts[ aPrinter ]; + } + + rData.m_pParser = p_it->second.m_aInfo.m_pParser; + rData.m_aContext = p_it->second.m_aInfo.m_aContext; +#else + (void)rData; +#endif +} + +FILE* CPDManager::startSpool( const OUString& rPrintername, bool bQuickCommand ) +{ +#if ENABLE_DBUS && ENABLE_GIO + SAL_INFO( "vcl.unx.print", "startSpool: " << rPrintername << " " << (bQuickCommand ? "true" : "false") ); + if( m_aCPDDestMap.find( rPrintername ) == m_aCPDDestMap.end() ) + { + SAL_INFO( "vcl.unx.print", "defer to PrinterInfoManager::startSpool" ); + return PrinterInfoManager::startSpool( rPrintername, bQuickCommand ); + } + OUString aTmpURL, aTmpFile; + osl_createTempFile( nullptr, nullptr, &aTmpURL.pData ); + osl_getSystemPathFromFileURL( aTmpURL.pData, &aTmpFile.pData ); + OString aSysFile = OUStringToOString( aTmpFile, osl_getThreadTextEncoding() ); + FILE* fp = fopen( aSysFile.getStr(), "w" ); + if( fp ) + m_aSpoolFiles[fp] = aSysFile; + + return fp; +#else + (void)rPrintername; + (void)bQuickCommand; + return nullptr; +#endif +} + +#if ENABLE_DBUS && ENABLE_GIO +void CPDManager::getOptionsFromDocumentSetup( const JobData& rJob, bool bBanner, const OString& rJobName, int& rNumOptions, GVariant **arr ) +{ + GVariantBuilder *builder; + builder = g_variant_builder_new(G_VARIANT_TYPE("a(ss)")); + g_variant_builder_add(builder, "(ss)", "job-name", rJobName.getStr()); + if( rJob.m_pParser == rJob.m_aContext.getParser() && rJob.m_pParser ) { + std::size_t i; + std::size_t nKeys = rJob.m_aContext.countValuesModified(); + ::std::vector< const PPDKey* > aKeys( nKeys ); + for( i = 0; i < nKeys; i++ ) + aKeys[i] = rJob.m_aContext.getModifiedKey( i ); + for( i = 0; i < nKeys; i++ ) { + const PPDKey* pKey = aKeys[i]; + const PPDValue* pValue = rJob.m_aContext.getValue( pKey ); + OUString sPayLoad; + if (pValue) { + sPayLoad = pValue->m_bCustomOption ? pValue->m_aCustomOption : pValue->m_aOption; + } + if (!sPayLoad.isEmpty()) { + OString aKey = OUStringToOString( pKey->getKey(), RTL_TEXTENCODING_ASCII_US ); + OString aValue = OUStringToOString( sPayLoad, RTL_TEXTENCODING_ASCII_US ); + if (aKey.equals("Duplex")) { + aKey = OString("sides"); + } else if (aKey.equals("Resolution")) { + aKey = OString("printer-resolution"); + } else if (aKey.equals("PageSize")) { + aKey = OString("media"); + } + if (aKey.equals("sides")) { + if (aValue.equals("None")) { + aValue = OString("one-sided"); + } else if (aValue.equals("DuplexNoTumble")) { + aValue = OString("two-sided-long-edge"); + } else if (aValue.equals("DuplexTumble")) { + aValue = OString("two-sided-short-edge"); + } + } + g_variant_builder_add(builder, "(ss)", aKey.getStr(), aValue.getStr()); + } + } + } + if( rJob.m_nPDFDevice > 0 && rJob.m_nCopies > 1 ) + { + OString aVal( OString::number( rJob.m_nCopies ) ); + g_variant_builder_add(builder, "(ss)", "copies", aVal.getStr()); + rNumOptions++; + // TODO: something for collate + // Maybe this is the equivalent ipp attribute: + if (rJob.m_bCollate) { + g_variant_builder_add(builder, "(ss)", "multiple-document-handling", "separate-documents-collated-copies"); + } else { + g_variant_builder_add(builder, "(ss)", "multiple-document-handling", "separate-documents-uncollated-copies"); + } + rNumOptions++; + } + if( ! bBanner ) + { + g_variant_builder_add(builder, "(ss)", "job-sheets", "none"); + rNumOptions++; + } + if (rJob.m_eOrientation == orientation::Portrait) { + g_variant_builder_add(builder, "(ss)", "orientation-requested", "portrait"); + rNumOptions++; + } else if (rJob.m_eOrientation == orientation::Landscape) { + g_variant_builder_add(builder, "(ss)", "orientation-requested", "landscape"); + rNumOptions++; + } + (*arr) = g_variant_new("a(ss)", builder); + g_variant_builder_unref(builder); +} +#endif + +bool CPDManager::endSpool( const OUString& rPrintername, const OUString& rJobTitle, FILE* pFile, const JobData& rDocumentJobData, bool bBanner, const OUString& rFaxNumber ) +{ + bool success = false; +#if ENABLE_DBUS && ENABLE_GIO + SAL_INFO( "vcl.unx.print", "endSpool: " << rPrintername << "," << rJobTitle << " copy count = " << rDocumentJobData.m_nCopies ); + std::unordered_map< OUString, CPDPrinter * >::iterator dest_it = + m_aCPDDestMap.find( rPrintername ); + if( dest_it == m_aCPDDestMap.end() ) + { + SAL_INFO( "vcl.unx.print", "defer to PrinterInfoManager::endSpool" ); + return PrinterInfoManager::endSpool( rPrintername, rJobTitle, pFile, rDocumentJobData, bBanner, rFaxNumber ); + } + + std::unordered_map< FILE*, OString, FPtrHash >::const_iterator it = m_aSpoolFiles.find( pFile ); + if( it != m_aSpoolFiles.end() ) + { + fclose( pFile ); + rtl_TextEncoding aEnc = osl_getThreadTextEncoding(); + OString sJobName(OUStringToOString(rJobTitle, aEnc)); + if (!rFaxNumber.isEmpty()) + { + sJobName = OUStringToOString(rFaxNumber, aEnc); + } + OString aSysFile = it->second; + CPDPrinter* pDest = dest_it->second; + GVariant* ret; + gint job_id; + int nNumOptions = 0; + GVariant *pArr = nullptr; + getOptionsFromDocumentSetup( rDocumentJobData, bBanner, sJobName, nNumOptions, &pArr ); + ret = g_dbus_proxy_call_sync (pDest->backend, "printFile", + g_variant_new( + "(ssi@a(ss))", + (pDest->id), + aSysFile.getStr(), + nNumOptions, + pArr + ), + G_DBUS_CALL_FLAGS_NONE, + -1, nullptr, nullptr); + g_variant_get (ret, "(i)", &job_id); + if (job_id != -1) { + success = true; + } + g_variant_unref(ret); + unlink( it->second.getStr() ); + m_aSpoolFiles.erase(it); + } +#else + (void)rPrintername; + (void)rJobTitle; + (void)pFile; + (void)rDocumentJobData; + (void)bBanner; + (void)rFaxNumber; +#endif + return success; +} + +bool CPDManager::checkPrintersChanged( bool ) +{ +#if ENABLE_DBUS && ENABLE_GIO + bool bChanged = m_aPrintersChanged; + m_aPrintersChanged = false; + g_dbus_connection_emit_signal (m_pConnection, + nullptr, + "/org/libreoffice/PrintDialog", + "org.openprinting.PrintFrontend", + "RefreshBackend", + nullptr, + nullptr); + return bChanged; +#else + return false; +#endif +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ + diff --git a/vcl/unx/generic/printer/cupsmgr.cxx b/vcl/unx/generic/printer/cupsmgr.cxx new file mode 100644 index 000000000..6acfe1db6 --- /dev/null +++ b/vcl/unx/generic/printer/cupsmgr.cxx @@ -0,0 +1,964 @@ +/* -*- 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 <cups/cups.h> +#include <cups/http.h> +#include <cups/ipp.h> +#include <cups/ppd.h> + +#include <unistd.h> + +#include <unx/cupsmgr.hxx> + +#include <o3tl/string_view.hxx> +#include <osl/thread.h> +#include <osl/file.h> +#include <osl/conditn.hxx> + +#include <rtl/strbuf.hxx> +#include <rtl/ustrbuf.hxx> +#include <sal/log.hxx> + +#include <officecfg/Office/Common.hxx> + +#include <vcl/svapp.hxx> +#include <vcl/weld.hxx> +#include <vcl/window.hxx> + +#include <algorithm> +#include <cstddef> +#include <string_view> + +using namespace psp; +using namespace osl; + +namespace { + +struct GetPPDAttribs +{ + osl::Condition m_aCondition; + OString m_aParameter; + OString m_aResult; + int m_nRefs; + bool* m_pResetRunning; + osl::Mutex* m_pSyncMutex; + + GetPPDAttribs( const char * m_pParameter, + bool* pResetRunning, osl::Mutex* pSyncMutex ) + : m_aParameter( m_pParameter ), + m_pResetRunning( pResetRunning ), + m_pSyncMutex( pSyncMutex ) + { + m_nRefs = 2; + m_aCondition.reset(); + } + + ~GetPPDAttribs() + { + if( !m_aResult.isEmpty() ) + unlink( m_aResult.getStr() ); + } + + void unref() + { + if( --m_nRefs == 0 ) + { + *m_pResetRunning = false; + delete this; + } + } + + void executeCall() + { + // This CUPS method is not at all thread-safe we need + // to dup the pointer to a static buffer it returns ASAP +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + const char* pResult = cupsGetPPD(m_aParameter.getStr()); + OString aResult = pResult ? OString(pResult) : OString(); +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + MutexGuard aGuard( *m_pSyncMutex ); + m_aResult = aResult; + m_aCondition.set(); + unref(); + } + + OString waitResult( TimeValue const *pDelay ) + { + m_pSyncMutex->release(); + + if (m_aCondition.wait( pDelay ) != Condition::result_ok + ) + { + SAL_WARN("vcl.unx.print", + "cupsGetPPD " << m_aParameter << " timed out"); + } + m_pSyncMutex->acquire(); + + OString aRetval = m_aResult; + m_aResult.clear(); + unref(); + + return aRetval; + } +}; + +} + +extern "C" { + static void getPPDWorker(void* pData) + { + osl_setThreadName("CUPSManager getPPDWorker"); + GetPPDAttribs* pAttribs = static_cast<GetPPDAttribs*>(pData); + pAttribs->executeCall(); + } +} + +OString CUPSManager::threadedCupsGetPPD( const char* pPrinter ) +{ + OString aResult; + + m_aGetPPDMutex.acquire(); + // if one thread hangs in cupsGetPPD already, don't start another + if( ! m_bPPDThreadRunning ) + { + m_bPPDThreadRunning = true; + GetPPDAttribs* pAttribs = new GetPPDAttribs( pPrinter, + &m_bPPDThreadRunning, + &m_aGetPPDMutex ); + + oslThread aThread = osl_createThread( getPPDWorker, pAttribs ); + + TimeValue aValue; + aValue.Seconds = 5; + aValue.Nanosec = 0; + + // NOTE: waitResult release and acquires the GetPPD mutex + aResult = pAttribs->waitResult( &aValue ); + osl_destroyThread( aThread ); + } + m_aGetPPDMutex.release(); + + return aResult; +} + +static const char* setPasswordCallback( const char* /*pIn*/ ) +{ + const char* pRet = nullptr; + + PrinterInfoManager& rMgr = PrinterInfoManager::get(); + if( rMgr.getType() == PrinterInfoManager::Type::CUPS ) // sanity check + pRet = static_cast<CUPSManager&>(rMgr).authenticateUser(); + return pRet; +} + +/* + * CUPSManager class + */ + +CUPSManager* CUPSManager::tryLoadCUPS() +{ + CUPSManager* pManager = nullptr; + static const char* pEnv = getenv("SAL_DISABLE_CUPS"); + + if (!pEnv || !*pEnv) + pManager = new CUPSManager(); + return pManager; +} + +extern "C" +{ +static void run_dest_thread_stub( void* pThis ) +{ + osl_setThreadName("CUPSManager cupsGetDests"); + CUPSManager::runDestThread( pThis ); +} +} + +CUPSManager::CUPSManager() : + PrinterInfoManager( PrinterInfoManager::Type::CUPS ), + m_nDests( 0 ), + m_pDests( nullptr ), + m_bNewDests( false ), + m_bPPDThreadRunning( false ) +{ + m_aDestThread = osl_createThread( run_dest_thread_stub, this ); +} + +CUPSManager::~CUPSManager() +{ + if( m_aDestThread ) + { + osl_joinWithThread( m_aDestThread ); + osl_destroyThread( m_aDestThread ); + } + + if (m_nDests && m_pDests) + cupsFreeDests( m_nDests, static_cast<cups_dest_t*>(m_pDests) ); +} + +void CUPSManager::runDestThread( void* pThis ) +{ + static_cast<CUPSManager*>(pThis)->runDests(); +} + +void CUPSManager::runDests() +{ + SAL_INFO("vcl.unx.print", "starting cupsGetDests"); + cups_dest_t* pDests = nullptr; + + // n#722902 - do a fast-failing check for cups working *at all* first + http_t* p_http; +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + if( (p_http=httpConnectEncrypt( + cupsServer(), + ippPort(), + cupsEncryption())) == nullptr ) + return; + + int nDests = cupsGetDests2(p_http, &pDests); + SAL_INFO("vcl.unx.print", "came out of cupsGetDests"); + + osl::MutexGuard aGuard( m_aCUPSMutex ); + m_nDests = nDests; + m_pDests = pDests; + m_bNewDests = true; + SAL_INFO("vcl.unx.print", "finished cupsGetDests"); + + httpClose(p_http); +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif +} + +void CUPSManager::initialize() +{ + // get normal printers, clear printer list + PrinterInfoManager::initialize(); + + // check whether thread has completed + // if not behave like old printing system + osl::MutexGuard aGuard( m_aCUPSMutex ); + + if( ! m_bNewDests ) + return; + + // dest thread has run, clean up + if( m_aDestThread ) + { + osl_joinWithThread( m_aDestThread ); + osl_destroyThread( m_aDestThread ); + m_aDestThread = nullptr; + } + m_bNewDests = false; + + // clear old stuff + m_aCUPSDestMap.clear(); + + if( ! (m_nDests && m_pDests ) ) + return; + + // check for CUPS server(?) > 1.2 + // since there is no API to query, check for options that were + // introduced in dests with 1.2 + // this is needed to check for %%IncludeFeature support + // (#i65684#, #i65491#) + bool bUsePDF = false; + cups_dest_t* pDest = static_cast<cups_dest_t*>(m_pDests); + const char* pOpt = cupsGetOption( "printer-info", + pDest->num_options, + pDest->options ); + if( pOpt ) + { + m_bUseIncludeFeature = true; + bUsePDF = officecfg::Office::Common::Print::Option::Printer::PDFAsStandardPrintJobFormat::get(); + } + + m_aGlobalDefaults.setDefaultBackend(bUsePDF); + + // do not send include JobPatch; CUPS will insert that itself + // TODO: currently unknown which versions of CUPS insert JobPatches + // so currently it is assumed CUPS = don't insert JobPatch files + m_bUseJobPatch = false; + + rtl_TextEncoding aEncoding = osl_getThreadTextEncoding(); + int nPrinter = m_nDests; + + // reset global default PPD options; these are queried on demand from CUPS + m_aGlobalDefaults.m_pParser = nullptr; + m_aGlobalDefaults.m_aContext = PPDContext(); + + // add CUPS printers, should there be a printer + // with the same name as a CUPS printer, overwrite it + while( nPrinter-- ) + { + pDest = static_cast<cups_dest_t*>(m_pDests)+nPrinter; + OUString aPrinterName = OStringToOUString( pDest->name, aEncoding ); + if( pDest->instance && *pDest->instance ) + { + aPrinterName += "/" + + OStringToOUString( pDest->instance, aEncoding ); + } + + // initialize printer with possible configuration from psprint.conf + bool bSetToGlobalDefaults = m_aPrinters.find( aPrinterName ) == m_aPrinters.end(); + Printer aPrinter = m_aPrinters[ aPrinterName ]; + if( bSetToGlobalDefaults ) + aPrinter.m_aInfo = m_aGlobalDefaults; + aPrinter.m_aInfo.m_aPrinterName = aPrinterName; + if( pDest->is_default ) + m_aDefaultPrinter = aPrinterName; + + for( int k = 0; k < pDest->num_options; k++ ) + { + if(!strcmp(pDest->options[k].name, "printer-info")) + aPrinter.m_aInfo.m_aComment=OStringToOUString(pDest->options[k].value, aEncoding); + if(!strcmp(pDest->options[k].name, "printer-location")) + aPrinter.m_aInfo.m_aLocation=OStringToOUString(pDest->options[k].value, aEncoding); + if(!strcmp(pDest->options[k].name, "auth-info-required")) + aPrinter.m_aInfo.m_aAuthInfoRequired=OStringToOUString(pDest->options[k].value, aEncoding); + } + + // note: the parser that goes with the PrinterInfo + // is created implicitly by the JobData::operator=() + // when it detects the NULL ptr m_pParser. + // if we wanted to fill in the parser here this + // would mean we'd have to download PPDs for each and + // every printer - which would be really bad runtime + // behaviour + aPrinter.m_aInfo.m_pParser = nullptr; + aPrinter.m_aInfo.m_aContext.setParser( nullptr ); + std::unordered_map< OUString, PPDContext >::const_iterator c_it = m_aDefaultContexts.find( aPrinterName ); + if( c_it != m_aDefaultContexts.end() ) + { + aPrinter.m_aInfo.m_pParser = c_it->second.getParser(); + aPrinter.m_aInfo.m_aContext = c_it->second; + } + aPrinter.m_aInfo.setDefaultBackend(bUsePDF); + aPrinter.m_aInfo.m_aDriverName = "CUPS:" + aPrinterName; + + m_aPrinters[ aPrinter.m_aInfo.m_aPrinterName ] = aPrinter; + m_aCUPSDestMap[ aPrinter.m_aInfo.m_aPrinterName ] = nPrinter; + } + + // remove everything that is not a CUPS printer and not + // a special purpose printer (PDF, Fax) + std::unordered_map< OUString, Printer >::iterator it = m_aPrinters.begin(); + while(it != m_aPrinters.end()) + { + if( m_aCUPSDestMap.find( it->first ) != m_aCUPSDestMap.end() ) + { + ++it; + continue; + } + + if( !it->second.m_aInfo.m_aFeatures.isEmpty() ) + { + ++it; + continue; + } + it = m_aPrinters.erase(it); + } + + cupsSetPasswordCB( setPasswordCallback ); +} + +static void updatePrinterContextInfo( ppd_group_t* pPPDGroup, PPDContext& rContext ) +{ + rtl_TextEncoding aEncoding = osl_getThreadTextEncoding(); + for( int i = 0; i < pPPDGroup->num_options; i++ ) + { + ppd_option_t* pOption = pPPDGroup->options + i; + for( int n = 0; n < pOption->num_choices; n++ ) + { + ppd_choice_t* pChoice = pOption->choices + n; + if( pChoice->marked ) + { + const PPDKey* pKey = rContext.getParser()->getKey( OStringToOUString( pOption->keyword, aEncoding ) ); + if( pKey ) + { + const PPDValue* pValue = pKey->getValue( OStringToOUString( pChoice->choice, aEncoding ) ); + if( pValue ) + { + if( pValue != pKey->getDefaultValue() ) + { + rContext.setValue( pKey, pValue, true ); + SAL_INFO("vcl.unx.print", "key " << pOption->keyword << " is set to " << pChoice->choice); + + } + else + SAL_INFO("vcl.unx.print", "key " << pOption->keyword << " is defaulted to " << pChoice->choice); + } + else + SAL_INFO("vcl.unx.print", "caution: value " << pChoice->choice << " not found in key " << pOption->keyword); + } + else + SAL_INFO("vcl.unx.print", "caution: key " << pOption->keyword << " not found in parser"); + } + } + } + + // recurse through subgroups + for( int g = 0; g < pPPDGroup->num_subgroups; g++ ) + { + updatePrinterContextInfo( pPPDGroup->subgroups + g, rContext ); + } +} + +const PPDParser* CUPSManager::createCUPSParser( const OUString& rPrinter ) +{ + const PPDParser* pNewParser = nullptr; + OUString aPrinter; + + if( rPrinter.startsWith("CUPS:") ) + aPrinter = rPrinter.copy( 5 ); + else + aPrinter = rPrinter; + + if( m_aCUPSMutex.tryToAcquire() ) + { + if (m_nDests && m_pDests) + { + std::unordered_map< OUString, int >::iterator dest_it = + m_aCUPSDestMap.find( aPrinter ); + if( dest_it != m_aCUPSDestMap.end() ) + { + cups_dest_t* pDest = static_cast<cups_dest_t*>(m_pDests) + dest_it->second; + OString aPPDFile = threadedCupsGetPPD( pDest->name ); + SAL_INFO("vcl.unx.print", + "PPD for " << aPrinter << " is " << aPPDFile); + if( !aPPDFile.isEmpty() ) + { + rtl_TextEncoding aEncoding = osl_getThreadTextEncoding(); + OUString aFileName( OStringToOUString( aPPDFile, aEncoding ) ); + // update the printer info with context information +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + ppd_file_t* pPPD = ppdOpenFile( aPPDFile.getStr() ); +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + if( pPPD ) + { + // create the new parser + PPDParser* pCUPSParser = new PPDParser( aFileName ); + pCUPSParser->m_aFile = rPrinter; + pNewParser = pCUPSParser; + +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + /*int nConflicts =*/ cupsMarkOptions( pPPD, pDest->num_options, pDest->options ); +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + SAL_INFO("vcl.unx.print", "processing the following options for printer " << pDest->name << " (instance " << (pDest->instance == nullptr ? "null" : pDest->instance) << "):"); + for( int k = 0; k < pDest->num_options; k++ ) + SAL_INFO("vcl.unx.print", + " \"" << pDest->options[k].name << + "\" = \"" << pDest->options[k].value << "\""); + PrinterInfo& rInfo = m_aPrinters[ aPrinter ].m_aInfo; + + // remember the default context for later use + PPDContext& rContext = m_aDefaultContexts[ aPrinter ]; + rContext.setParser( pNewParser ); + // set system default paper; printer CUPS PPD options + // may overwrite it + setDefaultPaper( rContext ); + for( int i = 0; i < pPPD->num_groups; i++ ) + updatePrinterContextInfo( pPPD->groups + i, rContext ); + + rInfo.m_pParser = pNewParser; + rInfo.m_aContext = rContext; + + // clean up the mess +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + ppdClose( pPPD ); +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + + } + else + SAL_INFO("vcl.unx.print", "ppdOpenFile failed, falling back to generic driver"); + + // remove temporary PPD file + if (!getenv("SAL_CUPS_PPD_RETAIN_TMP")) + unlink( aPPDFile.getStr() ); + } + else + SAL_INFO("vcl.unx.print", "cupsGetPPD failed, falling back to generic driver"); + } + else + SAL_INFO("vcl.unx.print", "no dest found for printer " << aPrinter); + } + m_aCUPSMutex.release(); + } + else + SAL_WARN("vcl.unx.print", "could not acquire CUPS mutex !!!" ); + + if( ! pNewParser ) + { + // get the default PPD + pNewParser = PPDParser::getParser( "SGENPRT" ); + SAL_INFO("vcl.unx.print", "Parsing default SGENPRT PPD" ); + + PrinterInfo& rInfo = m_aPrinters[ aPrinter ].m_aInfo; + + rInfo.m_pParser = pNewParser; + rInfo.m_aContext.setParser( pNewParser ); + } + + return pNewParser; +} + +void CUPSManager::setupJobContextData( JobData& rData ) +{ + std::unordered_map< OUString, int >::iterator dest_it = + m_aCUPSDestMap.find( rData.m_aPrinterName ); + + if( dest_it == m_aCUPSDestMap.end() ) + return PrinterInfoManager::setupJobContextData( rData ); + + std::unordered_map< OUString, Printer >::iterator p_it = + m_aPrinters.find( rData.m_aPrinterName ); + if( p_it == m_aPrinters.end() ) // huh ? + { + SAL_WARN("vcl.unx.print", "CUPS printer list in disorder, " + "no dest for printer " << rData.m_aPrinterName); + return; + } + + if( p_it->second.m_aInfo.m_pParser == nullptr ) + { + // in turn calls createCUPSParser + // which updates the printer info + p_it->second.m_aInfo.m_pParser = PPDParser::getParser( p_it->second.m_aInfo.m_aDriverName ); + } + if( p_it->second.m_aInfo.m_aContext.getParser() == nullptr ) + { + OUString aPrinter; + if( p_it->second.m_aInfo.m_aDriverName.startsWith("CUPS:") ) + aPrinter = p_it->second.m_aInfo.m_aDriverName.copy( 5 ); + else + aPrinter = p_it->second.m_aInfo.m_aDriverName; + + p_it->second.m_aInfo.m_aContext = m_aDefaultContexts[ aPrinter ]; + } + + rData.m_pParser = p_it->second.m_aInfo.m_pParser; + rData.m_aContext = p_it->second.m_aInfo.m_aContext; +} + +FILE* CUPSManager::startSpool( const OUString& rPrintername, bool bQuickCommand ) +{ + SAL_INFO( "vcl.unx.print", "startSpool: " << rPrintername << " " << (bQuickCommand ? "true" : "false") ); + + if( m_aCUPSDestMap.find( rPrintername ) == m_aCUPSDestMap.end() ) + { + SAL_INFO( "vcl.unx.print", "defer to PrinterInfoManager::startSpool" ); + return PrinterInfoManager::startSpool( rPrintername, bQuickCommand ); + } + + OUString aTmpURL, aTmpFile; + osl_createTempFile( nullptr, nullptr, &aTmpURL.pData ); + osl_getSystemPathFromFileURL( aTmpURL.pData, &aTmpFile.pData ); + OString aSysFile = OUStringToOString( aTmpFile, osl_getThreadTextEncoding() ); + FILE* fp = fopen( aSysFile.getStr(), "w" ); + if( fp ) + m_aSpoolFiles[fp] = aSysFile; + + return fp; +} + +namespace { + +struct less_ppd_key +{ + bool operator()(const PPDKey* left, const PPDKey* right) + { return left->getOrderDependency() < right->getOrderDependency(); } +}; + +} + +void CUPSManager::getOptionsFromDocumentSetup( const JobData& rJob, bool bBanner, int& rNumOptions, void** rOptions ) +{ + rNumOptions = 0; + *rOptions = nullptr; + + // emit features ordered to OrderDependency + // ignore features that are set to default + + // sanity check + if( rJob.m_pParser == rJob.m_aContext.getParser() && rJob.m_pParser ) + { + std::size_t i; + std::size_t nKeys = rJob.m_aContext.countValuesModified(); + ::std::vector< const PPDKey* > aKeys( nKeys ); + for( i = 0; i < nKeys; i++ ) + aKeys[i] = rJob.m_aContext.getModifiedKey( i ); + ::std::sort( aKeys.begin(), aKeys.end(), less_ppd_key() ); + + for( i = 0; i < nKeys; i++ ) + { + const PPDKey* pKey = aKeys[i]; + const PPDValue* pValue = rJob.m_aContext.getValue( pKey ); + OUString sPayLoad; + if (pValue && pValue->m_eType == eInvocation) + { + sPayLoad = pValue->m_bCustomOption ? pValue->m_aCustomOption : pValue->m_aOption; + } + + if (!sPayLoad.isEmpty()) + { + OString aKey = OUStringToOString( pKey->getKey(), RTL_TEXTENCODING_ASCII_US ); + OString aValue = OUStringToOString( sPayLoad, RTL_TEXTENCODING_ASCII_US ); + rNumOptions = cupsAddOption( aKey.getStr(), aValue.getStr(), rNumOptions, reinterpret_cast<cups_option_t**>(rOptions) ); + } + } + } + + if( rJob.m_nPDFDevice > 0 && rJob.m_nCopies > 1 ) + { + OString aVal( OString::number( rJob.m_nCopies ) ); + rNumOptions = cupsAddOption( "copies", aVal.getStr(), rNumOptions, reinterpret_cast<cups_option_t**>(rOptions) ); + aVal = OString::boolean(rJob.m_bCollate); + rNumOptions = cupsAddOption( "collate", aVal.getStr(), rNumOptions, reinterpret_cast<cups_option_t**>(rOptions) ); + } + if( ! bBanner ) + { + rNumOptions = cupsAddOption( "job-sheets", "none", rNumOptions, reinterpret_cast<cups_option_t**>(rOptions) ); + } +} + +namespace +{ + class RTSPWDialog : public weld::GenericDialogController + { + std::unique_ptr<weld::Label> m_xText; + std::unique_ptr<weld::Label> m_xDomainLabel; + std::unique_ptr<weld::Entry> m_xDomainEdit; + std::unique_ptr<weld::Label> m_xUserLabel; + std::unique_ptr<weld::Entry> m_xUserEdit; + std::unique_ptr<weld::Label> m_xPassLabel; + std::unique_ptr<weld::Entry> m_xPassEdit; + + public: + RTSPWDialog(weld::Window* pParent, std::string_view rServer, std::string_view rUserName); + + OString getDomain() const + { + return OUStringToOString( m_xDomainEdit->get_text(), osl_getThreadTextEncoding() ); + } + + OString getUserName() const + { + return OUStringToOString( m_xUserEdit->get_text(), osl_getThreadTextEncoding() ); + } + + OString getPassword() const + { + return OUStringToOString( m_xPassEdit->get_text(), osl_getThreadTextEncoding() ); + } + + void SetDomainVisible(bool bShow) + { + m_xDomainLabel->set_visible(bShow); + m_xDomainEdit->set_visible(bShow); + } + + void SetUserVisible(bool bShow) + { + m_xUserLabel->set_visible(bShow); + m_xUserEdit->set_visible(bShow); + } + + void SetPassVisible(bool bShow) + { + m_xPassLabel->set_visible(bShow); + m_xPassEdit->set_visible(bShow); + } + }; + + RTSPWDialog::RTSPWDialog(weld::Window* pParent, std::string_view rServer, std::string_view rUserName) + : GenericDialogController(pParent, "vcl/ui/cupspassworddialog.ui", "CUPSPasswordDialog") + , m_xText(m_xBuilder->weld_label("text")) + , m_xDomainLabel(m_xBuilder->weld_label("label3")) + , m_xDomainEdit(m_xBuilder->weld_entry("domain")) + , m_xUserLabel(m_xBuilder->weld_label("label1")) + , m_xUserEdit(m_xBuilder->weld_entry("user")) + , m_xPassLabel(m_xBuilder->weld_label("label2")) + , m_xPassEdit(m_xBuilder->weld_entry("pass")) + { + OUString aText(m_xText->get_label()); + aText = aText.replaceFirst("%s", OStringToOUString(rServer, osl_getThreadTextEncoding())); + m_xText->set_label(aText); + m_xDomainEdit->set_text("WORKGROUP"); + if (rUserName.empty()) + m_xUserEdit->grab_focus(); + else + { + m_xUserEdit->set_text(OStringToOUString(rUserName, osl_getThreadTextEncoding())); + m_xPassEdit->grab_focus(); + } + } + + bool AuthenticateQuery(std::string_view rServer, OString& rUserName, OString& rPassword) + { + bool bRet = false; + + RTSPWDialog aDialog(Application::GetDefDialogParent(), rServer, rUserName); + if (aDialog.run() == RET_OK) + { + rUserName = aDialog.getUserName(); + rPassword = aDialog.getPassword(); + bRet = true; + } + + return bRet; + } +} + +namespace +{ + OString EscapeCupsOption(const OString& rIn) + { + OStringBuffer sRet; + sal_Int32 nLen = rIn.getLength(); + for (sal_Int32 i = 0; i < nLen; ++i) + { + switch(rIn[i]) + { + case '\\': + case '\'': + case '\"': + case ',': + case ' ': + case '\f': + case '\n': + case '\r': + case '\t': + case '\v': + sRet.append('\\'); + break; + } + sRet.append(rIn[i]); + } + return sRet.makeStringAndClear(); + } +} + +bool CUPSManager::endSpool( const OUString& rPrintername, const OUString& rJobTitle, FILE* pFile, const JobData& rDocumentJobData, bool bBanner, const OUString& rFaxNumber ) +{ + SAL_INFO( "vcl.unx.print", "endSpool: " << rPrintername << "," << rJobTitle << " copy count = " << rDocumentJobData.m_nCopies ); + + int nJobID = 0; + + osl::MutexGuard aGuard( m_aCUPSMutex ); + + std::unordered_map< OUString, int >::iterator dest_it = + m_aCUPSDestMap.find( rPrintername ); + if( dest_it == m_aCUPSDestMap.end() ) + { + SAL_INFO( "vcl.unx.print", "defer to PrinterInfoManager::endSpool" ); + return PrinterInfoManager::endSpool( rPrintername, rJobTitle, pFile, rDocumentJobData, bBanner, rFaxNumber ); + } + + std::unordered_map< FILE*, OString, FPtrHash >::const_iterator it = m_aSpoolFiles.find( pFile ); + if( it != m_aSpoolFiles.end() ) + { + fclose( pFile ); + rtl_TextEncoding aEnc = osl_getThreadTextEncoding(); + + // setup cups options + int nNumOptions = 0; + cups_option_t* pOptions = nullptr; + auto ppOptions = reinterpret_cast<void**>(&pOptions); + getOptionsFromDocumentSetup( rDocumentJobData, bBanner, nNumOptions, ppOptions ); + + PrinterInfo aInfo(getPrinterInfo(rPrintername)); + if (!aInfo.m_aAuthInfoRequired.isEmpty()) + { + bool bDomain(false), bUser(false), bPass(false); + sal_Int32 nIndex = 0; + do + { + std::u16string_view aToken = o3tl::getToken(aInfo.m_aAuthInfoRequired, 0, ',', nIndex); + if (aToken == u"domain") + bDomain = true; + else if (aToken == u"username") + bUser = true; + else if (aToken == u"password") + bPass = true; + } + while (nIndex >= 0); + + if (bDomain || bUser || bPass) + { + OString sPrinterName(OUStringToOString(rPrintername, RTL_TEXTENCODING_UTF8)); + OString sUser = cupsUser(); + RTSPWDialog aDialog(Application::GetDefDialogParent(), sPrinterName, sUser); + aDialog.SetDomainVisible(bDomain); + aDialog.SetUserVisible(bUser); + aDialog.SetPassVisible(bPass); + + if (aDialog.run() == RET_OK) + { + OString sAuth; + if (bDomain) + sAuth = EscapeCupsOption(aDialog.getDomain()); + if (bUser) + { + if (bDomain) + sAuth += ","; + sAuth += EscapeCupsOption(aDialog.getUserName()); + } + if (bPass) + { + if (bUser || bDomain) + sAuth += ","; + sAuth += EscapeCupsOption(aDialog.getPassword()); + } + nNumOptions = cupsAddOption("auth-info", sAuth.getStr(), nNumOptions, &pOptions); + } + } + } + + OString sJobName(OUStringToOString(rJobTitle, aEnc)); + + //fax4CUPS, "the job name will be dialled for you" + //so override the jobname with the desired number + if (!rFaxNumber.isEmpty()) + { + sJobName = OUStringToOString(rFaxNumber, aEnc); + } + + cups_dest_t* pDest = static_cast<cups_dest_t*>(m_pDests) + dest_it->second; + nJobID = cupsPrintFile(pDest->name, + it->second.getStr(), + sJobName.getStr(), + nNumOptions, pOptions); + SAL_INFO("vcl.unx.print", "cupsPrintFile( " << pDest->name << ", " + << it->second << ", " << rJobTitle << ", " << nNumOptions + << ", " << pOptions << " ) returns " << nJobID); + for( int n = 0; n < nNumOptions; n++ ) + SAL_INFO("vcl.unx.print", + " option " << pOptions[n].name << "=" << pOptions[n].value); +#if OSL_DEBUG_LEVEL > 1 + OString aCmd( "cp " ); + aCmd += it->second.getStr(); + aCmd += OString( " $HOME/cupsprint.ps" ); + system( aCmd.getStr() ); +#endif + + unlink( it->second.getStr() ); + m_aSpoolFiles.erase(it); + if( pOptions ) + cupsFreeOptions( nNumOptions, pOptions ); + } + + return nJobID != 0; +} + +bool CUPSManager::checkPrintersChanged( bool bWait ) +{ + bool bChanged = false; + if( bWait ) + { + if( m_aDestThread ) + { + // initial asynchronous detection still running + SAL_INFO("vcl.unx.print", "syncing cups discovery thread"); + osl_joinWithThread( m_aDestThread ); + osl_destroyThread( m_aDestThread ); + m_aDestThread = nullptr; + SAL_INFO("vcl.unx.print", "done: syncing cups discovery thread"); + } + else + { + // #i82321# check for cups printer updates + // with this change the whole asynchronous detection in a thread is + // almost useless. The only relevance left is for some stalled systems + // where the user can set SAL_DISABLE_SYNCHRONOUS_PRINTER_DETECTION + // (see vcl/unx/source/gdi/salprnpsp.cxx) + // so that checkPrintersChanged( true ) will never be called + + // there is no way to query CUPS whether the printer list has changed + // so get the dest list anew + if( m_nDests && m_pDests ) + cupsFreeDests( m_nDests, static_cast<cups_dest_t*>(m_pDests) ); + m_nDests = 0; + m_pDests = nullptr; + runDests(); + } + } + if( m_aCUPSMutex.tryToAcquire() ) + { + bChanged = m_bNewDests; + m_aCUPSMutex.release(); + } + + if( ! bChanged ) + { + bChanged = PrinterInfoManager::checkPrintersChanged( bWait ); + // #i54375# ensure new merging with CUPS list in :initialize + if( bChanged ) + m_bNewDests = true; + } + + if( bChanged ) + initialize(); + + return bChanged; +} + +const char* CUPSManager::authenticateUser() +{ + const char* pRet = nullptr; + + osl::MutexGuard aGuard( m_aCUPSMutex ); + + OString aUser = cupsUser(); + OString aServer = cupsServer(); + OString aPassword; + if (AuthenticateQuery(aServer, aUser, aPassword)) + { + m_aPassword = aPassword; + m_aUser = aUser; + cupsSetUser( m_aUser.getStr() ); + pRet = m_aPassword.getStr(); + } + + return pRet; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/generic/printer/jobdata.cxx b/vcl/unx/generic/printer/jobdata.cxx new file mode 100644 index 000000000..9707a8109 --- /dev/null +++ b/vcl/unx/generic/printer/jobdata.cxx @@ -0,0 +1,316 @@ +/* -*- 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 <officecfg/Office/Common.hxx> +#include <jobdata.hxx> +#include <printerinfomanager.hxx> +#include <tools/stream.hxx> +#include <o3tl/string_view.hxx> + +#include <rtl/strbuf.hxx> +#include <memory> + +using namespace psp; + +JobData& JobData::operator=(const JobData& rRight) +{ + if(this == &rRight) + return *this; + + m_nCopies = rRight.m_nCopies; + m_bCollate = rRight.m_bCollate; + m_nLeftMarginAdjust = rRight.m_nLeftMarginAdjust; + m_nRightMarginAdjust = rRight.m_nRightMarginAdjust; + m_nTopMarginAdjust = rRight.m_nTopMarginAdjust; + m_nBottomMarginAdjust = rRight.m_nBottomMarginAdjust; + m_nColorDepth = rRight.m_nColorDepth; + m_eOrientation = rRight.m_eOrientation; + m_aPrinterName = rRight.m_aPrinterName; + m_bPapersizeFromSetup = rRight.m_bPapersizeFromSetup; + m_pParser = rRight.m_pParser; + m_aContext = rRight.m_aContext; + m_nPSLevel = rRight.m_nPSLevel; + m_nPDFDevice = rRight.m_nPDFDevice; + m_nColorDevice = rRight.m_nColorDevice; + + if( !m_pParser && !m_aPrinterName.isEmpty() ) + { + PrinterInfoManager& rMgr = PrinterInfoManager::get(); + rMgr.setupJobContextData( *this ); + } + return *this; +} + +void JobData::setCollate( bool bCollate ) +{ + if (m_nPDFDevice > 0) + { + m_bCollate = bCollate; + return; + } + const PPDParser* pParser = m_aContext.getParser(); + if( !pParser ) + return; + + const PPDKey* pKey = pParser->getKey( "Collate" ); + if( !pKey ) + return; + + const PPDValue* pVal = nullptr; + if( bCollate ) + pVal = pKey->getValue( "True" ); + else + { + pVal = pKey->getValue( "False" ); + if( ! pVal ) + pVal = pKey->getValue( "None" ); + } + m_aContext.setValue( pKey, pVal ); +} + +void JobData::setPaper( int i_nWidth, int i_nHeight ) +{ + if( m_pParser ) + { + OUString aPaper( m_pParser->matchPaper( i_nWidth, i_nHeight ) ); + + const PPDKey* pKey = m_pParser->getKey( "PageSize" ); + const PPDValue* pValue = pKey ? pKey->getValueCaseInsensitive( aPaper ) : nullptr; + + if (pKey && pValue) + m_aContext.setValue( pKey, pValue ); + } +} + +void JobData::setPaperBin( int i_nPaperBin ) +{ + if( m_pParser ) + { + const PPDKey* pKey = m_pParser->getKey( "InputSlot" ); + const PPDValue* pValue = pKey ? pKey->getValue( i_nPaperBin ) : nullptr; + + if (pKey && pValue) + m_aContext.setValue( pKey, pValue ); + } +} + +bool JobData::getStreamBuffer( void*& pData, sal_uInt32& bytes ) +{ + // consistency checks + if( ! m_pParser ) + m_pParser = m_aContext.getParser(); + if( m_pParser != m_aContext.getParser() || + ! m_pParser ) + return false; + + SvMemoryStream aStream; + + // write header job data + aStream.WriteLine("JobData 1"); + + OStringBuffer aLine; + + aLine.append("printer="); + aLine.append(OUStringToOString(m_aPrinterName, RTL_TEXTENCODING_UTF8)); + aStream.WriteLine(aLine); + aLine.setLength(0); + + aLine.append("orientation="); + if (m_eOrientation == orientation::Landscape) + aLine.append("Landscape"); + else + aLine.append("Portrait"); + aStream.WriteLine(aLine); + aLine.setLength(0); + + aLine.append("copies="); + aLine.append(static_cast<sal_Int32>(m_nCopies)); + aStream.WriteLine(aLine); + aLine.setLength(0); + + if (m_nPDFDevice > 0) + { + aLine.append("collate="); + aLine.append(OString::boolean(m_bCollate)); + aStream.WriteLine(aLine); + aLine.setLength(0); + } + + aLine.append("marginadjustment="); + aLine.append(static_cast<sal_Int32>(m_nLeftMarginAdjust)); + aLine.append(','); + aLine.append(static_cast<sal_Int32>(m_nRightMarginAdjust)); + aLine.append(','); + aLine.append(static_cast<sal_Int32>(m_nTopMarginAdjust)); + aLine.append(','); + aLine.append(static_cast<sal_Int32>(m_nBottomMarginAdjust)); + aStream.WriteLine(aLine); + aLine.setLength(0); + + aLine.append("colordepth="); + aLine.append(static_cast<sal_Int32>(m_nColorDepth)); + aStream.WriteLine(aLine); + aLine.setLength(0); + + aLine.append("pslevel="); + aLine.append(static_cast<sal_Int32>(m_nPSLevel)); + aStream.WriteLine(aLine); + aLine.setLength(0); + + aLine.append("pdfdevice="); + aLine.append(static_cast<sal_Int32>(m_nPDFDevice)); + aStream.WriteLine(aLine); + aLine.setLength(0); + + aLine.append("colordevice="); + aLine.append(static_cast<sal_Int32>(m_nColorDevice)); + aStream.WriteLine(aLine); + aLine.setLength(0); + + // now append the PPDContext stream buffer + aStream.WriteLine( "PPDContextData" ); + sal_uLong nBytes; + std::unique_ptr<char[]> pContextBuffer(m_aContext.getStreamableBuffer( nBytes )); + if( nBytes ) + aStream.WriteBytes( pContextBuffer.get(), nBytes ); + pContextBuffer.reset(); + + // success + bytes = static_cast<sal_uInt32>(aStream.Tell()); + pData = std::malloc( bytes ); + memcpy( pData, aStream.GetData(), bytes ); + return true; +} + +bool JobData::constructFromStreamBuffer( const void* pData, sal_uInt32 bytes, JobData& rJobData ) +{ + SvMemoryStream aStream( const_cast<void*>(pData), bytes, StreamMode::READ ); + OString aLine; + bool bVersion = false; + bool bPrinter = false; + bool bOrientation = false; + bool bCopies = false; + bool bContext = false; + bool bMargin = false; + bool bColorDepth = false; + bool bColorDevice = false; + bool bPSLevel = false; + bool bPDFDevice = false; + + const char printerEquals[] = "printer="; + const char orientatationEquals[] = "orientation="; + const char copiesEquals[] = "copies="; + const char collateEquals[] = "collate="; + const char marginadjustmentEquals[] = "marginadjustment="; + const char colordepthEquals[] = "colordepth="; + const char colordeviceEquals[] = "colordevice="; + const char pslevelEquals[] = "pslevel="; + const char pdfdeviceEquals[] = "pdfdevice="; + + while( ! aStream.eof() ) + { + aStream.ReadLine( aLine ); + if (aLine.startsWith("JobData")) + bVersion = true; + else if (aLine.startsWith(printerEquals)) + { + bPrinter = true; + rJobData.m_aPrinterName = OStringToOUString(aLine.subView(RTL_CONSTASCII_LENGTH(printerEquals)), RTL_TEXTENCODING_UTF8); + } + else if (aLine.startsWith(orientatationEquals)) + { + bOrientation = true; + rJobData.m_eOrientation = o3tl::equalsIgnoreAsciiCase(aLine.subView(RTL_CONSTASCII_LENGTH(orientatationEquals)), "landscape") ? orientation::Landscape : orientation::Portrait; + } + else if (aLine.startsWith(copiesEquals)) + { + bCopies = true; + rJobData.m_nCopies = o3tl::toInt32(aLine.subView(RTL_CONSTASCII_LENGTH(copiesEquals))); + } + else if (aLine.startsWith(collateEquals)) + { + rJobData.m_bCollate = aLine.copy(RTL_CONSTASCII_LENGTH(collateEquals)).toBoolean(); + } + else if (aLine.startsWith(marginadjustmentEquals)) + { + bMargin = true; + sal_Int32 nIdx {RTL_CONSTASCII_LENGTH(marginadjustmentEquals)}; + rJobData.m_nLeftMarginAdjust = o3tl::toInt32(o3tl::getToken(aLine, 0, ',', nIdx)); + rJobData.m_nRightMarginAdjust = o3tl::toInt32(o3tl::getToken(aLine, 0, ',', nIdx)); + rJobData.m_nTopMarginAdjust = o3tl::toInt32(o3tl::getToken(aLine, 0, ',', nIdx)); + rJobData.m_nBottomMarginAdjust = o3tl::toInt32(o3tl::getToken(aLine, 0, ',', nIdx)); + } + else if (aLine.startsWith(colordepthEquals)) + { + bColorDepth = true; + rJobData.m_nColorDepth = o3tl::toInt32(aLine.subView(RTL_CONSTASCII_LENGTH(colordepthEquals))); + } + else if (aLine.startsWith(colordeviceEquals)) + { + bColorDevice = true; + rJobData.m_nColorDevice = o3tl::toInt32(aLine.subView(RTL_CONSTASCII_LENGTH(colordeviceEquals))); + } + else if (aLine.startsWith(pslevelEquals)) + { + bPSLevel = true; + rJobData.m_nPSLevel = o3tl::toInt32(aLine.subView(RTL_CONSTASCII_LENGTH(pslevelEquals))); + } + else if (aLine.startsWith(pdfdeviceEquals)) + { + bPDFDevice = true; + rJobData.m_nPDFDevice = o3tl::toInt32(aLine.subView(RTL_CONSTASCII_LENGTH(pdfdeviceEquals))); + } + else if (aLine == "PPDContextData" && bPrinter) + { + PrinterInfoManager& rManager = PrinterInfoManager::get(); + const PrinterInfo& rInfo = rManager.getPrinterInfo( rJobData.m_aPrinterName ); + rJobData.m_pParser = PPDParser::getParser( rInfo.m_aDriverName ); + if( rJobData.m_pParser ) + { + rJobData.m_aContext.setParser( rJobData.m_pParser ); + sal_uInt64 nBytes = bytes - aStream.Tell(); + std::vector<char> aRemain(nBytes+1); + nBytes = aStream.ReadBytes(aRemain.data(), nBytes); + if (nBytes) + { + aRemain.resize(nBytes+1); + aRemain[nBytes] = 0; + rJobData.m_aContext.rebuildFromStreamBuffer(aRemain); + bContext = true; + } + } + } + } + + return bVersion && bPrinter && bOrientation && bCopies && bContext && bMargin && bPSLevel && bPDFDevice && bColorDevice && bColorDepth; +} + +void JobData::resolveDefaultBackend() +{ + if (m_nPSLevel == 0 && m_nPDFDevice == 0) + setDefaultBackend(officecfg::Office::Common::Print::Option::Printer::PDFAsStandardPrintJobFormat::get()); +} + +void JobData::setDefaultBackend(bool bUsePDF) +{ + if (bUsePDF && m_nPSLevel == 0 && m_nPDFDevice == 0) + m_nPDFDevice = 1; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/generic/printer/ppdparser.cxx b/vcl/unx/generic/printer/ppdparser.cxx new file mode 100644 index 000000000..c16d257e2 --- /dev/null +++ b/vcl/unx/generic/printer/ppdparser.cxx @@ -0,0 +1,1991 @@ +/* -*- 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 <stdlib.h> + +#include <comphelper/string.hxx> +#include <o3tl/string_view.hxx> +#include <i18nlangtag/languagetag.hxx> +#include <ppdparser.hxx> +#include <strhelper.hxx> +#include <vcl/svapp.hxx> +#include <vcl/settings.hxx> + +#include <unx/helper.hxx> +#include <unx/cupsmgr.hxx> +#include <unx/cpdmgr.hxx> + +#include <tools/urlobj.hxx> +#include <tools/stream.hxx> +#include <tools/zcodec.hxx> +#include <o3tl/safeint.hxx> +#include <osl/file.hxx> +#include <osl/process.h> +#include <osl/thread.h> +#include <rtl/strbuf.hxx> +#include <rtl/ustrbuf.hxx> +#include <sal/log.hxx> +#include <salhelper/linkhelper.hxx> + +#include <com/sun/star/lang/Locale.hpp> + +#include <mutex> +#include <unordered_map> + +#ifdef ENABLE_CUPS +#include <cups/cups.h> +#endif + +#include <config_dbus.h> +#include <config_gio.h> +#include <o3tl/hash_combine.hxx> + +namespace psp +{ + class PPDTranslator + { + struct LocaleEqual + { + bool operator()(const css::lang::Locale& i_rLeft, + const css::lang::Locale& i_rRight) const + { + return i_rLeft.Language == i_rRight.Language && + i_rLeft.Country == i_rRight.Country && + i_rLeft.Variant == i_rRight.Variant; + } + }; + + struct LocaleHash + { + size_t operator()(const css::lang::Locale& rLocale) const + { + std::size_t seed = 0; + o3tl::hash_combine(seed, rLocale.Language.hashCode()); + o3tl::hash_combine(seed, rLocale.Country.hashCode()); + o3tl::hash_combine(seed, rLocale.Variant.hashCode()); + return seed; + } + }; + + typedef std::unordered_map< css::lang::Locale, OUString, LocaleHash, LocaleEqual > translation_map; + typedef std::unordered_map< OUString, translation_map > key_translation_map; + + key_translation_map m_aTranslations; + public: + PPDTranslator() {} + + void insertValue( + const OUString& i_rKey, + const OUString& i_rOption, + const OUString& i_rValue, + const OUString& i_rTranslation, + const css::lang::Locale& i_rLocale + ); + + void insertOption( const OUString& i_rKey, + const OUString& i_rOption, + const OUString& i_rTranslation, + const css::lang::Locale& i_rLocale ) + { + insertValue( i_rKey, i_rOption, OUString(), i_rTranslation, i_rLocale ); + } + + void insertKey( const OUString& i_rKey, + const OUString& i_rTranslation, + const css::lang::Locale& i_rLocale = css::lang::Locale() ) + { + insertValue( i_rKey, OUString(), OUString(), i_rTranslation, i_rLocale ); + } + + OUString translateValue( + const OUString& i_rKey, + const OUString& i_rOption + ) const; + + OUString translateOption( const OUString& i_rKey, + const OUString& i_rOption ) const + { + return translateValue( i_rKey, i_rOption ); + } + + OUString translateKey( const OUString& i_rKey ) const + { + return translateValue( i_rKey, OUString() ); + } + }; + + static css::lang::Locale normalizeInputLocale( + const css::lang::Locale& i_rLocale + ) + { + css::lang::Locale aLoc( i_rLocale ); + if( aLoc.Language.isEmpty() ) + { + // empty locale requested, fill in application UI locale + aLoc = Application::GetSettings().GetUILanguageTag().getLocale(); + + #if OSL_DEBUG_LEVEL > 1 + static const char* pEnvLocale = getenv( "SAL_PPDPARSER_LOCALE" ); + if( pEnvLocale && *pEnvLocale ) + { + OString aStr( pEnvLocale ); + sal_Int32 nLen = aStr.getLength(); + aLoc.Language = OStringToOUString( aStr.copy( 0, std::min(nLen, 2) ), RTL_TEXTENCODING_MS_1252 ); + if( nLen >=5 && aStr[2] == '_' ) + aLoc.Country = OStringToOUString( aStr.copy( 3, 2 ), RTL_TEXTENCODING_MS_1252 ); + else + aLoc.Country.clear(); + aLoc.Variant.clear(); + } + #endif + } + /* FIXME-BCP47: using Variant, uppercase? */ + aLoc.Language = aLoc.Language.toAsciiLowerCase(); + aLoc.Country = aLoc.Country.toAsciiUpperCase(); + aLoc.Variant = aLoc.Variant.toAsciiUpperCase(); + + return aLoc; + } + + void PPDTranslator::insertValue( + const OUString& i_rKey, + const OUString& i_rOption, + const OUString& i_rValue, + const OUString& i_rTranslation, + const css::lang::Locale& i_rLocale + ) + { + OUStringBuffer aKey( i_rKey.getLength() + i_rOption.getLength() + i_rValue.getLength() + 2 ); + aKey.append( i_rKey ); + if( !i_rOption.isEmpty() || !i_rValue.isEmpty() ) + { + aKey.append( ':' ); + aKey.append( i_rOption ); + } + if( !i_rValue.isEmpty() ) + { + aKey.append( ':' ); + aKey.append( i_rValue ); + } + if( !aKey.isEmpty() && !i_rTranslation.isEmpty() ) + { + OUString aK( aKey.makeStringAndClear() ); + css::lang::Locale aLoc; + /* FIXME-BCP47: using Variant, uppercase? */ + aLoc.Language = i_rLocale.Language.toAsciiLowerCase(); + aLoc.Country = i_rLocale.Country.toAsciiUpperCase(); + aLoc.Variant = i_rLocale.Variant.toAsciiUpperCase(); + m_aTranslations[ aK ][ aLoc ] = i_rTranslation; + } + } + + OUString PPDTranslator::translateValue( + const OUString& i_rKey, + const OUString& i_rOption + ) const + { + OUString aResult; + + OUStringBuffer aKey( i_rKey.getLength() + i_rOption.getLength() + 2 ); + aKey.append( i_rKey ); + if( !i_rOption.isEmpty() ) + { + aKey.append( ':' ); + aKey.append( i_rOption ); + } + if( !aKey.isEmpty() ) + { + OUString aK( aKey.makeStringAndClear() ); + key_translation_map::const_iterator it = m_aTranslations.find( aK ); + if( it != m_aTranslations.end() ) + { + const translation_map& rMap( it->second ); + + css::lang::Locale aLoc( normalizeInputLocale( css::lang::Locale() ) ); + /* FIXME-BCP47: use LanguageTag::getFallbackStrings()? */ + for( int nTry = 0; nTry < 4; nTry++ ) + { + translation_map::const_iterator tr = rMap.find( aLoc ); + if( tr != rMap.end() ) + { + aResult = tr->second; + break; + } + switch( nTry ) + { + case 0: aLoc.Variant.clear();break; + case 1: aLoc.Country.clear();break; + case 2: aLoc.Language.clear();break; + } + } + } + } + return aResult; + } + + class PPDCache + { + public: + std::vector< std::unique_ptr<PPDParser> > aAllParsers; + std::optional<std::unordered_map< OUString, OUString >> xAllPPDFiles; + }; +} + +using namespace psp; + +namespace +{ + PPDCache& getPPDCache() + { + static PPDCache thePPDCache; + return thePPDCache; + } + +class PPDDecompressStream +{ +private: + PPDDecompressStream(const PPDDecompressStream&) = delete; + PPDDecompressStream& operator=(const PPDDecompressStream&) = delete; + + std::unique_ptr<SvFileStream> mpFileStream; + std::unique_ptr<SvMemoryStream> mpMemStream; + OUString maFileName; + +public: + explicit PPDDecompressStream( const OUString& rFile ); + ~PPDDecompressStream(); + + bool IsOpen() const; + bool eof() const; + OString ReadLine(); + void Open( const OUString& i_rFile ); + void Close(); + const OUString& GetFileName() const { return maFileName; } +}; + +} + +PPDDecompressStream::PPDDecompressStream( const OUString& i_rFile ) +{ + Open( i_rFile ); +} + +PPDDecompressStream::~PPDDecompressStream() +{ + Close(); +} + +void PPDDecompressStream::Open( const OUString& i_rFile ) +{ + Close(); + + mpFileStream.reset( new SvFileStream( i_rFile, StreamMode::READ ) ); + maFileName = mpFileStream->GetFileName(); + + if( ! mpFileStream->IsOpen() ) + { + Close(); + return; + } + + OString aLine; + mpFileStream->ReadLine( aLine ); + mpFileStream->Seek( 0 ); + + // check for compress'ed or gzip'ed file + if( aLine.getLength() <= 1 || + static_cast<unsigned char>(aLine[0]) != 0x1f || + static_cast<unsigned char>(aLine[1]) != 0x8b /* check for gzip */ ) + return; + + // so let's try to decompress the stream + mpMemStream.reset( new SvMemoryStream( 4096, 4096 ) ); + ZCodec aCodec; + aCodec.BeginCompression( ZCODEC_DEFAULT_COMPRESSION, /*gzLib*/true ); + tools::Long nComp = aCodec.Decompress( *mpFileStream, *mpMemStream ); + aCodec.EndCompression(); + if( nComp < 0 ) + { + // decompression failed, must be an uncompressed stream after all + mpMemStream.reset(); + mpFileStream->Seek( 0 ); + } + else + { + // compression successful, can get rid of file stream + mpFileStream.reset(); + mpMemStream->Seek( 0 ); + } +} + +void PPDDecompressStream::Close() +{ + mpMemStream.reset(); + mpFileStream.reset(); +} + +bool PPDDecompressStream::IsOpen() const +{ + return (mpMemStream || (mpFileStream && mpFileStream->IsOpen())); +} + +bool PPDDecompressStream::eof() const +{ + return ( mpMemStream ? mpMemStream->eof() : ( mpFileStream == nullptr || mpFileStream->eof() ) ); +} + +OString PPDDecompressStream::ReadLine() +{ + OString o_rLine; + if( mpMemStream ) + mpMemStream->ReadLine( o_rLine ); + else if( mpFileStream ) + mpFileStream->ReadLine( o_rLine ); + return o_rLine; +} + +static osl::FileBase::RC resolveLink( const OUString& i_rURL, OUString& o_rResolvedURL, OUString& o_rBaseName, osl::FileStatus::Type& o_rType) +{ + salhelper::LinkResolver aResolver(osl_FileStatus_Mask_FileName | + osl_FileStatus_Mask_Type | + osl_FileStatus_Mask_FileURL); + + osl::FileBase::RC aRet = aResolver.fetchFileStatus(i_rURL, 10/*nLinkLevel*/); + + if (aRet == osl::FileBase::E_None) + { + o_rResolvedURL = aResolver.m_aStatus.getFileURL(); + o_rBaseName = aResolver.m_aStatus.getFileName(); + o_rType = aResolver.m_aStatus.getFileType(); + } + + return aRet; +} + +void PPDParser::scanPPDDir( const OUString& rDir ) +{ + static struct suffix_t + { + const char* pSuffix; + const sal_Int32 nSuffixLen; + } const pSuffixes[] = + { { ".PS", 3 }, { ".PPD", 4 }, { ".PS.GZ", 6 }, { ".PPD.GZ", 7 } }; + + PPDCache &rPPDCache = getPPDCache(); + + osl::Directory aDir( rDir ); + if ( aDir.open() != osl::FileBase::E_None ) + return; + + osl::DirectoryItem aItem; + + INetURLObject aPPDDir(rDir); + while( aDir.getNextItem( aItem ) == osl::FileBase::E_None ) + { + osl::FileStatus aStatus( osl_FileStatus_Mask_FileName ); + if( aItem.getFileStatus( aStatus ) == osl::FileBase::E_None ) + { + OUString aFileURL, aFileName; + osl::FileStatus::Type eType = osl::FileStatus::Unknown; + OUString aURL = rDir + "/" + aStatus.getFileName(); + + if(resolveLink( aURL, aFileURL, aFileName, eType ) == osl::FileBase::E_None) + { + if( eType == osl::FileStatus::Regular ) + { + INetURLObject aPPDFile = aPPDDir; + aPPDFile.Append( aFileName ); + + // match extension + for(const suffix_t & rSuffix : pSuffixes) + { + if( aFileName.getLength() > rSuffix.nSuffixLen ) + { + if( aFileName.endsWithIgnoreAsciiCaseAsciiL( rSuffix.pSuffix, rSuffix.nSuffixLen ) ) + { + (*rPPDCache.xAllPPDFiles)[ aFileName.copy( 0, aFileName.getLength() - rSuffix.nSuffixLen ) ] = aPPDFile.PathToFileName(); + break; + } + } + } + } + else if( eType == osl::FileStatus::Directory ) + { + scanPPDDir( aFileURL ); + } + } + } + } + aDir.close(); +} + +void PPDParser::initPPDFiles(PPDCache &rPPDCache) +{ + if( rPPDCache.xAllPPDFiles ) + return; + + rPPDCache.xAllPPDFiles.emplace(); + + // check installation directories + std::vector< OUString > aPathList; + psp::getPrinterPathList( aPathList, PRINTER_PPDDIR ); + for (auto const& path : aPathList) + { + INetURLObject aPPDDir( path, INetProtocol::File, INetURLObject::EncodeMechanism::All ); + scanPPDDir( aPPDDir.GetMainURL( INetURLObject::DecodeMechanism::NONE ) ); + } + if( rPPDCache.xAllPPDFiles->find( OUString( "SGENPRT" ) ) != rPPDCache.xAllPPDFiles->end() ) + return; + + // last try: search in directory of executable (mainly for setup) + OUString aExe; + if( osl_getExecutableFile( &aExe.pData ) == osl_Process_E_None ) + { + INetURLObject aDir( aExe ); + aDir.removeSegment(); + SAL_INFO("vcl.unx.print", "scanning last chance dir: " + << aDir.GetMainURL(INetURLObject::DecodeMechanism::NONE)); + scanPPDDir( aDir.GetMainURL( INetURLObject::DecodeMechanism::NONE ) ); + SAL_INFO("vcl.unx.print", "SGENPRT " + << (rPPDCache.xAllPPDFiles->find("SGENPRT") == + rPPDCache.xAllPPDFiles->end() ? "not found" : "found")); + } +} + +OUString PPDParser::getPPDFile( const OUString& rFile ) +{ + INetURLObject aPPD( rFile, INetProtocol::File, INetURLObject::EncodeMechanism::All ); + // someone might enter a full qualified name here + PPDDecompressStream aStream( aPPD.PathToFileName() ); + if( ! aStream.IsOpen() ) + { + std::unordered_map< OUString, OUString >::const_iterator it; + PPDCache &rPPDCache = getPPDCache(); + + bool bRetry = true; + do + { + initPPDFiles(rPPDCache); + // some PPD files contain dots beside the extension, so try name first + // and cut of points after that + OUString aBase( rFile ); + sal_Int32 nLastIndex = aBase.lastIndexOf( '/' ); + if( nLastIndex >= 0 ) + aBase = aBase.copy( nLastIndex+1 ); + do + { + it = rPPDCache.xAllPPDFiles->find( aBase ); + nLastIndex = aBase.lastIndexOf( '.' ); + if( nLastIndex > 0 ) + aBase = aBase.copy( 0, nLastIndex ); + } while( it == rPPDCache.xAllPPDFiles->end() && nLastIndex > 0 ); + + if( it == rPPDCache.xAllPPDFiles->end() && bRetry ) + { + // a new file ? rehash + rPPDCache.xAllPPDFiles.reset(); + bRetry = false; + // note this is optimized for office start where + // no new files occur and initPPDFiles is called only once + } + } while( ! rPPDCache.xAllPPDFiles ); + + if( it != rPPDCache.xAllPPDFiles->end() ) + aStream.Open( it->second ); + } + + OUString aRet; + if( aStream.IsOpen() ) + { + OString aLine = aStream.ReadLine(); + if (aLine.startsWith("*PPD-Adobe")) + aRet = aStream.GetFileName(); + else + { + // our *Include hack does usually not begin + // with *PPD-Adobe, so try some lines for *Include + int nLines = 10; + while (aLine.indexOf("*Include") != 0 && --nLines) + aLine = aStream.ReadLine(); + if( nLines ) + aRet = aStream.GetFileName(); + } + } + + return aRet; +} + +const PPDParser* PPDParser::getParser( const OUString& rFile ) +{ + // Recursive because we can get re-entered via CUPSManager::createCUPSParser + static std::recursive_mutex aMutex; + std::scoped_lock aGuard( aMutex ); + + OUString aFile = rFile; + if( !rFile.startsWith( "CUPS:" ) && !rFile.startsWith( "CPD:" ) ) + aFile = getPPDFile( rFile ); + if( aFile.isEmpty() ) + { + SAL_INFO("vcl.unx.print", "Could not get printer PPD file \"" + << rFile << "\" !"); + return nullptr; + } + else + SAL_INFO("vcl.unx.print", "Parsing printer info from \"" + << rFile << "\" !"); + + + PPDCache &rPPDCache = getPPDCache(); + for( auto const & i : rPPDCache.aAllParsers ) + if( i->m_aFile == aFile ) + return i.get(); + + PPDParser* pNewParser = nullptr; + if( !aFile.startsWith( "CUPS:" ) && !aFile.startsWith( "CPD:" ) ) + pNewParser = new PPDParser( aFile ); + else + { + PrinterInfoManager& rMgr = PrinterInfoManager::get(); + if( rMgr.getType() == PrinterInfoManager::Type::CUPS ) + { +#ifdef ENABLE_CUPS + pNewParser = const_cast<PPDParser*>(static_cast<CUPSManager&>(rMgr).createCUPSParser( aFile )); +#endif + } else if ( rMgr.getType() == PrinterInfoManager::Type::CPD ) + { +#if ENABLE_DBUS && ENABLE_GIO + pNewParser = const_cast<PPDParser*>(static_cast<CPDManager&>(rMgr).createCPDParser( aFile )); +#endif + } + } + if( pNewParser ) + { + // this may actually be the SGENPRT parser, + // so ensure uniqueness here (but don't remove last we delete us!) + if (std::none_of( + rPPDCache.aAllParsers.begin(), + rPPDCache.aAllParsers.end(), + [pNewParser] (std::unique_ptr<PPDParser> const & x) { return x.get() == pNewParser; } )) + { + // insert new parser to vector + rPPDCache.aAllParsers.emplace_back(pNewParser); + } + } + return pNewParser; +} + +PPDParser::PPDParser(const OUString& rFile, const std::vector<PPDKey*>& keys) + : m_aFile(rFile) + , m_bColorDevice(false) + , m_bType42Capable(false) + , m_nLanguageLevel(0) + , m_aFileEncoding(RTL_TEXTENCODING_MS_1252) + , m_pImageableAreas(nullptr) + , m_pDefaultPaperDimension(nullptr) + , m_pPaperDimensions(nullptr) + , m_pDefaultInputSlot(nullptr) + , m_pDefaultResolution(nullptr) + , m_pTranslator(new PPDTranslator()) +{ + for (auto & key: keys) + { + insertKey( std::unique_ptr<PPDKey>(key) ); + } + + // fill in shortcuts + const PPDKey* pKey; + + pKey = getKey( "PageSize" ); + + if ( pKey ) { + std::unique_ptr<PPDKey> pImageableAreas(new PPDKey("ImageableArea")); + std::unique_ptr<PPDKey> pPaperDimensions(new PPDKey("PaperDimension")); +#if defined(CUPS_VERSION_MAJOR) +#if (CUPS_VERSION_MAJOR == 1 && CUPS_VERSION_MINOR >= 7) || CUPS_VERSION_MAJOR > 1 + for (int i = 0; i < pKey->countValues(); i++) { + const PPDValue* pValue = pKey -> getValue(i); + OUString aValueName = pValue -> m_aOption; + PPDValue* pImageableAreaValue = pImageableAreas -> insertValue( aValueName, eQuoted ); + PPDValue* pPaperDimensionValue = pPaperDimensions -> insertValue( aValueName, eQuoted ); + rtl_TextEncoding aEncoding = osl_getThreadTextEncoding(); + OString o = OUStringToOString( aValueName, aEncoding ); + pwg_media_t *pPWGMedia = pwgMediaForPWG(o.pData->buffer); + if (pPWGMedia != nullptr) { + OUStringBuffer aBuf( 256 ); + aBuf = "0 0 " + + OUString::number(PWG_TO_POINTS(pPWGMedia -> width)) + + " " + + OUString::number(PWG_TO_POINTS(pPWGMedia -> length)); + if ( pImageableAreaValue ) + pImageableAreaValue->m_aValue = aBuf.makeStringAndClear(); + aBuf.append( PWG_TO_POINTS(pPWGMedia -> width) ); + aBuf.append( " " ); + aBuf.append( PWG_TO_POINTS(pPWGMedia -> length) ); + if ( pPaperDimensionValue ) + pPaperDimensionValue->m_aValue = aBuf.makeStringAndClear(); + if (aValueName.equals(pKey -> getDefaultValue() -> m_aOption)) { + pImageableAreas -> m_pDefaultValue = pImageableAreaValue; + pPaperDimensions -> m_pDefaultValue = pPaperDimensionValue; + } + } + } +#endif // HAVE_CUPS_API_1_7 +#endif + insertKey(std::move(pImageableAreas)); + insertKey(std::move(pPaperDimensions)); + } + + m_pImageableAreas = getKey( "ImageableArea" ); + const PPDValue* pDefaultImageableArea = nullptr; + if( m_pImageableAreas ) + pDefaultImageableArea = m_pImageableAreas->getDefaultValue(); + if (m_pImageableAreas == nullptr) { + SAL_WARN( "vcl.unx.print", "no ImageableArea in " << m_aFile); + } + if (pDefaultImageableArea == nullptr) { + SAL_WARN( "vcl.unx.print", "no DefaultImageableArea in " << m_aFile); + } + + m_pPaperDimensions = getKey( "PaperDimension" ); + if( m_pPaperDimensions ) + m_pDefaultPaperDimension = m_pPaperDimensions->getDefaultValue(); + if (m_pPaperDimensions == nullptr) { + SAL_WARN( "vcl.unx.print", "no PaperDimensions in " << m_aFile); + } + if (m_pDefaultPaperDimension == nullptr) { + SAL_WARN( "vcl.unx.print", "no DefaultPaperDimensions in " << m_aFile); + } + + auto pResolutions = getKey( "Resolution" ); + if( pResolutions ) + m_pDefaultResolution = pResolutions->getDefaultValue(); + if (pResolutions == nullptr) { + SAL_WARN( "vcl.unx.print", "no Resolution in " << m_aFile); + } + SAL_INFO_IF(!m_pDefaultResolution, "vcl.unx.print", "no DefaultResolution in " + m_aFile); + + auto pInputSlots = getKey( "InputSlot" ); + if( pInputSlots ) + m_pDefaultInputSlot = pInputSlots->getDefaultValue(); + SAL_INFO_IF(!pInputSlots, "vcl.unx.print", "no InputSlot in " << m_aFile); + SAL_INFO_IF(!m_pDefaultInputSlot, "vcl.unx.print", "no DefaultInputSlot in " << m_aFile); + + auto pFontList = getKey( "Font" ); + if (pFontList == nullptr) { + SAL_WARN( "vcl.unx.print", "no Font in " << m_aFile); + } + + // fill in direct values + if( (pKey = getKey( "print-color-mode" )) ) + m_bColorDevice = pKey->countValues() > 1; +} + +PPDParser::PPDParser( const OUString& rFile ) : + m_aFile( rFile ), + m_bColorDevice( false ), + m_bType42Capable( false ), + m_nLanguageLevel( 0 ), + m_aFileEncoding( RTL_TEXTENCODING_MS_1252 ), + m_pImageableAreas( nullptr ), + m_pDefaultPaperDimension( nullptr ), + m_pPaperDimensions( nullptr ), + m_pDefaultInputSlot( nullptr ), + m_pDefaultResolution( nullptr ), + m_pTranslator( new PPDTranslator() ) +{ + // read in the file + std::vector< OString > aLines; + PPDDecompressStream aStream( m_aFile ); + if( aStream.IsOpen() ) + { + bool bLanguageEncoding = false; + while( ! aStream.eof() ) + { + OString aCurLine = aStream.ReadLine(); + if( aCurLine.startsWith("*") ) + { + if (aCurLine.matchIgnoreAsciiCase("*include:")) + { + aCurLine = aCurLine.copy(9); + aCurLine = comphelper::string::strip(aCurLine, ' '); + aCurLine = comphelper::string::strip(aCurLine, '\t'); + aCurLine = comphelper::string::stripEnd(aCurLine, '\r'); + aCurLine = comphelper::string::stripEnd(aCurLine, '\n'); + aCurLine = comphelper::string::strip(aCurLine, '"'); + aStream.Close(); + aStream.Open(getPPDFile(OStringToOUString(aCurLine, m_aFileEncoding))); + continue; + } + else if( ! bLanguageEncoding && + aCurLine.matchIgnoreAsciiCase("*languageencoding") ) + { + bLanguageEncoding = true; // generally only the first one counts + OString aLower = aCurLine.toAsciiLowerCase(); + if( aLower.indexOf("isolatin1", 17 ) != -1 || + aLower.indexOf("windowsansi", 17 ) != -1 ) + m_aFileEncoding = RTL_TEXTENCODING_MS_1252; + else if( aLower.indexOf("isolatin2", 17 ) != -1 ) + m_aFileEncoding = RTL_TEXTENCODING_ISO_8859_2; + else if( aLower.indexOf("isolatin5", 17 ) != -1 ) + m_aFileEncoding = RTL_TEXTENCODING_ISO_8859_5; + else if( aLower.indexOf("jis83-rksj", 17 ) != -1 ) + m_aFileEncoding = RTL_TEXTENCODING_SHIFT_JIS; + else if( aLower.indexOf("macstandard", 17 ) != -1 ) + m_aFileEncoding = RTL_TEXTENCODING_APPLE_ROMAN; + else if( aLower.indexOf("utf-8", 17 ) != -1 ) + m_aFileEncoding = RTL_TEXTENCODING_UTF8; + } + } + aLines.push_back( aCurLine ); + } + } + aStream.Close(); + + // now get the Values + parse( aLines ); +#if OSL_DEBUG_LEVEL > 1 + SAL_INFO("vcl.unx.print", "acquired " << m_aKeys.size() + << " Keys from PPD " << m_aFile << ":"); + for (auto const& key : m_aKeys) + { + const PPDKey* pKey = key.second.get(); + char const* pSetupType = "<unknown>"; + switch( pKey->m_eSetupType ) + { + case PPDKey::SetupType::ExitServer: pSetupType = "ExitServer";break; + case PPDKey::SetupType::Prolog: pSetupType = "Prolog";break; + case PPDKey::SetupType::DocumentSetup: pSetupType = "DocumentSetup";break; + case PPDKey::SetupType::PageSetup: pSetupType = "PageSetup";break; + case PPDKey::SetupType::JCLSetup: pSetupType = "JCLSetup";break; + case PPDKey::SetupType::AnySetup: pSetupType = "AnySetup";break; + default: break; + } + SAL_INFO("vcl.unx.print", "\t\"" << pKey->getKey() << "\" (" + << pKey->countValues() << "values) OrderDependency: " + << pKey->m_nOrderDependency << pSetupType ); + for( int j = 0; j < pKey->countValues(); j++ ) + { + const PPDValue* pValue = pKey->getValue( j ); + char const* pVType = "<unknown>"; + switch( pValue->m_eType ) + { + case eInvocation: pVType = "invocation";break; + case eQuoted: pVType = "quoted";break; + case eString: pVType = "string";break; + case eSymbol: pVType = "symbol";break; + case eNo: pVType = "no";break; + default: break; + } + SAL_INFO("vcl.unx.print", "\t\t" + << (pValue == pKey->m_pDefaultValue ? "(Default:) " : "") + << "option: \"" << pValue->m_aOption + << "\", value: type " << pVType << " \"" + << pValue->m_aValue << "\""); + } + } + SAL_INFO("vcl.unx.print", + "constraints: (" << m_aConstraints.size() << " found)"); + for (auto const& constraint : m_aConstraints) + { + SAL_INFO("vcl.unx.print", "*\"" << constraint.m_pKey1->getKey() << "\" \"" + << (constraint.m_pOption1 ? constraint.m_pOption1->m_aOption : "<nil>") + << "\" *\"" << constraint.m_pKey2->getKey() << "\" \"" + << (constraint.m_pOption2 ? constraint.m_pOption2->m_aOption : "<nil>") + << "\""); + } +#endif + + // fill in shortcuts + const PPDKey* pKey; + + m_pImageableAreas = getKey( "ImageableArea" ); + const PPDValue * pDefaultImageableArea = nullptr; + if( m_pImageableAreas ) + pDefaultImageableArea = m_pImageableAreas->getDefaultValue(); + if (m_pImageableAreas == nullptr) { + SAL_WARN( "vcl.unx.print", "no ImageableArea in " << m_aFile); + } + if (pDefaultImageableArea == nullptr) { + SAL_WARN( "vcl.unx.print", "no DefaultImageableArea in " << m_aFile); + } + + m_pPaperDimensions = getKey( "PaperDimension" ); + if( m_pPaperDimensions ) + m_pDefaultPaperDimension = m_pPaperDimensions->getDefaultValue(); + if (m_pPaperDimensions == nullptr) { + SAL_WARN( "vcl.unx.print", "no PaperDimensions in " << m_aFile); + } + if (m_pDefaultPaperDimension == nullptr) { + SAL_WARN( "vcl.unx.print", "no DefaultPaperDimensions in " << m_aFile); + } + + auto pResolutions = getKey( "Resolution" ); + if( pResolutions ) + m_pDefaultResolution = pResolutions->getDefaultValue(); + if (pResolutions == nullptr) { + SAL_WARN( "vcl.unx.print", "no Resolution in " << m_aFile); + } + SAL_INFO_IF(!m_pDefaultResolution, "vcl.unx.print", "no DefaultResolution in " + m_aFile); + + auto pInputSlots = getKey( "InputSlot" ); + if( pInputSlots ) + m_pDefaultInputSlot = pInputSlots->getDefaultValue(); + SAL_INFO_IF(!pInputSlots, "vcl.unx.print", "no InputSlot in " << m_aFile); + SAL_INFO_IF(!m_pDefaultInputSlot, "vcl.unx.print", "no DefaultInputSlot in " << m_aFile); + + auto pFontList = getKey( "Font" ); + if (pFontList == nullptr) { + SAL_WARN( "vcl.unx.print", "no Font in " << m_aFile); + } + + // fill in direct values + if ((pKey = getKey("ColorDevice"))) + { + if (const PPDValue* pValue = pKey->getValue(0)) + m_bColorDevice = pValue->m_aValue.startsWithIgnoreAsciiCase("true"); + } + + if ((pKey = getKey("LanguageLevel"))) + { + if (const PPDValue* pValue = pKey->getValue(0)) + m_nLanguageLevel = pValue->m_aValue.toInt32(); + } + if ((pKey = getKey("TTRasterizer"))) + { + if (const PPDValue* pValue = pKey->getValue(0)) + m_bType42Capable = pValue->m_aValue.equalsIgnoreAsciiCase( "Type42" ); + } +} + +PPDParser::~PPDParser() +{ + m_pTranslator.reset(); +} + +void PPDParser::insertKey( std::unique_ptr<PPDKey> pKey ) +{ + m_aOrderedKeys.push_back( pKey.get() ); + m_aKeys[ pKey->getKey() ] = std::move(pKey); +} + +const PPDKey* PPDParser::getKey( int n ) const +{ + return (n >= 0 && o3tl::make_unsigned(n) < m_aOrderedKeys.size()) ? m_aOrderedKeys[n] : nullptr; +} + +const PPDKey* PPDParser::getKey( const OUString& rKey ) const +{ + PPDParser::hash_type::const_iterator it = m_aKeys.find( rKey ); + return it != m_aKeys.end() ? it->second.get() : nullptr; +} + +bool PPDParser::hasKey( const PPDKey* pKey ) const +{ + return pKey && ( m_aKeys.find( pKey->getKey() ) != m_aKeys.end() ); +} + +static sal_uInt8 getNibble( char cChar ) +{ + sal_uInt8 nRet = 0; + if( cChar >= '0' && cChar <= '9' ) + nRet = sal_uInt8( cChar - '0' ); + else if( cChar >= 'A' && cChar <= 'F' ) + nRet = 10 + sal_uInt8( cChar - 'A' ); + else if( cChar >= 'a' && cChar <= 'f' ) + nRet = 10 + sal_uInt8( cChar - 'a' ); + return nRet; +} + +OUString PPDParser::handleTranslation(const OString& i_rString, bool bIsGlobalized) +{ + sal_Int32 nOrigLen = i_rString.getLength(); + OStringBuffer aTrans( nOrigLen ); + const char* pStr = i_rString.getStr(); + const char* pEnd = pStr + nOrigLen; + while( pStr < pEnd ) + { + if( *pStr == '<' ) + { + pStr++; + char cChar; + while( *pStr != '>' && pStr < pEnd-1 ) + { + cChar = getNibble( *pStr++ ) << 4; + cChar |= getNibble( *pStr++ ); + aTrans.append( cChar ); + } + pStr++; + } + else + aTrans.append( *pStr++ ); + } + return OStringToOUString( aTrans, bIsGlobalized ? RTL_TEXTENCODING_UTF8 : m_aFileEncoding ); +} + +namespace +{ + bool oddDoubleQuoteCount(OStringBuffer &rBuffer) + { + bool bHasOddCount = false; + for (sal_Int32 i = 0; i < rBuffer.getLength(); ++i) + { + if (rBuffer[i] == '"') + bHasOddCount = !bHasOddCount; + } + return bHasOddCount; + } +} + +void PPDParser::parse( ::std::vector< OString >& rLines ) +{ + // Name for PPD group into which all options are put for which the PPD + // does not explicitly define a group. + // This is similar to how CUPS handles it, + // s. Sweet, Michael R. (2001): Common UNIX Printing System, p. 251: + // "Each option in turn is associated with a group stored in the + // ppd_group_t structure. Groups can be specified in the PPD file; if an + // option is not associated with a group, it is put in a "General" or + // "Extra" group depending on the option. + static constexpr OStringLiteral aDefaultPPDGroupName("General"); + + std::vector< OString >::iterator line = rLines.begin(); + PPDParser::hash_type::const_iterator keyit; + + // name of the PPD group that is currently being processed + OString aCurrentGroup = aDefaultPPDGroupName; + + while( line != rLines.end() ) + { + OString aCurrentLine( *line ); + ++line; + + SAL_INFO("vcl.unx.print", "Parse line '" << aCurrentLine << "'"); + + if (aCurrentLine.getLength() < 2 || aCurrentLine[0] != '*') + continue; + if( aCurrentLine[1] == '%' ) + continue; + + OString aKey = GetCommandLineToken( 0, aCurrentLine.getToken(0, ':') ); + sal_Int32 nPos = aKey.indexOf('/'); + if (nPos != -1) + aKey = aKey.copy(0, nPos); + if(!aKey.isEmpty()) + { + aKey = aKey.copy(1); // remove the '*' + } + if(aKey.isEmpty()) + { + continue; + } + + if (aKey == "CloseGroup") + { + aCurrentGroup = aDefaultPPDGroupName; + continue; + } + if (aKey == "OpenGroup") + { + OString aGroupName = aCurrentLine; + sal_Int32 nPosition = aGroupName.indexOf('/'); + if (nPosition != -1) + { + aGroupName = aGroupName.copy(0, nPosition); + } + + aCurrentGroup = GetCommandLineToken(1, aGroupName); + continue; + } + if ((aKey == "CloseUI") || + (aKey == "JCLCloseUI") || + (aKey == "End") || + (aKey == "JCLEnd") || + (aKey == "OpenSubGroup") || + (aKey == "CloseSubGroup")) + { + continue; + } + + if ((aKey == "OpenUI") || (aKey == "JCLOpenUI")) + { + parseOpenUI( aCurrentLine, aCurrentGroup); + continue; + } + else if (aKey == "OrderDependency") + { + parseOrderDependency( aCurrentLine ); + continue; + } + else if (aKey == "UIConstraints" || + aKey == "NonUIConstraints") + { + continue; // parsed in pass 2 + } + else if( aKey == "CustomPageSize" ) // currently not handled + continue; + else if (aKey.startsWith("Custom", &aKey) ) + { + //fdo#43049 very basic support for Custom entries, we ignore the + //validation params and types + OUString aUniKey(OStringToOUString(aKey, RTL_TEXTENCODING_MS_1252)); + keyit = m_aKeys.find( aUniKey ); + if(keyit != m_aKeys.end()) + { + PPDKey* pKey = keyit->second.get(); + pKey->insertValue("Custom", eInvocation, true); + } + continue; + } + + // default values are parsed in pass 2 + if (aKey.startsWith("Default")) + continue; + + bool bQuery = false; + if (aKey[0] == '?') + { + aKey = aKey.copy(1); + bQuery = true; + } + + OUString aUniKey(OStringToOUString(aKey, RTL_TEXTENCODING_MS_1252)); + // handle CUPS extension for globalized PPDs + /* FIXME-BCP47: really only ISO 639-1 two character language codes? + * goodnight... */ + bool bIsGlobalizedLine = false; + css::lang::Locale aTransLocale; + if( ( aUniKey.getLength() > 3 && aUniKey[ 2 ] == '.' ) || + ( aUniKey.getLength() > 5 && aUniKey[ 2 ] == '_' && aUniKey[ 5 ] == '.' ) ) + { + if( aUniKey[ 2 ] == '.' ) + { + aTransLocale.Language = aUniKey.copy( 0, 2 ); + aUniKey = aUniKey.copy( 3 ); + } + else + { + aTransLocale.Language = aUniKey.copy( 0, 2 ); + aTransLocale.Country = aUniKey.copy( 3, 2 ); + aUniKey = aUniKey.copy( 6 ); + } + bIsGlobalizedLine = true; + } + + OUString aOption; + nPos = aCurrentLine.indexOf(':'); + if( nPos != -1 ) + { + aOption = OStringToOUString( + aCurrentLine.subView( 1, nPos-1 ), RTL_TEXTENCODING_MS_1252 ); + aOption = GetCommandLineToken( 1, aOption ); + sal_Int32 nTransPos = aOption.indexOf( '/' ); + if( nTransPos != -1 ) + aOption = aOption.copy(0, nTransPos); + } + + PPDValueType eType = eNo; + OUString aValue; + OUString aOptionTranslation; + OUString aValueTranslation; + if( nPos != -1 ) + { + // found a colon, there may be an option + OString aLine = aCurrentLine.copy( 1, nPos-1 ); + aLine = WhitespaceToSpace( aLine ); + sal_Int32 nTransPos = aLine.indexOf('/'); + if (nTransPos != -1) + aOptionTranslation = handleTranslation( aLine.copy(nTransPos+1), bIsGlobalizedLine ); + + // read in more lines if necessary for multiline values + aLine = aCurrentLine.copy( nPos+1 ); + if (!aLine.isEmpty()) + { + OStringBuffer aBuffer(aLine); + while (line != rLines.end() && oddDoubleQuoteCount(aBuffer)) + { + // copy the newlines also + aBuffer.append('\n'); + aBuffer.append(*line); + ++line; + } + aLine = aBuffer.makeStringAndClear(); + } + aLine = WhitespaceToSpace( aLine ); + + // #i100644# handle a missing value (actually a broken PPD) + if( aLine.isEmpty() ) + { + if( !aOption.isEmpty() && + !aUniKey.startsWith( "JCL" ) ) + eType = eInvocation; + else + eType = eQuoted; + } + // check for invocation or quoted value + else if(aLine[0] == '"') + { + aLine = aLine.copy(1); + nTransPos = aLine.indexOf('"'); + if (nTransPos == -1) + nTransPos = aLine.getLength(); + aValue = OStringToOUString(aLine.subView(0, nTransPos), RTL_TEXTENCODING_MS_1252); + // after the second doublequote can follow a / and a translation + if (nTransPos < aLine.getLength() - 2) + { + aValueTranslation = handleTranslation( aLine.copy( nTransPos+2 ), bIsGlobalizedLine ); + } + // check for quoted value + if( !aOption.isEmpty() && + !aUniKey.startsWith( "JCL" ) ) + eType = eInvocation; + else + eType = eQuoted; + } + // check for symbol value + else if(aLine[0] == '^') + { + aLine = aLine.copy(1); + aValue = OStringToOUString(aLine, RTL_TEXTENCODING_MS_1252); + eType = eSymbol; + } + else + { + // must be a string value then + // strictly this is false because string values + // can contain any whitespace which is reduced + // to one space by now + // who cares ... + nTransPos = aLine.indexOf('/'); + if (nTransPos == -1) + nTransPos = aLine.getLength(); + aValue = OStringToOUString(aLine.subView(0, nTransPos), RTL_TEXTENCODING_MS_1252); + if (nTransPos+1 < aLine.getLength()) + aValueTranslation = handleTranslation( aLine.copy( nTransPos+1 ), bIsGlobalizedLine ); + eType = eString; + } + } + + // handle globalized PPD entries + if( bIsGlobalizedLine ) + { + // handle main key translations of form: + // *ll_CC.Translation MainKeyword/translated text: "" + if( aUniKey == "Translation" ) + { + m_pTranslator->insertKey( aOption, aOptionTranslation, aTransLocale ); + } + // handle options translations of for: + // *ll_CC.MainKeyword OptionKeyword/translated text: "" + else + { + m_pTranslator->insertOption( aUniKey, aOption, aOptionTranslation, aTransLocale ); + } + continue; + } + + PPDKey* pKey = nullptr; + keyit = m_aKeys.find( aUniKey ); + if( keyit == m_aKeys.end() ) + { + pKey = new PPDKey( aUniKey ); + insertKey( std::unique_ptr<PPDKey>(pKey) ); + } + else + pKey = keyit->second.get(); + + if( eType == eNo && bQuery ) + continue; + + PPDValue* pValue = pKey->insertValue( aOption, eType ); + if( ! pValue ) + continue; + pValue->m_aValue = aValue; + + if( !aOptionTranslation.isEmpty() ) + m_pTranslator->insertOption( aUniKey, aOption, aOptionTranslation, aTransLocale ); + if( !aValueTranslation.isEmpty() ) + m_pTranslator->insertValue( aUniKey, aOption, aValue, aValueTranslation, aTransLocale ); + + // eventually update query and remove from option list + if( bQuery && !pKey->m_bQueryValue ) + { + pKey->m_bQueryValue = true; + pKey->eraseValue( pValue->m_aOption ); + } + } + + // second pass: fill in defaults + for( const auto& aLine : rLines ) + { + if (aLine.startsWith("*Default")) + { + SAL_INFO("vcl.unx.print", "Found a default: '" << aLine << "'"); + OUString aKey(OStringToOUString(aLine.subView(8), RTL_TEXTENCODING_MS_1252)); + sal_Int32 nPos = aKey.indexOf( ':' ); + if( nPos != -1 ) + { + aKey = aKey.copy(0, nPos); + OUString aOption(OStringToOUString( + WhitespaceToSpace(aLine.subView(nPos+9)), + RTL_TEXTENCODING_MS_1252)); + keyit = m_aKeys.find( aKey ); + if( keyit != m_aKeys.end() ) + { + PPDKey* pKey = keyit->second.get(); + const PPDValue* pDefValue = pKey->getValue( aOption ); + if( pKey->m_pDefaultValue == nullptr ) + pKey->m_pDefaultValue = pDefValue; + } + else + { + // some PPDs contain defaults for keys that + // do not exist otherwise + // (example: DefaultResolution) + // so invent that key here and have a default value + std::unique_ptr<PPDKey> pKey(new PPDKey( aKey )); + pKey->insertValue( aOption, eInvocation /*or what ?*/ ); + pKey->m_pDefaultValue = pKey->getValue( aOption ); + insertKey( std::move(pKey) ); + } + } + } + else if (aLine.startsWith("*UIConstraints") || + aLine.startsWith("*NonUIConstraints")) + { + parseConstraint( aLine ); + } + } +} + +void PPDParser::parseOpenUI(const OString& rLine, std::string_view rPPDGroup) +{ + OUString aTranslation; + OString aKey = rLine; + + sal_Int32 nPos = aKey.indexOf(':'); + if( nPos != -1 ) + aKey = aKey.copy(0, nPos); + nPos = aKey.indexOf('/'); + if( nPos != -1 ) + { + aTranslation = handleTranslation( aKey.copy( nPos + 1 ), false ); + aKey = aKey.copy(0, nPos); + } + aKey = GetCommandLineToken( 1, aKey ); + aKey = aKey.copy(1); + + OUString aUniKey(OStringToOUString(aKey, RTL_TEXTENCODING_MS_1252)); + PPDParser::hash_type::const_iterator keyit = m_aKeys.find( aUniKey ); + PPDKey* pKey; + if( keyit == m_aKeys.end() ) + { + pKey = new PPDKey( aUniKey ); + insertKey( std::unique_ptr<PPDKey>(pKey) ); + } + else + pKey = keyit->second.get(); + + pKey->m_bUIOption = true; + m_pTranslator->insertKey( pKey->getKey(), aTranslation ); + + pKey->m_aGroup = OStringToOUString(rPPDGroup, RTL_TEXTENCODING_MS_1252); +} + +void PPDParser::parseOrderDependency(const OString& rLine) +{ + OString aLine(rLine); + sal_Int32 nPos = aLine.indexOf(':'); + if( nPos != -1 ) + aLine = aLine.copy( nPos+1 ); + + sal_Int32 nOrder = GetCommandLineToken( 0, aLine ).toInt32(); + OString aSetup = GetCommandLineToken( 1, aLine ); + OUString aKey(OStringToOUString(GetCommandLineToken(2, aLine), RTL_TEXTENCODING_MS_1252)); + if( aKey[ 0 ] != '*' ) + return; // invalid order dependency + aKey = aKey.replaceAt( 0, 1, u"" ); + + PPDKey* pKey; + PPDParser::hash_type::const_iterator keyit = m_aKeys.find( aKey ); + if( keyit == m_aKeys.end() ) + { + pKey = new PPDKey( aKey ); + insertKey( std::unique_ptr<PPDKey>(pKey) ); + } + else + pKey = keyit->second.get(); + + pKey->m_nOrderDependency = nOrder; + if( aSetup == "ExitServer" ) + pKey->m_eSetupType = PPDKey::SetupType::ExitServer; + else if( aSetup == "Prolog" ) + pKey->m_eSetupType = PPDKey::SetupType::Prolog; + else if( aSetup == "DocumentSetup" ) + pKey->m_eSetupType = PPDKey::SetupType::DocumentSetup; + else if( aSetup == "PageSetup" ) + pKey->m_eSetupType = PPDKey::SetupType::PageSetup; + else if( aSetup == "JCLSetup" ) + pKey->m_eSetupType = PPDKey::SetupType::JCLSetup; + else + pKey->m_eSetupType = PPDKey::SetupType::AnySetup; +} + +void PPDParser::parseConstraint( const OString& rLine ) +{ + bool bFailed = false; + + OUString aLine(OStringToOUString(rLine, RTL_TEXTENCODING_MS_1252)); + sal_Int32 nIdx = rLine.indexOf(':'); + if (nIdx != -1) + aLine = aLine.replaceAt(0, nIdx + 1, u""); + PPDConstraint aConstraint; + int nTokens = GetCommandLineTokenCount( aLine ); + for( int i = 0; i < nTokens; i++ ) + { + OUString aToken = GetCommandLineToken( i, aLine ); + if( !aToken.isEmpty() && aToken[ 0 ] == '*' ) + { + aToken = aToken.replaceAt( 0, 1, u"" ); + if( aConstraint.m_pKey1 ) + aConstraint.m_pKey2 = getKey( aToken ); + else + aConstraint.m_pKey1 = getKey( aToken ); + } + else + { + if( aConstraint.m_pKey2 ) + { + if( ! ( aConstraint.m_pOption2 = aConstraint.m_pKey2->getValue( aToken ) ) ) + bFailed = true; + } + else if( aConstraint.m_pKey1 ) + { + if( ! ( aConstraint.m_pOption1 = aConstraint.m_pKey1->getValue( aToken ) ) ) + bFailed = true; + } + else + // constraint for nonexistent keys; this happens + // e.g. in HP4PLUS3 + bFailed = true; + } + } + // there must be two keywords + if( ! aConstraint.m_pKey1 || ! aConstraint.m_pKey2 || bFailed ) + { + SAL_INFO("vcl.unx.print", + "Warning: constraint \"" << rLine << "\" is invalid"); + } + else + m_aConstraints.push_back( aConstraint ); +} + +OUString PPDParser::getDefaultPaperDimension() const +{ + if( m_pDefaultPaperDimension ) + return m_pDefaultPaperDimension->m_aOption; + + return OUString(); +} + +bool PPDParser::getMargins( + std::u16string_view rPaperName, + int& rLeft, int& rRight, + int& rUpper, int& rLower ) const +{ + if( ! m_pImageableAreas || ! m_pPaperDimensions ) + return false; + + int nPDim=-1, nImArea=-1, i; + for( i = 0; i < m_pImageableAreas->countValues(); i++ ) + if( rPaperName == m_pImageableAreas->getValue( i )->m_aOption ) + nImArea = i; + for( i = 0; i < m_pPaperDimensions->countValues(); i++ ) + if( rPaperName == m_pPaperDimensions->getValue( i )->m_aOption ) + nPDim = i; + if( nPDim == -1 || nImArea == -1 ) + return false; + + double ImLLx, ImLLy, ImURx, ImURy; + double PDWidth, PDHeight; + OUString aArea = m_pImageableAreas->getValue( nImArea )->m_aValue; + ImLLx = StringToDouble( GetCommandLineToken( 0, aArea ) ); + ImLLy = StringToDouble( GetCommandLineToken( 1, aArea ) ); + ImURx = StringToDouble( GetCommandLineToken( 2, aArea ) ); + ImURy = StringToDouble( GetCommandLineToken( 3, aArea ) ); + aArea = m_pPaperDimensions->getValue( nPDim )->m_aValue; + PDWidth = StringToDouble( GetCommandLineToken( 0, aArea ) ); + PDHeight = StringToDouble( GetCommandLineToken( 1, aArea ) ); + rLeft = static_cast<int>(ImLLx + 0.5); + rLower = static_cast<int>(ImLLy + 0.5); + rUpper = static_cast<int>(PDHeight - ImURy + 0.5); + rRight = static_cast<int>(PDWidth - ImURx + 0.5); + + return true; +} + +bool PPDParser::getPaperDimension( + std::u16string_view rPaperName, + int& rWidth, int& rHeight ) const +{ + if( ! m_pPaperDimensions ) + return false; + + int nPDim=-1; + for( int i = 0; i < m_pPaperDimensions->countValues(); i++ ) + if( rPaperName == m_pPaperDimensions->getValue( i )->m_aOption ) + nPDim = i; + if( nPDim == -1 ) + return false; + + double PDWidth, PDHeight; + OUString aArea = m_pPaperDimensions->getValue( nPDim )->m_aValue; + PDWidth = StringToDouble( GetCommandLineToken( 0, aArea ) ); + PDHeight = StringToDouble( GetCommandLineToken( 1, aArea ) ); + rHeight = static_cast<int>(PDHeight + 0.5); + rWidth = static_cast<int>(PDWidth + 0.5); + + return true; +} + +OUString PPDParser::matchPaper( int nWidth, int nHeight ) const +{ + if( ! m_pPaperDimensions ) + return OUString(); + + int nPDim = -1; + double fSort = 2e36, fNewSort; + + for( int i = 0; i < m_pPaperDimensions->countValues(); i++ ) + { + OUString aArea = m_pPaperDimensions->getValue( i )->m_aValue; + double PDWidth = StringToDouble( GetCommandLineToken( 0, aArea ) ); + double PDHeight = StringToDouble( GetCommandLineToken( 1, aArea ) ); + PDWidth /= static_cast<double>(nWidth); + PDHeight /= static_cast<double>(nHeight); + if( PDWidth >= 0.9 && PDWidth <= 1.1 && + PDHeight >= 0.9 && PDHeight <= 1.1 ) + { + fNewSort = + (1.0-PDWidth)*(1.0-PDWidth) + (1.0-PDHeight)*(1.0-PDHeight); + if( fNewSort == 0.0 ) // perfect match + return m_pPaperDimensions->getValue( i )->m_aOption; + + if( fNewSort < fSort ) + { + fSort = fNewSort; + nPDim = i; + } + } + } + + static bool bDontSwap = false; + if( nPDim == -1 && ! bDontSwap ) + { + // swap portrait/landscape and try again + bDontSwap = true; + OUString rRet = matchPaper( nHeight, nWidth ); + bDontSwap = false; + return rRet; + } + + return nPDim != -1 ? m_pPaperDimensions->getValue( nPDim )->m_aOption : OUString(); +} + +OUString PPDParser::getDefaultInputSlot() const +{ + if( m_pDefaultInputSlot ) + return m_pDefaultInputSlot->m_aValue; + return OUString(); +} + +void PPDParser::getResolutionFromString(std::u16string_view rString, + int& rXRes, int& rYRes ) +{ + rXRes = rYRes = 300; + + const size_t nDPIPos {rString.find( u"dpi" )}; + if( nDPIPos != std::u16string_view::npos ) + { + const size_t nPos {rString.find( 'x' )}; + if( nPos != std::u16string_view::npos ) + { + rXRes = o3tl::toInt32(rString.substr( 0, nPos )); + rYRes = o3tl::toInt32(rString.substr(nPos+1, nDPIPos - nPos - 1)); + } + else + rXRes = rYRes = o3tl::toInt32(rString.substr( 0, nDPIPos )); + } +} + +void PPDParser::getDefaultResolution( int& rXRes, int& rYRes ) const +{ + if( m_pDefaultResolution ) + { + getResolutionFromString( m_pDefaultResolution->m_aValue, rXRes, rYRes ); + return; + } + + rXRes = 300; + rYRes = 300; +} + +OUString PPDParser::translateKey( const OUString& i_rKey ) const +{ + OUString aResult( m_pTranslator->translateKey( i_rKey ) ); + if( aResult.isEmpty() ) + aResult = i_rKey; + return aResult; +} + +OUString PPDParser::translateOption( const OUString& i_rKey, + const OUString& i_rOption ) const +{ + OUString aResult( m_pTranslator->translateOption( i_rKey, i_rOption ) ); + if( aResult.isEmpty() ) + aResult = i_rOption; + return aResult; +} + +/* + * PPDKey + */ + +PPDKey::PPDKey( const OUString& rKey ) : + m_aKey( rKey ), + m_pDefaultValue( nullptr ), + m_bQueryValue( false ), + m_bUIOption( false ), + m_nOrderDependency( 100 ), + m_eSetupType( SetupType::AnySetup ) +{ +} + +PPDKey::~PPDKey() +{ +} + +const PPDValue* PPDKey::getValue( int n ) const +{ + return (n >= 0 && o3tl::make_unsigned(n) < m_aOrderedValues.size()) ? m_aOrderedValues[n] : nullptr; +} + +const PPDValue* PPDKey::getValue( const OUString& rOption ) const +{ + PPDKey::hash_type::const_iterator it = m_aValues.find( rOption ); + return it != m_aValues.end() ? &it->second : nullptr; +} + +const PPDValue* PPDKey::getValueCaseInsensitive( const OUString& rOption ) const +{ + const PPDValue* pValue = getValue( rOption ); + if( ! pValue ) + { + for( size_t n = 0; n < m_aOrderedValues.size() && ! pValue; n++ ) + if( m_aOrderedValues[n]->m_aOption.equalsIgnoreAsciiCase( rOption ) ) + pValue = m_aOrderedValues[n]; + } + + return pValue; +} + +void PPDKey::eraseValue( const OUString& rOption ) +{ + PPDKey::hash_type::iterator it = m_aValues.find( rOption ); + if( it == m_aValues.end() ) + return; + + auto vit = std::find(m_aOrderedValues.begin(), m_aOrderedValues.end(), &(it->second )); + if( vit != m_aOrderedValues.end() ) + m_aOrderedValues.erase( vit ); + + m_aValues.erase( it ); +} + +PPDValue* PPDKey::insertValue(const OUString& rOption, PPDValueType eType, bool bCustomOption) +{ + if( m_aValues.find( rOption ) != m_aValues.end() ) + return nullptr; + + PPDValue aValue; + aValue.m_aOption = rOption; + aValue.m_bCustomOption = bCustomOption; + aValue.m_eType = eType; + m_aValues[ rOption ] = aValue; + PPDValue* pValue = &m_aValues[rOption]; + m_aOrderedValues.push_back( pValue ); + return pValue; +} + +/* + * PPDContext + */ + +PPDContext::PPDContext() : + m_pParser( nullptr ) +{ +} + +PPDContext& PPDContext::operator=( PPDContext&& rCopy ) +{ + std::swap(m_pParser, rCopy.m_pParser); + std::swap(m_aCurrentValues, rCopy.m_aCurrentValues); + return *this; +} + +const PPDKey* PPDContext::getModifiedKey( std::size_t n ) const +{ + if( m_aCurrentValues.size() <= n ) + return nullptr; + + hash_type::const_iterator it = m_aCurrentValues.begin(); + std::advance(it, n); + return it->first; +} + +void PPDContext::setParser( const PPDParser* pParser ) +{ + if( pParser != m_pParser ) + { + m_aCurrentValues.clear(); + m_pParser = pParser; + } +} + +const PPDValue* PPDContext::getValue( const PPDKey* pKey ) const +{ + if( ! m_pParser ) + return nullptr; + + hash_type::const_iterator it = m_aCurrentValues.find( pKey ); + if( it != m_aCurrentValues.end() ) + return it->second; + + if( ! m_pParser->hasKey( pKey ) ) + return nullptr; + + const PPDValue* pValue = pKey->getDefaultValue(); + if( ! pValue ) + pValue = pKey->getValue( 0 ); + + return pValue; +} + +const PPDValue* PPDContext::setValue( const PPDKey* pKey, const PPDValue* pValue, bool bDontCareForConstraints ) +{ + if( ! m_pParser || ! pKey ) + return nullptr; + + // pValue can be NULL - it means ignore this option + + if( ! m_pParser->hasKey( pKey ) ) + return nullptr; + + // check constraints + if( pValue ) + { + if( bDontCareForConstraints ) + { + m_aCurrentValues[ pKey ] = pValue; + } + else if( checkConstraints( pKey, pValue, true ) ) + { + m_aCurrentValues[ pKey ] = pValue; + + // after setting this value, check all constraints ! + hash_type::iterator it = m_aCurrentValues.begin(); + while( it != m_aCurrentValues.end() ) + { + if( it->first != pKey && + ! checkConstraints( it->first, it->second, false ) ) + { + SAL_INFO("vcl.unx.print", "PPDContext::setValue: option " + << it->first->getKey() + << " (" << it->second->m_aOption + << ") is constrained after setting " + << pKey->getKey() + << " to " << pValue->m_aOption); + resetValue( it->first, true ); + it = m_aCurrentValues.begin(); + } + else + ++it; + } + } + } + else + m_aCurrentValues[ pKey ] = nullptr; + + return pValue; +} + +bool PPDContext::checkConstraints( const PPDKey* pKey, const PPDValue* pValue ) +{ + if( ! m_pParser || ! pKey || ! pValue ) + return false; + + // ensure that this key is already in the list if it exists at all + if( m_aCurrentValues.find( pKey ) != m_aCurrentValues.end() ) + return checkConstraints( pKey, pValue, false ); + + // it is not in the list, insert it temporarily + bool bRet = false; + if( m_pParser->hasKey( pKey ) ) + { + const PPDValue* pDefValue = pKey->getDefaultValue(); + m_aCurrentValues[ pKey ] = pDefValue; + bRet = checkConstraints( pKey, pValue, false ); + m_aCurrentValues.erase( pKey ); + } + + return bRet; +} + +bool PPDContext::resetValue( const PPDKey* pKey, bool bDefaultable ) +{ + if( ! pKey || ! m_pParser || ! m_pParser->hasKey( pKey ) ) + return false; + + const PPDValue* pResetValue = pKey->getValue( "None" ); + if( ! pResetValue ) + pResetValue = pKey->getValue( "False" ); + if( ! pResetValue && bDefaultable ) + pResetValue = pKey->getDefaultValue(); + + bool bRet = pResetValue && ( setValue( pKey, pResetValue ) == pResetValue ); + + return bRet; +} + +bool PPDContext::checkConstraints( const PPDKey* pKey, const PPDValue* pNewValue, bool bDoReset ) +{ + if( ! pNewValue ) + return true; + + // sanity checks + if( ! m_pParser ) + return false; + + if( pKey->getValue( pNewValue->m_aOption ) != pNewValue ) + return false; + + // None / False and the default can always be set, but be careful ! + // setting them might influence constrained values + if( pNewValue->m_aOption == "None" || pNewValue->m_aOption == "False" || + pNewValue == pKey->getDefaultValue() ) + return true; + + const ::std::vector< PPDParser::PPDConstraint >& rConstraints( m_pParser->getConstraints() ); + for (auto const& constraint : rConstraints) + { + const PPDKey* pLeft = constraint.m_pKey1; + const PPDKey* pRight = constraint.m_pKey2; + if( ! pLeft || ! pRight || ( pKey != pLeft && pKey != pRight ) ) + continue; + + const PPDKey* pOtherKey = pKey == pLeft ? pRight : pLeft; + const PPDValue* pOtherKeyOption = pKey == pLeft ? constraint.m_pOption2 : constraint.m_pOption1; + const PPDValue* pKeyOption = pKey == pLeft ? constraint.m_pOption1 : constraint.m_pOption2; + + // syntax *Key1 option1 *Key2 option2 + if( pKeyOption && pOtherKeyOption ) + { + if( pNewValue != pKeyOption ) + continue; + if( pOtherKeyOption == getValue( pOtherKey ) ) + { + return false; + } + } + // syntax *Key1 option *Key2 or *Key1 *Key2 option + else if( pOtherKeyOption || pKeyOption ) + { + if( pKeyOption ) + { + if( ! ( pOtherKeyOption = getValue( pOtherKey ) ) ) + continue; // this should not happen, PPD broken + + if( pKeyOption == pNewValue && + pOtherKeyOption->m_aOption != "None" && + pOtherKeyOption->m_aOption != "False" ) + { + // check if the other value can be reset and + // do so if possible + if( bDoReset && resetValue( pOtherKey ) ) + continue; + + return false; + } + } + else if( pOtherKeyOption ) + { + if( getValue( pOtherKey ) == pOtherKeyOption && + pNewValue->m_aOption != "None" && + pNewValue->m_aOption != "False" ) + return false; + } + else + { + // this should not happen, PPD is broken + } + } + // syntax *Key1 *Key2 + else + { + const PPDValue* pOtherValue = getValue( pOtherKey ); + if( pOtherValue->m_aOption != "None" && + pOtherValue->m_aOption != "False" && + pNewValue->m_aOption != "None" && + pNewValue->m_aOption != "False" ) + return false; + } + } + return true; +} + +char* PPDContext::getStreamableBuffer( sal_uLong& rBytes ) const +{ + rBytes = 0; + if( m_aCurrentValues.empty() ) + return nullptr; + for (auto const& elem : m_aCurrentValues) + { + OString aCopy(OUStringToOString(elem.first->getKey(), RTL_TEXTENCODING_MS_1252)); + rBytes += aCopy.getLength(); + rBytes += 1; // for ':' + if( elem.second ) + { + aCopy = OUStringToOString(elem.second->m_aOption, RTL_TEXTENCODING_MS_1252); + rBytes += aCopy.getLength(); + } + else + rBytes += 4; + rBytes += 1; // for '\0' + } + rBytes += 1; + char* pBuffer = new char[ rBytes ]; + memset( pBuffer, 0, rBytes ); + char* pRun = pBuffer; + for (auto const& elem : m_aCurrentValues) + { + OString aCopy(OUStringToOString(elem.first->getKey(), RTL_TEXTENCODING_MS_1252)); + int nBytes = aCopy.getLength(); + memcpy( pRun, aCopy.getStr(), nBytes ); + pRun += nBytes; + *pRun++ = ':'; + if( elem.second ) + aCopy = OUStringToOString(elem.second->m_aOption, RTL_TEXTENCODING_MS_1252); + else + aCopy = "*nil"; + nBytes = aCopy.getLength(); + memcpy( pRun, aCopy.getStr(), nBytes ); + pRun += nBytes; + + *pRun++ = 0; + } + return pBuffer; +} + +void PPDContext::rebuildFromStreamBuffer(const std::vector<char> &rBuffer) +{ + if( ! m_pParser ) + return; + + m_aCurrentValues.clear(); + + const size_t nBytes = rBuffer.size() - 1; + size_t nRun = 0; + while (nRun < nBytes && rBuffer[nRun]) + { + OString aLine(rBuffer.data() + nRun); + sal_Int32 nPos = aLine.indexOf(':'); + if( nPos != -1 ) + { + const PPDKey* pKey = m_pParser->getKey( OStringToOUString( aLine.subView( 0, nPos ), RTL_TEXTENCODING_MS_1252 ) ); + if( pKey ) + { + const PPDValue* pValue = nullptr; + OUString aOption( + OStringToOUString(aLine.subView(nPos+1), RTL_TEXTENCODING_MS_1252)); + if (aOption != "*nil") + pValue = pKey->getValue( aOption ); + m_aCurrentValues[ pKey ] = pValue; + SAL_INFO("vcl.unx.print", + "PPDContext::rebuildFromStreamBuffer: read PPDKeyValue { " + << pKey->getKey() << " , " + << (pValue ? aOption : "<nil>") + << " }"); + } + } + nRun += aLine.getLength()+1; + } +} + +int PPDContext::getRenderResolution() const +{ + // initialize to reasonable default, if parser is not set + int nDPI = 300; + if( m_pParser ) + { + int nDPIx = 300, nDPIy = 300; + const PPDKey* pKey = m_pParser->getKey( "Resolution" ); + if( pKey ) + { + const PPDValue* pValue = getValue( pKey ); + if( pValue ) + PPDParser::getResolutionFromString( pValue->m_aOption, nDPIx, nDPIy ); + else + m_pParser->getDefaultResolution( nDPIx, nDPIy ); + } + else + m_pParser->getDefaultResolution( nDPIx, nDPIy ); + + nDPI = std::max(nDPIx, nDPIy); + } + return nDPI; +} + +void PPDContext::getPageSize( OUString& rPaper, int& rWidth, int& rHeight ) const +{ + // initialize to reasonable default, if parser is not set + rPaper = "A4"; + rWidth = 595; + rHeight = 842; + if( !m_pParser ) + return; + + const PPDKey* pKey = m_pParser->getKey( "PageSize" ); + if( !pKey ) + return; + + const PPDValue* pValue = getValue( pKey ); + if( pValue ) + { + rPaper = pValue->m_aOption; + m_pParser->getPaperDimension( rPaper, rWidth, rHeight ); + } + else + { + rPaper = m_pParser->getDefaultPaperDimension(); + m_pParser->getDefaultPaperDimension( rWidth, rHeight ); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/generic/printer/printerinfomanager.cxx b/vcl/unx/generic/printer/printerinfomanager.cxx new file mode 100644 index 000000000..ae87f6adf --- /dev/null +++ b/vcl/unx/generic/printer/printerinfomanager.cxx @@ -0,0 +1,892 @@ +/* -*- 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 <unx/cpdmgr.hxx> +#include <unx/cupsmgr.hxx> +#include <unx/gendata.hxx> +#include <unx/helper.hxx> + +#include <tools/urlobj.hxx> +#include <tools/config.hxx> + +#include <i18nutil/paper.hxx> +#include <rtl/strbuf.hxx> +#include <sal/log.hxx> + +#include <osl/file.hxx> +#include <osl/thread.hxx> +#include <osl/mutex.hxx> +#include <o3tl/string_view.hxx> + +// filename of configuration files +constexpr OUStringLiteral PRINT_FILENAME = u"psprint.conf"; +// the group of the global defaults +constexpr OStringLiteral GLOBAL_DEFAULTS_GROUP = "__Global_Printer_Defaults__"; + +#include <cstddef> +#include <unordered_set> + +using namespace psp; +using namespace osl; + +namespace psp +{ + class SystemQueueInfo final : public Thread + { + mutable Mutex m_aMutex; + bool m_bChanged; + std::vector< PrinterInfoManager::SystemPrintQueue > + m_aQueues; + OUString m_aCommand; + + virtual void SAL_CALL run() override; + + public: + SystemQueueInfo(); + virtual ~SystemQueueInfo() override; + + bool hasChanged() const; + OUString getCommand() const; + + // sets changed status to false; therefore not const + void getSystemQueues( std::vector< PrinterInfoManager::SystemPrintQueue >& rQueues ); + }; +} // namespace + +/* +* class PrinterInfoManager +*/ + +PrinterInfoManager& PrinterInfoManager::get() +{ + // can't move to GenericUnixSalData, because of vcl/null/printerinfomanager.cxx + GenericUnixSalData* pSalData = GetGenericUnixSalData(); + PrinterInfoManager* pPIM = pSalData->m_pPrinterInfoManager.get(); + if (pPIM) + return *pPIM; + + pPIM = CPDManager::tryLoadCPD(); + if (!pPIM) + pPIM = CUPSManager::tryLoadCUPS(); + if (!pPIM) + pPIM = new PrinterInfoManager(); + pSalData->m_pPrinterInfoManager.reset(pPIM); + pPIM->initialize(); + + SAL_INFO("vcl.unx.print", "created PrinterInfoManager of type " + << static_cast<int>(pPIM->getType())); + return *pPIM; +} + +PrinterInfoManager::PrinterInfoManager( Type eType ) : + m_eType( eType ), + m_bUseIncludeFeature( false ), + m_bUseJobPatch( true ), + m_aSystemDefaultPaper( "A4" ) +{ + if( eType == Type::Default ) + m_pQueueInfo.reset( new SystemQueueInfo ); + + m_aSystemDefaultPaper = OStringToOUString( + PaperInfo::toPSName(PaperInfo::getSystemDefaultPaper().getPaper()), + RTL_TEXTENCODING_UTF8); +} + +PrinterInfoManager::~PrinterInfoManager() +{ +#if OSL_DEBUG_LEVEL > 1 + SAL_INFO("vcl.unx.print", "PrinterInfoManager: " + << "destroyed Manager of type " + << ((int) getType())); +#endif +} + +bool PrinterInfoManager::checkPrintersChanged( bool bWait ) +{ + // check if files were created, deleted or modified since initialize() + bool bChanged = false; + for (auto const& watchFile : m_aWatchFiles) + { + DirectoryItem aItem; + if( DirectoryItem::get( watchFile.m_aFilePath, aItem ) ) + { + if( watchFile.m_aModified.Seconds != 0 ) + { + bChanged = true; // file probably has vanished + break; + } + } + else + { + FileStatus aStatus( osl_FileStatus_Mask_ModifyTime ); + if( aItem.getFileStatus( aStatus ) ) + { + bChanged = true; // unlikely but not impossible + break; + } + else + { + TimeValue aModified = aStatus.getModifyTime(); + if( aModified.Seconds != watchFile.m_aModified.Seconds ) + { + bChanged = true; + break; + } + } + } + } + + if( bWait && m_pQueueInfo ) + { +#if OSL_DEBUG_LEVEL > 1 + SAL_INFO("vcl.unx.print", "syncing printer discovery thread."); +#endif + m_pQueueInfo->join(); +#if OSL_DEBUG_LEVEL > 1 + SAL_INFO("vcl.unx.print", "done: syncing printer discovery thread."); +#endif + } + + if( ! bChanged && m_pQueueInfo ) + bChanged = m_pQueueInfo->hasChanged(); + if( bChanged ) + { + initialize(); + } + + return bChanged; +} + +void PrinterInfoManager::initialize() +{ + m_bUseIncludeFeature = false; + m_aPrinters.clear(); + m_aWatchFiles.clear(); + OUString aDefaultPrinter; + + // first initialize the global defaults + // have to iterate over all possible files + // there should be only one global setup section in all + // available config files + m_aGlobalDefaults = PrinterInfo(); + + // need a parser for the PPDContext. generic printer should do. + m_aGlobalDefaults.m_pParser = PPDParser::getParser( "SGENPRT" ); + m_aGlobalDefaults.m_aContext.setParser( m_aGlobalDefaults.m_pParser ); + + if( ! m_aGlobalDefaults.m_pParser ) + { +#if OSL_DEBUG_LEVEL > 1 + SAL_INFO("vcl.unx.print", "Error: no default PPD file " + << "SGENPRT available, shutting down psprint..."); +#endif + return; + } + + std::vector< OUString > aDirList; + psp::getPrinterPathList( aDirList, nullptr ); + for (auto const& printDir : aDirList) + { + INetURLObject aFile( printDir, INetProtocol::File, INetURLObject::EncodeMechanism::All ); + aFile.Append( PRINT_FILENAME ); + Config aConfig( aFile.PathToFileName() ); + if( aConfig.HasGroup( GLOBAL_DEFAULTS_GROUP ) ) + { +#if OSL_DEBUG_LEVEL > 1 + SAL_INFO("vcl.unx.print", "found global defaults in " + << aFile.PathToFileName()); +#endif + aConfig.SetGroup( GLOBAL_DEFAULTS_GROUP ); + + OString aValue( aConfig.ReadKey( "Copies" ) ); + if (!aValue.isEmpty()) + m_aGlobalDefaults.m_nCopies = aValue.toInt32(); + + aValue = aConfig.ReadKey( "Orientation" ); + if (!aValue.isEmpty()) + m_aGlobalDefaults.m_eOrientation = aValue.equalsIgnoreAsciiCase("Landscape") ? orientation::Landscape : orientation::Portrait; + + aValue = aConfig.ReadKey( "MarginAdjust" ); + if (!aValue.isEmpty()) + { + sal_Int32 nIdx {0}; + m_aGlobalDefaults.m_nLeftMarginAdjust = o3tl::toInt32(o3tl::getToken(aValue, 0, ',', nIdx)); + m_aGlobalDefaults.m_nRightMarginAdjust = o3tl::toInt32(o3tl::getToken(aValue, 0, ',', nIdx)); + m_aGlobalDefaults.m_nTopMarginAdjust = o3tl::toInt32(o3tl::getToken(aValue, 0, ',', nIdx)); + m_aGlobalDefaults.m_nBottomMarginAdjust = o3tl::toInt32(o3tl::getToken(aValue, 0, ',', nIdx)); + } + + aValue = aConfig.ReadKey( "ColorDepth", "24" ); + if (!aValue.isEmpty()) + m_aGlobalDefaults.m_nColorDepth = aValue.toInt32(); + + aValue = aConfig.ReadKey( "ColorDevice" ); + if (!aValue.isEmpty()) + m_aGlobalDefaults.m_nColorDevice = aValue.toInt32(); + + aValue = aConfig.ReadKey( "PSLevel" ); + if (!aValue.isEmpty()) + m_aGlobalDefaults.m_nPSLevel = aValue.toInt32(); + + aValue = aConfig.ReadKey( "PDFDevice" ); + if (!aValue.isEmpty()) + m_aGlobalDefaults.m_nPDFDevice = aValue.toInt32(); + + // get the PPDContext of global JobData + for( int nKey = 0; nKey < aConfig.GetKeyCount(); ++nKey ) + { + OString aKey( aConfig.GetKeyName( nKey ) ); + if (aKey.startsWith("PPD_")) + { + aValue = aConfig.ReadKey( aKey ); + const PPDKey* pKey = m_aGlobalDefaults.m_pParser->getKey(OStringToOUString(aKey.subView(4), RTL_TEXTENCODING_ISO_8859_1)); + if( pKey ) + { + m_aGlobalDefaults.m_aContext. + setValue( pKey, + aValue == "*nil" ? nullptr : pKey->getValue(OStringToOUString(aValue, RTL_TEXTENCODING_ISO_8859_1)), + true ); + } + } + } + } + } + setDefaultPaper( m_aGlobalDefaults.m_aContext ); + + // now collect all available printers + for (auto const& printDir : aDirList) + { + INetURLObject aDir( printDir, INetProtocol::File, INetURLObject::EncodeMechanism::All ); + INetURLObject aFile( aDir ); + aFile.Append( PRINT_FILENAME ); + + // check directory validity + OUString aUniPath; + FileBase::getFileURLFromSystemPath( aDir.PathToFileName(), aUniPath ); + Directory aDirectory( aUniPath ); + if( aDirectory.open() ) + continue; + aDirectory.close(); + + FileBase::getFileURLFromSystemPath( aFile.PathToFileName(), aUniPath ); + FileStatus aStatus( osl_FileStatus_Mask_ModifyTime ); + DirectoryItem aItem; + + // setup WatchFile list + WatchFile aWatchFile; + aWatchFile.m_aFilePath = aUniPath; + if( ! DirectoryItem::get( aUniPath, aItem ) && + ! aItem.getFileStatus( aStatus ) ) + { + aWatchFile.m_aModified = aStatus.getModifyTime(); + } + else + { + aWatchFile.m_aModified.Seconds = 0; + aWatchFile.m_aModified.Nanosec = 0; + } + m_aWatchFiles.push_back( aWatchFile ); + + Config aConfig( aFile.PathToFileName() ); + for( int nGroup = 0; nGroup < aConfig.GetGroupCount(); nGroup++ ) + { + aConfig.SetGroup( aConfig.GetGroupName( nGroup ) ); + OString aValue = aConfig.ReadKey( "Printer" ); + if (!aValue.isEmpty()) + { + OUString aPrinterName; + + sal_Int32 nNamePos = aValue.indexOf('/'); + // check for valid value of "Printer" + if (nNamePos == -1) + continue; + + Printer aPrinter; + // initialize to global defaults + aPrinter.m_aInfo = m_aGlobalDefaults; + + aPrinterName = OStringToOUString(aValue.subView(nNamePos+1), + RTL_TEXTENCODING_UTF8); + aPrinter.m_aInfo.m_aPrinterName = aPrinterName; + aPrinter.m_aInfo.m_aDriverName = OStringToOUString(aValue.subView(0, nNamePos), RTL_TEXTENCODING_UTF8); + + // set parser, merge settings + // don't do this for CUPS printers as this is done + // by the CUPS system itself + if( !aPrinter.m_aInfo.m_aDriverName.startsWith( "CUPS:" ) ) + { + aPrinter.m_aInfo.m_pParser = PPDParser::getParser( aPrinter.m_aInfo.m_aDriverName ); + aPrinter.m_aInfo.m_aContext.setParser( aPrinter.m_aInfo.m_pParser ); + // note: setParser also purges the context + + // ignore this printer if its driver is not found + if( ! aPrinter.m_aInfo.m_pParser ) + continue; + + // merge the ppd context keys if the printer has the same keys and values + // this is a bit tricky, since it involves mixing two PPDs + // without constraints which might end up badly + // this feature should be use with caution + // it is mainly to select default paper sizes for new printers + for( std::size_t nPPDValueModified = 0; nPPDValueModified < m_aGlobalDefaults.m_aContext.countValuesModified(); nPPDValueModified++ ) + { + const PPDKey* pDefKey = m_aGlobalDefaults.m_aContext.getModifiedKey( nPPDValueModified ); + const PPDValue* pDefValue = m_aGlobalDefaults.m_aContext.getValue( pDefKey ); + const PPDKey* pPrinterKey = pDefKey ? aPrinter.m_aInfo.m_pParser->getKey( pDefKey->getKey() ) : nullptr; + if( pDefKey && pPrinterKey ) + // at least the options exist in both PPDs + { + if( pDefValue ) + { + const PPDValue* pPrinterValue = pPrinterKey->getValue( pDefValue->m_aOption ); + if( pPrinterValue ) + // the printer has a corresponding option for the key + aPrinter.m_aInfo.m_aContext.setValue( pPrinterKey, pPrinterValue ); + } + else + aPrinter.m_aInfo.m_aContext.setValue( pPrinterKey, nullptr ); + } + } + + aValue = aConfig.ReadKey( "Command" ); + // no printer without a command + if (aValue.isEmpty()) + { + /* TODO: + * porters: please append your platform to the Solaris + * case if your platform has SystemV printing per default. + */ + #if defined __sun + aValue = "lp"; + #else + aValue = "lpr"; + #endif + } + aPrinter.m_aInfo.m_aCommand = OStringToOUString(aValue, RTL_TEXTENCODING_UTF8); + } + + aValue = aConfig.ReadKey( "QuickCommand" ); + aPrinter.m_aInfo.m_aQuickCommand = OStringToOUString(aValue, RTL_TEXTENCODING_UTF8); + + aValue = aConfig.ReadKey( "Features" ); + aPrinter.m_aInfo.m_aFeatures = OStringToOUString(aValue, RTL_TEXTENCODING_UTF8); + + // override the settings in m_aGlobalDefaults if keys exist + aValue = aConfig.ReadKey( "DefaultPrinter" ); + if (aValue != "0" && !aValue.equalsIgnoreAsciiCase("false")) + aDefaultPrinter = aPrinterName; + + aValue = aConfig.ReadKey( "Location" ); + aPrinter.m_aInfo.m_aLocation = OStringToOUString(aValue, RTL_TEXTENCODING_UTF8); + + aValue = aConfig.ReadKey( "Comment" ); + aPrinter.m_aInfo.m_aComment = OStringToOUString(aValue, RTL_TEXTENCODING_UTF8); + + aValue = aConfig.ReadKey( "Copies" ); + if (!aValue.isEmpty()) + aPrinter.m_aInfo.m_nCopies = aValue.toInt32(); + + aValue = aConfig.ReadKey( "Orientation" ); + if (!aValue.isEmpty()) + aPrinter.m_aInfo.m_eOrientation = aValue.equalsIgnoreAsciiCase("Landscape") ? orientation::Landscape : orientation::Portrait; + + aValue = aConfig.ReadKey( "MarginAdjust" ); + if (!aValue.isEmpty()) + { + sal_Int32 nIdx {0}; + aPrinter.m_aInfo.m_nLeftMarginAdjust = o3tl::toInt32(o3tl::getToken(aValue, 0, ',', nIdx)); + aPrinter.m_aInfo.m_nRightMarginAdjust = o3tl::toInt32(o3tl::getToken(aValue, 0, ',', nIdx)); + aPrinter.m_aInfo.m_nTopMarginAdjust = o3tl::toInt32(o3tl::getToken(aValue, 0, ',', nIdx)); + aPrinter.m_aInfo.m_nBottomMarginAdjust = o3tl::toInt32(o3tl::getToken(aValue, 0, ',', nIdx)); + } + + aValue = aConfig.ReadKey( "ColorDepth" ); + if (!aValue.isEmpty()) + aPrinter.m_aInfo.m_nColorDepth = aValue.toInt32(); + + aValue = aConfig.ReadKey( "ColorDevice" ); + if (!aValue.isEmpty()) + aPrinter.m_aInfo.m_nColorDevice = aValue.toInt32(); + + aValue = aConfig.ReadKey( "PSLevel" ); + if (!aValue.isEmpty()) + aPrinter.m_aInfo.m_nPSLevel = aValue.toInt32(); + + aValue = aConfig.ReadKey( "PDFDevice" ); + if (!aValue.isEmpty()) + aPrinter.m_aInfo.m_nPDFDevice = aValue.toInt32(); + + // now iterate over all keys to extract multi key information: + // 1. PPDContext information + for( int nKey = 0; nKey < aConfig.GetKeyCount(); ++nKey ) + { + OString aKey( aConfig.GetKeyName( nKey ) ); + if( aKey.startsWith("PPD_") && aPrinter.m_aInfo.m_pParser ) + { + aValue = aConfig.ReadKey( aKey ); + const PPDKey* pKey = aPrinter.m_aInfo.m_pParser->getKey(OStringToOUString(aKey.subView(4), RTL_TEXTENCODING_ISO_8859_1)); + if( pKey ) + { + aPrinter.m_aInfo.m_aContext. + setValue( pKey, + aValue == "*nil" ? nullptr : pKey->getValue(OStringToOUString(aValue, RTL_TEXTENCODING_ISO_8859_1)), + true ); + } + } + } + + setDefaultPaper( aPrinter.m_aInfo.m_aContext ); + + // if it's a "Generic Printer", apply defaults from config... + aPrinter.m_aInfo.resolveDefaultBackend(); + + // finally insert printer + FileBase::getFileURLFromSystemPath( aFile.PathToFileName(), aPrinter.m_aFile ); + std::unordered_map< OUString, Printer >::const_iterator find_it = + m_aPrinters.find( aPrinterName ); + if( find_it != m_aPrinters.end() ) + { + aPrinter.m_aAlternateFiles = find_it->second.m_aAlternateFiles; + aPrinter.m_aAlternateFiles.insert( find_it->second.m_aFile ); + } + m_aPrinters[ aPrinterName ] = aPrinter; + } + } + } + + // set default printer + if( !m_aPrinters.empty() ) + { + if( m_aPrinters.find( aDefaultPrinter ) == m_aPrinters.end() ) + aDefaultPrinter = m_aPrinters.begin()->first; + } + else + aDefaultPrinter.clear(); + m_aDefaultPrinter = aDefaultPrinter; + + if( m_eType != Type::Default ) + return; + + // add a default printer for every available print queue + // merge paper default printer, all else from global defaults + PrinterInfo aMergeInfo( m_aGlobalDefaults ); + aMergeInfo.m_aDriverName = "SGENPRT"; + aMergeInfo.m_aFeatures = "autoqueue"; + + if( !m_aDefaultPrinter.isEmpty() ) + { + PrinterInfo aDefaultInfo( getPrinterInfo( m_aDefaultPrinter ) ); + + const PPDKey* pDefKey = aDefaultInfo.m_pParser->getKey( "PageSize" ); + const PPDKey* pMergeKey = aMergeInfo.m_pParser->getKey( "PageSize" ); + const PPDValue* pDefValue = aDefaultInfo.m_aContext.getValue( pDefKey ); + const PPDValue* pMergeValue = pMergeKey ? pMergeKey->getValue( pDefValue->m_aOption ) : nullptr; + if( pMergeKey && pMergeValue ) + aMergeInfo.m_aContext.setValue( pMergeKey, pMergeValue ); + } + + if( m_pQueueInfo && m_pQueueInfo->hasChanged() ) + { + m_aSystemPrintCommand = m_pQueueInfo->getCommand(); + m_pQueueInfo->getSystemQueues( m_aSystemPrintQueues ); + m_pQueueInfo.reset(); + } + for (auto const& printQueue : m_aSystemPrintQueues) + { + OUString aPrinterName = "<" + printQueue.m_aQueue + ">"; + + if( m_aPrinters.find( aPrinterName ) != m_aPrinters.end() ) + // probably user made this one permanent + continue; + + OUString aCmd( m_aSystemPrintCommand ); + aCmd = aCmd.replaceAll( "(PRINTER)", printQueue.m_aQueue ); + + Printer aPrinter; + + // initialize to merged defaults + aPrinter.m_aInfo = aMergeInfo; + aPrinter.m_aInfo.m_aPrinterName = aPrinterName; + aPrinter.m_aInfo.m_aCommand = aCmd; + aPrinter.m_aInfo.m_aComment = printQueue.m_aComment; + aPrinter.m_aInfo.m_aLocation = printQueue.m_aLocation; + + m_aPrinters[ aPrinterName ] = aPrinter; + } +} + +void PrinterInfoManager::listPrinters( ::std::vector< OUString >& rVector ) const +{ + rVector.clear(); + for (auto const& printer : m_aPrinters) + rVector.push_back(printer.first); +} + +const PrinterInfo& PrinterInfoManager::getPrinterInfo( const OUString& rPrinter ) const +{ + static PrinterInfo aEmptyInfo; + std::unordered_map< OUString, Printer >::const_iterator it = m_aPrinters.find( rPrinter ); + + SAL_WARN_IF( it == m_aPrinters.end(), "vcl", "Do not ask for info about nonexistent printers" ); + + return it != m_aPrinters.end() ? it->second.m_aInfo : aEmptyInfo; +} + +bool PrinterInfoManager::checkFeatureToken( const OUString& rPrinterName, const char* pToken ) const +{ + const PrinterInfo& rPrinterInfo( getPrinterInfo( rPrinterName ) ); + sal_Int32 nIndex = 0; + while( nIndex != -1 ) + { + OUString aOuterToken = rPrinterInfo.m_aFeatures.getToken( 0, ',', nIndex ); + if( aOuterToken.getToken( 0, '=' ).equalsIgnoreAsciiCaseAscii( pToken ) ) + return true; + } + return false; +} + +FILE* PrinterInfoManager::startSpool( const OUString& rPrintername, bool bQuickCommand ) +{ + const PrinterInfo& rPrinterInfo = getPrinterInfo (rPrintername); + const OUString& rCommand = (bQuickCommand && !rPrinterInfo.m_aQuickCommand.isEmpty() ) ? + rPrinterInfo.m_aQuickCommand : rPrinterInfo.m_aCommand; + OString aShellCommand = OUStringToOString (rCommand, RTL_TEXTENCODING_ISO_8859_1) + + " 2>/dev/null"; + + return popen (aShellCommand.getStr(), "w"); +} + +bool PrinterInfoManager::endSpool( const OUString& /*rPrintername*/, const OUString& /*rJobTitle*/, FILE* pFile, const JobData& /*rDocumentJobData*/, bool /*bBanner*/, const OUString& /*rFaxNumber*/ ) +{ + return (0 == pclose( pFile )); +} + +void PrinterInfoManager::setupJobContextData( JobData& rData ) +{ + std::unordered_map< OUString, Printer >::iterator it = + m_aPrinters.find( rData.m_aPrinterName ); + if( it != m_aPrinters.end() ) + { + rData.m_pParser = it->second.m_aInfo.m_pParser; + rData.m_aContext = it->second.m_aInfo.m_aContext; + } +} + +void PrinterInfoManager::setDefaultPaper( PPDContext& rContext ) const +{ + if( ! rContext.getParser() ) + return; + + const PPDKey* pPageSizeKey = rContext.getParser()->getKey( "PageSize" ); + if( ! pPageSizeKey ) + return; + + std::size_t nModified = rContext.countValuesModified(); + auto set = false; + for (std::size_t i = 0; i != nModified; ++i) { + if (rContext.getModifiedKey(i) == pPageSizeKey) { + set = true; + break; + } + } + + if( set ) // paper was set already, do not modify + { +#if OSL_DEBUG_LEVEL > 1 + SAL_WARN("vcl.unx.print", "not setting default paper, already set " + << rContext.getValue( pPageSizeKey )->m_aOption); +#endif + return; + } + + // paper not set, fill in default value + const PPDValue* pPaperVal = nullptr; + int nValues = pPageSizeKey->countValues(); + for( int i = 0; i < nValues && ! pPaperVal; i++ ) + { + const PPDValue* pVal = pPageSizeKey->getValue( i ); + if( pVal->m_aOption.equalsIgnoreAsciiCase( m_aSystemDefaultPaper ) ) + pPaperVal = pVal; + } + if( pPaperVal ) + { +#if OSL_DEBUG_LEVEL > 1 + SAL_INFO("vcl.unx.print", "setting default paper " + << pPaperVal->m_aOption); +#endif + rContext.setValue( pPageSizeKey, pPaperVal ); +#if OSL_DEBUG_LEVEL > 1 + SAL_INFO("vcl.unx.print", "-> got paper " + << rContext.getValue( pPageSizeKey )->m_aOption); +#endif + } +} + +SystemQueueInfo::SystemQueueInfo() : + m_bChanged( false ) +{ + create(); +} + +SystemQueueInfo::~SystemQueueInfo() +{ + static const char* pNoSyncDetection = getenv( "SAL_DISABLE_SYNCHRONOUS_PRINTER_DETECTION" ); + if( ! pNoSyncDetection || !*pNoSyncDetection ) + join(); + else + terminate(); +} + +bool SystemQueueInfo::hasChanged() const +{ + MutexGuard aGuard( m_aMutex ); + bool bChanged = m_bChanged; + return bChanged; +} + +void SystemQueueInfo::getSystemQueues( std::vector< PrinterInfoManager::SystemPrintQueue >& rQueues ) +{ + MutexGuard aGuard( m_aMutex ); + rQueues = m_aQueues; + m_bChanged = false; +} + +OUString SystemQueueInfo::getCommand() const +{ + MutexGuard aGuard( m_aMutex ); + OUString aRet = m_aCommand; + return aRet; +} + +namespace { + +struct SystemCommandParameters; + +} + +typedef void(* tokenHandler)(const std::vector< OString >&, + std::vector< PrinterInfoManager::SystemPrintQueue >&, + const SystemCommandParameters*); + +namespace { + +struct SystemCommandParameters +{ + const char* pQueueCommand; + const char* pPrintCommand; + const char* pForeToken; + const char* pAftToken; + unsigned int nForeTokenCount; + tokenHandler pHandler; +}; + +} + +#if ! (defined(LINUX) || defined(NETBSD) || defined(FREEBSD) || defined(OPENBSD)) +static void lpgetSysQueueTokenHandler( + const std::vector< OString >& i_rLines, + std::vector< PrinterInfoManager::SystemPrintQueue >& o_rQueues, + const SystemCommandParameters* ) +{ + rtl_TextEncoding aEncoding = osl_getThreadTextEncoding(); + std::unordered_set< OUString > aUniqueSet; + std::unordered_set< OUString > aOnlySet; + aUniqueSet.insert( OUString( "_all" ) ); + aUniqueSet.insert( OUString( "_default" ) ); + + // the eventual "all" attribute of the "_all" queue tells us, which + // printers are to be used for this user at all + + // find _all: line + OString aAllLine( "_all:" ); + OString aAllAttr( "all=" ); + auto it = std::find_if(i_rLines.begin(), i_rLines.end(), + [&aAllLine](const OString& rLine) { return rLine.indexOf( aAllLine, 0 ) == 0; }); + if( it != i_rLines.end() ) + { + // now find the "all" attribute + ++it; + it = std::find_if(it, i_rLines.end(), + [&aAllAttr](const OString& rLine) { return WhitespaceToSpace( rLine ).startsWith( aAllAttr ); }); + if( it != i_rLines.end() ) + { + // insert the comma separated entries into the set of printers to use + OString aClean( WhitespaceToSpace( *it ) ); + sal_Int32 nPos = aAllAttr.getLength(); + while( nPos != -1 ) + { + OString aTok( aClean.getToken( 0, ',', nPos ) ); + if( !aTok.isEmpty() ) + aOnlySet.insert( OStringToOUString( aTok, aEncoding ) ); + } + } + } + + bool bInsertAttribute = false; + OString aDescrStr( "description=" ); + OString aLocStr( "location=" ); + for (auto const& line : i_rLines) + { + sal_Int32 nPos = 0; + // find the begin of a new printer section + nPos = line.indexOf( ':', 0 ); + if( nPos != -1 ) + { + OUString aSysQueue( OStringToOUString( line.copy( 0, nPos ), aEncoding ) ); + // do not insert duplicates (e.g. lpstat tends to produce such lines) + // in case there was a "_all" section, insert only those printer explicitly + // set in the "all" attribute + if( aUniqueSet.find( aSysQueue ) == aUniqueSet.end() && + ( aOnlySet.empty() || aOnlySet.find( aSysQueue ) != aOnlySet.end() ) + ) + { + o_rQueues.push_back( PrinterInfoManager::SystemPrintQueue() ); + o_rQueues.back().m_aQueue = aSysQueue; + o_rQueues.back().m_aLocation = aSysQueue; + aUniqueSet.insert( aSysQueue ); + bInsertAttribute = true; + } + else + bInsertAttribute = false; + continue; + } + if( bInsertAttribute && ! o_rQueues.empty() ) + { + // look for "description" attribute, insert as comment + nPos = line.indexOf( aDescrStr, 0 ); + if( nPos != -1 ) + { + OString aComment( WhitespaceToSpace( line.copy(nPos+12) ) ); + if( !aComment.isEmpty() ) + o_rQueues.back().m_aComment = OStringToOUString(aComment, aEncoding); + continue; + } + // look for "location" attribute, inser as location + nPos = line.indexOf( aLocStr, 0 ); + if( nPos != -1 ) + { + OString aLoc( WhitespaceToSpace( line.copy(nPos+9) ) ); + if( !aLoc.isEmpty() ) + o_rQueues.back().m_aLocation = OStringToOUString(aLoc, aEncoding); + continue; + } + } + } +} +#endif +static void standardSysQueueTokenHandler( + const std::vector< OString >& i_rLines, + std::vector< PrinterInfoManager::SystemPrintQueue >& o_rQueues, + const SystemCommandParameters* i_pParms) +{ + rtl_TextEncoding aEncoding = osl_getThreadTextEncoding(); + std::unordered_set< OUString > aUniqueSet; + OString aForeToken( i_pParms->pForeToken ); + OString aAftToken( i_pParms->pAftToken ); + /* Normal Unix print queue discovery, also used for Darwin 5 LPR printing + */ + for (auto const& line : i_rLines) + { + sal_Int32 nPos = 0; + + // search for a line describing a printer: + // find if there are enough tokens before the name + for( unsigned int i = 0; i < i_pParms->nForeTokenCount && nPos != -1; i++ ) + { + nPos = line.indexOf( aForeToken, nPos ); + if( nPos != -1 && line.getLength() >= nPos+aForeToken.getLength() ) + nPos += aForeToken.getLength(); + } + if( nPos != -1 ) + { + // find if there is the token after the queue + sal_Int32 nAftPos = line.indexOf( aAftToken, nPos ); + if( nAftPos != -1 ) + { + // get the queue name between fore and aft tokens + OUString aSysQueue( OStringToOUString( line.subView( nPos, nAftPos - nPos ), aEncoding ) ); + // do not insert duplicates (e.g. lpstat tends to produce such lines) + if( aUniqueSet.insert( aSysQueue ).second ) + { + o_rQueues.emplace_back( ); + o_rQueues.back().m_aQueue = aSysQueue; + o_rQueues.back().m_aLocation = aSysQueue; + } + } + } + } +} + +const struct SystemCommandParameters aParms[] = +{ + #if defined(LINUX) || defined(NETBSD) || defined(FREEBSD) || defined(OPENBSD) + { "/usr/sbin/lpc status", "lpr -P \"(PRINTER)\"", "", ":", 0, standardSysQueueTokenHandler }, + { "lpc status", "lpr -P \"(PRINTER)\"", "", ":", 0, standardSysQueueTokenHandler }, + { "LANG=C;LC_ALL=C;export LANG LC_ALL;lpstat -s", "lp -d \"(PRINTER)\"", "system for ", ": ", 1, standardSysQueueTokenHandler } + #else + { "LANG=C;LC_ALL=C;export LANG LC_ALL;lpget list", "lp -d \"(PRINTER)\"", "", ":", 0, lpgetSysQueueTokenHandler }, + { "LANG=C;LC_ALL=C;export LANG LC_ALL;lpstat -s", "lp -d \"(PRINTER)\"", "system for ", ": ", 1, standardSysQueueTokenHandler }, + { "/usr/sbin/lpc status", "lpr -P \"(PRINTER)\"", "", ":", 0, standardSysQueueTokenHandler }, + { "lpc status", "lpr -P \"(PRINTER)\"", "", ":", 0, standardSysQueueTokenHandler } + #endif +}; + +void SystemQueueInfo::run() +{ + osl_setThreadName("LPR psp::SystemQueueInfo"); + + char pBuffer[1024]; + std::vector< OString > aLines; + + /* Discover which command we can use to get a list of all printer queues */ + for(const auto & rParm : aParms) + { + aLines.clear(); +#if OSL_DEBUG_LEVEL > 1 + SAL_INFO("vcl.unx.print", "trying print queue command \"" + << rParm.pQueueCommand + << "\" ..."); +#endif + OString aCmdLine = rParm.pQueueCommand + OString::Concat(" 2>/dev/null"); + FILE *pPipe; + if( (pPipe = popen( aCmdLine.getStr(), "r" )) ) + { + while( fgets( pBuffer, 1024, pPipe ) ) + aLines.emplace_back( pBuffer ); + if( ! pclose( pPipe ) ) + { + std::vector< PrinterInfoManager::SystemPrintQueue > aSysPrintQueues; + rParm.pHandler( aLines, aSysPrintQueues, &rParm ); + MutexGuard aGuard( m_aMutex ); + m_bChanged = true; + m_aQueues = aSysPrintQueues; + m_aCommand = OUString::createFromAscii( rParm.pPrintCommand ); +#if OSL_DEBUG_LEVEL > 1 + SAL_INFO("vcl.unx.print", "printing queue command: success."); +#endif + break; + } + } +#if OSL_DEBUG_LEVEL > 1 + SAL_INFO("vcl.unx.print", "printing queue command: failed."); +#endif + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/generic/window/salframe.cxx b/vcl/unx/generic/window/salframe.cxx new file mode 100644 index 000000000..98b68c98e --- /dev/null +++ b/vcl/unx/generic/window/salframe.cxx @@ -0,0 +1,4093 @@ +/* -*- 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 <string.h> +#include <string_view> +#include <stdio.h> +#include <stdlib.h> + +#include <tools/debug.hxx> + +#include <vcl/event.hxx> +#include <vcl/toolkit/floatwin.hxx> +#include <vcl/keycodes.hxx> +#include <vcl/settings.hxx> +#include <vcl/BitmapReadAccess.hxx> +#include <vcl/opengl/OpenGLContext.hxx> +#include <vcl/BitmapTools.hxx> + +#include <X11/Xlib.h> +#include <X11/Xutil.h> +#include <X11/Xatom.h> +#include <X11/keysym.h> +#include <X11/extensions/shape.h> + +#include <unx/saldisp.hxx> +#include <unx/salgdi.h> +#include <unx/salframe.h> +#include <unx/wmadaptor.hxx> +#include <unx/salbmp.h> +#include <unx/i18n_ic.hxx> +#include <unx/i18n_keysym.hxx> +#include <opengl/zone.hxx> + +#include <unx/gensys.h> +#include <window.h> + +#include <sal/macros.h> +#include <sal/log.hxx> +#include <o3tl/safeint.hxx> +#include <o3tl/string_view.hxx> +#include <com/sun/star/uno/Exception.hpp> + +#include <svdata.hxx> +#include <bitmaps.hlst> + +#include <optional> + +#include <algorithm> + +#ifndef Button6 +# define Button6 6 +#endif +#ifndef Button7 +# define Button7 7 +#endif + +using namespace vcl_sal; + +constexpr auto CLIENT_EVENTS = StructureNotifyMask + | SubstructureNotifyMask + | KeyPressMask + | KeyReleaseMask + | ButtonPressMask + | ButtonReleaseMask + | PointerMotionMask + | EnterWindowMask + | LeaveWindowMask + | FocusChangeMask + | ExposureMask + | VisibilityChangeMask + | PropertyChangeMask + | ColormapChangeMask; + +static ::Window hPresentationWindow = None, hPresFocusWindow = None; +static ::std::list< ::Window > aPresentationReparentList; +static int nVisibleFloats = 0; + +static void doReparentPresentationDialogues( SalDisplay const * pDisplay ) +{ + GetGenericUnixSalData()->ErrorTrapPush(); + for (auto const& elem : aPresentationReparentList) + { + int x, y; + ::Window aRoot, aChild; + unsigned int w, h, bw, d; + XGetGeometry( pDisplay->GetDisplay(), + elem, + &aRoot, + &x, &y, &w, &h, &bw, &d ); + XTranslateCoordinates( pDisplay->GetDisplay(), + hPresentationWindow, + aRoot, + x, y, + &x, &y, + &aChild ); + XReparentWindow( pDisplay->GetDisplay(), + elem, + aRoot, + x, y ); + } + aPresentationReparentList.clear(); + if( hPresFocusWindow ) + XSetInputFocus( pDisplay->GetDisplay(), hPresFocusWindow, PointerRoot, CurrentTime ); + XSync( pDisplay->GetDisplay(), False ); + GetGenericUnixSalData()->ErrorTrapPop(); +} + +bool X11SalFrame::IsOverrideRedirect() const +{ + return + ((nStyle_ & SalFrameStyleFlags::INTRO) && !pDisplay_->getWMAdaptor()->supportsSplash()) + || + (!( nStyle_ & ~SalFrameStyleFlags::DEFAULT ) && !pDisplay_->getWMAdaptor()->supportsFullScreen()) + ; +} + +bool X11SalFrame::IsFloatGrabWindow() const +{ + static const char* pDisableGrab = getenv( "SAL_DISABLE_FLOATGRAB" ); + + return + ( ( !pDisableGrab || !*pDisableGrab ) && + ( + (nStyle_ & SalFrameStyleFlags::FLOAT) && + ! (nStyle_ & SalFrameStyleFlags::TOOLTIP) && + ! (nStyle_ & SalFrameStyleFlags::OWNERDRAWDECORATION) + ) + ); +} + +void X11SalFrame::setXEmbedInfo() +{ + if( !m_bXEmbed ) + return; + + tools::Long aInfo[2]; + aInfo[0] = 1; // XEMBED protocol version + aInfo[1] = (bMapped_ ? 1 : 0); // XEMBED_MAPPED + XChangeProperty( pDisplay_->GetDisplay(), + mhWindow, + pDisplay_->getWMAdaptor()->getAtom( WMAdaptor::XEMBED_INFO ), + pDisplay_->getWMAdaptor()->getAtom( WMAdaptor::XEMBED_INFO ), + 32, + PropModeReplace, + reinterpret_cast<unsigned char*>(aInfo), + SAL_N_ELEMENTS(aInfo) ); +} + +void X11SalFrame::askForXEmbedFocus( sal_Int32 i_nTimeCode ) +{ + XEvent aEvent; + + memset( &aEvent, 0, sizeof(aEvent) ); + aEvent.xclient.window = mhForeignParent; + aEvent.xclient.type = ClientMessage; + aEvent.xclient.message_type = pDisplay_->getWMAdaptor()->getAtom( WMAdaptor::XEMBED ); + aEvent.xclient.format = 32; + aEvent.xclient.data.l[0] = i_nTimeCode ? i_nTimeCode : CurrentTime; + aEvent.xclient.data.l[1] = 3; // XEMBED_REQUEST_FOCUS + aEvent.xclient.data.l[2] = 0; + aEvent.xclient.data.l[3] = 0; + aEvent.xclient.data.l[4] = 0; + + GetGenericUnixSalData()->ErrorTrapPush(); + XSendEvent( pDisplay_->GetDisplay(), + mhForeignParent, + False, NoEventMask, &aEvent ); + XSync( pDisplay_->GetDisplay(), False ); + GetGenericUnixSalData()->ErrorTrapPop(); +} + +typedef std::vector< unsigned long > NetWmIconData; + +namespace +{ + constexpr rtl::OUStringConstExpr SV_ICON_SIZE48[] = + { + MAINAPP_48_8, + MAINAPP_48_8, + ODT_48_8, + OTT_48_8, + ODS_48_8, + OTS_48_8, + ODG_48_8, + MAINAPP_48_8, + ODP_48_8, + MAINAPP_48_8, + ODM_48_8, + MAINAPP_48_8, + ODB_48_8, + ODF_48_8 + }; + + constexpr rtl::OUStringConstExpr SV_ICON_SIZE32[] = + { + MAINAPP_32_8, + MAINAPP_32_8, + ODT_32_8, + OTT_32_8, + ODS_32_8, + OTS_32_8, + ODG_32_8, + MAINAPP_32_8, + ODP_32_8, + MAINAPP_32_8, + ODM_32_8, + MAINAPP_32_8, + ODB_32_8, + ODF_32_8 + }; + + constexpr rtl::OUStringConstExpr SV_ICON_SIZE16[] = + { + MAINAPP_16_8, + MAINAPP_16_8, + ODT_16_8, + OTT_16_8, + ODS_16_8, + OTS_16_8, + ODG_16_8, + MAINAPP_16_8, + ODP_16_8, + MAINAPP_16_8, + ODM_16_8, + MAINAPP_16_8, + ODB_16_8, + ODF_16_8 + }; +} + +static void CreateNetWmAppIcon( sal_uInt16 nIcon, NetWmIconData& netwm_icon ) +{ + const int sizes[ 3 ] = { 48, 32, 16 }; + netwm_icon.resize( 48 * 48 + 32 * 32 + 16 * 16 + 3 * 2 ); + int pos = 0; + for(int size : sizes) + { + OUString sIcon; + if( size >= 48 ) + sIcon = SV_ICON_SIZE48[nIcon]; + else if( size >= 32 ) + sIcon = SV_ICON_SIZE32[nIcon]; + else + sIcon = SV_ICON_SIZE16[nIcon]; + + BitmapEx aIcon = vcl::bitmap::loadFromName(sIcon, ImageLoadFlags::IgnoreScalingFactor); + + if( aIcon.IsEmpty()) + continue; + vcl::bitmap::convertBitmap32To24Plus8(aIcon, aIcon); + Bitmap icon = aIcon.GetBitmap(); + AlphaMask mask = aIcon.GetAlpha(); + BitmapReadAccess* iconData = icon.AcquireReadAccess(); + BitmapReadAccess* maskData = mask.AcquireReadAccess(); + netwm_icon[ pos++ ] = size; // width + netwm_icon[ pos++ ] = size; // height + for( int y = 0; y < size; ++y ) + for( int x = 0; x < size; ++x ) + { + BitmapColor col = iconData->GetColor( y, x ); + BitmapColor alpha = maskData->GetColor( y, x ); + netwm_icon[ pos++ ] = (((( 255 - alpha.GetBlue()) * 256U ) + col.GetRed()) * 256 + col.GetGreen()) * 256 + col.GetBlue(); + } + Bitmap::ReleaseAccess( iconData ); + mask.ReleaseAccess( maskData ); + } + netwm_icon.resize( pos ); +} + +static bool lcl_SelectAppIconPixmap( SalDisplay const *pDisplay, SalX11Screen nXScreen, + sal_uInt16 nIcon, sal_uInt16 iconSize, + Pixmap& icon_pixmap, Pixmap& icon_mask, NetWmIconData& netwm_icon) +{ + CreateNetWmAppIcon( nIcon, netwm_icon ); + + OUString sIcon; + + if( iconSize >= 48 ) + sIcon = SV_ICON_SIZE48[nIcon]; + else if( iconSize >= 32 ) + sIcon = SV_ICON_SIZE32[nIcon]; + else if( iconSize >= 16 ) + sIcon = SV_ICON_SIZE16[nIcon]; + else + return false; + + BitmapEx aIcon = vcl::bitmap::loadFromName(sIcon, ImageLoadFlags::IgnoreScalingFactor); + + if( aIcon.IsEmpty() ) + return false; + + X11SalBitmap *pBitmap = dynamic_cast < X11SalBitmap * > + (aIcon.ImplGetBitmapSalBitmap().get()); + if (!pBitmap) // FIXME: opengl , TODO SKIA + return false; + + icon_pixmap = XCreatePixmap( pDisplay->GetDisplay(), + pDisplay->GetRootWindow( nXScreen ), + iconSize, iconSize, + DefaultDepth( pDisplay->GetDisplay(), + nXScreen.getXScreen() ) + ); + + SalTwoRect aRect(0, 0, iconSize, iconSize, 0, 0, iconSize, iconSize); + + pBitmap->ImplDraw( icon_pixmap, + nXScreen, + DefaultDepth( pDisplay->GetDisplay(), + nXScreen.getXScreen() ), + aRect, + DefaultGC( pDisplay->GetDisplay(), + nXScreen.getXScreen() ) ); + + icon_mask = None; + + if( aIcon.IsAlpha() ) + { + icon_mask = XCreatePixmap( pDisplay->GetDisplay(), + pDisplay->GetRootWindow( pDisplay->GetDefaultXScreen() ), + iconSize, iconSize, 1); + + XGCValues aValues; + aValues.foreground = 0xffffffff; + aValues.background = 0; + aValues.function = GXcopy; + GC aMonoGC = XCreateGC( pDisplay->GetDisplay(), icon_mask, + GCFunction|GCForeground|GCBackground, &aValues ); + + Bitmap aMask = aIcon.GetAlpha(); + aMask.Invert(); + + X11SalBitmap *pMask = static_cast < X11SalBitmap * > + (aMask.ImplGetSalBitmap().get()); + + pMask->ImplDraw(icon_mask, nXScreen, 1, aRect, aMonoGC); + XFreeGC( pDisplay->GetDisplay(), aMonoGC ); + } + + return true; +} + +void X11SalFrame::Init( SalFrameStyleFlags nSalFrameStyle, SalX11Screen nXScreen, SystemParentData const * pParentData, bool bUseGeometry ) +{ + if( nXScreen.getXScreen() >= GetDisplay()->GetXScreenCount() ) + nXScreen = GetDisplay()->GetDefaultXScreen(); + if( mpParent ) + nXScreen = mpParent->m_nXScreen; + + m_nXScreen = nXScreen; + nStyle_ = nSalFrameStyle; + XWMHints Hints; + Hints.flags = InputHint; + Hints.input = (nSalFrameStyle & SalFrameStyleFlags::OWNERDRAWDECORATION) ? False : True; + NetWmIconData netwm_icon; + + int x = 0, y = 0; + unsigned int w = 500, h = 500; + XSetWindowAttributes Attributes; + + int nAttrMask = CWBorderPixel + | CWBackPixmap + | CWColormap + | CWOverrideRedirect + | CWEventMask + ; + Attributes.border_pixel = 0; + Attributes.background_pixmap = None; + Attributes.colormap = GetDisplay()->GetColormap( m_nXScreen ).GetXColormap(); + Attributes.override_redirect = False; + Attributes.event_mask = CLIENT_EVENTS; + + const SalVisual& rVis = GetDisplay()->GetVisual( m_nXScreen ); + ::Window aFrameParent = pParentData ? pParentData->aWindow : GetDisplay()->GetRootWindow( m_nXScreen ); + ::Window aClientLeader = None; + + if( bUseGeometry ) + { + x = maGeometry.nX; + y = maGeometry.nY; + w = maGeometry.nWidth; + h = maGeometry.nHeight; + } + + if( (nSalFrameStyle & SalFrameStyleFlags::FLOAT) && + ! (nSalFrameStyle & SalFrameStyleFlags::OWNERDRAWDECORATION) + ) + { + if( nShowState_ == X11ShowState::Unknown ) + { + w = 10; + h = 10; + } + Attributes.override_redirect = True; + } + else if( nSalFrameStyle & SalFrameStyleFlags::SYSTEMCHILD ) + { + SAL_WARN_IF( !mpParent, "vcl", "SalFrameStyleFlags::SYSTEMCHILD window without parent" ); + if( mpParent ) + { + aFrameParent = mpParent->mhWindow; + // FIXME: since with SalFrameStyleFlags::SYSTEMCHILD + // multiple X11SalFrame objects can have the same shell window + // dispatching events in saldisp.cxx is unclear (the first frame) + // wins. HTH this correctly is unclear yet + // for the time being, treat set the shell window to own window + // like for a normal frame + // mhShellWindow = mpParent->GetShellWindow(); + } + } + else if( pParentData ) + { + // plugin parent may be killed unexpectedly by plugging + // process; start permanently ignoring X errors... + GetGenericUnixSalData()->ErrorTrapPush(); + + nStyle_ |= SalFrameStyleFlags::PLUG; + Attributes.override_redirect = True; + if( pParentData->nSize >= sizeof(SystemParentData) ) + m_bXEmbed = pParentData->bXEmbedSupport; + + int x_ret, y_ret; + unsigned int bw, d; + ::Window aRoot, aParent; + + XGetGeometry( GetXDisplay(), pParentData->aWindow, + &aRoot, &x_ret, &y_ret, &w, &h, &bw, &d ); + mhForeignParent = pParentData->aWindow; + + mhShellWindow = aParent = mhForeignParent; + ::Window* pChildren; + unsigned int nChildren; + bool bBreak = false; + do + { + XQueryTree( GetDisplay()->GetDisplay(), mhShellWindow, + &aRoot, &aParent, &pChildren, &nChildren ); + XFree( pChildren ); + if( aParent != aRoot ) + mhShellWindow = aParent; + int nCount = 0; + Atom* pProps = XListProperties( GetDisplay()->GetDisplay(), + mhShellWindow, + &nCount ); + for( int i = 0; i < nCount && ! bBreak; ++i ) + bBreak = (pProps[i] == XA_WM_HINTS); + if( pProps ) + XFree( pProps ); + } while( aParent != aRoot && ! bBreak ); + + // check if this is really one of our own frames + // do not change the input mask in that case + bool bIsReallyOurFrame = false; + for (auto pSalFrame : GetDisplay()->getFrames() ) + if ( static_cast<const X11SalFrame*>( pSalFrame )->GetWindow() == mhForeignParent ) + { + bIsReallyOurFrame = true; + break; + } + if (!bIsReallyOurFrame) + { + XSelectInput( GetDisplay()->GetDisplay(), mhForeignParent, StructureNotifyMask | FocusChangeMask ); + XSelectInput( GetDisplay()->GetDisplay(), mhShellWindow, StructureNotifyMask | FocusChangeMask ); + } + } + else + { + if( ! bUseGeometry ) + { + Size aScreenSize( GetDisplay()->getDataForScreen( m_nXScreen ).m_aSize ); + w = aScreenSize.Width(); + h = aScreenSize.Height(); + if( nSalFrameStyle & SalFrameStyleFlags::SIZEABLE && + nSalFrameStyle & SalFrameStyleFlags::MOVEABLE ) + { + Size aBestFitSize(bestmaxFrameSizeForScreenSize(aScreenSize)); + w = aBestFitSize.Width(); + h = aBestFitSize.Height(); + } + if( ! mpParent ) + { + // find the last document window (if any) + const X11SalFrame* pFrame = nullptr; + bool bIsDocumentWindow = false; + for (auto pSalFrame : GetDisplay()->getFrames() ) + { + pFrame = static_cast< const X11SalFrame* >( pSalFrame ); + if( !pFrame->mpParent + && !pFrame->mbFullScreen + && ( pFrame->nStyle_ & SalFrameStyleFlags::SIZEABLE ) + && pFrame->GetUnmirroredGeometry().nWidth + && pFrame->GetUnmirroredGeometry().nHeight ) + { + bIsDocumentWindow = true; + break; + } + } + + if( bIsDocumentWindow ) + { + // set a document position and size + // the first frame gets positioned by the window manager + const SalFrameGeometry& rGeom( pFrame->GetUnmirroredGeometry() ); + x = rGeom.nX; + y = rGeom.nY; + if( x+static_cast<int>(w)+40 <= static_cast<int>(aScreenSize.Width()) && + y+static_cast<int>(h)+40 <= static_cast<int>(aScreenSize.Height()) + ) + { + y += 40; + x += 40; + } + else + { + x = 10; // leave some space for decoration + y = 20; + } + } + else if( GetDisplay()->IsXinerama() ) + { + // place frame on same screen as mouse pointer + ::Window aRoot, aChild; + int root_x = 0, root_y = 0, lx, ly; + unsigned int mask; + XQueryPointer( GetXDisplay(), + GetDisplay()->GetRootWindow( m_nXScreen ), + &aRoot, &aChild, + &root_x, &root_y, &lx, &ly, &mask ); + const std::vector< tools::Rectangle >& rScreens = GetDisplay()->GetXineramaScreens(); + for(const auto & rScreen : rScreens) + if( rScreen.Contains( Point( root_x, root_y ) ) ) + { + x = rScreen.Left(); + y = rScreen.Top(); + break; + } + } + } + } + Attributes.win_gravity = pDisplay_->getWMAdaptor()->getInitWinGravity(); + nAttrMask |= CWWinGravity; + if( mpParent ) + { + Attributes.save_under = True; + nAttrMask |= CWSaveUnder; + } + if( IsOverrideRedirect() ) + Attributes.override_redirect = True; + // default icon + if( !(nStyle_ & SalFrameStyleFlags::INTRO) && !(nStyle_ & SalFrameStyleFlags::NOICON)) + { + bool bOk=false; + try + { + bOk = lcl_SelectAppIconPixmap( pDisplay_, m_nXScreen, + mnIconID != SV_ICON_ID_OFFICE ? mnIconID : + (mpParent ? mpParent->mnIconID : SV_ICON_ID_OFFICE), 32, + Hints.icon_pixmap, Hints.icon_mask, netwm_icon ); + } + catch( css::uno::Exception& ) + { + // can happen - no ucb during early startup + } + if( bOk ) + { + Hints.flags |= IconPixmapHint; + if( Hints.icon_mask ) + Hints.flags |= IconMaskHint; + } + } + + // find the top level frame of the transience hierarchy + X11SalFrame* pFrame = this; + while( pFrame->mpParent ) + pFrame = pFrame->mpParent; + if( pFrame->nStyle_ & SalFrameStyleFlags::PLUG ) + { + // if the top level window is a plugin window, + // then we should place us in the same window group as + // the parent application (or none if there is no window group + // hint in the parent). + if( pFrame->GetShellWindow() ) + { + XWMHints* pWMHints = XGetWMHints( pDisplay_->GetDisplay(), + pFrame->GetShellWindow() ); + if( pWMHints ) + { + if( pWMHints->flags & WindowGroupHint ) + { + Hints.flags |= WindowGroupHint; + Hints.window_group = pWMHints->window_group; + } + XFree( pWMHints ); + } + } + } + else + { + Hints.flags |= WindowGroupHint; + Hints.window_group = pFrame->GetShellWindow(); + // note: for a normal document window this will produce None + // as the window is not yet created and the shell window is + // initialized to None. This must be corrected after window creation. + aClientLeader = GetDisplay()->GetDrawable( m_nXScreen ); + } + } + + nShowState_ = X11ShowState::Unknown; + bViewable_ = true; + bMapped_ = false; + nVisibility_ = VisibilityFullyObscured; + mhWindow = XCreateWindow( GetXDisplay(), + aFrameParent, + x, y, + w, h, + 0, + rVis.GetDepth(), + InputOutput, + rVis.GetVisual(), + nAttrMask, + &Attributes ); + // FIXME: see above: fake shell window for now to own window + if( pParentData == nullptr ) + { + mhShellWindow = mhWindow; + } + + // correct window group if necessary + if( (Hints.flags & WindowGroupHint) == WindowGroupHint ) + { + if( Hints.window_group == None ) + Hints.window_group = GetShellWindow(); + } + + maGeometry.nX = x; + maGeometry.nY = y; + maGeometry.nWidth = w; + maGeometry.nHeight = h; + updateScreenNumber(); + + XSync( GetXDisplay(), False ); + setXEmbedInfo(); + + Time nUserTime = (nStyle_ & (SalFrameStyleFlags::OWNERDRAWDECORATION | SalFrameStyleFlags::TOOLWINDOW) ) == SalFrameStyleFlags::NONE ? + pDisplay_->GetLastUserEventTime() : 0; + pDisplay_->getWMAdaptor()->setUserTime( this, nUserTime ); + + if( ! pParentData && ! IsChildWindow() && ! Attributes.override_redirect ) + { + XSetWMHints( GetXDisplay(), mhWindow, &Hints ); + // WM Protocols && internals + Atom a[3]; + int n = 0; + a[n++] = pDisplay_->getWMAdaptor()->getAtom( WMAdaptor::WM_DELETE_WINDOW ); + +// LibreOffice advertises NET_WM_PING atom, so mutter rightfully warns of an unresponsive application during debugging. +// Hack that out unconditionally for debug builds, as per https://bugzilla.redhat.com/show_bug.cgi?id=981149 +// upstream refuses to make this configurable in any way. +// NOTE: You need to use the 'gen' backend for this to work (SAL_USE_VCLPLUGIN=gen) +#if OSL_DEBUG_LEVEL < 1 + if( pDisplay_->getWMAdaptor()->getAtom( WMAdaptor::NET_WM_PING ) ) + a[n++] = pDisplay_->getWMAdaptor()->getAtom( WMAdaptor::NET_WM_PING ); +#endif + + if( nSalFrameStyle & SalFrameStyleFlags::OWNERDRAWDECORATION ) + a[n++] = pDisplay_->getWMAdaptor()->getAtom( WMAdaptor::WM_TAKE_FOCUS ); + XSetWMProtocols( GetXDisplay(), GetShellWindow(), a, n ); + + // force wm class hint + mnExtStyle = ~0; + if (mpParent) + m_sWMClass = mpParent->m_sWMClass; + SetExtendedFrameStyle( 0 ); + + XSizeHints* pHints = XAllocSizeHints(); + pHints->flags = PWinGravity | PPosition; + pHints->win_gravity = GetDisplay()->getWMAdaptor()->getPositionWinGravity(); + pHints->x = 0; + pHints->y = 0; + if( mbFullScreen ) + { + pHints->flags |= PMaxSize | PMinSize; + pHints->max_width = w+100; + pHints->max_height = h+100; + pHints->min_width = w; + pHints->min_height = h; + } + XSetWMNormalHints( GetXDisplay(), + GetShellWindow(), + pHints ); + XFree (pHints); + + // set PID and WM_CLIENT_MACHINE + pDisplay_->getWMAdaptor()->setClientMachine( this ); + pDisplay_->getWMAdaptor()->setPID( this ); + + // set client leader + if( aClientLeader ) + { + XChangeProperty( GetXDisplay(), + mhWindow, + pDisplay_->getWMAdaptor()->getAtom( WMAdaptor::WM_CLIENT_LEADER), + XA_WINDOW, + 32, + PropModeReplace, + reinterpret_cast<unsigned char*>(&aClientLeader), + 1 + ); + } +#define DECOFLAGS (SalFrameStyleFlags::MOVEABLE | SalFrameStyleFlags::SIZEABLE | SalFrameStyleFlags::CLOSEABLE) + int nDecoFlags = WMAdaptor::decoration_All; + if( (nStyle_ & SalFrameStyleFlags::PARTIAL_FULLSCREEN) || + (nStyle_ & SalFrameStyleFlags::OWNERDRAWDECORATION) + ) + nDecoFlags = 0; + else if( (nStyle_ & DECOFLAGS ) != DECOFLAGS || (nStyle_ & SalFrameStyleFlags::TOOLWINDOW) ) + { + if( nStyle_ & DECOFLAGS ) + // if any decoration, then show a border + nDecoFlags = WMAdaptor::decoration_Border; + else + nDecoFlags = 0; + + if( ! mpParent && (nStyle_ & DECOFLAGS) ) + // don't add a min button if window should be decorationless + nDecoFlags |= WMAdaptor::decoration_MinimizeBtn; + if( nStyle_ & SalFrameStyleFlags::CLOSEABLE ) + nDecoFlags |= WMAdaptor::decoration_CloseBtn; + if( nStyle_ & SalFrameStyleFlags::SIZEABLE ) + { + nDecoFlags |= WMAdaptor::decoration_Resize; + if( ! (nStyle_ & SalFrameStyleFlags::TOOLWINDOW) ) + nDecoFlags |= WMAdaptor::decoration_MaximizeBtn; + } + if( nStyle_ & SalFrameStyleFlags::MOVEABLE ) + nDecoFlags |= WMAdaptor::decoration_Title; + } + + WMWindowType eType = WMWindowType::Normal; + if( nStyle_ & SalFrameStyleFlags::INTRO ) + eType = WMWindowType::Splash; + if( (nStyle_ & SalFrameStyleFlags::DIALOG) && hPresentationWindow == None ) + eType = WMWindowType::ModelessDialogue; + if( nStyle_ & SalFrameStyleFlags::TOOLWINDOW ) + eType = WMWindowType::Utility; + if( nStyle_ & SalFrameStyleFlags::OWNERDRAWDECORATION ) + eType = WMWindowType::Toolbar; + if( (nStyle_ & SalFrameStyleFlags::PARTIAL_FULLSCREEN) + && GetDisplay()->getWMAdaptor()->isLegacyPartialFullscreen() ) + eType = WMWindowType::Dock; + + GetDisplay()->getWMAdaptor()-> + setFrameTypeAndDecoration( this, + eType, + nDecoFlags, + hPresentationWindow ? nullptr : mpParent ); + + if( (nStyle_ & (SalFrameStyleFlags::DEFAULT | + SalFrameStyleFlags::OWNERDRAWDECORATION| + SalFrameStyleFlags::FLOAT | + SalFrameStyleFlags::INTRO | + SalFrameStyleFlags::PARTIAL_FULLSCREEN) ) + == SalFrameStyleFlags::DEFAULT ) + pDisplay_->getWMAdaptor()->maximizeFrame( this ); + + if( !netwm_icon.empty() && GetDisplay()->getWMAdaptor()->getAtom( WMAdaptor::NET_WM_ICON )) + XChangeProperty( GetXDisplay(), mhWindow, + GetDisplay()->getWMAdaptor()->getAtom( WMAdaptor::NET_WM_ICON ), + XA_CARDINAL, 32, PropModeReplace, reinterpret_cast<unsigned char*>(netwm_icon.data()), netwm_icon.size()); + } + + m_nWorkArea = GetDisplay()->getWMAdaptor()->getCurrentWorkArea(); + + // Pointer + SetPointer( PointerStyle::Arrow ); +} + +X11SalFrame::X11SalFrame( SalFrame *pParent, SalFrameStyleFlags nSalFrameStyle, + SystemParentData const * pSystemParent ) : + m_nXScreen( 0 ), + maAlwaysOnTopRaiseTimer( "vcl::X11SalFrame maAlwaysOnTopRaiseTimer" ) +{ + GenericUnixSalData *pData = GetGenericUnixSalData(); + + mpParent = static_cast< X11SalFrame* >( pParent ); + + mbTransientForRoot = false; + + pDisplay_ = vcl_sal::getSalDisplay(pData); + // insert frame in framelist + pDisplay_->registerFrame( this ); + + mhWindow = None; + mhShellWindow = None; + mhStackingWindow = None; + mhForeignParent = None; + m_bSetFocusOnMap = false; + + pGraphics_ = nullptr; + pFreeGraphics_ = nullptr; + + hCursor_ = None; + nCaptured_ = 0; + + mbSendExtKeyModChange = false; + mnExtKeyMod = ModKeyFlags::NONE; + + nShowState_ = X11ShowState::Unknown; + nWidth_ = 0; + nHeight_ = 0; + nStyle_ = SalFrameStyleFlags::NONE; + mnExtStyle = 0; + bAlwaysOnTop_ = false; + + // set bViewable_ to true: hack GetClientSize to report something + // different to 0/0 before first map + bViewable_ = true; + bMapped_ = false; + bDefaultPosition_ = true; + nVisibility_ = VisibilityFullyObscured; + m_nWorkArea = 0; + m_bXEmbed = false; + + + mpInputContext = nullptr; + mbInputFocus = False; + + maAlwaysOnTopRaiseTimer.SetInvokeHandler( LINK( this, X11SalFrame, HandleAlwaysOnTopRaise ) ); + maAlwaysOnTopRaiseTimer.SetTimeout( 100 ); + + meWindowType = WMWindowType::Normal; + mbMaximizedVert = false; + mbMaximizedHorz = false; + mbFullScreen = false; + + mnIconID = SV_ICON_ID_OFFICE; + + if( mpParent ) + mpParent->maChildren.push_back( this ); + + Init( nSalFrameStyle, GetDisplay()->GetDefaultXScreen(), pSystemParent ); +} + +X11SalFrame::~X11SalFrame() +{ + notifyDelete(); + + m_vClipRectangles.clear(); + + if( mhStackingWindow ) + aPresentationReparentList.remove( mhStackingWindow ); + + // remove from parent's list + if( mpParent ) + mpParent->maChildren.remove( this ); + + // deregister on SalDisplay + pDisplay_->deregisterFrame( this ); + + // unselect all events, some may be still in the queue anyway + if( ! IsSysChildWindow() ) + XSelectInput( GetXDisplay(), GetShellWindow(), 0 ); + XSelectInput( GetXDisplay(), GetWindow(), 0 ); + + ShowFullScreen( false, 0 ); + + if( bMapped_ ) + Show( false ); + + if( mpInputContext ) + { + mpInputContext->UnsetICFocus(); + mpInputContext->Unmap(); + mpInputContext.reset(); + } + + if( GetWindow() == hPresentationWindow ) + { + hPresentationWindow = None; + doReparentPresentationDialogues( GetDisplay() ); + } + + if( pGraphics_ ) + { + pGraphics_->DeInit(); + pGraphics_.reset(); + } + + if( pFreeGraphics_ ) + { + pFreeGraphics_->DeInit(); + pFreeGraphics_.reset(); + } + + // reset all OpenGL contexts using this window + rtl::Reference<OpenGLContext> pContext = ImplGetSVData()->maGDIData.mpLastContext; + while( pContext.is() ) + { + if (static_cast<const GLX11Window&>(pContext->getOpenGLWindow()).win == mhWindow) + pContext->reset(); + pContext = pContext->mpPrevContext; + } + + XDestroyWindow( GetXDisplay(), mhWindow ); +} + +void X11SalFrame::SetExtendedFrameStyle( SalExtStyle nStyle ) +{ + if( nStyle != mnExtStyle && ! IsChildWindow() ) + { + mnExtStyle = nStyle; + updateWMClass(); + } +} + +const SystemEnvData* X11SalFrame::GetSystemData() const +{ + X11SalFrame *pFrame = const_cast<X11SalFrame*>(this); + pFrame->maSystemChildData.pDisplay = GetXDisplay(); + pFrame->maSystemChildData.SetWindowHandle(pFrame->GetWindow()); + pFrame->maSystemChildData.pSalFrame = pFrame; + pFrame->maSystemChildData.pWidget = nullptr; + pFrame->maSystemChildData.pVisual = GetDisplay()->GetVisual( m_nXScreen ).GetVisual(); + pFrame->maSystemChildData.nScreen = m_nXScreen.getXScreen(); + pFrame->maSystemChildData.aShellWindow = pFrame->GetShellWindow(); + pFrame->maSystemChildData.toolkit = SystemEnvData::Toolkit::Gen; + pFrame->maSystemChildData.platform = SystemEnvData::Platform::Xcb; + return &maSystemChildData; +} + +SalGraphics *X11SalFrame::AcquireGraphics() +{ + if( pGraphics_ ) + return nullptr; + + if( pFreeGraphics_ ) + { + pGraphics_ = std::move(pFreeGraphics_); + } + else + { + pGraphics_.reset(new X11SalGraphics()); + pGraphics_->Init( this, GetWindow(), m_nXScreen ); + } + + return pGraphics_.get(); +} + +void X11SalFrame::ReleaseGraphics( SalGraphics *pGraphics ) +{ + SAL_WARN_IF( pGraphics != pGraphics_.get(), "vcl", "SalFrame::ReleaseGraphics pGraphics!=pGraphics_" ); + + if( pGraphics != pGraphics_.get() ) + return; + + pFreeGraphics_ = std::move(pGraphics_); +} + +void X11SalFrame::updateGraphics( bool bClear ) +{ + Drawable aDrawable = bClear ? None : GetWindow(); + if( pGraphics_ ) + pGraphics_->SetDrawable( aDrawable, nullptr, m_nXScreen ); + if( pFreeGraphics_ ) + pFreeGraphics_->SetDrawable( aDrawable, nullptr, m_nXScreen ); +} + +void X11SalFrame::SetIcon( sal_uInt16 nIcon ) +{ + if ( IsChildWindow() ) + return; + + // 0 == default icon -> #1 + if ( nIcon == 0 ) + nIcon = 1; + + mnIconID = nIcon; + + XIconSize *pIconSize = nullptr; + int nSizes = 0; + int iconSize = 32; + if ( XGetIconSizes( GetXDisplay(), GetDisplay()->GetRootWindow( m_nXScreen ), &pIconSize, &nSizes ) ) + { +#if OSL_DEBUG_LEVEL > 1 + SAL_INFO("vcl.window", "X11SalFrame::SetIcon(): found " + << nSizes + << " IconSizes:"); +#endif + int i; + for( i=0; i<nSizes; i++) + { + // select largest supported icon + if( pIconSize[i].max_width > iconSize ) + { + iconSize = pIconSize[i].max_width; + } + +#if OSL_DEBUG_LEVEL > 1 + SAL_INFO("vcl.window", "min: " + << pIconSize[i].min_width + << ", " + << pIconSize[i].min_height); + SAL_INFO("vcl.window", "max: " + << pIconSize[i].max_width + << ", " + << pIconSize[i].max_height); + SAL_INFO("vcl.window", "inc: " + << pIconSize[i].width_inc + << ", " + << pIconSize[i].height_inc); +#endif + } + + XFree( pIconSize ); + } + else + { + const OUString& rWM( pDisplay_->getWMAdaptor()->getWindowManagerName() ); + if( rWM == "KWin" ) // assume KDE is running + iconSize = 48; + static bool bGnomeIconSize = false; + static bool bGnomeChecked = false; + if( ! bGnomeChecked ) + { + bGnomeChecked=true; + int nCount = 0; + Atom* pProps = XListProperties( GetXDisplay(), + GetDisplay()->GetRootWindow( m_nXScreen ), + &nCount ); + for( int i = 0; i < nCount && !bGnomeIconSize; i++ ) + { + char* pName = XGetAtomName( GetXDisplay(), pProps[i] ); + if( pName ) + { + if( !strcmp( pName, "GNOME_PANEL_DESKTOP_AREA" ) ) + bGnomeIconSize = true; + XFree( pName ); + } + } + if( pProps ) + XFree( pProps ); + } + if( bGnomeIconSize ) + iconSize = 48; + } + + XWMHints Hints; + Hints.flags = 0; + XWMHints *pHints = XGetWMHints( GetXDisplay(), GetShellWindow() ); + if( pHints ) + { + memcpy(&Hints, pHints, sizeof( XWMHints )); + XFree( pHints ); + } + pHints = &Hints; + + NetWmIconData netwm_icon; + bool bOk = lcl_SelectAppIconPixmap( GetDisplay(), m_nXScreen, + nIcon, iconSize, + pHints->icon_pixmap, pHints->icon_mask, netwm_icon ); + if ( !bOk ) + { + // load default icon (0) + bOk = lcl_SelectAppIconPixmap( GetDisplay(), m_nXScreen, + 0, iconSize, + pHints->icon_pixmap, pHints->icon_mask, netwm_icon ); + } + if( bOk ) + { + pHints->flags |= IconPixmapHint; + if( pHints->icon_mask ) + pHints->flags |= IconMaskHint; + + XSetWMHints( GetXDisplay(), GetShellWindow(), pHints ); + if( !netwm_icon.empty() && GetDisplay()->getWMAdaptor()->getAtom( WMAdaptor::NET_WM_ICON )) + XChangeProperty( GetXDisplay(), mhWindow, + GetDisplay()->getWMAdaptor()->getAtom( WMAdaptor::NET_WM_ICON ), + XA_CARDINAL, 32, PropModeReplace, reinterpret_cast<unsigned char*>(netwm_icon.data()), netwm_icon.size()); + } + +} + +void X11SalFrame::SetMaxClientSize( tools::Long nWidth, tools::Long nHeight ) +{ + if( IsChildWindow() ) + return; + + if( !GetShellWindow() || + (nStyle_ & (SalFrameStyleFlags::FLOAT|SalFrameStyleFlags::OWNERDRAWDECORATION) ) == SalFrameStyleFlags::FLOAT ) + return; + + XSizeHints* pHints = XAllocSizeHints(); + tools::Long nSupplied = 0; + XGetWMNormalHints( GetXDisplay(), + GetShellWindow(), + pHints, + &nSupplied + ); + pHints->max_width = nWidth; + pHints->max_height = nHeight; + pHints->flags |= PMaxSize; + XSetWMNormalHints( GetXDisplay(), + GetShellWindow(), + pHints ); + XFree( pHints ); +} + +void X11SalFrame::SetMinClientSize( tools::Long nWidth, tools::Long nHeight ) +{ + if( IsChildWindow() ) + return; + + if( !GetShellWindow() || + (nStyle_ & (SalFrameStyleFlags::FLOAT|SalFrameStyleFlags::OWNERDRAWDECORATION) ) == SalFrameStyleFlags::FLOAT ) + return; + + XSizeHints* pHints = XAllocSizeHints(); + tools::Long nSupplied = 0; + XGetWMNormalHints( GetXDisplay(), + GetShellWindow(), + pHints, + &nSupplied + ); + pHints->min_width = nWidth; + pHints->min_height = nHeight; + pHints->flags |= PMinSize; + XSetWMNormalHints( GetXDisplay(), + GetShellWindow(), + pHints ); + XFree( pHints ); +} + +// Show + Pos (x,y,z) + Size (width,height) + +void X11SalFrame::Show( bool bVisible, bool bNoActivate ) +{ + if( ( bVisible && bMapped_ ) + || ( !bVisible && !bMapped_ ) ) + return; + + // HACK: this is a workaround for (at least) kwin + // even though transient frames should be kept above their parent + // this does not necessarily hold true for DOCK type windows + // so artificially set ABOVE and remove it again on hide + if( mpParent && (mpParent->nStyle_ & SalFrameStyleFlags::PARTIAL_FULLSCREEN ) && pDisplay_->getWMAdaptor()->isLegacyPartialFullscreen()) + pDisplay_->getWMAdaptor()->enableAlwaysOnTop( this, bVisible ); + + bMapped_ = bVisible; + bViewable_ = bVisible; + setXEmbedInfo(); + if( bVisible ) + { + if( ! (nStyle_ & SalFrameStyleFlags::INTRO) ) + { + // hide all INTRO frames + for (auto pSalFrame : GetDisplay()->getFrames() ) + { + const X11SalFrame* pFrame = static_cast< const X11SalFrame* >( pSalFrame ); + // look for intro bit map; if present, hide it + if( pFrame->nStyle_ & SalFrameStyleFlags::INTRO ) + { + if( pFrame->bMapped_ ) + const_cast<X11SalFrame*>(pFrame)->Show( false ); + } + } + } + + // update NET_WM_STATE which may have been deleted due to earlier Show(false) + if( nShowState_ == X11ShowState::Hidden ) + GetDisplay()->getWMAdaptor()->frameIsMapping( this ); + + /* + * Actually this is rather exotic and currently happens only in conjunction + * with the basic dialogue editor, + * which shows a frame and instantly hides it again. After that the + * editor window is shown and the WM takes this as an opportunity + * to show our hidden transient frame also. So Show( false ) must + * withdraw the frame AND delete the WM_TRANSIENT_FOR property. + * In case the frame is shown again, the transient hint must be restored here. + */ + if( ! IsChildWindow() + && ! IsOverrideRedirect() + && ! IsFloatGrabWindow() + && mpParent + ) + { + GetDisplay()->getWMAdaptor()->changeReferenceFrame( this, mpParent ); + } + + // #i45160# switch to desktop where a dialog with parent will appear + if( mpParent && mpParent->m_nWorkArea != m_nWorkArea ) + GetDisplay()->getWMAdaptor()->switchToWorkArea( mpParent->m_nWorkArea ); + + if( IsFloatGrabWindow() && + mpParent && + nVisibleFloats == 0 && + ! GetDisplay()->GetCaptureFrame() ) + { + /* #i39420# + * outsmart KWin's "focus strictly under mouse" mode + * which insists on taking the focus from the document + * to the new float. Grab focus to parent frame BEFORE + * showing the float (cannot grab it to the float + * before show). + */ + XGrabPointer( GetXDisplay(), + mpParent->GetWindow(), + True, + PointerMotionMask | ButtonPressMask | ButtonReleaseMask, + GrabModeAsync, + GrabModeAsync, + None, + mpParent ? mpParent->GetCursor() : None, + CurrentTime + ); + } + + Time nUserTime = 0; + if( ! bNoActivate && !(nStyle_ & SalFrameStyleFlags::OWNERDRAWDECORATION) ) + nUserTime = pDisplay_->GetX11ServerTime(); + GetDisplay()->getWMAdaptor()->setUserTime( this, nUserTime ); + if( ! bNoActivate && (nStyle_ & SalFrameStyleFlags::TOOLWINDOW) ) + m_bSetFocusOnMap = true; + + // actually map the window + if( m_bXEmbed ) + askForXEmbedFocus( 0 ); + else + { + if( GetWindow() != GetShellWindow() && ! IsSysChildWindow() ) + { + if( IsChildWindow() ) + XMapWindow( GetXDisplay(), GetShellWindow() ); + XSelectInput( GetXDisplay(), GetShellWindow(), CLIENT_EVENTS ); + } + if( nStyle_ & SalFrameStyleFlags::FLOAT ) + XMapRaised( GetXDisplay(), GetWindow() ); + else + XMapWindow( GetXDisplay(), GetWindow() ); + } + XSelectInput( GetXDisplay(), GetWindow(), CLIENT_EVENTS ); + + if( maGeometry.nWidth > 0 + && maGeometry.nHeight > 0 + && ( nWidth_ != static_cast<int>(maGeometry.nWidth) + || nHeight_ != static_cast<int>(maGeometry.nHeight) ) ) + { + nWidth_ = maGeometry.nWidth; + nHeight_ = maGeometry.nHeight; + } + + XSync( GetXDisplay(), False ); + + if( IsFloatGrabWindow() ) + { + /* + * Sawfish and twm can be switched to enter-exit focus behaviour. In this case + * we must grab the pointer else the dumb WM will put the focus to the + * override-redirect float window. The application window will be deactivated + * which causes that the floats are destroyed, so the user can never click on + * a menu because it vanishes as soon as he enters it. + */ + nVisibleFloats++; + if( nVisibleFloats == 1 && ! GetDisplay()->GetCaptureFrame() ) + { + /* #i39420# now move grab to the new float window */ + XGrabPointer( GetXDisplay(), + GetWindow(), + True, + PointerMotionMask | ButtonPressMask | ButtonReleaseMask, + GrabModeAsync, + GrabModeAsync, + None, + mpParent ? mpParent->GetCursor() : None, + CurrentTime + ); + } + } + CallCallback( SalEvent::Resize, nullptr ); + + /* + * sometimes a message box/dialogue is brought up when a frame is not mapped + * the corresponding TRANSIENT_FOR hint is then set to the root window + * so that the dialogue shows in all cases. Correct it here if the + * frame is shown afterwards. + */ + if( ! IsChildWindow() + && ! IsOverrideRedirect() + && ! IsFloatGrabWindow() + ) + { + for (auto const& child : maChildren) + { + if( child->mbTransientForRoot ) + GetDisplay()->getWMAdaptor()->changeReferenceFrame( child, this ); + } + } + /* + * leave X11ShowState::Unknown as this indicates first mapping + * and is only reset int HandleSizeEvent + */ + if( nShowState_ != X11ShowState::Unknown ) + nShowState_ = X11ShowState::Normal; + + /* + * plugged windows don't necessarily get the + * focus on show because the parent may already be mapped + * and have the focus. So try to set the focus + * to the child on Show(true) + */ + if( (nStyle_ & SalFrameStyleFlags::PLUG) && ! m_bXEmbed ) + XSetInputFocus( GetXDisplay(), + GetWindow(), + RevertToParent, + CurrentTime ); + + if( mpParent ) + { + // push this frame so it will be in front of its siblings + // only necessary for insane transient behaviour of Dtwm/olwm + mpParent->maChildren.remove( this ); + mpParent->maChildren.push_front(this); + } + } + else + { + if( getInputContext() ) + getInputContext()->Unmap(); + + if( ! IsChildWindow() ) + { + /* FIXME: Is deleting the property really necessary ? It hurts + * owner drawn windows at least. + */ + if( mpParent && ! (nStyle_ & SalFrameStyleFlags::OWNERDRAWDECORATION) ) + XDeleteProperty( GetXDisplay(), GetShellWindow(), GetDisplay()->getWMAdaptor()->getAtom( WMAdaptor::WM_TRANSIENT_FOR ) ); + XWithdrawWindow( GetXDisplay(), GetShellWindow(), m_nXScreen.getXScreen() ); + } + else if( ! m_bXEmbed ) + XUnmapWindow( GetXDisplay(), GetWindow() ); + + nShowState_ = X11ShowState::Hidden; + if( IsFloatGrabWindow() && nVisibleFloats ) + { + nVisibleFloats--; + if( nVisibleFloats == 0 && ! GetDisplay()->GetCaptureFrame() ) + XUngrabPointer( GetXDisplay(), + CurrentTime ); + } + // flush here; there may be a very seldom race between + // the display connection used for clipboard and our connection + Flush(); + } +} + +void X11SalFrame::ToTop( SalFrameToTop nFlags ) +{ + if( ( nFlags & SalFrameToTop::RestoreWhenMin ) + && ! ( nStyle_ & SalFrameStyleFlags::FLOAT ) + && nShowState_ != X11ShowState::Hidden + && nShowState_ != X11ShowState::Unknown + ) + { + GetDisplay()->getWMAdaptor()->frameIsMapping( this ); + if( GetWindow() != GetShellWindow() && ! IsSysChildWindow() ) + XMapWindow( GetXDisplay(), GetShellWindow() ); + XMapWindow( GetXDisplay(), GetWindow() ); + } + + ::Window aToTopWindow = IsSysChildWindow() ? GetWindow() : GetShellWindow(); + if( ! (nFlags & SalFrameToTop::GrabFocusOnly) ) + { + XRaiseWindow( GetXDisplay(), aToTopWindow ); + } + + if( ( ( nFlags & SalFrameToTop::GrabFocus ) || ( nFlags & SalFrameToTop::GrabFocusOnly ) ) + && bMapped_ ) + { + if( m_bXEmbed ) + askForXEmbedFocus( 0 ); + else + XSetInputFocus( GetXDisplay(), aToTopWindow, RevertToParent, CurrentTime ); + } + else if( ( nFlags & SalFrameToTop::RestoreWhenMin ) || ( nFlags & SalFrameToTop::ForegroundTask ) ) + { + Time nTimestamp = pDisplay_->GetX11ServerTime(); + GetDisplay()->getWMAdaptor()->activateWindow( this, nTimestamp ); + } +} + +void X11SalFrame::GetWorkArea( tools::Rectangle& rWorkArea ) +{ + rWorkArea = pDisplay_->getWMAdaptor()->getWorkArea( 0 ); +} + +void X11SalFrame::GetClientSize( tools::Long &rWidth, tools::Long &rHeight ) +{ + if( ! bViewable_ ) + { + rWidth = rHeight = 0; + return; + } + + rWidth = maGeometry.nWidth; + rHeight = maGeometry.nHeight; + + if( !rWidth || !rHeight ) + { + XWindowAttributes aAttrib; + + XGetWindowAttributes( GetXDisplay(), GetWindow(), &aAttrib ); + + maGeometry.nWidth = rWidth = aAttrib.width; + maGeometry.nHeight = rHeight = aAttrib.height; + } +} + +void X11SalFrame::Center( ) +{ + int nX, nY, nScreenWidth, nScreenHeight; + int nRealScreenWidth, nRealScreenHeight; + int nScreenX = 0, nScreenY = 0; + + const Size& aScreenSize = GetDisplay()->getDataForScreen( m_nXScreen ).m_aSize; + nScreenWidth = aScreenSize.Width(); + nScreenHeight = aScreenSize.Height(); + nRealScreenWidth = nScreenWidth; + nRealScreenHeight = nScreenHeight; + + if( GetDisplay()->IsXinerama() ) + { + // get xinerama screen we are on + // if there is a parent, use its center for screen determination + // else use the pointer + ::Window aRoot, aChild; + int root_x, root_y, x, y; + unsigned int mask; + if( mpParent ) + { + root_x = mpParent->maGeometry.nX + mpParent->maGeometry.nWidth/2; + root_y = mpParent->maGeometry.nY + mpParent->maGeometry.nHeight/2; + } + else + XQueryPointer( GetXDisplay(), + GetShellWindow(), + &aRoot, &aChild, + &root_x, &root_y, + &x, &y, + &mask ); + const std::vector< tools::Rectangle >& rScreens = GetDisplay()->GetXineramaScreens(); + for(const auto & rScreen : rScreens) + if( rScreen.Contains( Point( root_x, root_y ) ) ) + { + nScreenX = rScreen.Left(); + nScreenY = rScreen.Top(); + nRealScreenWidth = rScreen.GetWidth(); + nRealScreenHeight = rScreen.GetHeight(); + break; + } + } + + if( mpParent ) + { + X11SalFrame* pFrame = mpParent; + while( pFrame->mpParent ) + pFrame = pFrame->mpParent; + if( pFrame->maGeometry.nWidth < 1 || pFrame->maGeometry.nHeight < 1 ) + { + tools::Rectangle aRect; + pFrame->GetPosSize( aRect ); + pFrame->maGeometry.nX = aRect.Left(); + pFrame->maGeometry.nY = aRect.Top(); + pFrame->maGeometry.nWidth = aRect.GetWidth(); + pFrame->maGeometry.nHeight = aRect.GetHeight(); + } + + if( pFrame->nStyle_ & SalFrameStyleFlags::PLUG ) + { + ::Window aRoot; + unsigned int bw, depth; + XGetGeometry( GetXDisplay(), + pFrame->GetShellWindow(), + &aRoot, + &nScreenX, &nScreenY, + reinterpret_cast<unsigned int*>(&nScreenWidth), + reinterpret_cast<unsigned int*>(&nScreenHeight), + &bw, &depth ); + } + else + { + nScreenX = pFrame->maGeometry.nX; + nScreenY = pFrame->maGeometry.nY; + nScreenWidth = pFrame->maGeometry.nWidth; + nScreenHeight = pFrame->maGeometry.nHeight; + } + } + + if( mpParent && mpParent->nShowState_ == X11ShowState::Normal ) + { + if( maGeometry.nWidth >= mpParent->maGeometry.nWidth && + maGeometry.nHeight >= mpParent->maGeometry.nHeight ) + { + nX = nScreenX + 40; + nY = nScreenY + 40; + } + else + { + // center the window relative to the top level frame + nX = (nScreenWidth - static_cast<int>(maGeometry.nWidth) ) / 2 + nScreenX; + nY = (nScreenHeight - static_cast<int>(maGeometry.nHeight)) / 2 + nScreenY; + } + } + else + { + // center the window relative to screen + nX = (nRealScreenWidth - static_cast<int>(maGeometry.nWidth) ) / 2 + nScreenX; + nY = (nRealScreenHeight - static_cast<int>(maGeometry.nHeight)) / 2 + nScreenY; + } + nX = nX < 0 ? 0 : nX; + nY = nY < 0 ? 0 : nY; + + bDefaultPosition_ = False; + if( mpParent ) + { + nX -= mpParent->maGeometry.nX; + nY -= mpParent->maGeometry.nY; + } + + Point aPoint(nX, nY); + SetPosSize( tools::Rectangle( aPoint, Size( maGeometry.nWidth, maGeometry.nHeight ) ) ); +} + +void X11SalFrame::updateScreenNumber() +{ + if( GetDisplay()->IsXinerama() && GetDisplay()->GetXineramaScreens().size() > 1 ) + { + Point aPoint( maGeometry.nX, maGeometry.nY ); + const std::vector<tools::Rectangle>& rScreenRects( GetDisplay()->GetXineramaScreens() ); + size_t nScreens = rScreenRects.size(); + for( size_t i = 0; i < nScreens; i++ ) + { + if( rScreenRects[i].Contains( aPoint ) ) + { + maGeometry.nDisplayScreenNumber = static_cast<unsigned int>(i); + break; + } + } + } + else + maGeometry.nDisplayScreenNumber = m_nXScreen.getXScreen(); +} + +void X11SalFrame::SetPosSize( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight, sal_uInt16 nFlags ) +{ + if( nStyle_ & SalFrameStyleFlags::PLUG ) + return; + + // relative positioning in X11SalFrame::SetPosSize + tools::Rectangle aPosSize( Point( maGeometry.nX, maGeometry.nY ), Size( maGeometry.nWidth, maGeometry.nHeight ) ); + aPosSize.Justify(); + + if( ! ( nFlags & SAL_FRAME_POSSIZE_X ) ) + { + nX = aPosSize.Left(); + if( mpParent ) + nX -= mpParent->maGeometry.nX; + } + if( ! ( nFlags & SAL_FRAME_POSSIZE_Y ) ) + { + nY = aPosSize.Top(); + if( mpParent ) + nY -= mpParent->maGeometry.nY; + } + if( ! ( nFlags & SAL_FRAME_POSSIZE_WIDTH ) ) + nWidth = aPosSize.GetWidth(); + if( ! ( nFlags & SAL_FRAME_POSSIZE_HEIGHT ) ) + nHeight = aPosSize.GetHeight(); + + aPosSize = tools::Rectangle( Point( nX, nY ), Size( nWidth, nHeight ) ); + + if( ! ( nFlags & ( SAL_FRAME_POSSIZE_X | SAL_FRAME_POSSIZE_Y ) ) ) + { + if( bDefaultPosition_ ) + { + maGeometry.nWidth = aPosSize.GetWidth(); + maGeometry.nHeight = aPosSize.GetHeight(); + Center(); + } + else + SetSize( Size( nWidth, nHeight ) ); + } + else + SetPosSize( aPosSize ); + + bDefaultPosition_ = False; +} + +void X11SalFrame::SetAlwaysOnTop( bool bOnTop ) +{ + if( ! IsOverrideRedirect() ) + { + bAlwaysOnTop_ = bOnTop; + pDisplay_->getWMAdaptor()->enableAlwaysOnTop( this, bOnTop ); + } +} + +constexpr auto FRAMESTATE_MASK_GEOMETRY = + WindowStateMask::X | WindowStateMask::Y | + WindowStateMask::Width | WindowStateMask::Height; +constexpr auto FRAMESTATE_MASK_MAXIMIZED_GEOMETRY = + WindowStateMask::MaximizedX | WindowStateMask::MaximizedY | + WindowStateMask::MaximizedWidth | WindowStateMask::MaximizedHeight; + +void X11SalFrame::SetWindowState( const SalFrameState *pState ) +{ + if (pState == nullptr) + return; + + // Request for position or size change + if (pState->mnMask & FRAMESTATE_MASK_GEOMETRY) + { + /* #i44325# + * if maximized, set restore size and guess maximized size from last time + * in state change below maximize window + */ + if( ! IsChildWindow() && + (pState->mnMask & WindowStateMask::State) && + (pState->mnState & WindowStateState::Maximized) && + (pState->mnMask & FRAMESTATE_MASK_GEOMETRY) == FRAMESTATE_MASK_GEOMETRY && + (pState->mnMask & FRAMESTATE_MASK_MAXIMIZED_GEOMETRY) == FRAMESTATE_MASK_MAXIMIZED_GEOMETRY + ) + { + XSizeHints* pHints = XAllocSizeHints(); + tools::Long nSupplied = 0; + XGetWMNormalHints( GetXDisplay(), + GetShellWindow(), + pHints, + &nSupplied ); + pHints->flags |= PPosition | PWinGravity; + pHints->x = pState->mnX; + pHints->y = pState->mnY; + pHints->win_gravity = pDisplay_->getWMAdaptor()->getPositionWinGravity(); + XSetWMNormalHints( GetXDisplay(), + GetShellWindow(), + pHints ); + XFree( pHints ); + + XMoveResizeWindow( GetXDisplay(), GetShellWindow(), + pState->mnX, pState->mnY, + pState->mnWidth, pState->mnHeight ); + // guess maximized geometry from last time + maGeometry.nX = pState->mnMaximizedX; + maGeometry.nY = pState->mnMaximizedY; + maGeometry.nWidth = pState->mnMaximizedWidth; + maGeometry.nHeight = pState->mnMaximizedHeight; + updateScreenNumber(); + } + else + { + bool bDoAdjust = false; + tools::Rectangle aPosSize; + // initialize with current geometry + if ((pState->mnMask & FRAMESTATE_MASK_GEOMETRY) != FRAMESTATE_MASK_GEOMETRY) + GetPosSize (aPosSize); + + sal_uInt16 nPosFlags = 0; + + // change requested properties + if (pState->mnMask & WindowStateMask::X) + { + aPosSize.SetPosX(pState->mnX - (mpParent ? mpParent->maGeometry.nX : 0)); + nPosFlags |= SAL_FRAME_POSSIZE_X; + } + if (pState->mnMask & WindowStateMask::Y) + { + aPosSize.SetPosY(pState->mnY - (mpParent ? mpParent->maGeometry.nY : 0)); + nPosFlags |= SAL_FRAME_POSSIZE_Y; + } + if (pState->mnMask & WindowStateMask::Width) + { + tools::Long nWidth = pState->mnWidth > 0 ? pState->mnWidth - 1 : 0; + aPosSize.setWidth (nWidth); + bDoAdjust = true; + } + if (pState->mnMask & WindowStateMask::Height) + { + int nHeight = pState->mnHeight > 0 ? pState->mnHeight - 1 : 0; + aPosSize.setHeight (nHeight); + bDoAdjust = true; + } + + const Size& aScreenSize = pDisplay_->getDataForScreen( m_nXScreen ).m_aSize; + + if( bDoAdjust && aPosSize.GetWidth() <= aScreenSize.Width() + && aPosSize.GetHeight() <= aScreenSize.Height() ) + { + SalFrameGeometry aGeom = maGeometry; + + if( ! (nStyle_ & ( SalFrameStyleFlags::FLOAT | SalFrameStyleFlags::PLUG ) ) && + mpParent && + aGeom.nLeftDecoration == 0 && + aGeom.nTopDecoration == 0 ) + { + aGeom = mpParent->maGeometry; + if( aGeom.nLeftDecoration == 0 && + aGeom.nTopDecoration == 0 ) + { + aGeom.nLeftDecoration = 5; + aGeom.nTopDecoration = 20; + aGeom.nRightDecoration = 5; + aGeom.nBottomDecoration = 5; + } + } + + auto nRight = aPosSize.Right() + (mpParent ? mpParent->maGeometry.nX : 0); + auto nBottom = aPosSize.Bottom() + (mpParent ? mpParent->maGeometry.nY : 0); + auto nLeft = aPosSize.Left() + (mpParent ? mpParent->maGeometry.nX : 0); + auto nTop = aPosSize.Top() + (mpParent ? mpParent->maGeometry.nY : 0); + + // adjust position so that frame fits onto screen + if( nRight+static_cast<tools::Long>(aGeom.nRightDecoration) > aScreenSize.Width()-1 ) + aPosSize.Move( aScreenSize.Width() - nRight - static_cast<tools::Long>(aGeom.nRightDecoration), 0 ); + if( nBottom+static_cast<tools::Long>(aGeom.nBottomDecoration) > aScreenSize.Height()-1 ) + aPosSize.Move( 0, aScreenSize.Height() - nBottom - static_cast<tools::Long>(aGeom.nBottomDecoration) ); + if( nLeft < static_cast<tools::Long>(aGeom.nLeftDecoration) ) + aPosSize.Move( static_cast<tools::Long>(aGeom.nLeftDecoration) - nLeft, 0 ); + if( nTop < static_cast<tools::Long>(aGeom.nTopDecoration) ) + aPosSize.Move( 0, static_cast<tools::Long>(aGeom.nTopDecoration) - nTop ); + } + + SetPosSize(aPosSize.getX(), aPosSize.getY(), + aPosSize.GetWidth(), aPosSize.GetHeight(), + SAL_FRAME_POSSIZE_WIDTH | SAL_FRAME_POSSIZE_HEIGHT | + nPosFlags); + } + } + + // request for status change + if (!(pState->mnMask & WindowStateMask::State)) + return; + + if (pState->mnState & WindowStateState::Maximized) + { + nShowState_ = X11ShowState::Normal; + if( ! (pState->mnState & (WindowStateState::MaximizedHorz|WindowStateState::MaximizedVert) ) ) + Maximize(); + else + { + bool bHorz(pState->mnState & WindowStateState::MaximizedHorz); + bool bVert(pState->mnState & WindowStateState::MaximizedVert); + GetDisplay()->getWMAdaptor()->maximizeFrame( this, bHorz, bVert ); + } + maRestorePosSize.SetLeft( pState->mnX ); + maRestorePosSize.SetTop( pState->mnY ); + maRestorePosSize.SetRight( maRestorePosSize.Left() + pState->mnWidth ); + maRestorePosSize.SetRight( maRestorePosSize.Left() + pState->mnHeight ); + } + else if( mbMaximizedHorz || mbMaximizedVert ) + GetDisplay()->getWMAdaptor()->maximizeFrame( this, false, false ); + + if (pState->mnState & WindowStateState::Minimized) + { + if (nShowState_ == X11ShowState::Unknown) + nShowState_ = X11ShowState::Normal; + Minimize(); + } + if (pState->mnState & WindowStateState::Normal) + { + if (nShowState_ != X11ShowState::Normal) + Restore(); + } +} + +bool X11SalFrame::GetWindowState( SalFrameState* pState ) +{ + if( X11ShowState::Minimized == nShowState_ ) + pState->mnState = WindowStateState::Minimized; + else + pState->mnState = WindowStateState::Normal; + + tools::Rectangle aPosSize; + if( maRestorePosSize.IsEmpty() ) + GetPosSize( aPosSize ); + else + aPosSize = maRestorePosSize; + + if( mbMaximizedHorz ) + pState->mnState |= WindowStateState::MaximizedHorz; + if( mbMaximizedVert ) + pState->mnState |= WindowStateState::MaximizedVert; + + pState->mnX = aPosSize.Left(); + pState->mnY = aPosSize.Top(); + pState->mnWidth = aPosSize.GetWidth(); + pState->mnHeight = aPosSize.GetHeight(); + + pState->mnMask = FRAMESTATE_MASK_GEOMETRY | WindowStateMask::State; + + if (! maRestorePosSize.IsEmpty() ) + { + GetPosSize( aPosSize ); + pState->mnState |= WindowStateState::Maximized; + pState->mnMaximizedX = aPosSize.Left(); + pState->mnMaximizedY = aPosSize.Top(); + pState->mnMaximizedWidth = aPosSize.GetWidth(); + pState->mnMaximizedHeight = aPosSize.GetHeight(); + pState->mnMask |= FRAMESTATE_MASK_MAXIMIZED_GEOMETRY; + } + + return true; +} + +// native menu implementation - currently empty +void X11SalFrame::DrawMenuBar() +{ +} + +void X11SalFrame::SetMenu( SalMenu* ) +{ +} + +void X11SalFrame::GetPosSize( tools::Rectangle &rPosSize ) +{ + if( maGeometry.nWidth < 1 || maGeometry.nHeight < 1 ) + { + const Size& aScreenSize = pDisplay_->getDataForScreen( m_nXScreen ).m_aSize; + tools::Long w = aScreenSize.Width() - maGeometry.nLeftDecoration - maGeometry.nRightDecoration; + tools::Long h = aScreenSize.Height() - maGeometry.nTopDecoration - maGeometry.nBottomDecoration; + + rPosSize = tools::Rectangle( Point( maGeometry.nX, maGeometry.nY ), Size( w, h ) ); + } + else + rPosSize = tools::Rectangle( Point( maGeometry.nX, maGeometry.nY ), + Size( maGeometry.nWidth, maGeometry.nHeight ) ); +} + +void X11SalFrame::SetSize( const Size &rSize ) +{ + if( rSize.IsEmpty() ) + return; + + if( ! ( nStyle_ & SalFrameStyleFlags::SIZEABLE ) + && ! IsChildWindow() + && ( nStyle_ & (SalFrameStyleFlags::FLOAT|SalFrameStyleFlags::OWNERDRAWDECORATION) ) != SalFrameStyleFlags::FLOAT ) + { + XSizeHints* pHints = XAllocSizeHints(); + tools::Long nSupplied = 0; + XGetWMNormalHints( GetXDisplay(), + GetShellWindow(), + pHints, + &nSupplied + ); + pHints->min_width = rSize.Width(); + pHints->min_height = rSize.Height(); + pHints->max_width = rSize.Width(); + pHints->max_height = rSize.Height(); + pHints->flags |= PMinSize | PMaxSize; + XSetWMNormalHints( GetXDisplay(), + GetShellWindow(), + pHints ); + XFree( pHints ); + } + XResizeWindow( GetXDisplay(), IsSysChildWindow() ? GetWindow() : GetShellWindow(), rSize.Width(), rSize.Height() ); + if( GetWindow() != GetShellWindow() ) + { + if( nStyle_ & SalFrameStyleFlags::PLUG ) + XMoveResizeWindow( GetXDisplay(), GetWindow(), 0, 0, rSize.Width(), rSize.Height() ); + else + XResizeWindow( GetXDisplay(), GetWindow(), rSize.Width(), rSize.Height() ); + } + + maGeometry.nWidth = rSize.Width(); + maGeometry.nHeight = rSize.Height(); + + // allow the external status window to reposition + if (mbInputFocus && mpInputContext != nullptr) + mpInputContext->SetICFocus ( this ); +} + +void X11SalFrame::SetPosSize( const tools::Rectangle &rPosSize ) +{ + XWindowChanges values; + values.x = rPosSize.Left(); + values.y = rPosSize.Top(); + values.width = rPosSize.GetWidth(); + values.height = rPosSize.GetHeight(); + + if( !values.width || !values.height ) + return; + + if( mpParent && ! IsSysChildWindow() ) + { + if( AllSettings::GetLayoutRTL() ) + values.x = mpParent->maGeometry.nWidth-values.width-1-values.x; + + ::Window aChild; + // coordinates are relative to parent, so translate to root coordinates + XTranslateCoordinates( GetDisplay()->GetDisplay(), + mpParent->GetWindow(), + GetDisplay()->GetRootWindow( m_nXScreen ), + values.x, values.y, + &values.x, &values.y, + & aChild ); + } + + bool bMoved = false; + bool bSized = false; + if( values.x != maGeometry.nX || values.y != maGeometry.nY ) + bMoved = true; + if( values.width != static_cast<int>(maGeometry.nWidth) || values.height != static_cast<int>(maGeometry.nHeight) ) + bSized = true; + + // do not set WMNormalHints for... + if( + // child windows + ! IsChildWindow() + // popups (menu, help window, etc.) + && (nStyle_ & (SalFrameStyleFlags::FLOAT|SalFrameStyleFlags::OWNERDRAWDECORATION) ) != SalFrameStyleFlags::FLOAT + // shown, sizeable windows + && ( nShowState_ == X11ShowState::Unknown || + nShowState_ == X11ShowState::Hidden || + ! ( nStyle_ & SalFrameStyleFlags::SIZEABLE ) + ) + ) + { + XSizeHints* pHints = XAllocSizeHints(); + tools::Long nSupplied = 0; + XGetWMNormalHints( GetXDisplay(), + GetShellWindow(), + pHints, + &nSupplied + ); + if( ! ( nStyle_ & SalFrameStyleFlags::SIZEABLE ) ) + { + pHints->min_width = rPosSize.GetWidth(); + pHints->min_height = rPosSize.GetHeight(); + pHints->max_width = rPosSize.GetWidth(); + pHints->max_height = rPosSize.GetHeight(); + pHints->flags |= PMinSize | PMaxSize; + } + if( nShowState_ == X11ShowState::Unknown || nShowState_ == X11ShowState::Hidden ) + { + pHints->flags |= PPosition | PWinGravity; + pHints->x = values.x; + pHints->y = values.y; + pHints->win_gravity = pDisplay_->getWMAdaptor()->getPositionWinGravity(); + } + if( mbFullScreen ) + { + pHints->max_width = 10000; + pHints->max_height = 10000; + pHints->flags |= PMaxSize; + } + XSetWMNormalHints( GetXDisplay(), + GetShellWindow(), + pHints ); + XFree( pHints ); + } + + XMoveResizeWindow( GetXDisplay(), IsSysChildWindow() ? GetWindow() : GetShellWindow(), values.x, values.y, values.width, values.height ); + if( GetShellWindow() != GetWindow() ) + { + if( nStyle_ & SalFrameStyleFlags::PLUG ) + XMoveResizeWindow( GetXDisplay(), GetWindow(), 0, 0, values.width, values.height ); + else + XMoveResizeWindow( GetXDisplay(), GetWindow(), values.x, values.y, values.width, values.height ); + } + + maGeometry.nX = values.x; + maGeometry.nY = values.y; + maGeometry.nWidth = values.width; + maGeometry.nHeight = values.height; + if( IsSysChildWindow() && mpParent ) + { + // translate back to root coordinates + maGeometry.nX += mpParent->maGeometry.nX; + maGeometry.nY += mpParent->maGeometry.nY; + } + + updateScreenNumber(); + if( bSized && ! bMoved ) + CallCallback( SalEvent::Resize, nullptr ); + else if( bMoved && ! bSized ) + CallCallback( SalEvent::Move, nullptr ); + else + CallCallback( SalEvent::MoveResize, nullptr ); + + // allow the external status window to reposition + if (mbInputFocus && mpInputContext != nullptr) + mpInputContext->SetICFocus ( this ); +} + +void X11SalFrame::Minimize() +{ + if( IsSysChildWindow() ) + return; + + if( X11ShowState::Unknown == nShowState_ || X11ShowState::Hidden == nShowState_ ) + { + SAL_WARN( "vcl", "X11SalFrame::Minimize on withdrawn window" ); + return; + } + + if( XIconifyWindow( GetXDisplay(), + GetShellWindow(), + pDisplay_->GetDefaultXScreen().getXScreen() ) ) + nShowState_ = X11ShowState::Minimized; +} + +void X11SalFrame::Maximize() +{ + if( IsSysChildWindow() ) + return; + + if( X11ShowState::Minimized == nShowState_ ) + { + GetDisplay()->getWMAdaptor()->frameIsMapping( this ); + XMapWindow( GetXDisplay(), GetShellWindow() ); + nShowState_ = X11ShowState::Normal; + } + + pDisplay_->getWMAdaptor()->maximizeFrame( this ); +} + +void X11SalFrame::Restore() +{ + if( IsSysChildWindow() ) + return; + + if( X11ShowState::Unknown == nShowState_ || X11ShowState::Hidden == nShowState_ ) + { + SAL_INFO( "vcl", "X11SalFrame::Restore on withdrawn window" ); + return; + } + + if( X11ShowState::Minimized == nShowState_ ) + { + GetDisplay()->getWMAdaptor()->frameIsMapping( this ); + XMapWindow( GetXDisplay(), GetShellWindow() ); + nShowState_ = X11ShowState::Normal; + } + + pDisplay_->getWMAdaptor()->maximizeFrame( this, false, false ); +} + +void X11SalFrame::SetScreenNumber( unsigned int nNewScreen ) +{ + if( nNewScreen == maGeometry.nDisplayScreenNumber ) + return; + + if( GetDisplay()->IsXinerama() && GetDisplay()->GetXineramaScreens().size() > 1 ) + { + if( nNewScreen >= GetDisplay()->GetXineramaScreens().size() ) + return; + + tools::Rectangle aOldScreenRect( GetDisplay()->GetXineramaScreens()[maGeometry.nDisplayScreenNumber] ); + tools::Rectangle aNewScreenRect( GetDisplay()->GetXineramaScreens()[nNewScreen] ); + bool bVisible = bMapped_; + if( bVisible ) + Show( false ); + maGeometry.nX = aNewScreenRect.Left() + (maGeometry.nX - aOldScreenRect.Left()); + maGeometry.nY = aNewScreenRect.Top() + (maGeometry.nY - aOldScreenRect.Top()); + createNewWindow( None, m_nXScreen ); + if( bVisible ) + Show( true ); + maGeometry.nDisplayScreenNumber = nNewScreen; + } + else if( nNewScreen < GetDisplay()->GetXScreenCount() ) + { + bool bVisible = bMapped_; + if( bVisible ) + Show( false ); + createNewWindow( None, SalX11Screen( nNewScreen ) ); + if( bVisible ) + Show( true ); + maGeometry.nDisplayScreenNumber = nNewScreen; + } +} + +void X11SalFrame::SetApplicationID( const OUString &rWMClass ) +{ + if( rWMClass != m_sWMClass && ! IsChildWindow() ) + { + m_sWMClass = rWMClass; + updateWMClass(); + for (auto const& child : maChildren) + child->SetApplicationID(rWMClass); + } +} + +void X11SalFrame::updateWMClass() +{ + XClassHint* pClass = XAllocClassHint(); + OString aResName = SalGenericSystem::getFrameResName(); + pClass->res_name = const_cast<char*>(aResName.getStr()); + + OString aResClass = OUStringToOString(m_sWMClass, RTL_TEXTENCODING_ASCII_US); + const char *pResClass = !aResClass.isEmpty() ? aResClass.getStr() : + SalGenericSystem::getFrameClassName(); + + pClass->res_class = const_cast<char*>(pResClass); + XSetClassHint( GetXDisplay(), GetShellWindow(), pClass ); + XFree( pClass ); +} + +void X11SalFrame::ShowFullScreen( bool bFullScreen, sal_Int32 nScreen ) +{ + if( GetDisplay()->IsXinerama() && GetDisplay()->GetXineramaScreens().size() > 1 ) + { + if( mbFullScreen == bFullScreen ) + return; + if( bFullScreen ) + { + maRestorePosSize = tools::Rectangle( Point( maGeometry.nX, maGeometry.nY ), + Size( maGeometry.nWidth, maGeometry.nHeight ) ); + tools::Rectangle aRect; + if( nScreen < 0 || o3tl::make_unsigned(nScreen) >= GetDisplay()->GetXineramaScreens().size() ) + aRect = tools::Rectangle( Point(0,0), GetDisplay()->GetScreenSize( m_nXScreen ) ); + else + aRect = GetDisplay()->GetXineramaScreens()[nScreen]; + nStyle_ |= SalFrameStyleFlags::PARTIAL_FULLSCREEN; + bool bVisible = bMapped_; + if( bVisible ) + Show( false ); + maGeometry.nX = aRect.Left(); + maGeometry.nY = aRect.Top(); + maGeometry.nWidth = aRect.GetWidth(); + maGeometry.nHeight = aRect.GetHeight(); + mbMaximizedHorz = mbMaximizedVert = false; + mbFullScreen = true; + createNewWindow( None, m_nXScreen ); + if( GetDisplay()->getWMAdaptor()->isLegacyPartialFullscreen() ) + GetDisplay()->getWMAdaptor()->enableAlwaysOnTop( this, true ); + else + GetDisplay()->getWMAdaptor()->showFullScreen( this, true ); + if( bVisible ) + Show(true); + + } + else + { + mbFullScreen = false; + nStyle_ &= ~SalFrameStyleFlags::PARTIAL_FULLSCREEN; + bool bVisible = bMapped_; + tools::Rectangle aRect = maRestorePosSize; + maRestorePosSize = tools::Rectangle(); + if( bVisible ) + Show( false ); + createNewWindow( None, m_nXScreen ); + if( !aRect.IsEmpty() ) + SetPosSize( aRect.Left(), aRect.Top(), aRect.GetWidth(), aRect.GetHeight(), + SAL_FRAME_POSSIZE_X | SAL_FRAME_POSSIZE_Y | + SAL_FRAME_POSSIZE_WIDTH | SAL_FRAME_POSSIZE_HEIGHT ); + if( bVisible ) + Show( true ); + } + } + else + { + if( nScreen < 0 || o3tl::make_unsigned(nScreen) >= GetDisplay()->GetXScreenCount() ) + nScreen = m_nXScreen.getXScreen(); + if( nScreen != static_cast<int>(m_nXScreen.getXScreen()) ) + { + bool bVisible = bMapped_; + if( mbFullScreen ) + pDisplay_->getWMAdaptor()->showFullScreen( this, false ); + if( bVisible ) + Show( false ); + createNewWindow( None, SalX11Screen( nScreen ) ); + if( mbFullScreen ) + pDisplay_->getWMAdaptor()->showFullScreen( this, true ); + if( bVisible ) + Show( true ); + } + if( mbFullScreen == bFullScreen ) + return; + + pDisplay_->getWMAdaptor()->showFullScreen( this, bFullScreen ); + } +} + +void X11SalFrame::StartPresentation( bool bStart ) +{ + maScreenSaverInhibitor.inhibit( bStart, + u"presentation", + true, // isX11 + mhWindow, + GetXDisplay() ); + + if( ! bStart && hPresentationWindow != None ) + doReparentPresentationDialogues( GetDisplay() ); + hPresentationWindow = (bStart && IsOverrideRedirect() ) ? GetWindow() : None; + + if( bStart && hPresentationWindow ) + { + /* #i10559# workaround for WindowMaker: try to restore + * current focus after presentation window is gone + */ + int revert_to = 0; + XGetInputFocus( GetXDisplay(), &hPresFocusWindow, &revert_to ); + } +} + +// Pointer + +void X11SalFrame::SetPointer( PointerStyle ePointerStyle ) +{ + hCursor_ = pDisplay_->GetPointer( ePointerStyle ); + XDefineCursor( GetXDisplay(), GetWindow(), hCursor_ ); + + if( IsCaptured() || nVisibleFloats > 0 ) + XChangeActivePointerGrab( GetXDisplay(), + PointerMotionMask|ButtonPressMask|ButtonReleaseMask, + hCursor_, + CurrentTime ); +} + +void X11SalFrame::SetPointerPos(tools::Long nX, tools::Long nY) +{ + /* when the application tries to center the mouse in the dialog the + * window isn't mapped already. So use coordinates relative to the root window. + */ + unsigned int nWindowLeft = maGeometry.nX + nX; + unsigned int nWindowTop = maGeometry.nY + nY; + + XWarpPointer( GetXDisplay(), None, pDisplay_->GetRootWindow( pDisplay_->GetDefaultXScreen() ), + 0, 0, 0, 0, nWindowLeft, nWindowTop); +} + +// delay handling of extended text input +#if !defined(__synchronous_extinput__) +void +X11SalFrame::HandleExtTextEvent (XClientMessageEvent const *pEvent) +{ + #if SAL_TYPES_SIZEOFLONG > 4 + void* pExtTextEvent = reinterpret_cast<void*>( (pEvent->data.l[0] & 0xffffffff) + | (pEvent->data.l[1] << 32) ); + #else + void* pExtTextEvent = reinterpret_cast<void*>(pEvent->data.l[0]); + #endif + SalEvent nExtTextEventType = SalEvent(pEvent->data.l[2]); + + CallCallback(nExtTextEventType, pExtTextEvent); + + switch (nExtTextEventType) + { + case SalEvent::EndExtTextInput: + break; + + case SalEvent::ExtTextInput: + break; + + default: + SAL_WARN("vcl.window", + "X11SalFrame::HandleExtTextEvent: invalid extended input."); + } +} +#endif /* defined(__synchronous_extinput__) */ + +// PostEvent + +bool X11SalFrame::PostEvent(std::unique_ptr<ImplSVEvent> pData) +{ + GetDisplay()->SendInternalEvent( this, pData.release() ); + return true; +} + +// Title + +void X11SalFrame::SetTitle( const OUString& rTitle ) +{ + if( ! ( IsChildWindow() || (nStyle_ & SalFrameStyleFlags::FLOAT ) ) ) + { + m_aTitle = rTitle; + GetDisplay()->getWMAdaptor()->setWMName( this, rTitle ); + } +} + +void X11SalFrame::Flush() +{ + if( pGraphics_ ) + pGraphics_->Flush(); + XFlush( GetDisplay()->GetDisplay() ); +} + +// Keyboard + +void X11SalFrame::SetInputContext( SalInputContext* pContext ) +{ + if (pContext == nullptr) + return; + + // 1. We should create an input context for this frame + // only when InputContextFlags::Text is set. + + if (!(pContext->mnOptions & InputContextFlags::Text)) + { + if( mpInputContext ) + mpInputContext->Unmap(); + return; + } + + // 2. We should use on-the-spot inputstyle + // only when InputContextFlags::ExtTExt is set. + + if (mpInputContext == nullptr) + { + mpInputContext.reset( new SalI18N_InputContext( this ) ); + if (mpInputContext->UseContext()) + { + mpInputContext->ExtendEventMask( GetShellWindow() ); + if (mbInputFocus) + mpInputContext->SetICFocus( this ); + } + } + else + mpInputContext->Map( this ); +} + +void X11SalFrame::EndExtTextInput( EndExtTextInputFlags ) +{ + if (mpInputContext != nullptr) + mpInputContext->EndExtTextInput(); +} + +OUString X11SalFrame::GetKeyName( sal_uInt16 nKeyCode ) +{ + return GetDisplay()->GetKeyName( nKeyCode ); +} + +bool X11SalFrame::MapUnicodeToKeyCode( sal_Unicode , LanguageType , vcl::KeyCode& ) +{ + // not supported yet + return false; +} + +LanguageType X11SalFrame::GetInputLanguage() +{ + // could be improved by checking unicode ranges of the last input + return LANGUAGE_DONTKNOW; +} + +// Settings + +void X11SalFrame::UpdateSettings( AllSettings& rSettings ) +{ + StyleSettings aStyleSettings = rSettings.GetStyleSettings(); + aStyleSettings.SetCursorBlinkTime( 500 ); + aStyleSettings.SetMenuBarTextColor( aStyleSettings.GetPersonaMenuBarTextColor().value_or( COL_BLACK ) ); + rSettings.SetStyleSettings( aStyleSettings ); +} + +void X11SalFrame::CaptureMouse( bool bCapture ) +{ + nCaptured_ = pDisplay_->CaptureMouse( bCapture ? this : nullptr ); +} + +void X11SalFrame::SetParent( SalFrame* pNewParent ) +{ + if( mpParent != pNewParent ) + { + if( mpParent ) + mpParent->maChildren.remove( this ); + + mpParent = static_cast<X11SalFrame*>(pNewParent); + mpParent->maChildren.push_back( this ); + if( mpParent->m_nXScreen != m_nXScreen ) + createNewWindow( None, mpParent->m_nXScreen ); + GetDisplay()->getWMAdaptor()->changeReferenceFrame( this, mpParent ); + } +} + +SalFrame* X11SalFrame::GetParent() const +{ + return mpParent; +} + +void X11SalFrame::createNewWindow( ::Window aNewParent, SalX11Screen nXScreen ) +{ + bool bWasVisible = bMapped_; + if( bWasVisible ) + Show( false ); + + if( nXScreen.getXScreen() >= GetDisplay()->GetXScreenCount() ) + nXScreen = m_nXScreen; + + SystemParentData aParentData; + aParentData.nSize = sizeof(SystemParentData); + aParentData.aWindow = aNewParent; + aParentData.bXEmbedSupport = (aNewParent != None && m_bXEmbed); // caution: this is guesswork + if( aNewParent == None ) + { + aParentData.aWindow = None; + m_bXEmbed = false; + } + else + { + // is new parent a root window ? + Display* pDisp = GetDisplay()->GetDisplay(); + int nScreens = GetDisplay()->GetXScreenCount(); + for( int i = 0; i < nScreens; i++ ) + { + if( aNewParent == RootWindow( pDisp, i ) ) + { + nXScreen = SalX11Screen( i ); + aParentData.aWindow = None; + m_bXEmbed = false; + break; + } + } + } + + // first deinit frame + updateGraphics(true); + if( mpInputContext ) + { + mpInputContext->UnsetICFocus(); + mpInputContext->Unmap(); + } + if( GetWindow() == hPresentationWindow ) + { + hPresentationWindow = None; + doReparentPresentationDialogues( GetDisplay() ); + } + XDestroyWindow( GetXDisplay(), mhWindow ); + mhWindow = None; + + // now init with new parent again + if ( aParentData.aWindow != None ) + Init( nStyle_ | SalFrameStyleFlags::PLUG, nXScreen, &aParentData ); + else + Init( nStyle_ & ~SalFrameStyleFlags::PLUG, nXScreen, nullptr, true ); + + // update graphics if necessary + updateGraphics(false); + + if( ! m_aTitle.isEmpty() ) + SetTitle( m_aTitle ); + + if( mpParent ) + { + if( mpParent->m_nXScreen != m_nXScreen ) + SetParent( nullptr ); + else + pDisplay_->getWMAdaptor()->changeReferenceFrame( this, mpParent ); + } + + if( bWasVisible ) + Show( true ); + + std::list< X11SalFrame* > aChildren = maChildren; + for (auto const& child : aChildren) + child->createNewWindow( None, m_nXScreen ); + + // FIXME: SalObjects +} + +void X11SalFrame::SetPluginParent( SystemParentData* pNewParent ) +{ + if( pNewParent->nSize >= sizeof(SystemParentData) ) + m_bXEmbed = pNewParent->aWindow != None && pNewParent->bXEmbedSupport; + + createNewWindow(pNewParent->aWindow); +} + +// Sound +void X11SalFrame::Beep() +{ + GetDisplay()->Beep(); +} + +// Event Handling + +static sal_uInt16 sal_GetCode( int state ) +{ + sal_uInt16 nCode = 0; + + if( state & Button1Mask ) + nCode |= MOUSE_LEFT; + if( state & Button2Mask ) + nCode |= MOUSE_MIDDLE; + if( state & Button3Mask ) + nCode |= MOUSE_RIGHT; + + if( state & ShiftMask ) + nCode |= KEY_SHIFT; + if( state & ControlMask ) + nCode |= KEY_MOD1; + if( state & Mod1Mask ) + nCode |= KEY_MOD2; + + // Map Meta/Super modifier to MOD3 on all Unix systems + // except macOS + if( state & Mod3Mask ) + nCode |= KEY_MOD3; + + return nCode; +} + +SalFrame::SalPointerState X11SalFrame::GetPointerState() +{ + SalPointerState aState; + ::Window aRoot, aChild; + int rx, ry, wx, wy; + unsigned int nMask = 0; + XQueryPointer( GetXDisplay(), + GetShellWindow(), + &aRoot, + &aChild, + &rx, &ry, + &wx, &wy, + &nMask + ); + + aState.maPos = Point(wx, wy); + aState.mnState = sal_GetCode( nMask ); + return aState; +} + +KeyIndicatorState X11SalFrame::GetIndicatorState() +{ + return vcl_sal::getSalDisplay(GetGenericUnixSalData())->GetIndicatorState(); +} + +void X11SalFrame::SimulateKeyPress( sal_uInt16 nKeyCode ) +{ + vcl_sal::getSalDisplay(GetGenericUnixSalData())->SimulateKeyPress(nKeyCode); +} + +namespace +{ +struct CompressWheelEventsData +{ + XEvent* firstEvent; + bool ignore; + int count; // number of compressed events +}; + +Bool compressWheelEvents( Display*, XEvent* event, XPointer p ) +{ + CompressWheelEventsData* data = reinterpret_cast< CompressWheelEventsData* >( p ); + if( data->ignore ) + return False; // we're already after the events to compress + if( event->type == ButtonPress || event->type == ButtonRelease ) + { + const unsigned int mask = Button1Mask << ( event->xbutton.button - Button1 ); + if( event->xbutton.button == data->firstEvent->xbutton.button + && event->xbutton.window == data->firstEvent->xbutton.window + && event->xbutton.x == data->firstEvent->xbutton.x + && event->xbutton.y == data->firstEvent->xbutton.y + && ( event->xbutton.state | mask ) == ( data->firstEvent->xbutton.state | mask )) + { + // Count if it's another press (i.e. wheel start event). + if( event->type == ButtonPress ) + ++data->count; + return True; // And remove the event from the queue. + } + } + // Non-matching event, skip certain events that cannot possibly affect input processing, + // but otherwise ignore all further events. + switch( event->type ) + { + case Expose: + case NoExpose: + break; + default: + data->ignore = true; + break; + } + return False; +} + +} // namespace + +bool X11SalFrame::HandleMouseEvent( XEvent *pEvent ) +{ + SalMouseEvent aMouseEvt; + SalEvent nEvent = SalEvent::NONE; + bool bClosePopups = false; + + if( nVisibleFloats && pEvent->type == EnterNotify ) + return false; + + if( LeaveNotify == pEvent->type || EnterNotify == pEvent->type ) + { + /* + * some WMs (and/or) applications have a passive grab on + * mouse buttons (XGrabButton). This leads to enter/leave notifies + * with mouse buttons pressed in the state mask before the actual + * ButtonPress event gets dispatched. But EnterNotify + * is reported in vcl as MouseMove event. Some office code + * decides that a pressed button in a MouseMove belongs to + * a drag operation which leads to doing things differently. + * + * ignore Enter/LeaveNotify resulting from grabs so that + * help windows do not disappear just after appearing + * + * hopefully this workaround will not break anything. + */ + if( pEvent->xcrossing.mode == NotifyGrab || pEvent->xcrossing.mode == NotifyUngrab ) + return false; + + aMouseEvt.mnX = pEvent->xcrossing.x; + aMouseEvt.mnY = pEvent->xcrossing.y; + aMouseEvt.mnTime = pEvent->xcrossing.time; + aMouseEvt.mnCode = sal_GetCode( pEvent->xcrossing.state ); + aMouseEvt.mnButton = 0; + + nEvent = LeaveNotify == pEvent->type + ? SalEvent::MouseLeave + : SalEvent::MouseMove; + } + else if( pEvent->type == MotionNotify ) + { + aMouseEvt.mnX = pEvent->xmotion.x; + aMouseEvt.mnY = pEvent->xmotion.y; + aMouseEvt.mnTime = pEvent->xmotion.time; + aMouseEvt.mnCode = sal_GetCode( pEvent->xmotion.state ); + + aMouseEvt.mnButton = 0; + + nEvent = SalEvent::MouseMove; + if( nVisibleFloats > 0 && mpParent ) + { + Cursor aCursor = mpParent->GetCursor(); + if( pEvent->xmotion.x >= 0 && pEvent->xmotion.x < static_cast<int>(maGeometry.nWidth) && + pEvent->xmotion.y >= 0 && pEvent->xmotion.y < static_cast<int>(maGeometry.nHeight) ) + aCursor = None; + + XChangeActivePointerGrab( GetXDisplay(), + PointerMotionMask|ButtonPressMask|ButtonReleaseMask, + aCursor, + CurrentTime ); + } + } + else + { + // let mouse events reach the correct window + if( nVisibleFloats < 1 ) + { + if( ! (nStyle_ & SalFrameStyleFlags::OWNERDRAWDECORATION) ) + XUngrabPointer( GetXDisplay(), CurrentTime ); + } + else if( pEvent->type == ButtonPress ) + { + // see if the user clicks outside all of the floats + // if yes release the grab + bool bInside = false; + for (auto pSalFrame : GetDisplay()->getFrames() ) + { + const X11SalFrame* pFrame = static_cast< const X11SalFrame* >( pSalFrame ); + if( pFrame->IsFloatGrabWindow() && + pFrame->bMapped_ && + pEvent->xbutton.x_root >= pFrame->maGeometry.nX && + pEvent->xbutton.x_root < pFrame->maGeometry.nX + static_cast<int>(pFrame->maGeometry.nWidth) && + pEvent->xbutton.y_root >= pFrame->maGeometry.nY && + pEvent->xbutton.y_root < pFrame->maGeometry.nY + static_cast<int>(pFrame->maGeometry.nHeight) ) + { + bInside = true; + break; + } + } + if( ! bInside ) + { + // need not take care of the XUngrabPointer in Show( false ) + // because XUngrabPointer does not produce errors if pointer + // is not grabbed + XUngrabPointer( GetXDisplay(), CurrentTime ); + bClosePopups = true; + + /* #i15246# only close popups if pointer is outside all our frames + * cannot use our own geometry data here because stacking + * is unknown (the above case implicitly assumes + * that floats are on top which should be true) + */ + ::Window aRoot, aChild; + int root_x, root_y, win_x, win_y; + unsigned int mask_return; + if( XQueryPointer( GetXDisplay(), + GetDisplay()->GetRootWindow( m_nXScreen ), + &aRoot, &aChild, + &root_x, &root_y, + &win_x, &win_y, + &mask_return ) + && aChild // pointer may not be in any child + ) + { + for (auto pSalFrame : GetDisplay()->getFrames() ) + { + const X11SalFrame* pFrame = static_cast< const X11SalFrame* >( pSalFrame ); + if( ! pFrame->IsFloatGrabWindow() + && ( pFrame->GetWindow() == aChild || + pFrame->GetShellWindow() == aChild || + pFrame->GetStackingWindow() == aChild ) + ) + { + // #i63638# check that pointer is inside window, not + // only inside stacking window + if( root_x >= pFrame->maGeometry.nX && root_x < sal::static_int_cast< int >(pFrame->maGeometry.nX+pFrame->maGeometry.nWidth) && + root_y >= pFrame->maGeometry.nY && root_y < sal::static_int_cast< int >(pFrame->maGeometry.nX+pFrame->maGeometry.nHeight) ) + { + bClosePopups = false; + } + break; + } + } + } + } + } + + if( m_bXEmbed && pEvent->xbutton.button == Button1 ) + askForXEmbedFocus( pEvent->xbutton.time ); + + if( pEvent->xbutton.button == Button1 || + pEvent->xbutton.button == Button2 || + pEvent->xbutton.button == Button3 ) + { + aMouseEvt.mnX = pEvent->xbutton.x; + aMouseEvt.mnY = pEvent->xbutton.y; + aMouseEvt.mnTime = pEvent->xbutton.time; + aMouseEvt.mnCode = sal_GetCode( pEvent->xbutton.state ); + + if( Button1 == pEvent->xbutton.button ) + aMouseEvt.mnButton = MOUSE_LEFT; + else if( Button2 == pEvent->xbutton.button ) + aMouseEvt.mnButton = MOUSE_MIDDLE; + else if( Button3 == pEvent->xbutton.button ) + aMouseEvt.mnButton = MOUSE_RIGHT; + + nEvent = ButtonPress == pEvent->type + ? SalEvent::MouseButtonDown + : SalEvent::MouseButtonUp; + } + else if( pEvent->xbutton.button == Button4 || + pEvent->xbutton.button == Button5 || + pEvent->xbutton.button == Button6 || + pEvent->xbutton.button == Button7 ) + { + const bool bIncrement( + pEvent->xbutton.button == Button4 || + pEvent->xbutton.button == Button6 ); + const bool bHoriz( + pEvent->xbutton.button == Button6 || + pEvent->xbutton.button == Button7 ); + + if( pEvent->type == ButtonRelease ) + return false; + + static sal_uLong nLines = 0; + if( ! nLines ) + { + char* pEnv = getenv( "SAL_WHEELLINES" ); + nLines = pEnv ? atoi( pEnv ) : 3; + if( nLines > 10 ) + nLines = SAL_WHEELMOUSE_EVENT_PAGESCROLL; + } + + // Compress consecutive wheel events (way too fine scrolling may cause lags if one scrolling steps takes long). + CompressWheelEventsData data; + data.firstEvent = pEvent; + data.count = 1; + XEvent dummy; + do + { + data.ignore = false; + } while( XCheckIfEvent( pEvent->xany.display, &dummy, compressWheelEvents, reinterpret_cast< XPointer >( &data ))); + + SalWheelMouseEvent aWheelEvt; + aWheelEvt.mnTime = pEvent->xbutton.time; + aWheelEvt.mnX = pEvent->xbutton.x; + aWheelEvt.mnY = pEvent->xbutton.y; + aWheelEvt.mnDelta = ( bIncrement ? 120 : -120 ) * data.count; + aWheelEvt.mnNotchDelta = bIncrement ? 1 : -1; + aWheelEvt.mnScrollLines = nLines * data.count; + aWheelEvt.mnCode = sal_GetCode( pEvent->xbutton.state ); + aWheelEvt.mbHorz = bHoriz; + + nEvent = SalEvent::WheelMouse; + + if( AllSettings::GetLayoutRTL() ) + aWheelEvt.mnX = nWidth_-1-aWheelEvt.mnX; + return CallCallback( nEvent, &aWheelEvt ); + } + } + + bool nRet = false; + if( nEvent == SalEvent::MouseLeave + || ( aMouseEvt.mnX < nWidth_ && aMouseEvt.mnX > -1 && + aMouseEvt.mnY < nHeight_ && aMouseEvt.mnY > -1 ) + || pDisplay_->MouseCaptured( this ) + ) + { + if( AllSettings::GetLayoutRTL() ) + aMouseEvt.mnX = nWidth_-1-aMouseEvt.mnX; + nRet = CallCallback( nEvent, &aMouseEvt ); + } + + if( bClosePopups ) + { + /* #108213# close popups after dispatching the event outside the popup; + * applications do weird things. + */ + ImplSVData* pSVData = ImplGetSVData(); + if (pSVData->mpWinData->mpFirstFloat) + { + if (!(pSVData->mpWinData->mpFirstFloat->GetPopupModeFlags() + & FloatWinPopupFlags::NoAppFocusClose)) + pSVData->mpWinData->mpFirstFloat->EndPopupMode(FloatWinPopupEndFlags::Cancel + | FloatWinPopupEndFlags::CloseAll); + } + } + + return nRet; +} + +namespace { + +// F10 means either KEY_F10 or KEY_MENU, which has to be decided +// in the independent part. +struct KeyAlternate +{ + sal_uInt16 nKeyCode; + sal_Unicode nCharCode; + KeyAlternate() : nKeyCode( 0 ), nCharCode( 0 ) {} + KeyAlternate( sal_uInt16 nKey, sal_Unicode nChar = 0 ) : nKeyCode( nKey ), nCharCode( nChar ) {} +}; + +} + +static KeyAlternate +GetAlternateKeyCode( const sal_uInt16 nKeyCode ) +{ + KeyAlternate aAlternate; + + switch( nKeyCode ) + { + case KEY_F10: aAlternate = KeyAlternate( KEY_MENU );break; + case KEY_F24: aAlternate = KeyAlternate( KEY_SUBTRACT, '-' );break; + } + + return aAlternate; +} + +void X11SalFrame::beginUnicodeSequence() +{ + OUString& rSeq( GetGenericUnixSalData()->GetUnicodeCommand() ); + vcl::DeletionListener aDeleteWatch( this ); + + if( !rSeq.isEmpty() ) + endUnicodeSequence(); + + rSeq = "u"; + + if( ! aDeleteWatch.isDeleted() ) + { + ExtTextInputAttr nTextAttr = ExtTextInputAttr::Underline; + SalExtTextInputEvent aEv; + aEv.maText = rSeq; + aEv.mpTextAttr = &nTextAttr; + aEv.mnCursorPos = 0; + aEv.mnCursorFlags = 0; + + CallCallback(SalEvent::ExtTextInput, static_cast<void*>(&aEv)); + } +} + +bool X11SalFrame::appendUnicodeSequence( sal_Unicode c ) +{ + bool bRet = false; + OUString& rSeq( GetGenericUnixSalData()->GetUnicodeCommand() ); + if( !rSeq.isEmpty() ) + { + // range check + if( (c >= '0' && c <= '9') || + (c >= 'a' && c <= 'f') || + (c >= 'A' && c <= 'F') ) + { + rSeq += OUStringChar(c); + std::vector<ExtTextInputAttr> attribs( rSeq.getLength(), ExtTextInputAttr::Underline ); + + SalExtTextInputEvent aEv; + aEv.maText = rSeq; + aEv.mpTextAttr = attribs.data(); + aEv.mnCursorPos = 0; + aEv.mnCursorFlags = 0; + + CallCallback(SalEvent::ExtTextInput, static_cast<void*>(&aEv)); + bRet = true; + } + else + bRet = endUnicodeSequence(); + } + else + endUnicodeSequence(); + return bRet; +} + +bool X11SalFrame::endUnicodeSequence() +{ + OUString& rSeq( GetGenericUnixSalData()->GetUnicodeCommand() ); + + vcl::DeletionListener aDeleteWatch( this ); + if( rSeq.getLength() > 1 && rSeq.getLength() < 6 ) + { + // cut the "u" + std::u16string_view aNumbers( rSeq.subView( 1 ) ); + sal_uInt32 nValue = o3tl::toUInt32(aNumbers, 16); + if( nValue >= 32 ) + { + ExtTextInputAttr nTextAttr = ExtTextInputAttr::Underline; + SalExtTextInputEvent aEv; + aEv.maText = OUString( sal_Unicode(nValue) ); + aEv.mpTextAttr = &nTextAttr; + aEv.mnCursorPos = 0; + aEv.mnCursorFlags = 0; + CallCallback(SalEvent::ExtTextInput, static_cast<void*>(&aEv)); + } + } + bool bWasInput = !rSeq.isEmpty(); + rSeq.clear(); + if( bWasInput && ! aDeleteWatch.isDeleted() ) + CallCallback(SalEvent::EndExtTextInput, nullptr); + return bWasInput; +} + +bool X11SalFrame::HandleKeyEvent( XKeyEvent *pEvent ) +{ + if( pEvent->type == KeyRelease ) + { + // Ignore autorepeat keyrelease events. If there is a series of keypress+keyrelease+keypress events + // generated by holding down a key, and if these are from autorepeat (keyrelease and the following keypress + // have the same timestamp), drop the autorepeat keyrelease event. Not exactly sure why this is done + // (possibly hiding differences between platforms, or just making it more sensible, because technically + // the key has not been released at all). + bool ignore = false; + // Discard queued excessive autorepeat events. + // If the user presses and holds down a key, the autorepeating keypress events + // may overload LO (e.g. if the key is PageDown and the LO cannot keep up scrolling). + // Reduce the load by simply discarding such excessive events (so for a KeyRelease event, + // check if it's followed by matching KeyPress+KeyRelease pair(s) and discard those). + // This shouldn't have any negative effects - unlike with normal (non-autorepeat + // events), the user is unlikely to rely on the exact number of resulting actions + // (since autorepeat generates keypress events rather quickly and it's hard to estimate + // how many exactly) and the idea should be just keeping the key pressed until something + // happens (in which case more events that just lag LO shouldn't make a difference). + Display* dpy = pEvent->display; + XKeyEvent previousRelease = *pEvent; + while( XPending( dpy )) + { + XEvent nextEvent1; + bool discard1 = false; + XNextEvent( dpy, &nextEvent1 ); + if( nextEvent1.type == KeyPress && nextEvent1.xkey.time == previousRelease.time + && !nextEvent1.xkey.send_event && nextEvent1.xkey.window == previousRelease.window + && nextEvent1.xkey.state == previousRelease.state && nextEvent1.xkey.keycode == previousRelease.keycode ) + { // This looks like another autorepeat keypress. + ignore = true; + if( XPending( dpy )) + { + XEvent nextEvent2; + XNextEvent( dpy, &nextEvent2 ); + if( nextEvent2.type == KeyRelease && nextEvent2.xkey.time <= ( previousRelease.time + 100 ) + && !nextEvent2.xkey.send_event && nextEvent2.xkey.window == previousRelease.window + && nextEvent2.xkey.state == previousRelease.state && nextEvent2.xkey.keycode == previousRelease.keycode ) + { // And the matching keyrelease -> drop them both. + discard1 = true; + previousRelease = nextEvent2.xkey; + ignore = false; // There either will be another autorepeating keypress that'll lead to discarding + // the pEvent keyrelease, it this discarding makes that keyrelease the last one. + } + else + { + XPutBackEvent( dpy, &nextEvent2 ); + break; + } + } + } + if( !discard1 ) + { // Unrelated event, put back and stop compressing. + XPutBackEvent( dpy, &nextEvent1 ); + break; + } + } + if( ignore ) // This autorepeating keyrelease is followed by another keypress. + return false; + } + + KeySym nKeySym; + KeySym nUnmodifiedKeySym; + int nLen = 2048; + char *pPrintable = static_cast<char*>(alloca( nLen )); + + // singlebyte code composed by input method, the new default + if (mpInputContext != nullptr && mpInputContext->UseContext()) + { + // returns a keysym as well as the pPrintable (in system encoding) + // printable may be empty. + Status nStatus; + nKeySym = pDisplay_->GetKeySym( pEvent, pPrintable, &nLen, + &nUnmodifiedKeySym, + &nStatus, mpInputContext->GetContext() ); + if ( nStatus == XBufferOverflow ) + { + // In case of overflow, XmbLookupString (called by GetKeySym) + // returns required size + // TODO : check if +1 is needed for 0 terminator + nLen += 1; + pPrintable = static_cast<char*>(alloca( nLen )); + nKeySym = pDisplay_->GetKeySym( pEvent, pPrintable, &nLen, + &nUnmodifiedKeySym, + &nStatus, mpInputContext->GetContext() ); + } + } + else + { + // fallback, this should never ever be called + Status nStatus = 0; + nKeySym = pDisplay_->GetKeySym( pEvent, pPrintable, &nLen, &nUnmodifiedKeySym, &nStatus ); + } + + SalKeyEvent aKeyEvt; + sal_uInt16 nKeyCode; + sal_uInt16 nModCode = 0; + char aDummy; + + if( pEvent->state & ShiftMask ) + nModCode |= KEY_SHIFT; + if( pEvent->state & ControlMask ) + nModCode |= KEY_MOD1; + if( pEvent->state & Mod1Mask ) + nModCode |= KEY_MOD2; + + if( nModCode != (KEY_SHIFT|KEY_MOD1) ) + endUnicodeSequence(); + + if( nKeySym == XK_Shift_L || nKeySym == XK_Shift_R + || nKeySym == XK_Control_L || nKeySym == XK_Control_R + || nKeySym == XK_Alt_L || nKeySym == XK_Alt_R + || nKeySym == XK_Meta_L || nKeySym == XK_Meta_R + || nKeySym == XK_Super_L || nKeySym == XK_Super_R ) + { + SalKeyModEvent aModEvt; + aModEvt.mbDown = false; // auto-accelerator feature not supported here. + aModEvt.mnModKeyCode = ModKeyFlags::NONE; + if( pEvent->type == KeyPress && mnExtKeyMod == ModKeyFlags::NONE ) + mbSendExtKeyModChange = true; + else if( pEvent->type == KeyRelease && mbSendExtKeyModChange ) + { + aModEvt.mnModKeyCode = mnExtKeyMod; + mnExtKeyMod = ModKeyFlags::NONE; + } + + // pressing just the ctrl key leads to a keysym of XK_Control but + // the event state does not contain ControlMask. In the release + // event it's the other way round: it does contain the Control mask. + // The modifier mode therefore has to be adapted manually. + ModKeyFlags nExtModMask = ModKeyFlags::NONE; + sal_uInt16 nModMask = 0; + switch( nKeySym ) + { + case XK_Control_L: + nExtModMask = ModKeyFlags::LeftMod1; + nModMask = KEY_MOD1; + break; + case XK_Control_R: + nExtModMask = ModKeyFlags::RightMod1; + nModMask = KEY_MOD1; + break; + case XK_Alt_L: + nExtModMask = ModKeyFlags::LeftMod2; + nModMask = KEY_MOD2; + break; + case XK_Alt_R: + nExtModMask = ModKeyFlags::RightMod2; + nModMask = KEY_MOD2; + break; + case XK_Shift_L: + nExtModMask = ModKeyFlags::LeftShift; + nModMask = KEY_SHIFT; + break; + case XK_Shift_R: + nExtModMask = ModKeyFlags::RightShift; + nModMask = KEY_SHIFT; + break; + // Map Meta/Super keys to MOD3 modifier on all Unix systems + // except macOS + case XK_Meta_L: + case XK_Super_L: + nExtModMask = ModKeyFlags::LeftMod3; + nModMask = KEY_MOD3; + break; + case XK_Meta_R: + case XK_Super_R: + nExtModMask = ModKeyFlags::RightMod3; + nModMask = KEY_MOD3; + break; + } + if( pEvent->type == KeyRelease ) + { + nModCode &= ~nModMask; + mnExtKeyMod &= ~nExtModMask; + } + else + { + nModCode |= nModMask; + mnExtKeyMod |= nExtModMask; + } + + aModEvt.mnCode = nModCode; + + return CallCallback( SalEvent::KeyModChange, &aModEvt ); + } + + mbSendExtKeyModChange = false; + + // try to figure out the vcl code for the keysym + // #i52338# use the unmodified KeySym if there is none for the real KeySym + // because the independent part has only keycodes for unshifted keys + nKeyCode = pDisplay_->GetKeyCode( nKeySym, &aDummy ); + if( nKeyCode == 0 ) + nKeyCode = pDisplay_->GetKeyCode( nUnmodifiedKeySym, &aDummy ); + + // try to figure out a printable if XmbLookupString returns only a keysym + // and NOT a printable. Do not store it in pPrintable[0] since it is expected to + // be in system encoding, not unicode. + // #i8988##, if KeySym and printable look equally promising then prefer KeySym + // the printable is bound to the encoding so the KeySym might contain more + // information (in et_EE locale: "Compose + Z + <" delivers "," in printable and + // (the desired) Zcaron in KeySym + sal_Unicode nKeyString = 0x0; + if ( (nLen == 0) + || ((nLen == 1) && (nKeySym > 0)) ) + nKeyString = KeysymToUnicode (nKeySym); + // if we have nothing we give up + if( !nKeyCode && !nLen && !nKeyString) + return false; + + vcl::DeletionListener aDeleteWatch( this ); + + if( nModCode == (KEY_SHIFT | KEY_MOD1) && pEvent->type == KeyPress ) + { + sal_uInt16 nSeqKeyCode = pDisplay_->GetKeyCode( nUnmodifiedKeySym, &aDummy ); + if( nSeqKeyCode == KEY_U ) + { + beginUnicodeSequence(); + return true; + } + else if( nSeqKeyCode >= KEY_0 && nSeqKeyCode <= KEY_9 ) + { + if( appendUnicodeSequence( u'0' + sal_Unicode(nSeqKeyCode - KEY_0) ) ) + return true; + } + else if( nSeqKeyCode >= KEY_A && nSeqKeyCode <= KEY_F ) + { + if( appendUnicodeSequence( u'a' + sal_Unicode(nSeqKeyCode - KEY_A) ) ) + return true; + } + else + endUnicodeSequence(); + } + + if( aDeleteWatch.isDeleted() ) + return false; + + rtl_TextEncoding nEncoding = osl_getThreadTextEncoding(); + + sal_Unicode *pBuffer; + sal_Unicode *pString; + sal_Size nBufferSize = nLen * 2; + sal_Size nSize; + pBuffer = static_cast<sal_Unicode*>(malloc( nBufferSize + 2 )); + pBuffer[ 0 ] = 0; + + if (nKeyString != 0) + { + pString = &nKeyString; + nSize = 1; + } + else if (nLen > 0 && nEncoding != RTL_TEXTENCODING_UNICODE) + { + // create text converter + rtl_TextToUnicodeConverter aConverter = + rtl_createTextToUnicodeConverter( nEncoding ); + rtl_TextToUnicodeContext aContext = + rtl_createTextToUnicodeContext( aConverter ); + + sal_uInt32 nConversionInfo; + sal_Size nConvertedChars; + + // convert to single byte text stream + nSize = rtl_convertTextToUnicode( + aConverter, aContext, + pPrintable, nLen, + pBuffer, nBufferSize, + RTL_TEXTTOUNICODE_FLAGS_UNDEFINED_IGNORE | + RTL_TEXTTOUNICODE_FLAGS_INVALID_IGNORE, + &nConversionInfo, &nConvertedChars ); + + // destroy converter + rtl_destroyTextToUnicodeContext( aConverter, aContext ); + rtl_destroyTextToUnicodeConverter( aConverter ); + + pString = pBuffer; + } + else if (nLen > 0 /* nEncoding == RTL_TEXTENCODING_UNICODE */) + { + pString = reinterpret_cast<sal_Unicode*>(pPrintable); + nSize = nLen; + } + else + { + pString = pBuffer; + nSize = 0; + } + + if ( mpInputContext != nullptr + && mpInputContext->UseContext() + && KeyRelease != pEvent->type + && ( (nSize > 1) + || (nSize > 0 && mpInputContext->IsPreeditMode())) ) + { + mpInputContext->CommitKeyEvent(pString, nSize); + } + else + // normal single character keyinput + { + aKeyEvt.mnCode = nKeyCode | nModCode; + aKeyEvt.mnRepeat = 0; + aKeyEvt.mnCharCode = pString[ 0 ]; + + if( KeyRelease == pEvent->type ) + { + CallCallback( SalEvent::KeyUp, &aKeyEvt ); + } + else + { + if ( ! CallCallback(SalEvent::KeyInput, &aKeyEvt) ) + { + // independent layer doesn't want to handle key-event, so check + // whether the keycode may have an alternate meaning + KeyAlternate aAlternate = GetAlternateKeyCode( nKeyCode ); + if ( aAlternate.nKeyCode != 0 ) + { + aKeyEvt.mnCode = aAlternate.nKeyCode | nModCode; + if( aAlternate.nCharCode ) + aKeyEvt.mnCharCode = aAlternate.nCharCode; + CallCallback(SalEvent::KeyInput, &aKeyEvt); + } + } + } + } + + // update the spot location for PreeditPosition IME style + + if (! aDeleteWatch.isDeleted()) + { + if (mpInputContext != nullptr && mpInputContext->UseContext()) + mpInputContext->UpdateSpotLocation(); + } + + free (pBuffer); + return true; +} + +bool X11SalFrame::HandleFocusEvent( XFocusChangeEvent const *pEvent ) +{ + // ReflectionX in Windows mode changes focus while mouse is grabbed + if( nVisibleFloats > 0 && GetDisplay()->getWMAdaptor()->getWindowManagerName() == "ReflectionX Windows" ) + return true; + + /* ignore focusout resulting from keyboard grabs + * we do not grab it and are not interested when + * someone else does CDE e.g. does a XGrabKey on arrow keys + * handle focus events with mode NotifyWhileGrabbed + * because with CDE alt-tab focus changing we do not get + * normal focus events + * cast focus event to the input context, otherwise the + * status window does not follow the application frame + */ + + if ( mpInputContext != nullptr ) + { + if( FocusIn == pEvent->type ) + mpInputContext->SetICFocus( this ); + } + + if ( pEvent->mode == NotifyNormal || pEvent->mode == NotifyWhileGrabbed || + ( ( nStyle_ & SalFrameStyleFlags::PLUG ) && pEvent->window == GetShellWindow() ) + ) + { + if( hPresentationWindow != None && hPresentationWindow != GetShellWindow() ) + return false; + + if( FocusIn == pEvent->type ) + { + GetSalInstance()->updatePrinterUpdate(); + mbInputFocus = True; + ImplSVData* pSVData = ImplGetSVData(); + + bool nRet = CallCallback( SalEvent::GetFocus, nullptr ); + if ((mpParent != nullptr && nStyle_ == SalFrameStyleFlags::NONE) + && pSVData->mpWinData->mpFirstFloat) + { + FloatWinPopupFlags nMode = pSVData->mpWinData->mpFirstFloat->GetPopupModeFlags(); + pSVData->mpWinData->mpFirstFloat->SetPopupModeFlags( + nMode & ~FloatWinPopupFlags::NoAppFocusClose); + } + return nRet; + } + else + { + mbInputFocus = False; + mbSendExtKeyModChange = false; + mnExtKeyMod = ModKeyFlags::NONE; + return CallCallback( SalEvent::LoseFocus, nullptr ); + } + } + + return false; +} + +bool X11SalFrame::HandleExposeEvent( XEvent const *pEvent ) +{ + XRectangle aRect = { 0, 0, 0, 0 }; + sal_uInt16 nCount = 0; + + if( pEvent->type == Expose ) + { + aRect.x = pEvent->xexpose.x; + aRect.y = pEvent->xexpose.y; + aRect.width = pEvent->xexpose.width; + aRect.height = pEvent->xexpose.height; + nCount = pEvent->xexpose.count; + } + else if( pEvent->type == GraphicsExpose ) + { + aRect.x = pEvent->xgraphicsexpose.x; + aRect.y = pEvent->xgraphicsexpose.y; + aRect.width = pEvent->xgraphicsexpose.width; + aRect.height = pEvent->xgraphicsexpose.height; + nCount = pEvent->xgraphicsexpose.count; + } + + if( IsOverrideRedirect() && mbFullScreen && + aPresentationReparentList.empty() ) + // we are in fullscreen mode -> override redirect + // focus is possibly lost, so reget it + XSetInputFocus( GetXDisplay(), GetShellWindow(), RevertToNone, CurrentTime ); + + // width and height are extents, so they are of by one for rectangle + maPaintRegion.Union( tools::Rectangle( Point(aRect.x, aRect.y), Size(aRect.width+1, aRect.height+1) ) ); + + if( nCount ) + // wait for last expose rectangle, do not wait for resize timer + // if a completed graphics expose sequence is available + return true; + + SalPaintEvent aPEvt( maPaintRegion.Left(), maPaintRegion.Top(), maPaintRegion.GetWidth(), maPaintRegion.GetHeight() ); + + CallCallback( SalEvent::Paint, &aPEvt ); + maPaintRegion = tools::Rectangle(); + + return true; +} + +void X11SalFrame::RestackChildren( ::Window* pTopLevelWindows, int nTopLevelWindows ) +{ + if( maChildren.empty() ) + return; + + int nWindow = nTopLevelWindows; + while( nWindow-- ) + if( pTopLevelWindows[nWindow] == GetStackingWindow() ) + break; + if( nWindow < 0 ) + return; + + for (auto const& child : maChildren) + { + if( child->bMapped_ ) + { + int nChild = nWindow; + while( nChild-- ) + { + if( pTopLevelWindows[nChild] == child->GetStackingWindow() ) + { + // if a child is behind its parent, place it above the + // parent (for insane WMs like Dtwm and olwm) + XWindowChanges aCfg; + aCfg.sibling = GetStackingWindow(); + aCfg.stack_mode = Above; + XConfigureWindow( GetXDisplay(), child->GetStackingWindow(), CWSibling|CWStackMode, &aCfg ); + break; + } + } + } + } + for (auto const& child : maChildren) + { + child->RestackChildren( pTopLevelWindows, nTopLevelWindows ); + } +} + +void X11SalFrame::RestackChildren() +{ + if( maChildren.empty() ) + return; + + ::Window aRoot, aParent, *pChildren = nullptr; + unsigned int nChildren; + if( XQueryTree( GetXDisplay(), + GetDisplay()->GetRootWindow( m_nXScreen ), + &aRoot, + &aParent, + &pChildren, + &nChildren ) ) + { + RestackChildren( pChildren, nChildren ); + XFree( pChildren ); + } +} + +static Bool size_event_predicate( Display*, XEvent* event, XPointer arg ) +{ + if( event->type != ConfigureNotify ) + return False; + X11SalFrame* frame = reinterpret_cast< X11SalFrame* >( arg ); + XConfigureEvent* pEvent = &event->xconfigure; + if( pEvent->window != frame->GetShellWindow() + && pEvent->window != frame->GetWindow() + && pEvent->window != frame->GetForeignParent() + && pEvent->window != frame->GetStackingWindow()) + { // ignored at top of HandleSizeEvent() + return False; + } + if( pEvent->window == frame->GetStackingWindow()) + return False; // filtered later in HandleSizeEvent() + // at this point we know that there is another similar event in the queue + frame->setPendingSizeEvent(); + return False; // but do not process the new event out of order +} + +void X11SalFrame::setPendingSizeEvent() +{ + mPendingSizeEvent = true; +} + +bool X11SalFrame::HandleSizeEvent( XConfigureEvent *pEvent ) +{ + // NOTE: if you add more tests in this function, make sure to update size_event_predicate() + // so that it finds exactly the same events + + if ( pEvent->window != GetShellWindow() + && pEvent->window != GetWindow() + && pEvent->window != GetForeignParent() + && pEvent->window != GetStackingWindow() + ) + { + // could be as well a sys-child window (aka SalObject) + return true; + } + + if( ( nStyle_ & SalFrameStyleFlags::PLUG ) && pEvent->window == GetShellWindow() ) + { + // just update the children's positions + RestackChildren(); + return true; + } + + if( pEvent->window == GetForeignParent() ) + XResizeWindow( GetXDisplay(), + GetWindow(), + pEvent->width, + pEvent->height ); + + ::Window hDummy; + XTranslateCoordinates( GetXDisplay(), + GetWindow(), + pDisplay_->GetRootWindow( pDisplay_->GetDefaultXScreen() ), + 0, 0, + &pEvent->x, &pEvent->y, + &hDummy ); + + if( pEvent->window == GetStackingWindow() ) + { + if( maGeometry.nX != pEvent->x || maGeometry.nY != pEvent->y ) + { + maGeometry.nX = pEvent->x; + maGeometry.nY = pEvent->y; + CallCallback( SalEvent::Move, nullptr ); + } + return true; + } + + // check size hints in first time SalFrame::Show + if( X11ShowState::Unknown == nShowState_ && bMapped_ ) + nShowState_ = X11ShowState::Normal; + + // Avoid a race condition where resizing this window to one size and shortly after that + // to another size generates first size event with the old size and only after that + // with the new size, temporarily making us think the old size is valid (bnc#674806). + // So if there is another size event for this window pending, ignore this one. + mPendingSizeEvent = false; + XEvent dummy; + XCheckIfEvent( GetXDisplay(), &dummy, size_event_predicate, reinterpret_cast< XPointer >( this )); + if( mPendingSizeEvent ) + return true; + + nWidth_ = pEvent->width; + nHeight_ = pEvent->height; + + bool bMoved = ( pEvent->x != maGeometry.nX || pEvent->y != maGeometry.nY ); + bool bSized = ( pEvent->width != static_cast<int>(maGeometry.nWidth) || pEvent->height != static_cast<int>(maGeometry.nHeight) ); + + maGeometry.nX = pEvent->x; + maGeometry.nY = pEvent->y; + maGeometry.nWidth = pEvent->width; + maGeometry.nHeight = pEvent->height; + updateScreenNumber(); + + // update children's position + RestackChildren(); + + if( bSized && ! bMoved ) + CallCallback( SalEvent::Resize, nullptr ); + else if( bMoved && ! bSized ) + CallCallback( SalEvent::Move, nullptr ); + else if( bMoved && bSized ) + CallCallback( SalEvent::MoveResize, nullptr ); + + return true; +} + +IMPL_LINK_NOARG(X11SalFrame, HandleAlwaysOnTopRaise, Timer *, void) +{ + if( bMapped_ ) + ToTop( SalFrameToTop::NONE ); +} + +bool X11SalFrame::HandleReparentEvent( XReparentEvent *pEvent ) +{ + Display *pDisplay = pEvent->display; + ::Window hWM_Parent; + ::Window hRoot, *Children, hDummy; + unsigned int nChildren; + + static const char* pDisableStackingCheck = getenv( "SAL_DISABLE_STACKING_CHECK" ); + + GetGenericUnixSalData()->ErrorTrapPush(); + + /* + * don't rely on the new parent from the event. + * the event may be "out of date", that is the window manager + * window may not exist anymore. This can happen if someone + * shows a frame and hides it again quickly (not that it would + * be very sensible) + */ + hWM_Parent = GetShellWindow(); + do + { + Children = nullptr; + XQueryTree( pDisplay, + hWM_Parent, + &hRoot, + &hDummy, + &Children, + &nChildren ); + + bool bError = GetGenericUnixSalData()->ErrorTrapPop( false ); + GetGenericUnixSalData()->ErrorTrapPush(); + + if( bError ) + { + hWM_Parent = GetShellWindow(); + break; + } + /* this sometimes happens if a Show(true) is + * immediately followed by Show(false) (which is braindead anyway) + */ + if( hDummy == hWM_Parent ) + hDummy = hRoot; + if( hDummy != hRoot ) + hWM_Parent = hDummy; + if( Children ) + XFree( Children ); + } while( hDummy != hRoot ); + + if( GetStackingWindow() == None + && hWM_Parent != hPresentationWindow + && hWM_Parent != GetShellWindow() + && ( ! pDisableStackingCheck || ! *pDisableStackingCheck ) + ) + { + mhStackingWindow = hWM_Parent; + XSelectInput( pDisplay, GetStackingWindow(), StructureNotifyMask ); + } + + if( hWM_Parent == pDisplay_->GetRootWindow( pDisplay_->GetDefaultXScreen() ) + || hWM_Parent == GetForeignParent() + || pEvent->parent == pDisplay_->GetRootWindow( pDisplay_->GetDefaultXScreen() ) + || ( nStyle_ & SalFrameStyleFlags::FLOAT ) ) + { + // Reparenting before Destroy + aPresentationReparentList.remove( GetStackingWindow() ); + mhStackingWindow = None; + GetGenericUnixSalData()->ErrorTrapPop(); + return false; + } + + /* + * evil hack to show decorated windows on top + * of override redirect presentation windows: + * reparent the window manager window to the presentation window + * does not work with non-reparenting WMs + * in future this should not be necessary anymore with + * _NET_WM_STATE_FULLSCREEN available + */ + if( hPresentationWindow != None + && hPresentationWindow != GetWindow() + && GetStackingWindow() != None + && GetStackingWindow() != GetDisplay()->GetRootWindow( m_nXScreen ) + ) + { + int x = 0, y = 0; + ::Window aChild; + XTranslateCoordinates( GetXDisplay(), + GetStackingWindow(), + GetDisplay()->GetRootWindow( m_nXScreen ), + 0, 0, + &x, &y, + &aChild + ); + XReparentWindow( GetXDisplay(), + GetStackingWindow(), + hPresentationWindow, + x, y + ); + aPresentationReparentList.push_back( GetStackingWindow() ); + } + + int nLeft = 0, nTop = 0; + XTranslateCoordinates( GetXDisplay(), + GetShellWindow(), + hWM_Parent, + 0, 0, + &nLeft, + &nTop, + &hDummy ); + maGeometry.nLeftDecoration = nLeft > 0 ? nLeft-1 : 0; + maGeometry.nTopDecoration = nTop > 0 ? nTop-1 : 0; + + /* + * decorations are not symmetric, + * so need real geometries here + * (this will fail with virtual roots ?) + */ + + // reset error occurred + GetGenericUnixSalData()->ErrorTrapPop(); + GetGenericUnixSalData()->ErrorTrapPush(); + + int xp, yp, x, y; + unsigned int wp, w, hp, h, bw, d; + XGetGeometry( GetXDisplay(), + GetShellWindow(), + &hRoot, + &x, &y, &w, &h, &bw, &d ); + XGetGeometry( GetXDisplay(), + hWM_Parent, + &hRoot, + &xp, &yp, &wp, &hp, &bw, &d ); + bool bResized = false; + bool bError = GetGenericUnixSalData()->ErrorTrapPop( false ); + GetGenericUnixSalData()->ErrorTrapPush(); + + if( ! bError ) + { + maGeometry.nRightDecoration = wp - w - maGeometry.nLeftDecoration; + maGeometry.nBottomDecoration = hp - h - maGeometry.nTopDecoration; + /* + * note: this works because hWM_Parent is direct child of root, + * not necessarily parent of GetShellWindow() + */ + maGeometry.nX = xp + nLeft; + maGeometry.nY = yp + nTop; + bResized = w != maGeometry.nWidth || h != maGeometry.nHeight; + maGeometry.nWidth = w; + maGeometry.nHeight = h; + } + + // limit width and height if we are too large: #47757 + // olwm and fvwm need this, it doesn't harm the rest + + // #i81311# do this only for sizable frames + if( nStyle_ & SalFrameStyleFlags::SIZEABLE ) + { + Size aScreenSize = GetDisplay()->GetScreenSize( m_nXScreen ); + int nScreenWidth = aScreenSize.Width(); + int nScreenHeight = aScreenSize.Height(); + int nFrameWidth = maGeometry.nWidth + maGeometry.nLeftDecoration + maGeometry.nRightDecoration; + int nFrameHeight = maGeometry.nHeight + maGeometry.nTopDecoration + maGeometry.nBottomDecoration; + + if ((nFrameWidth > nScreenWidth) || (nFrameHeight > nScreenHeight)) + { + Size aSize(maGeometry.nWidth, maGeometry.nHeight); + + if (nFrameWidth > nScreenWidth) + aSize.setWidth( nScreenWidth - maGeometry.nRightDecoration - maGeometry.nLeftDecoration ); + if (nFrameHeight > nScreenHeight) + aSize.setHeight( nScreenHeight - maGeometry.nBottomDecoration - maGeometry.nTopDecoration ); + + SetSize( aSize ); + bResized = false; + } + } + if( bResized ) + CallCallback( SalEvent::Resize, nullptr ); + + GetGenericUnixSalData()->ErrorTrapPop(); + + return true; +} + +bool X11SalFrame::HandleStateEvent( XPropertyEvent const *pEvent ) +{ + Atom actual_type; + int actual_format; + unsigned long nitems, bytes_after; + unsigned char *prop = nullptr; + + if( 0 != XGetWindowProperty( GetXDisplay(), + GetShellWindow(), + pEvent->atom, // property + 0, // long_offset (32bit) + 2, // long_length (32bit) + False, // delete + pEvent->atom, // req_type + &actual_type, + &actual_format, + &nitems, + &bytes_after, + &prop ) + || ! prop + ) + return false; + + DBG_ASSERT( actual_type == pEvent->atom + && 32 == actual_format + && 2 == nitems + && 0 == bytes_after, "HandleStateEvent" ); + + if( *reinterpret_cast<unsigned long*>(prop) == NormalState ) + nShowState_ = X11ShowState::Normal; + else if( *reinterpret_cast<unsigned long*>(prop) == IconicState ) + nShowState_ = X11ShowState::Minimized; + + XFree( prop ); + return true; +} + +bool X11SalFrame::HandleClientMessage( XClientMessageEvent *pEvent ) +{ + const WMAdaptor& rWMAdaptor( *pDisplay_->getWMAdaptor() ); + +#if !defined(__synchronous_extinput__) + if( pEvent->message_type == rWMAdaptor.getAtom( WMAdaptor::SAL_EXTTEXTEVENT ) ) + { + HandleExtTextEvent (pEvent); + return true; + } +#endif + else if( pEvent->message_type == rWMAdaptor.getAtom( WMAdaptor::SAL_QUITEVENT ) ) + { + SAL_WARN( "vcl", "X11SalFrame::Dispatch Quit" ); + Close(); // ??? + return true; + } + else if( pEvent->message_type == rWMAdaptor.getAtom( WMAdaptor::WM_PROTOCOLS ) ) + { + if( static_cast<Atom>(pEvent->data.l[0]) == rWMAdaptor.getAtom( WMAdaptor::NET_WM_PING ) ) + rWMAdaptor.answerPing( this, pEvent ); + else if( ! ( nStyle_ & SalFrameStyleFlags::PLUG ) + && ! (( nStyle_ & SalFrameStyleFlags::FLOAT ) && (nStyle_ & SalFrameStyleFlags::OWNERDRAWDECORATION)) + ) + { + if( static_cast<Atom>(pEvent->data.l[0]) == rWMAdaptor.getAtom( WMAdaptor::WM_DELETE_WINDOW ) ) + { + Close(); + return true; + } + else if( static_cast<Atom>(pEvent->data.l[0]) == rWMAdaptor.getAtom( WMAdaptor::WM_TAKE_FOCUS ) ) + { + // do nothing, we set the input focus in ToTop() if necessary +#if OSL_DEBUG_LEVEL > 1 + SAL_INFO("vcl.window", "got WM_TAKE_FOCUS on " + << ((nStyle_ & + SalFrameStyleFlags::OWNERDRAWDECORATION) ? + "ownerdraw" : + "NON OWNERDRAW" ) + << " window."); +#endif + } + } + } + else if( pEvent->message_type == rWMAdaptor.getAtom( WMAdaptor::XEMBED ) && + pEvent->window == GetWindow() ) + { + if( pEvent->data.l[1] == 1 || // XEMBED_WINDOW_ACTIVATE + pEvent->data.l[1] == 2 ) // XEMBED_WINDOW_DEACTIVATE + { + XFocusChangeEvent aEvent; + aEvent.type = (pEvent->data.l[1] == 1 ? FocusIn : FocusOut); + aEvent.serial = pEvent->serial; + aEvent.send_event = True; + aEvent.display = pEvent->display; + aEvent.window = pEvent->window; + aEvent.mode = NotifyNormal; + aEvent.detail = NotifyDetailNone; + HandleFocusEvent( &aEvent ); + } + } + return false; +} + +bool X11SalFrame::Dispatch( XEvent *pEvent ) +{ + bool nRet = false; + + if( -1 == nCaptured_ ) + { + CaptureMouse( true ); +#ifdef DBG_UTIL + if( -1 != nCaptured_ ) + pDisplay_->DbgPrintDisplayEvent("Captured", pEvent); +#endif + } + + if( pEvent->xany.window == GetShellWindow() || pEvent->xany.window == GetWindow() ) + { + switch( pEvent->type ) + { + case KeyPress: + nRet = HandleKeyEvent( &pEvent->xkey ); + break; + + case KeyRelease: + nRet = HandleKeyEvent( &pEvent->xkey ); + break; + + case ButtonPress: + // if we lose the focus in presentation mode + // there are good chances that we never get it back + // since the WM ignores us + if( IsOverrideRedirect() ) + { + XSetInputFocus( GetXDisplay(), GetShellWindow(), + RevertToNone, CurrentTime ); + } + [[fallthrough]]; + case ButtonRelease: + case MotionNotify: + case EnterNotify: + case LeaveNotify: + nRet = HandleMouseEvent( pEvent ); + break; + + case FocusIn: + case FocusOut: + nRet = HandleFocusEvent( &pEvent->xfocus ); + break; + + case Expose: + case GraphicsExpose: + nRet = HandleExposeEvent( pEvent ); + break; + + case MapNotify: + if( pEvent->xmap.window == GetShellWindow() ) + { + if( nShowState_ == X11ShowState::Hidden ) + { + /* + * workaround for (at least) KWin 2.2.2 + * which will map windows that were once transient + * even if they are withdrawn when the respective + * document is mapped. + */ + if( ! (nStyle_ & SalFrameStyleFlags::PLUG) ) + XUnmapWindow( GetXDisplay(), GetShellWindow() ); + break; + } + bMapped_ = true; + bViewable_ = true; + nRet = true; + if ( mpInputContext != nullptr ) + mpInputContext->Map( this ); + CallCallback( SalEvent::Resize, nullptr ); + + bool bSetFocus = m_bSetFocusOnMap; + + /* + * sometimes a message box/dialogue is brought up when a frame is not mapped + * the corresponding TRANSIENT_FOR hint is then set to the root window + * so that the dialogue shows in all cases. Correct it here if the + * frame is shown afterwards. + */ + if( ! IsChildWindow() + && ! IsOverrideRedirect() + && ! IsFloatGrabWindow() + ) + { + for (auto const& child : maChildren) + { + if( child->mbTransientForRoot ) + pDisplay_->getWMAdaptor()->changeReferenceFrame( child, this ); + } + } + + if( hPresentationWindow != None && GetShellWindow() == hPresentationWindow ) + XSetInputFocus( GetXDisplay(), GetShellWindow(), RevertToParent, CurrentTime ); + + if( bSetFocus ) + { + XSetInputFocus( GetXDisplay(), + GetShellWindow(), + RevertToParent, + CurrentTime ); + } + + RestackChildren(); + m_bSetFocusOnMap = false; + } + break; + + case UnmapNotify: + if( pEvent->xunmap.window == GetShellWindow() ) + { + bMapped_ = false; + bViewable_ = false; + nRet = true; + if ( mpInputContext != nullptr ) + mpInputContext->Unmap(); + CallCallback( SalEvent::Resize, nullptr ); + } + break; + + case ConfigureNotify: + if( pEvent->xconfigure.window == GetShellWindow() + || pEvent->xconfigure.window == GetWindow() ) + nRet = HandleSizeEvent( &pEvent->xconfigure ); + break; + + case VisibilityNotify: + nVisibility_ = pEvent->xvisibility.state; + nRet = true; + if( bAlwaysOnTop_ + && bMapped_ + && ! GetDisplay()->getWMAdaptor()->isAlwaysOnTopOK() + && nVisibility_ != VisibilityUnobscured ) + maAlwaysOnTopRaiseTimer.Start(); + break; + + case ReparentNotify: + nRet = HandleReparentEvent( &pEvent->xreparent ); + break; + + case MappingNotify: + break; + + case ColormapNotify: + nRet = false; + break; + + case PropertyNotify: + { + if( pEvent->xproperty.atom == pDisplay_->getWMAdaptor()->getAtom( WMAdaptor::WM_STATE ) ) + nRet = HandleStateEvent( &pEvent->xproperty ); + else + nRet = pDisplay_->getWMAdaptor()->handlePropertyNotify( this, &pEvent->xproperty ); + break; + } + + case ClientMessage: + nRet = HandleClientMessage( &pEvent->xclient ); + break; + } + } + else + { + switch( pEvent->type ) + { + case FocusIn: + case FocusOut: + if( ( nStyle_ & SalFrameStyleFlags::PLUG ) + && ( pEvent->xfocus.window == GetShellWindow() + || pEvent->xfocus.window == GetForeignParent() ) + ) + { + nRet = HandleFocusEvent( &pEvent->xfocus ); + } + break; + + case ConfigureNotify: + if( pEvent->xconfigure.window == GetForeignParent() || + pEvent->xconfigure.window == GetShellWindow() ) + nRet = HandleSizeEvent( &pEvent->xconfigure ); + + if( pEvent->xconfigure.window == GetStackingWindow() ) + nRet = HandleSizeEvent( &pEvent->xconfigure ); + + RestackChildren(); + break; + } + } + + return nRet; +} + +void X11SalFrame::ResetClipRegion() +{ + m_vClipRectangles.clear(); + + const int dest_kind = ShapeBounding; + const int op = ShapeSet; + const int ordering = YSorted; + + XWindowAttributes win_attrib; + XRectangle win_size; + + ::Window aShapeWindow = mhShellWindow; + + XGetWindowAttributes ( GetDisplay()->GetDisplay(), + aShapeWindow, + &win_attrib ); + + win_size.x = 0; + win_size.y = 0; + win_size.width = win_attrib.width; + win_size.height = win_attrib.height; + + XShapeCombineRectangles ( GetDisplay()->GetDisplay(), + aShapeWindow, + dest_kind, + 0, 0, // x_off, y_off + &win_size, // list of rectangles + 1, // number of rectangles + op, ordering ); +} + +void X11SalFrame::BeginSetClipRegion( sal_uInt32 /*nRects*/ ) +{ + m_vClipRectangles.clear(); +} + +void X11SalFrame::UnionClipRegion( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight ) +{ + m_vClipRectangles.emplace_back( XRectangle { static_cast<short>(nX), static_cast<short>(nY), + static_cast<unsigned short>(nWidth), static_cast<unsigned short>(nHeight) } ); +} + +void X11SalFrame::EndSetClipRegion() +{ + const int dest_kind = ShapeBounding; + const int ordering = YSorted; + const int op = ShapeSet; + + ::Window aShapeWindow = mhShellWindow; + XShapeCombineRectangles ( GetDisplay()->GetDisplay(), + aShapeWindow, + dest_kind, + 0, 0, // x_off, y_off + m_vClipRectangles.data(), + m_vClipRectangles.size(), + op, ordering ); + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/generic/window/salobj.cxx b/vcl/unx/generic/window/salobj.cxx new file mode 100644 index 000000000..c24e138e5 --- /dev/null +++ b/vcl/unx/generic/window/salobj.cxx @@ -0,0 +1,506 @@ +/* -*- 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 . + */ + +#if OSL_DEBUG_LEVEL > 1 +#include <stdio.h> +#endif + +#include <X11/Xlib.h> +#include <X11/Xutil.h> +#include <X11/extensions/shape.h> + + +#include <vcl/keycodes.hxx> +#include <vcl/event.hxx> +#include <sal/log.hxx> + +#include <unx/salinst.h> +#include <unx/saldisp.hxx> +#include <unx/salobj.h> + +#include <salframe.hxx> +#include <salwtype.hxx> + +// SalInstance member to create and destroy a SalObject + +SalObject* X11SalInstance::CreateObject( SalFrame* pParent, SystemWindowData* pWindowData, bool bShow ) +{ + return X11SalObject::CreateObject( pParent, pWindowData, bShow ); +} + +X11SalObject* X11SalObject::CreateObject( SalFrame* pParent, SystemWindowData* pWindowData, bool bShow ) +{ + int error_base, event_base; + X11SalObject* pObject = new X11SalObject(); + SystemEnvData* pObjData = const_cast<SystemEnvData*>(pObject->GetSystemData()); + + if ( ! XShapeQueryExtension( static_cast<Display*>(pObjData->pDisplay), + &event_base, &error_base ) ) + { + delete pObject; + return nullptr; + } + + pObject->mpParent = pParent; + + SalDisplay* pSalDisp = vcl_sal::getSalDisplay(GetGenericUnixSalData()); + const SystemEnvData* pEnv = pParent->GetSystemData(); + Display* pDisp = pSalDisp->GetDisplay(); + ::Window aObjectParent = static_cast<::Window>(pEnv->GetWindowHandle(pParent)); + pObject->maParentWin = aObjectParent; + + // find out on which screen that window is + XWindowAttributes aParentAttr; + XGetWindowAttributes( pDisp, aObjectParent, &aParentAttr ); + SalX11Screen nXScreen( XScreenNumberOfScreen( aParentAttr.screen ) ); + Visual* pVisual = (pWindowData && pWindowData->pVisual) ? + static_cast<Visual*>(pWindowData->pVisual) : + pSalDisp->GetVisual( nXScreen ).GetVisual(); + // get visual info + VisualID aVisID = XVisualIDFromVisual( pVisual ); + XVisualInfo aTemplate; + aTemplate.visualid = aVisID; + int nVisuals = 0; + XVisualInfo* pInfo = XGetVisualInfo( pDisp, VisualIDMask, &aTemplate, &nVisuals ); + // only one VisualInfo structure can match the visual id + SAL_WARN_IF( nVisuals != 1, "vcl", "match count for visual id is not 1" ); + unsigned int nDepth = pInfo->depth; + XFree( pInfo ); + XSetWindowAttributes aAttribs; + aAttribs.event_mask = StructureNotifyMask + | ButtonPressMask + | ButtonReleaseMask + | PointerMotionMask + | EnterWindowMask + | LeaveWindowMask + | FocusChangeMask + | ExposureMask + ; + + pObject->maPrimary = + XCreateSimpleWindow( pDisp, + aObjectParent, + 0, 0, + 1, 1, 0, + pSalDisp->GetColormap( nXScreen ).GetBlackPixel(), + pSalDisp->GetColormap( nXScreen ).GetWhitePixel() + ); + if( aVisID == pSalDisp->GetVisual( nXScreen ).GetVisualId() ) + { + pObject->maSecondary = + XCreateSimpleWindow( pDisp, + pObject->maPrimary, + 0, 0, + 1, 1, 0, + pSalDisp->GetColormap( nXScreen ).GetBlackPixel(), + pSalDisp->GetColormap( nXScreen ).GetWhitePixel() + ); + } + else + { +#if OSL_DEBUG_LEVEL > 1 + SAL_INFO("vcl.window", "visual id of vcl " + << std::hex + << static_cast<unsigned int> + (pSalDisp->GetVisual( nXScreen ).GetVisualId()) + << ", of visual " + << static_cast<unsigned int> + (aVisID)); +#endif + GetGenericUnixSalData()->ErrorTrapPush(); + + // create colormap for visual - there might not be one + pObject->maColormap = aAttribs.colormap = XCreateColormap( + pDisp, + pSalDisp->GetRootWindow( nXScreen ), + pVisual, + AllocNone ); + + pObject->maSecondary = + XCreateWindow( pDisp, + pSalDisp->GetRootWindow( nXScreen ), + 0, 0, + 1, 1, 0, + nDepth, InputOutput, + pVisual, + CWEventMask|CWColormap, &aAttribs ); + XSync( pDisp, False ); + if( GetGenericUnixSalData()->ErrorTrapPop( false ) ) + { + pObject->maSecondary = None; + delete pObject; + return nullptr; + } + XReparentWindow( pDisp, pObject->maSecondary, pObject->maPrimary, 0, 0 ); + } + + GetGenericUnixSalData()->ErrorTrapPush(); + if( bShow ) { + XMapWindow( pDisp, pObject->maSecondary ); + XMapWindow( pDisp, pObject->maPrimary ); + } + + pObjData->pDisplay = pDisp; + pObjData->SetWindowHandle(pObject->maSecondary); + pObjData->pWidget = nullptr; + pObjData->pVisual = pVisual; + + XSync(pDisp, False); + if( GetGenericUnixSalData()->ErrorTrapPop( false ) ) + { + delete pObject; + return nullptr; + } + + return pObject; +} + +void X11SalInstance::DestroyObject( SalObject* pObject ) +{ + delete pObject; +} + +// SalClipRegion is a member of SalObject +// definition of SalClipRegion my be found in vcl/inc/unx/salobj.h + +SalClipRegion::SalClipRegion() +{ + ClipRectangleList = nullptr; + numClipRectangles = 0; + maxClipRectangles = 0; +} + +SalClipRegion::~SalClipRegion() +{ +} + +void +SalClipRegion::BeginSetClipRegion( sal_uInt32 nRects ) +{ + ClipRectangleList.reset( new XRectangle[nRects] ); + numClipRectangles = 0; + maxClipRectangles = nRects; +} + +void +SalClipRegion::UnionClipRegion( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight ) +{ + if ( nWidth && nHeight && (numClipRectangles < maxClipRectangles) ) + { + XRectangle& aRect = ClipRectangleList[numClipRectangles]; + + aRect.x = static_cast<short>(nX); + aRect.y = static_cast<short>(nY); + aRect.width = static_cast<unsigned short>(nWidth); + aRect.height= static_cast<unsigned short>(nHeight); + + numClipRectangles++; + } +} + +// SalObject Implementation +X11SalObject::X11SalObject() + : mpParent(nullptr) + , maParentWin(0) + , maPrimary(0) + , maSecondary(0) + , maColormap(0) + , mbVisible(false) +{ + maSystemChildData.pDisplay = vcl_sal::getSalDisplay(GetGenericUnixSalData())->GetDisplay(); + maSystemChildData.SetWindowHandle(None); + maSystemChildData.pSalFrame = nullptr; + maSystemChildData.pWidget = nullptr; + maSystemChildData.pVisual = nullptr; + maSystemChildData.aShellWindow = 0; + maSystemChildData.toolkit = SystemEnvData::Toolkit::Gen; + maSystemChildData.platform = SystemEnvData::Platform::Xcb; + + std::list< SalObject* >& rObjects = vcl_sal::getSalDisplay(GetGenericUnixSalData())->getSalObjects(); + rObjects.push_back( this ); +} + +X11SalObject::~X11SalObject() +{ + std::list< SalObject* >& rObjects = vcl_sal::getSalDisplay(GetGenericUnixSalData())->getSalObjects(); + rObjects.remove( this ); + + GetGenericUnixSalData()->ErrorTrapPush(); + ::Window aObjectParent = maParentWin; + XSetWindowBackgroundPixmap(static_cast<Display*>(maSystemChildData.pDisplay), aObjectParent, None); + if ( maSecondary ) + XDestroyWindow( static_cast<Display*>(maSystemChildData.pDisplay), maSecondary ); + if ( maPrimary ) + XDestroyWindow( static_cast<Display*>(maSystemChildData.pDisplay), maPrimary ); + if ( maColormap ) + XFreeColormap(static_cast<Display*>(maSystemChildData.pDisplay), maColormap); + XSync( static_cast<Display*>(maSystemChildData.pDisplay), False ); + GetGenericUnixSalData()->ErrorTrapPop(); +} + +void +X11SalObject::ResetClipRegion() +{ + maClipRegion.ResetClipRegion(); + + const int dest_kind = ShapeBounding; + const int op = ShapeSet; + const int ordering = YSorted; + + XWindowAttributes win_attrib; + XRectangle win_size; + + ::Window aShapeWindow = maPrimary; + + XGetWindowAttributes ( static_cast<Display*>(maSystemChildData.pDisplay), + aShapeWindow, + &win_attrib ); + + win_size.x = 0; + win_size.y = 0; + win_size.width = win_attrib.width; + win_size.height = win_attrib.height; + + XShapeCombineRectangles ( static_cast<Display*>(maSystemChildData.pDisplay), + aShapeWindow, + dest_kind, + 0, 0, // x_off, y_off + &win_size, // list of rectangles + 1, // number of rectangles + op, ordering ); +} + +void +X11SalObject::BeginSetClipRegion( sal_uInt32 nRectCount ) +{ + maClipRegion.BeginSetClipRegion ( nRectCount ); +} + +void +X11SalObject::UnionClipRegion( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight ) +{ + maClipRegion.UnionClipRegion ( nX, nY, nWidth, nHeight ); +} + +void +X11SalObject::EndSetClipRegion() +{ + XRectangle *pRectangles = maClipRegion.EndSetClipRegion (); + const int nRectangles = maClipRegion.GetRectangleCount(); + + ::Window aShapeWindow = maPrimary; + + XShapeCombineRectangles ( static_cast<Display*>(maSystemChildData.pDisplay), + aShapeWindow, + ShapeBounding, + 0, 0, // x_off, y_off + pRectangles, + nRectangles, + ShapeSet, YSorted ); +} + +void +X11SalObject::SetPosSize( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight ) +{ + if ( maPrimary && maSecondary && nWidth && nHeight ) + { + XMoveResizeWindow( static_cast<Display*>(maSystemChildData.pDisplay), + maPrimary, + nX, nY, nWidth, nHeight ); + XMoveResizeWindow( static_cast<Display*>(maSystemChildData.pDisplay), + maSecondary, + 0, 0, nWidth, nHeight ); + } +} + +void +X11SalObject::Show( bool bVisible ) +{ + if (!maSystemChildData.GetWindowHandle(mpParent)) + return; + + if ( bVisible ) { + XMapWindow( static_cast<Display*>(maSystemChildData.pDisplay), + maSecondary ); + XMapWindow( static_cast<Display*>(maSystemChildData.pDisplay), + maPrimary ); + } else { + XUnmapWindow( static_cast<Display*>(maSystemChildData.pDisplay), + maPrimary ); + XUnmapWindow( static_cast<Display*>(maSystemChildData.pDisplay), + maSecondary ); + } + mbVisible = bVisible; +} + +void X11SalObject::GrabFocus() +{ + if( mbVisible ) + XSetInputFocus( static_cast<Display*>(maSystemChildData.pDisplay), + maSystemChildData.GetWindowHandle(mpParent), + RevertToNone, + CurrentTime ); +} + +const SystemEnvData* X11SalObject::GetSystemData() const +{ + return &maSystemChildData; +} + +static sal_uInt16 sal_GetCode( int state ) +{ + sal_uInt16 nCode = 0; + + if( state & Button1Mask ) + nCode |= MOUSE_LEFT; + if( state & Button2Mask ) + nCode |= MOUSE_MIDDLE; + if( state & Button3Mask ) + nCode |= MOUSE_RIGHT; + + if( state & ShiftMask ) + nCode |= KEY_SHIFT; + if( state & ControlMask ) + nCode |= KEY_MOD1; + if( state & Mod1Mask ) + nCode |= KEY_MOD2; + if( state & Mod3Mask ) + nCode |= KEY_MOD3; + + return nCode; +} + +bool X11SalObject::Dispatch( XEvent* pEvent ) +{ + std::list< SalObject* >& rObjects = vcl_sal::getSalDisplay(GetGenericUnixSalData())->getSalObjects(); + + for (auto const& elem : rObjects) + { + X11SalObject* pObject = static_cast<X11SalObject*>(elem); + if( pEvent->xany.window == pObject->maPrimary || + pEvent->xany.window == pObject->maSecondary ) + { + if( pObject->IsMouseTransparent() && ( + pEvent->type == ButtonPress || + pEvent->type == ButtonRelease || + pEvent->type == EnterNotify || + pEvent->type == LeaveNotify || + pEvent->type == MotionNotify + ) + ) + { + SalMouseEvent aEvt; + int dest_x, dest_y; + ::Window aChild = None; + XTranslateCoordinates( pEvent->xbutton.display, + pEvent->xbutton.root, + pObject->maParentWin, + pEvent->xbutton.x_root, + pEvent->xbutton.y_root, + &dest_x, &dest_y, + &aChild ); + aEvt.mnX = dest_x; + aEvt.mnY = dest_y; + aEvt.mnTime = pEvent->xbutton.time; + aEvt.mnCode = sal_GetCode( pEvent->xbutton.state ); + aEvt.mnButton = 0; + SalEvent nEvent = SalEvent::NONE; + if( pEvent->type == ButtonPress || + pEvent->type == ButtonRelease ) + { + switch( pEvent->xbutton.button ) + { + case Button1: aEvt.mnButton = MOUSE_LEFT;break; + case Button2: aEvt.mnButton = MOUSE_MIDDLE;break; + case Button3: aEvt.mnButton = MOUSE_RIGHT;break; + } + nEvent = (pEvent->type == ButtonPress) ? + SalEvent::MouseButtonDown : + SalEvent::MouseButtonUp; + } + else if( pEvent->type == EnterNotify ) + nEvent = SalEvent::MouseLeave; + else + nEvent = SalEvent::MouseMove; + pObject->mpParent->CallCallback( nEvent, &aEvt ); + } + else + { + switch( pEvent->type ) + { + case UnmapNotify: + pObject->mbVisible = false; + return true; + case MapNotify: + pObject->mbVisible = true; + return true; + case ButtonPress: + pObject->CallCallback( SalObjEvent::ToTop ); + return true; + case FocusIn: + pObject->CallCallback( SalObjEvent::GetFocus ); + return true; + case FocusOut: + pObject->CallCallback( SalObjEvent::LoseFocus ); + return true; + default: break; + } + } + return false; + } + } + return false; +} + +void X11SalObject::SetLeaveEnterBackgrounds(const css::uno::Sequence<css::uno::Any>& rLeaveArgs, const css::uno::Sequence<css::uno::Any>& rEnterArgs) +{ + SalDisplay* pSalDisp = vcl_sal::getSalDisplay(GetGenericUnixSalData()); + Display* pDisp = pSalDisp->GetDisplay(); + ::Window aObjectParent = maParentWin; + + bool bFreePixmap = false; + Pixmap aPixmap = None; + if (rEnterArgs.getLength() == 3) + { + rEnterArgs[0] >>= bFreePixmap; + sal_Int64 pixmapHandle = None; + rEnterArgs[1] >>= pixmapHandle; + aPixmap = pixmapHandle; + } + + XSetWindowBackgroundPixmap(pDisp, aObjectParent, aPixmap); + if (bFreePixmap) + XFreePixmap(pDisp, aPixmap); + + bFreePixmap = false; + aPixmap = None; + if (rLeaveArgs.getLength() == 3) + { + rLeaveArgs[0] >>= bFreePixmap; + sal_Int64 pixmapHandle = None; + rLeaveArgs[1] >>= pixmapHandle; + aPixmap = pixmapHandle; + } + + XSetWindowBackgroundPixmap(pDisp, maSecondary, aPixmap); + if (bFreePixmap) + XFreePixmap(pDisp, aPixmap); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/generic/window/screensaverinhibitor.cxx b/vcl/unx/generic/window/screensaverinhibitor.cxx new file mode 100644 index 000000000..36eac26c2 --- /dev/null +++ b/vcl/unx/generic/window/screensaverinhibitor.cxx @@ -0,0 +1,370 @@ +/* -*- 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 <functional> + +#include <unx/gensys.h> +#include <unx/screensaverinhibitor.hxx> + +#include <X11/Xlib.h> +#include <X11/Xatom.h> + +#if !defined(__sun) && !defined(AIX) +#include <X11/extensions/dpms.h> +#endif + +#include <config_gio.h> + +#if ENABLE_GIO +#include <gio/gio.h> + +#define FDO_DBUS_SERVICE "org.freedesktop.ScreenSaver" +#define FDO_DBUS_PATH "/org/freedesktop/ScreenSaver" +#define FDO_DBUS_INTERFACE "org.freedesktop.ScreenSaver" + +#define FDOPM_DBUS_SERVICE "org.freedesktop.PowerManagement.Inhibit" +#define FDOPM_DBUS_PATH "/org/freedesktop/PowerManagement/Inhibit" +#define FDOPM_DBUS_INTERFACE "org.freedesktop.PowerManagement.Inhibit" + +#define GSM_DBUS_SERVICE "org.gnome.SessionManager" +#define GSM_DBUS_PATH "/org/gnome/SessionManager" +#define GSM_DBUS_INTERFACE "org.gnome.SessionManager" + +// Mate <= 1.10 uses org.mate.SessionManager, > 1.10 will use org.gnome.SessionManager +#define MSM_DBUS_SERVICE "org.mate.SessionManager" +#define MSM_DBUS_PATH "/org/mate/SessionManager" +#define MSM_DBUS_INTERFACE "org.mate.SessionManager" +#endif + +#include <sal/log.hxx> + +void ScreenSaverInhibitor::inhibit( bool bInhibit, std::u16string_view sReason, + bool bIsX11, const std::optional<unsigned int>& xid, std::optional<Display*> pDisplay ) +{ + const char* appname = SalGenericSystem::getFrameClassName(); + const OString aReason = OUStringToOString( sReason, RTL_TEXTENCODING_UTF8 ); + + inhibitFDO( bInhibit, appname, aReason.getStr() ); + inhibitFDOPM( bInhibit, appname, aReason.getStr() ); + + if ( !bIsX11 ) + return; + + if (pDisplay) + { + inhibitXScreenSaver( bInhibit, *pDisplay ); + inhibitXAutoLock( bInhibit, *pDisplay ); + inhibitDPMS( bInhibit, *pDisplay ); + } + + if (xid) + { + inhibitGSM( bInhibit, appname, aReason.getStr(), *xid ); + inhibitMSM( bInhibit, appname, aReason.getStr(), *xid ); + } +} + +#if ENABLE_GIO +static void dbusInhibit( bool bInhibit, + const gchar* service, const gchar* path, const gchar* interface, + const std::function<GVariant*( GDBusProxy*, GError*& )>& fInhibit, + const std::function<GVariant*( GDBusProxy*, const guint, GError*& )>& fUnInhibit, + std::optional<guint>& rCookie ) +{ + if ( ( !bInhibit && !rCookie ) || + ( bInhibit && rCookie ) ) + { + return; + } + + GError *error = nullptr; + GDBusConnection *session_connection = g_bus_get_sync( G_BUS_TYPE_SESSION, nullptr, &error ); + if (session_connection == nullptr) { + SAL_WARN( "vcl.screensaverinhibitor", "failed to connect to dbus session bus" ); + + if (error != nullptr) { + SAL_WARN( "vcl.screensaverinhibitor", "Error: " << error->message ); + g_error_free( error ); + } + + return; + } + + GDBusProxy *proxy = g_dbus_proxy_new_sync( session_connection, + G_DBUS_PROXY_FLAGS_NONE, + nullptr, + service, + path, + interface, + nullptr, + nullptr ); + + g_object_unref( G_OBJECT( session_connection ) ); + + if (proxy == nullptr) { + SAL_INFO( "vcl.screensaverinhibitor", "could not get dbus proxy: " << service ); + return; + } + + GVariant *res = nullptr; + + if ( bInhibit ) + { + res = fInhibit( proxy, error ); + + if (res != nullptr) + { + guint nCookie; + + g_variant_get(res, "(u)", &nCookie); + g_variant_unref(res); + + rCookie = nCookie; + } + else + { + SAL_INFO( "vcl.screensaverinhibitor", service << ".Inhibit failed"); + } + } + else + { + res = fUnInhibit( proxy, *rCookie, error ); + rCookie.reset(); + + if (res != nullptr) + { + g_variant_unref(res); + } + else + { + SAL_INFO( "vcl.screensaverinhibitor", service << ".UnInhibit failed" ); + } + } + + if (error != nullptr) + { + SAL_INFO( "vcl.screensaverinhibitor", "Error: " << error->message ); + g_error_free( error ); + } + + g_object_unref( G_OBJECT( proxy ) ); +} +#endif // ENABLE_GIO + +void ScreenSaverInhibitor::inhibitFDO( bool bInhibit, const char* appname, const char* reason ) +{ +#if ENABLE_GIO + dbusInhibit( bInhibit, + FDO_DBUS_SERVICE, FDO_DBUS_PATH, FDO_DBUS_INTERFACE, + [appname, reason] ( GDBusProxy *proxy, GError*& error ) -> GVariant* { + return g_dbus_proxy_call_sync( proxy, "Inhibit", + g_variant_new("(ss)", appname, reason), + G_DBUS_CALL_FLAGS_NONE, -1, nullptr, &error ); + }, + [] ( GDBusProxy *proxy, const guint nCookie, GError*& error ) -> GVariant* { + return g_dbus_proxy_call_sync( proxy, "UnInhibit", + g_variant_new("(u)", nCookie), + G_DBUS_CALL_FLAGS_NONE, -1, nullptr, &error ); + }, + mnFDOCookie ); +#else + (void) this; + (void) bInhibit; + (void) appname; + (void) reason; +#endif // ENABLE_GIO +} + +void ScreenSaverInhibitor::inhibitFDOPM( bool bInhibit, const char* appname, const char* reason ) +{ +#if ENABLE_GIO + dbusInhibit( bInhibit, + FDOPM_DBUS_SERVICE, FDOPM_DBUS_PATH, FDOPM_DBUS_INTERFACE, + [appname, reason] ( GDBusProxy *proxy, GError*& error ) -> GVariant* { + return g_dbus_proxy_call_sync( proxy, "Inhibit", + g_variant_new("(ss)", appname, reason), + G_DBUS_CALL_FLAGS_NONE, -1, nullptr, &error ); + }, + [] ( GDBusProxy *proxy, const guint nCookie, GError*& error ) -> GVariant* { + return g_dbus_proxy_call_sync( proxy, "UnInhibit", + g_variant_new("(u)", nCookie), + G_DBUS_CALL_FLAGS_NONE, -1, nullptr, &error ); + }, + mnFDOPMCookie ); +#else + (void) this; + (void) bInhibit; + (void) appname; + (void) reason; +#endif // ENABLE_GIO +} + +void ScreenSaverInhibitor::inhibitGSM( bool bInhibit, const char* appname, const char* reason, const unsigned int xid ) +{ +#if ENABLE_GIO + dbusInhibit( bInhibit, + GSM_DBUS_SERVICE, GSM_DBUS_PATH, GSM_DBUS_INTERFACE, + [appname, reason, xid] ( GDBusProxy *proxy, GError*& error ) -> GVariant* { + return g_dbus_proxy_call_sync( proxy, "Inhibit", + g_variant_new("(susu)", + appname, + xid, + reason, + 8 //Inhibit the session being marked as idle + ), + G_DBUS_CALL_FLAGS_NONE, -1, nullptr, &error ); + }, + [] ( GDBusProxy *proxy, const guint nCookie, GError*& error ) -> GVariant* { + return g_dbus_proxy_call_sync( proxy, "Uninhibit", + g_variant_new("(u)", nCookie), + G_DBUS_CALL_FLAGS_NONE, -1, nullptr, &error ); + }, + mnGSMCookie ); +#else + (void) this; + (void) bInhibit; + (void) appname; + (void) reason; + (void) xid; +#endif // ENABLE_GIO +} + +void ScreenSaverInhibitor::inhibitMSM( bool bInhibit, const char* appname, const char* reason, const unsigned int xid ) +{ +#if ENABLE_GIO + dbusInhibit( bInhibit, + MSM_DBUS_SERVICE, MSM_DBUS_PATH, MSM_DBUS_INTERFACE, + [appname, reason, xid] ( GDBusProxy *proxy, GError*& error ) -> GVariant* { + return g_dbus_proxy_call_sync( proxy, "Inhibit", + g_variant_new("(susu)", + appname, + xid, + reason, + 8 //Inhibit the session being marked as idle + ), + G_DBUS_CALL_FLAGS_NONE, -1, nullptr, &error ); + }, + [] ( GDBusProxy *proxy, const guint nCookie, GError*& error ) -> GVariant* { + return g_dbus_proxy_call_sync( proxy, "Uninhibit", + g_variant_new("(u)", nCookie), + G_DBUS_CALL_FLAGS_NONE, -1, nullptr, &error ); + }, + mnMSMCookie ); +#else + (void) this; + (void) bInhibit; + (void) appname; + (void) reason; + (void) xid; +#endif // ENABLE_GIO +} + +/** + * Disable screensavers using the XSetScreenSaver/XGetScreenSaver API. + * + * Worth noting: xscreensaver explicitly ignores this and does its own + * timeout handling. + */ +void ScreenSaverInhibitor::inhibitXScreenSaver( bool bInhibit, Display* pDisplay ) +{ + int nTimeout, nInterval, bPreferBlanking, bAllowExposures; + XGetScreenSaver( pDisplay, &nTimeout, &nInterval, + &bPreferBlanking, &bAllowExposures ); + + // To disable/reenable we simply fiddle the timeout, whilst + // retaining all other properties. + if ( bInhibit && nTimeout) + { + mnXScreenSaverTimeout = nTimeout; + XResetScreenSaver( pDisplay ); + XSetScreenSaver( pDisplay, 0, nInterval, + bPreferBlanking, bAllowExposures ); + } + else if ( !bInhibit && mnXScreenSaverTimeout ) + { + XSetScreenSaver( pDisplay, *mnXScreenSaverTimeout, + nInterval, bPreferBlanking, + bAllowExposures ); + mnXScreenSaverTimeout.reset(); + } +} + + +/* definitions from xautolock.c (pl15) */ +#define XAUTOLOCK_DISABLE 1 +#define XAUTOLOCK_ENABLE 2 + +void ScreenSaverInhibitor::inhibitXAutoLock( bool bInhibit, Display* pDisplay ) +{ + ::Window aRootWindow = RootWindowOfScreen( ScreenOfDisplay( pDisplay, 0 ) ); + + Atom nAtom = XInternAtom( pDisplay, + "XAUTOLOCK_MESSAGE", + False ); + + if ( nAtom == None ) + { + return; + } + + int nMessage = bInhibit ? XAUTOLOCK_DISABLE : XAUTOLOCK_ENABLE; + + XChangeProperty( pDisplay, + aRootWindow, + nAtom, + XA_INTEGER, + 8, // format -- 8 bit quantity + PropModeReplace, + reinterpret_cast<unsigned char*>( &nMessage ), + sizeof( nMessage ) ); +} + +void ScreenSaverInhibitor::inhibitDPMS( bool bInhibit, Display* pDisplay ) +{ +#if !defined(__sun) && !defined(AIX) + int dummy; + // This won't change while X11 is running, hence + // we can evaluate only once and store as static + static bool bDPMSExtensionAvailable = ( DPMSQueryExtension( pDisplay, &dummy, &dummy) != 0 ); + + if ( !bDPMSExtensionAvailable ) + { + return; + } + + if ( bInhibit ) + { + CARD16 state; // unused by us + DPMSInfo( pDisplay, &state, &mbDPMSWasEnabled ); + + if ( mbDPMSWasEnabled ) + { + DPMSGetTimeouts( pDisplay, + &mnDPMSStandbyTimeout, + &mnDPMSSuspendTimeout, + &mnDPMSOffTimeout ); + DPMSSetTimeouts( pDisplay, + 0, + 0, + 0 ); + } + } + else if ( !bInhibit && mbDPMSWasEnabled ) + { + DPMSSetTimeouts( pDisplay, + mnDPMSStandbyTimeout, + mnDPMSSuspendTimeout, + mnDPMSOffTimeout ); + } +#endif // !defined(__sun) && !defined(AIX) +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |