diff options
Diffstat (limited to 'vcl/unx/generic/app')
-rw-r--r-- | vcl/unx/generic/app/gendata.cxx | 44 | ||||
-rw-r--r-- | vcl/unx/generic/app/gendisp.cxx | 69 | ||||
-rw-r--r-- | vcl/unx/generic/app/geninst.cxx | 77 | ||||
-rw-r--r-- | vcl/unx/generic/app/gensys.cxx | 116 | ||||
-rw-r--r-- | vcl/unx/generic/app/i18n_cb.cxx | 494 | ||||
-rw-r--r-- | vcl/unx/generic/app/i18n_ic.cxx | 604 | ||||
-rw-r--r-- | vcl/unx/generic/app/i18n_im.cxx | 410 | ||||
-rw-r--r-- | vcl/unx/generic/app/i18n_keysym.cxx | 358 | ||||
-rw-r--r-- | vcl/unx/generic/app/i18n_xkb.cxx | 107 | ||||
-rw-r--r-- | vcl/unx/generic/app/keysymnames.cxx | 507 | ||||
-rw-r--r-- | vcl/unx/generic/app/randrwrapper.cxx | 181 | ||||
-rw-r--r-- | vcl/unx/generic/app/saldata.cxx | 782 | ||||
-rw-r--r-- | vcl/unx/generic/app/saldisp.cxx | 2889 | ||||
-rw-r--r-- | vcl/unx/generic/app/salinst.cxx | 249 | ||||
-rw-r--r-- | vcl/unx/generic/app/saltimer.cxx | 68 | ||||
-rw-r--r-- | vcl/unx/generic/app/sm.cxx | 858 | ||||
-rw-r--r-- | vcl/unx/generic/app/wmadaptor.cxx | 2308 |
17 files changed, 10121 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..500d3b133 --- /dev/null +++ b/vcl/unx/generic/app/gendata.cxx @@ -0,0 +1,44 @@ +/* -*- 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> + +GenericUnixSalData::GenericUnixSalData(GenericUnixSalDataType const t, SalInstance* const pInstance) + : m_eType(t) + , m_pDisplay(nullptr) +{ + m_pInstance = pInstance; + SetSalData(this); +} + +GenericUnixSalData::~GenericUnixSalData() {} + +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..b6611631b --- /dev/null +++ b/vcl/unx/generic/app/geninst.cxx @@ -0,0 +1,77 @@ +/* -*- 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 + +#include <config_features.h> +#if HAVE_FEATURE_OPENGL +#include <vcl/opengl/OpenGLContext.hxx> +#endif +#include <unx/geninst.h> + +// SalYieldMutex + +SalYieldMutex::SalYieldMutex() +{ +#if HAVE_FEATURE_OPENGL + SetBeforeReleaseHandler( &OpenGLContext::prepareForYield ); +#endif +} + +SalYieldMutex::~SalYieldMutex() +{ +} + +SalGenericInstance::~SalGenericInstance() +{ +} + +OUString SalGenericInstance::getOSVersion() +{ + OUString aKernelVer = "unknown"; + +// not so generic, but at least shared between all unix backend +#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 ..." + OUString aVers = aKernelVer.getToken( 2, ' ' ); + // "3.16.7-29-desktop ..." + sal_Int32 nTooDetailed = aVers.indexOf( '.', 2); + if (nTooDetailed < 1 || nTooDetailed > 8) + aKernelVer = "Linux (misparsed version)"; + else // "3.16.7-29-desktop ..." + aKernelVer = "Linux " + aVers.copy(0, nTooDetailed); + } + fclose( pVersion ); + } +#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..4e5f9952c --- /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, 1024 /* 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..ad42c079c --- /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; +} + +static 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->aWindow; + + // 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 ) + { + if( pFrame ) + { + 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) ) + { + maClientData.pFrame = pFocusFrame; + + const SystemEnvData* pEnv = pFocusFrame->GetSystemData(); + ::Window aClientWindow = pEnv->aShellWindow; + ::Window aFocusWindow = pEnv->aWindow; + + 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 ) + { + 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..a9e9fb42f --- /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 ) + { + 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..742ebef20 --- /dev/null +++ b/vcl/unx/generic/app/keysymnames.cxx @@ -0,0 +1,507 @@ +/* -*- 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 <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 ! + + static 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"} + }; + + static 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" } + }; + + static 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" } + }; + + static 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" }, + }; + + static 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" }, + }; + + static 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" }, + }; + + static 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" }, + }; + + static 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" }, + }; + + static 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" }, + }; + + static 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" }, + }; + + static 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" }, + }; + + static 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" }, + }; + + static 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" }, + }; + + static 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" }, + }; + + static 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" }, + }; + + static 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" }, + }; + + static 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( const OUString& pLang, KeySym nSymbol ) + { + for(const auto & rKeyboard : aKeyboards) + { + if( pLang.equalsAscii( 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..cb3698365 --- /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; +} + +static 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 ) + { + int nRet = pWrapper->XRRUpdateConfiguration( pEvent ); + if( nRet == 1 && pEvent->type != ConfigureNotify) // this should then be a XRRScreenChangeNotifyEvent + { + // 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..f2fe146f5 --- /dev/null +++ b/vcl/unx/generic/app/saldata.cxx @@ -0,0 +1,782 @@ +/* -*- 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() +{ + SalData * p1 = ImplGetSVData()->mpSalData; + OSL_ASSERT(p1 != nullptr); + X11SalData * p2 = dynamic_cast< X11SalData * >(p1); + OSL_ASSERT(p2 != nullptr); + return p2; +} + +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; +} + +} + +static const struct timeval noyield_ = { 0, 0 }; +static 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( GenericUnixSalDataType t, SalInstance *pInstance ) + : GenericUnixSalData( t, pInstance ) +{ + 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) + { + // 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..679014cd5 --- /dev/null +++ b/vcl/unx/generic/app/saldisp.cxx @@ -0,0 +1,2889 @@ +/* -*- 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 <opengl/zone.hxx> + +#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 <vcl/opengl/OpenGLHelper.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( long w, 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(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::BestOpenGLVisual(Display* pDisplay, int nScreen, XVisualInfo& rVI) +{ + OpenGLZone aZone; + + XVisualInfo* pVI; + int aAttrib[] = { GLX_RGBA, + GLX_RED_SIZE, 8, + GLX_GREEN_SIZE, 8, + GLX_BLUE_SIZE, 8, + GLX_DEPTH_SIZE, 24, + GLX_STENCIL_SIZE, 8, + None }; + + pVI = glXChooseVisual( pDisplay, nScreen, aAttrib ); + if( !pVI ) + return false; + + rVI = *pVI; + XFree( pVI ); + + CHECK_GL_ERROR(); + return true; +} + +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; + +// TODO SKIA + bool bUseOpenGL = OpenGLHelper::isVCLOpenGLEnabled(); + if (bUseOpenGL && BestOpenGLVisual(pDisplay, nScreen, 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 long nDPI = static_cast<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 + */ + long xDPI = 96; + long yDPI = 96; + if (m_aScreens.size() == 1) { + xDPI = static_cast<long>(round(DisplayWidth(pDisp_, 0)*25.4/DisplayWidthMM(pDisp_, 0))); + yDPI = static_cast<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) + { + 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; + + 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->aWindow), + 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( GetSalData()->m_pInstance->GetYieldMutex()->IsCurrentThread(), + "will crash soon since solar mutex not locked in SalDisplay::Yield" ); + + XNextEvent( pDisp_, &aEvent ); + + // FIXME: under-convinced by Dispatch boolean return value vs. salframe. + Dispatch( &aEvent ); + +#ifdef DBG_UTIL + if( GetX11SalData()->HasXErrorOccurred() ) + { + XFlush( pDisp_ ); + DbgPrintDisplayEvent("SalDisplay::Yield (WasXError)", &aEvent); + } +#endif + GetX11SalData()->ResetXErrorOccurred(); +} + +bool 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 false; + } + + SalInstance* pInstance = GetSalData()->m_pInstance; + 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 false; + } + } + } + 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 true; + } + 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 + ) + { + return pFrame->Dispatch( pEvent ); + } + if( pEvent->type == ConfigureNotify && pEvent->xconfigure.window == pFrame->GetStackingWindow() ) + { + return pFrame->Dispatch( pEvent ); + } + } + + // dispatch to salobjects + X11SalObject::Dispatch( pEvent ); + + // is this perhaps a root window that changed size ? + processRandREvent( pEvent ); + + return false; +} + +#ifdef DBG_UTIL +void SalDisplay::DbgPrintDisplayEvent(const char *pComment, 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, long i_nX, long i_nY, long i_nWidth, 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_ ) ) + { + int nFramebuffers = 1; + XineramaScreenInfo* pScreens = XineramaQueryScreens( pDisp_, &nFramebuffers ); + if( pScreens ) + { + 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; + 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 ) + { + 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 static_cast<Color>(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 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 = 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..21d878cfa --- /dev/null +++ b/vcl/unx/generic/app/salinst.cxx @@ -0,0 +1,249 @@ +/* -*- 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 <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 <vcl/inputtypes.hxx> + +#include <salwtype.hxx> + +#include <config_features.h> +#include <vcl/skia/SkiaHelper.hxx> +#include <config_skia.h> +#if HAVE_FEATURE_SKIA +#include <skia/x11/gdiimpl.hxx> +#endif + +// 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( SAL_DATA_UNX, pInstance ); + + 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"); +#if HAVE_FEATURE_SKIA + X11SkiaSalGraphicsImpl::prepareSkia(); +#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<vcl::BackendCapabilities> X11SalInstance::GetBackendCapabilities() +{ + auto pBackendCapabilities = SalInstance::GetBackendCapabilities(); +#if HAVE_FEATURE_SKIA +#if SKIA_USE_BITMAP32 + if( SkiaHelper::isVCLSkiaEnabled()) + pBackendCapabilities->mbSupportsBitmap32 = true; +#endif +#endif + return pBackendCapabilities; +} + +/* 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..30ff437a2 --- /dev/null +++ b/vcl/unx/generic/app/sm.cxx @@ -0,0 +1,858 @@ +/* -*- 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() ); + OStringBuffer aRestartOption; + aRestartOption.append("--session="); + aRestartOption.append(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 ) + { + 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 ) + { + 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.copy(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..39aaf0073 --- /dev/null +++ b/vcl/unx/generic/app/wmadaptor.cxx @@ -0,0 +1,2308 @@ +/* -*- 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 <i18nlangtag/languagetag.hxx> +#include <rtl/locale.h> + +#include <osl/thread.h> +#include <osl/process.h> +#include <sal/macros.h> +#include <sal/log.hxx> +#include <configsettings.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 shade( X11SalFrame* pFrame, bool bToShaded ) 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, 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 shade( X11SalFrame* pFrame, bool bToShaded ) 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 + */ +static 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_SHADED", WMAdaptor::NET_WM_STATE_SHADED }, + { "_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 + */ + +static 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() ) + { + 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 ) + { + 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 ); + 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 ] ) + { + ::Window aWMChild = None; + 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 + ) + { + aWMChild = *reinterpret_cast< ::Window* >(pProperty); + XFree( pProperty ); + pProperty = nullptr; + ::Window aCheckWindow = None; + 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(); + + 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 ] ) + { + ::Window aWMChild = None; + 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 + ) + { + aWMChild = *reinterpret_cast< ::Window* >(pProperty); + XFree( pProperty ); + pProperty = nullptr; + ::Window aCheckWindow = None; + 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(); + 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 = aMetaVersion.getToken(0, '.', nIdx).toInt32(); + nVersionMinor = aMetaVersion.getToken(0, '.', nIdx).toInt32(); + } + 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( OUString())); + 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 ] ) + { + 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->mbShaded && m_aWMAtoms[ NET_WM_STATE_SHADED ] ) + aStateAtoms[ nStateAtoms++ ] = m_aWMAtoms[ NET_WM_STATE_SHADED ]; + 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 ) ) + { + /* + * for maximizing use NorthWestGravity (including decoration) + */ + XSizeHints hints; + 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_ != SHOWSTATE_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 ] ) + { + sal_uInt32 nWinWMState = 0; + + if( pFrame->mbMaximizedVert ) + nWinWMState |= 1 << 2; + if( pFrame->mbMaximizedHorz ) + nWinWMState |= 1 << 3; + if( pFrame->mbShaded ) + nWinWMState |= 1 << 5; + + 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 ) ) + { + /* + * for maximizing use NorthWestGravity (including decoration) + */ + XSizeHints hints; + 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_ != SHOWSTATE_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; + 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.IsInside( 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 ] ) + { + 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 ] ) + { + 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() + ) + { + ::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; + pFrame->mbShaded = false; + + if( pEvent->state == PropertyNewValue ) + { + Atom nType, *pStates; + int nFormat; + unsigned long nItems, nBytesLeft; + unsigned char* pData = nullptr; + 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; + else if( pStates[i] == m_aWMAtoms[ NET_WM_STATE_SHADED ] && m_aWMAtoms[ NET_WM_STATE_SHADED ] ) + pFrame->mbShaded = 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; + pFrame->mbShaded = 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; + if( nWinState & (1<<5) ) + pFrame->mbShaded = 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::shade + */ +void WMAdaptor::shade( X11SalFrame*, bool /*bToShaded*/ ) const +{ +} + +/* + * NetWMAdaptor::shade + */ +void NetWMAdaptor::shade( X11SalFrame* pFrame, bool bToShaded ) const +{ + if( m_aWMAtoms[ NET_WM_STATE ] + && m_aWMAtoms[ NET_WM_STATE_SHADED ] + && ( pFrame->nStyle_ & ~SalFrameStyleFlags::DEFAULT ) + ) + { + pFrame->mbShaded = bToShaded; + 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] = bToShaded ? 1 : 0; + aEvent.xclient.data.l[1] = m_aWMAtoms[ NET_WM_STATE_SHADED ]; + 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 ); + } + } +} + +/* + * GnomeWMAdaptor::shade + */ +void GnomeWMAdaptor::shade( X11SalFrame* pFrame, bool bToShaded ) const +{ + if( m_aWMAtoms[ WIN_STATE ] ) + { + pFrame->mbShaded = bToShaded; + 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<<5); + aEvent.xclient.data.l[1] = bToShaded ? (1<<5) : 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 + setGnomeWMState( pFrame ); + } +} + +/* + * 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.IsInside( 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*, long ) const +{ +} + +/* + * NetWMAdaptor::setUserTime + */ +void NetWMAdaptor::setUserTime( X11SalFrame* i_pFrame, 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] ) + { + long nPID = static_cast<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 ] ) + { + 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: */ |