diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:06:44 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:06:44 +0000 |
commit | ed5640d8b587fbcfed7dd7967f3de04b37a76f26 (patch) | |
tree | 7a5f7c6c9d02226d7471cb3cc8fbbf631b415303 /vcl/win/app | |
parent | Initial commit. (diff) | |
download | libreoffice-ed5640d8b587fbcfed7dd7967f3de04b37a76f26.tar.xz libreoffice-ed5640d8b587fbcfed7dd7967f3de04b37a76f26.zip |
Adding upstream version 4:7.4.7.upstream/4%7.4.7upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'vcl/win/app')
-rw-r--r-- | vcl/win/app/fileregistration.cxx | 186 | ||||
-rw-r--r-- | vcl/win/app/saldata.cxx | 78 | ||||
-rw-r--r-- | vcl/win/app/salinfo.cxx | 176 | ||||
-rw-r--r-- | vcl/win/app/salinst.cxx | 1008 | ||||
-rw-r--r-- | vcl/win/app/salshl.cxx | 112 | ||||
-rw-r--r-- | vcl/win/app/saltimer.cxx | 201 |
6 files changed, 1761 insertions, 0 deletions
diff --git a/vcl/win/app/fileregistration.cxx b/vcl/win/app/fileregistration.cxx new file mode 100644 index 000000000..bd31c4acd --- /dev/null +++ b/vcl/win/app/fileregistration.cxx @@ -0,0 +1,186 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * 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/. + */ + +#if !defined(NTDDI_VERSION) || NTDDI_VERSION < NTDDI_WIN8 +#define NTDDI_VERSION NTDDI_WIN8 // needed for IApplicationActivationManager +#endif + +#include <sal/config.h> + +#include <comphelper/scopeguard.hxx> +#include <o3tl/char16_t2wchar_t.hxx> +#include <officecfg/Office/Common.hxx> +#include <unotools/resmgr.hxx> +#include <vcl/abstdlg.hxx> +#include <vcl/fileregistration.hxx> + +#include <strings.hrc> +#include <svdata.hxx> + +#include <utility> + +#include <prewin.h> +#include <Shobjidl.h> +#include <systools/win32/comtools.hxx> +#include <versionhelpers.h> +#include <postwin.h> + +#define MAX_LONG_PATH 32767 + +namespace vcl::fileregistration +{ +static void LaunchModernSettingsDialogDefaultApps() +{ + sal::systools::COMReference<IApplicationActivationManager> pIf( + CLSID_ApplicationActivationManager, nullptr, CLSCTX_INPROC_SERVER); + + DWORD pid; + HRESULT hr = pIf->ActivateApplication(L"windows.immersivecontrolpanel_cw5n1h2txyewy" + L"!microsoft.windows.immersivecontrolpanel", + L"page=SettingsPageAppsDefaults", AO_NONE, &pid); + if (SUCCEEDED(hr)) + { + // Do not check error because we could at least open + // the "Default apps" setting. + pIf->ActivateApplication(L"windows.immersivecontrolpanel_cw5n1h2txyewy" + L"!microsoft.windows.immersivecontrolpanel", + L"page=SettingsPageAppsDefaults" + L"&target=SettingsPageAppsDefaultsDefaultAppsListView", + AO_NONE, &pid); + } +} + +static HRESULT +IsPathDefaultForClass(sal::systools::COMReference<IApplicationAssociationRegistration>& pAAR, + LPCWSTR aClassName, LPCWSTR progID) +{ + // Make sure the Prog ID matches what we have + LPWSTR registeredApp; + HRESULT hr + = pAAR->QueryCurrentDefault(aClassName, AT_FILEEXTENSION, AL_EFFECTIVE, ®isteredApp); + if (FAILED(hr)) + { + return hr; + } + + if (!wcsnicmp(registeredApp, progID, wcslen(progID))) + hr = S_OK; + else + hr = S_FALSE; + + CoTaskMemFree(registeredApp); + + return hr; +} + +static bool IsDefaultAppInstalledInReg() +{ + const wchar_t* keyPath = L"SOFTWARE\\LibreOffice\\UNO\\InstallPath"; + + WCHAR szRegPath[MAX_LONG_PATH]; + DWORD cbData = static_cast<DWORD>(MAX_LONG_PATH * sizeof(WCHAR)); + auto rc = RegGetValueW(HKEY_LOCAL_MACHINE, keyPath, nullptr, RRF_RT_REG_SZ, nullptr, + static_cast<PVOID>(szRegPath), &cbData); + if (rc != ERROR_SUCCESS) + return false; + + WCHAR szProcPath[MAX_LONG_PATH]; + if (!GetModuleFileNameW(nullptr, szProcPath, MAX_LONG_PATH)) + return false; + + WCHAR szFullProcPath[MAX_LONG_PATH]; + if (!GetFullPathNameW(szProcPath, MAX_LONG_PATH, szFullProcPath, nullptr)) + return false; + + if (!GetLongPathNameW(szFullProcPath, szFullProcPath, MAX_LONG_PATH)) + return false; + + if (!GetLongPathNameW(szRegPath, szRegPath, MAX_LONG_PATH)) + return false; + + if (wcslen(szRegPath) > 0 && wcsstr(szFullProcPath, szRegPath) != nullptr) + return true; + + return false; +} + +void LaunchRegistrationUI() +{ + try + { + sal::systools::CoInitializeGuard aGuard(COINIT_APARTMENTTHREADED); + if (IsWindows10OrGreater()) + { + LaunchModernSettingsDialogDefaultApps(); + } + else + { + sal::systools::COMReference<IApplicationAssociationRegistrationUI> pIf( + CLSID_ApplicationAssociationRegistrationUI, nullptr, CLSCTX_INPROC_SERVER); + + // LaunchAdvancedAssociationUI only works for applications registered under + // Software\RegisteredApplications. See scp2/source/ooo/registryitem_ooo.scp + const OUString expanded = Translate::ExpandVariables("%PRODUCTNAME %PRODUCTVERSION"); + pIf->LaunchAdvancedAssociationUI(o3tl::toW(expanded.getStr())); + } + } + catch (...) + { + // Just ignore any error here: this is not something we need to make sure to succeed + } +} + +void CheckFileExtRegistration(weld::Window* pDialogParent) +{ + if (!officecfg::Office::Common::Misc::PerformFileExtCheck::get()) + return; + + if (!IsDefaultAppInstalledInReg()) + return; + + sal::systools::CoInitializeGuard aGuard(COINIT_APARTMENTTHREADED, false, + sal::systools::CoInitializeGuard::WhenFailed::NoThrow); + sal::systools::COMReference<IApplicationAssociationRegistration> pAAR; + try + { + pAAR.CoCreateInstance(CLSID_ApplicationAssociationRegistration, nullptr, + CLSCTX_INPROC_SERVER); + } + catch (...) + { + // Just return on any error here: this is not something we need to make sure to succeed + return; + } + + static const std::pair<LPCWSTR, LPCWSTR> formats[] = { + { L".odp", L"LibreOffice.ImpressDocument.1" }, + { L".odt", L"LibreOffice.WriterDocument.1" }, + { L".ods", L"LibreOffice.CalcDocument.1" }, + }; + OUString aNonDefaults; + + for (const auto & [ szExt, szProgId ] : formats) + { + if (IsPathDefaultForClass(pAAR, szExt, szProgId) == S_FALSE) + aNonDefaults += OUString::Concat(o3tl::toU(szExt)) + "\n"; + } + + if (!aNonDefaults.isEmpty()) + { + OUString aMsg(VclResId(STR_FILEEXT_NONDEFAULT_ASK_MSG).replaceFirst("$1", aNonDefaults)); + + VclAbstractDialogFactory* pFact = VclAbstractDialogFactory::Create(); + ScopedVclPtr<VclAbstractDialog> pDlg(pFact->CreateFileExtCheckDialog( + pDialogParent, VclResId(STR_FILEEXT_NONDEFAULT_ASK_TITLE), aMsg)); + pDlg->Execute(); + } +} +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/vcl/win/app/saldata.cxx b/vcl/win/app/saldata.cxx new file mode 100644 index 000000000..31fa66163 --- /dev/null +++ b/vcl/win/app/saldata.cxx @@ -0,0 +1,78 @@ +/* -*- 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 <svsys.h> +#include <rtl/tencinfo.h> +#include <vcl/svapp.hxx> + +#include <win/saldata.hxx> + +rtl_TextEncoding ImplSalGetSystemEncoding() +{ + static const UINT nOldAnsiCodePage = 0; + static rtl_TextEncoding eEncoding = RTL_TEXTENCODING_MS_1252; + + UINT nAnsiCodePage = GetACP(); + if ( nAnsiCodePage != nOldAnsiCodePage ) + { + rtl_TextEncoding nEnc + = rtl_getTextEncodingFromWindowsCodePage(nAnsiCodePage); + if (nEnc != RTL_TEXTENCODING_DONTKNOW) + eEncoding = nEnc; + } + + return eEncoding; +} + +OUString ImplSalGetUniString(const char* pStr, sal_Int32 const nLen) +{ + return OUString( pStr, (-1 == nLen) ? strlen(pStr) : nLen, + ImplSalGetSystemEncoding(), + RTL_TEXTTOUNICODE_FLAGS_UNDEFINED_DEFAULT | + RTL_TEXTTOUNICODE_FLAGS_MBUNDEFINED_DEFAULT | + RTL_TEXTTOUNICODE_FLAGS_INVALID_DEFAULT ); +} + +int ImplSalWICompareAscii( const wchar_t* pStr1, const char* pStr2 ) +{ + int nRet; + char c2; + do + { + // change to LowerCase if the char is between 'A' and 'Z' + wchar_t c1 = *pStr1; + c2 = *pStr2; + if ( (c1 >= 65) && (c1 <= 90) ) + c1 += 32; + if ( (c2 >= 65) && (c2 <= 90) ) + c2 += 32; + nRet = static_cast<sal_Int32>(c1)- static_cast<sal_Int32>(static_cast<unsigned char>(c2)); + if ( nRet != 0 ) + break; + + pStr1++; + pStr2++; + } + while ( c2 ); + + return nRet; +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/win/app/salinfo.cxx b/vcl/win/app/salinfo.cxx new file mode 100644 index 000000000..2787797a6 --- /dev/null +++ b/vcl/win/app/salinfo.cxx @@ -0,0 +1,176 @@ +/* -*- 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 <svsys.h> +#include <o3tl/char16_t2wchar_t.hxx> + +#include <sal/log.hxx> +#include <vcl/window.hxx> + +#include <win/salsys.h> +#include <win/salframe.h> +#include <win/salinst.h> +#include <win/saldata.hxx> + +#include <svdata.hxx> + +#include <unordered_map> + +SalSystem* WinSalInstance::CreateSalSystem() +{ + return new WinSalSystem(); +} + +WinSalSystem::~WinSalSystem() +{ +} + +static BOOL CALLBACK ImplEnumMonitorProc( HMONITOR hMonitor, + HDC hDC, + LPRECT lpRect, + LPARAM dwData ) +{ + WinSalSystem* pSys = reinterpret_cast<WinSalSystem*>(dwData); + return pSys->handleMonitorCallback( reinterpret_cast<sal_IntPtr>(hMonitor), + reinterpret_cast<sal_IntPtr>(hDC), + reinterpret_cast<sal_IntPtr>(lpRect) ); +} + +bool WinSalSystem::handleMonitorCallback( sal_IntPtr hMonitor, sal_IntPtr, sal_IntPtr ) +{ + MONITORINFOEXW aInfo; + aInfo.cbSize = sizeof( aInfo ); + if( GetMonitorInfoW( reinterpret_cast<HMONITOR>(hMonitor), &aInfo ) ) + { + aInfo.szDevice[CCHDEVICENAME-1] = 0; + OUString aDeviceName( o3tl::toU(aInfo.szDevice) ); + std::map< OUString, unsigned int >::const_iterator it = + m_aDeviceNameToMonitor.find( aDeviceName ); + if( it != m_aDeviceNameToMonitor.end() ) + { + DisplayMonitor& rMon( m_aMonitors[ it->second ] ); + rMon.m_aArea = tools::Rectangle( Point( aInfo.rcMonitor.left, + aInfo.rcMonitor.top ), + Size( aInfo.rcMonitor.right - aInfo.rcMonitor.left, + aInfo.rcMonitor.bottom - aInfo.rcMonitor.top ) ); + if( (aInfo.dwFlags & MONITORINFOF_PRIMARY) != 0 ) + m_nPrimary = it->second; + } + } + return true; +} + +void WinSalSystem::clearMonitors() +{ + m_aMonitors.clear(); + m_nPrimary = 0; +} + +bool WinSalSystem::initMonitors() +{ + if( m_aMonitors.size() > 0 ) + return true; + + int nMonitors = GetSystemMetrics( SM_CMONITORS ); + if( nMonitors == 1 ) + { + int w = GetSystemMetrics( SM_CXSCREEN ); + int h = GetSystemMetrics( SM_CYSCREEN ); + m_aMonitors.push_back( DisplayMonitor( OUString(), + tools::Rectangle( Point(), Size( w, h ) ) ) ); + m_aDeviceNameToMonitor[ OUString() ] = 0; + m_nPrimary = 0; + } + else + { + DISPLAY_DEVICEW aDev; + aDev.cb = sizeof( aDev ); + DWORD nDevice = 0; + std::unordered_map< OUString, int > aDeviceStringCount; + while( EnumDisplayDevicesW( nullptr, nDevice++, &aDev, 0 ) ) + { + if( (aDev.StateFlags & DISPLAY_DEVICE_ACTIVE) + && !(aDev.StateFlags & DISPLAY_DEVICE_MIRRORING_DRIVER) ) // sort out non/disabled monitors + { + aDev.DeviceName[31] = 0; + aDev.DeviceString[127] = 0; + OUString aDeviceName( o3tl::toU(aDev.DeviceName) ); + OUString aDeviceString( o3tl::toU(aDev.DeviceString) ); + aDeviceStringCount[ aDeviceString ]++; + m_aDeviceNameToMonitor[ aDeviceName ] = m_aMonitors.size(); + m_aMonitors.push_back( DisplayMonitor( aDeviceString, + tools::Rectangle() ) ); + } + } + HDC aDesktopRC = GetDC( nullptr ); + EnumDisplayMonitors( aDesktopRC, nullptr, ImplEnumMonitorProc, reinterpret_cast<LPARAM>(this) ); + + // append monitor numbers to name strings + std::unordered_map< OUString, int > aDevCount( aDeviceStringCount ); + unsigned int nMonitorCount = m_aMonitors.size(); + for( unsigned int i = 0; i < nMonitorCount; i++ ) + { + const OUString& rDev( m_aMonitors[i].m_aName ); + if( aDeviceStringCount[ rDev ] > 1 ) + { + int nInstance = aDeviceStringCount[ rDev ] - (-- aDevCount[ rDev ] ); + m_aMonitors[ i ].m_aName = rDev + " (" + OUString::number( nInstance ) + ")"; + } + } + } + + return m_aMonitors.size() > 0; +} + +unsigned int WinSalSystem::GetDisplayScreenCount() +{ + initMonitors(); + return m_aMonitors.size(); +} + +unsigned int WinSalSystem::GetDisplayBuiltInScreen() +{ + initMonitors(); + return m_nPrimary; +} + +tools::Rectangle WinSalSystem::GetDisplayScreenPosSizePixel( unsigned int nScreen ) +{ + initMonitors(); + if (nScreen >= m_aMonitors.size()) + { + SAL_WARN("vcl", "Requested screen size/pos for screen #" + << nScreen << ", but only " << m_aMonitors.size() << " screens found."); + assert(false); + return tools::Rectangle(); + } + return m_aMonitors[nScreen].m_aArea; +} + +int WinSalSystem::ShowNativeMessageBox(const OUString& rTitle, const OUString& rMessage) +{ + ImplHideSplash(); + return MessageBoxW( + nullptr, + o3tl::toW(rMessage.getStr()), + o3tl::toW(rTitle.getStr()), + MB_TASKMODAL | MB_SETFOREGROUND | MB_ICONWARNING | MB_DEFBUTTON1); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/win/app/salinst.cxx b/vcl/win/app/salinst.cxx new file mode 100644 index 000000000..f62d8ef71 --- /dev/null +++ b/vcl/win/app/salinst.cxx @@ -0,0 +1,1008 @@ +/* -*- 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 <svsys.h> +#include <process.h> + +#include <osl/conditn.hxx> +#include <osl/file.hxx> +#include <rtl/ustrbuf.hxx> +#include <sal/log.hxx> +#include <tools/debug.hxx> +#include <tools/time.hxx> +#include <comphelper/processfactory.hxx> +#include <comphelper/solarmutex.hxx> +#include <comphelper/windowserrorstring.hxx> +#include <com/sun/star/uno/Reference.h> +#include <o3tl/char16_t2wchar_t.hxx> + +#include <dndhelper.hxx> +#include <vcl/inputtypes.hxx> +#include <vcl/opengl/OpenGLContext.hxx> +#include <vcl/sysdata.hxx> +#include <vcl/timer.hxx> +#include <vclpluginapi.h> + +#include <win/dnd_source.hxx> +#include <win/dnd_target.hxx> +#include <win/wincomp.hxx> +#include <win/salids.hrc> +#include <win/saldata.hxx> +#include <win/salinst.h> +#include <win/salframe.h> +#include <win/salobj.h> +#include <win/saltimer.h> +#include <win/salbmp.h> +#include <win/winlayout.hxx> + +#include <config_features.h> +#include <vcl/skia/SkiaHelper.hxx> +#if HAVE_FEATURE_SKIA +#include <config_skia.h> +#include <skia/salbmp.hxx> +#include <skia/win/gdiimpl.hxx> +#endif + +#include <salsys.hxx> + +#include <desktop/crashreport.hxx> + +#if defined _MSC_VER +#ifndef min +#define min(a,b) (((a) < (b)) ? (a) : (b)) +#endif +#ifndef max +#define max(a,b) (((a) > (b)) ? (a) : (b)) +#endif +#endif + +#include <prewin.h> + +#include <gdiplus.h> +#include <shlobj.h> + +#include <postwin.h> + +static LRESULT CALLBACK SalComWndProcW( HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam ); + +class SalYieldMutex : public comphelper::SolarMutex +{ +public: // for ImplSalYield() and ImplSalYieldMutexAcquireWithWait() + osl::Condition m_condition; /// for MsgWaitForMultipleObjects() + +protected: + virtual void doAcquire( sal_uInt32 nLockCount ) override; + virtual sal_uInt32 doRelease( bool bUnlockAll ) override; + + static void BeforeReleaseHandler(); + +public: + explicit SalYieldMutex(); + + virtual bool IsCurrentThread() const override; + virtual bool tryToAcquire() override; +}; + +SalYieldMutex::SalYieldMutex() +{ + SetBeforeReleaseHandler( &SalYieldMutex::BeforeReleaseHandler ); +} + +void SalYieldMutex::BeforeReleaseHandler() +{ + OpenGLContext::prepareForYield(); + + if ( GetSalData()->mnAppThreadId != GetCurrentThreadId() ) + { + // If we don't call these message, the Output from the + // Java clients doesn't come in the right order + GdiFlush(); + } +} + +/// note: while VCL is fully up and running (other threads started and +/// before shutdown), the main thread must acquire SolarMutex only via +/// this function to avoid deadlock +void SalYieldMutex::doAcquire( sal_uInt32 nLockCount ) +{ + WinSalInstance* pInst = GetSalData()->mpInstance; + if ( pInst && pInst->IsMainThread() ) + { + if ( pInst->m_nNoYieldLock ) + return; + // tdf#96887 If this is the main thread, then we must wait for two things: + // - the yield mutex being unlocked + // - SendMessage() being triggered + // This can nicely be done using MsgWaitForMultipleObjects, which is called in + // m_condition.wait(). The 2nd one is + // needed because if we don't reschedule, then we create deadlocks if a + // Window's create/destroy is called via SendMessage() from another thread. + // Have a look at the osl_waitCondition implementation for more info. + do { + // Calling Condition::reset frequently turns out to be a little expensive, + // and the vast majority of the time there is no contention, so first + // try just acquiring the mutex. + if (m_aMutex.tryToAcquire()) + break; + // reset condition *before* acquiring! + m_condition.reset(); + if (m_aMutex.tryToAcquire()) + break; + // wait for SalYieldMutex::release() to set the condition + osl::Condition::Result res = m_condition.wait(); + assert(osl::Condition::Result::result_ok == res); + (void) res; + } + while ( true ); + } + else + m_aMutex.acquire(); + ++m_nCount; + --nLockCount; + + comphelper::SolarMutex::doAcquire( nLockCount ); +} + +sal_uInt32 SalYieldMutex::doRelease( const bool bUnlockAll ) +{ + WinSalInstance* pInst = GetSalData()->mpInstance; + if ( pInst && pInst->m_nNoYieldLock && pInst->IsMainThread() ) + return 1; + + sal_uInt32 nCount = comphelper::SolarMutex::doRelease( bUnlockAll ); + // wake up ImplSalYieldMutexAcquireWithWait() after release + if ( 0 == m_nCount ) + m_condition.set(); + return nCount; +} + +bool SalYieldMutex::tryToAcquire() +{ + WinSalInstance* pInst = GetSalData()->mpInstance; + if ( pInst ) + { + if ( pInst->m_nNoYieldLock && pInst->IsMainThread() ) + return true; + else + return comphelper::SolarMutex::tryToAcquire(); + } + else + return false; +} + +void ImplSalYieldMutexAcquireWithWait( sal_uInt32 nCount ) +{ + WinSalInstance* pInst = GetSalData()->mpInstance; + if ( pInst ) + pInst->GetYieldMutex()->acquire( nCount ); +} + +bool ImplSalYieldMutexTryToAcquire() +{ + WinSalInstance* pInst = GetSalData()->mpInstance; + return pInst && pInst->GetYieldMutex()->tryToAcquire(); +} + +void ImplSalYieldMutexRelease() +{ + WinSalInstance* pInst = GetSalData()->mpInstance; + if ( pInst ) + { + GdiFlush(); + pInst->GetYieldMutex()->release(); + } +} + +bool SalYieldMutex::IsCurrentThread() const +{ + if ( !GetSalData()->mpInstance->m_nNoYieldLock ) + return SolarMutex::IsCurrentThread(); + else + return GetSalData()->mpInstance->IsMainThread(); +} + +void SalData::initKeyCodeMap() +{ + UINT nKey; + #define initKey( a, b )\ + nKey = LOWORD( VkKeyScanW( a ) );\ + if( nKey < 0xffff )\ + maVKMap[ nKey ] = b; + + maVKMap.clear(); + + initKey( L'+', KEY_ADD ); + initKey( L'-', KEY_SUBTRACT ); + initKey( L'*', KEY_MULTIPLY ); + initKey( L'/', KEY_DIVIDE ); + initKey( L'.', KEY_POINT ); + initKey( L',', KEY_COMMA ); + initKey( L'<', KEY_LESS ); + initKey( L'>', KEY_GREATER ); + initKey( L'=', KEY_EQUAL ); + initKey( L'~', KEY_TILDE ); + initKey( L'`', KEY_QUOTELEFT ); + initKey( L'[', KEY_BRACKETLEFT ); + initKey( L']', KEY_BRACKETRIGHT ); + initKey( L';', KEY_SEMICOLON ); + initKey( L'\'', KEY_QUOTERIGHT ); +} + +// SalData + +SalData::SalData() + : sal::systools::CoInitializeGuard(COINIT_APARTMENTTHREADED, false, + sal::systools::CoInitializeGuard::WhenFailed::NoThrow) + // put main thread in Single Threaded Apartment (STA) +{ + mhInst = nullptr; // default instance handle + mnCmdShow = 0; // default frame show style + mhDitherPal = nullptr; // dither palette + mhDitherDIB = nullptr; // dither memory handle + mpDitherDIB = nullptr; // dither memory + mpDitherDIBData = nullptr; // beginning of DIB data + mpDitherDiff = nullptr; // Dither mapping table + mpDitherLow = nullptr; // Dither mapping table + mpDitherHigh = nullptr; // Dither mapping table + mhSalObjMsgHook = nullptr; // hook to get interesting msg for SalObject + mhWantLeaveMsg = nullptr; // window handle, that want a MOUSELEAVE message + mpMouseLeaveTimer = nullptr; // Timer for MouseLeave Test + mpInstance = nullptr; // pointer of first instance + mpFirstFrame = nullptr; // pointer of first frame + mpFirstObject = nullptr; // pointer of first object window + mpFirstVD = nullptr; // first VirDev + mpFirstPrinter = nullptr; // first printing printer + mpHDCCache = nullptr; // Cache for three DC's + mh50Bmp = nullptr; // 50% Bitmap + mh50Brush = nullptr; // 50% Brush + int i; + for(i=0; i<MAX_STOCKPEN; i++) + { + maStockPenColorAry[i] = 0; + mhStockPenAry[i] = nullptr; + } + for(i=0; i<MAX_STOCKBRUSH; i++) + { + maStockBrushColorAry[i] = 0; + mhStockBrushAry[i] = nullptr; + } + mnStockPenCount = 0; // count of static pens + mnStockBrushCount = 0; // count of static brushes + mnSalObjWantKeyEvt = 0; // KeyEvent for the SalObj hook + mnCacheDCInUse = 0; // count of CacheDC in use + mbObjClassInit = false; // is SALOBJECTCLASS initialised + mbInPalChange = false; // is in WM_QUERYNEWPALETTE + mnAppThreadId = 0; // Id from Application-Thread + mbScrSvrEnabled = FALSE; // ScreenSaver enabled + mpFirstIcon = nullptr; // icon cache, points to first icon, NULL if none + mpSharedTempFontItem = nullptr; + mpOtherTempFontItem = nullptr; + mbThemeChanged = false; // true if visual theme was changed: throw away theme handles + mbThemeMenuSupport = false; + + // init with NULL + gdiplusToken = 0; + + initKeyCodeMap(); + + SetSalData( this ); + initNWF(); + + static Gdiplus::GdiplusStartupInput gdiplusStartupInput; + Gdiplus::GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, nullptr); +} + +SalData::~SalData() +{ + deInitNWF(); + SetSalData( nullptr ); + + if (gdiplusToken) + Gdiplus::GdiplusShutdown(gdiplusToken); +} + +bool OSSupportsDarkMode() +{ + bool bRet = false; + if (HMODULE h_ntdll = GetModuleHandleW(L"ntdll.dll")) + { + typedef LONG(WINAPI* RtlGetVersion_t)(PRTL_OSVERSIONINFOW); + if (auto RtlGetVersion + = reinterpret_cast<RtlGetVersion_t>(GetProcAddress(h_ntdll, "RtlGetVersion"))) + { + RTL_OSVERSIONINFOW vi2{}; + vi2.dwOSVersionInfoSize = sizeof(vi2); + if (RtlGetVersion(&vi2) == 0) + { + if (vi2.dwMajorVersion > 10) + bRet = true; + else if (vi2.dwMajorVersion == 10) + { + if (vi2.dwMinorVersion > 0) + bRet = true; + else if (vi2.dwBuildNumber >= 18362) + bRet = true; + } + } + } + } + return bRet; +} + +namespace { + +enum PreferredAppMode +{ + Default, + AllowDark, + ForceDark, + ForceLight, + Max +}; + +} + +extern "C" { +VCLPLUG_WIN_PUBLIC SalInstance* create_SalInstance() +{ + SalData* pSalData = new SalData(); + + STARTUPINFOW aSI; + aSI.cb = sizeof( aSI ); + GetStartupInfoW( &aSI ); + pSalData->mhInst = GetModuleHandleW( nullptr ); + pSalData->mnCmdShow = aSI.wShowWindow; + + pSalData->mnAppThreadId = GetCurrentThreadId(); + + static bool bSetAllowDarkMode = OSSupportsDarkMode(); // too early to additionally check LibreOffice's config + if (bSetAllowDarkMode) + { + typedef PreferredAppMode(WINAPI* SetPreferredAppMode_t)(PreferredAppMode); + if (HINSTANCE hUxthemeLib = LoadLibraryExW(L"uxtheme.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32)) + { + if (auto SetPreferredAppMode = reinterpret_cast<SetPreferredAppMode_t>(GetProcAddress(hUxthemeLib, MAKEINTRESOURCEA(135)))) + SetPreferredAppMode(AllowDark); + FreeLibrary(hUxthemeLib); + } + } + + // register frame class + WNDCLASSEXW aWndClassEx; + aWndClassEx.cbSize = sizeof( aWndClassEx ); + aWndClassEx.style = CS_OWNDC; + aWndClassEx.lpfnWndProc = SalFrameWndProcW; + aWndClassEx.cbClsExtra = 0; + aWndClassEx.cbWndExtra = SAL_FRAME_WNDEXTRA; + aWndClassEx.hInstance = pSalData->mhInst; + aWndClassEx.hCursor = nullptr; + aWndClassEx.hbrBackground = nullptr; + aWndClassEx.lpszMenuName = nullptr; + aWndClassEx.lpszClassName = SAL_FRAME_CLASSNAMEW; + ImplLoadSalIcon( SAL_RESID_ICON_DEFAULT, aWndClassEx.hIcon, aWndClassEx.hIconSm ); + if ( !RegisterClassExW( &aWndClassEx ) ) + return nullptr; + + aWndClassEx.hIcon = nullptr; + aWndClassEx.hIconSm = nullptr; + aWndClassEx.style |= CS_SAVEBITS; + aWndClassEx.lpszClassName = SAL_SUBFRAME_CLASSNAMEW; + if ( !RegisterClassExW( &aWndClassEx ) ) + return nullptr; + + // shadow effect for popups on XP + aWndClassEx.style |= CS_DROPSHADOW; + aWndClassEx.lpszClassName = SAL_TMPSUBFRAME_CLASSNAMEW; + if ( !RegisterClassExW( &aWndClassEx ) ) + return nullptr; + + aWndClassEx.style = 0; + aWndClassEx.lpfnWndProc = SalComWndProcW; + aWndClassEx.cbWndExtra = 0; + aWndClassEx.lpszClassName = SAL_COM_CLASSNAMEW; + if ( !RegisterClassExW( &aWndClassEx ) ) + return nullptr; + + HWND hComWnd = CreateWindowExW( WS_EX_TOOLWINDOW, SAL_COM_CLASSNAMEW, + L"", WS_POPUP, 0, 0, 0, 0, nullptr, nullptr, + pSalData->mhInst, nullptr ); + if ( !hComWnd ) + return nullptr; + + WinSalInstance* pInst = new WinSalInstance; + + // init instance (only one instance in this version !!!) + pSalData->mpInstance = pInst; + pInst->mhInst = pSalData->mhInst; + pInst->mhComWnd = hComWnd; + + // init static GDI Data + ImplInitSalGDI(); + + return pInst; +} +} + +WinSalInstance::WinSalInstance() + : SalInstance(std::make_unique<SalYieldMutex>()) + , mhInst( nullptr ) + , mhComWnd( nullptr ) + , m_nNoYieldLock( 0 ) +{ + ImplSVData* pSVData = ImplGetSVData(); + pSVData->maAppData.mxToolkitName = OUString("win"); + m_bSupportsOpenGL = true; +#if HAVE_FEATURE_SKIA + WinSkiaSalGraphicsImpl::prepareSkia(); +#if SKIA_USE_BITMAP32 + if (SkiaHelper::isVCLSkiaEnabled()) + m_bSupportsBitmap32 = true; +#endif +#endif +} + +WinSalInstance::~WinSalInstance() +{ + ImplFreeSalGDI(); + DestroyWindow( mhComWnd ); +#if HAVE_FEATURE_SKIA + SkiaHelper::cleanup(); +#endif +} + +static LRESULT ImplSalDispatchMessage( const MSG* pMsg ) +{ + SalData* pSalData = GetSalData(); + if ( pSalData->mpFirstObject && ImplSalPreDispatchMsg( pMsg ) ) + return 0; + LRESULT lResult = DispatchMessageW( pMsg ); + if ( pSalData->mpFirstObject ) + ImplSalPostDispatchMsg( pMsg ); + return lResult; +} + +// probably can't be static, because of SalTimer friend? (static gives C4211) +bool ImplSalYield(const bool bWait, const bool bHandleAllCurrentEvents) +{ + // used to abort further message processing on tick count wraps + static sal_uInt32 nLastTicks = 0; + + // we should never yield in m_nNoYieldLock mode! + const bool bNoYieldLock = (GetSalData()->mpInstance->m_nNoYieldLock > 0); + assert(!bNoYieldLock); + if (bNoYieldLock) + return false; + + MSG aMsg; + bool bWasMsg = false, bWasTimeoutMsg = false; + WinSalTimer* pTimer = static_cast<WinSalTimer*>(ImplGetSVData()->maSchedCtx.mpSalTimer); + + sal_uInt32 nCurTicks = GetTickCount(); + + do + { + if (!PeekMessageW(&aMsg, nullptr, 0, 0, PM_REMOVE)) + break; + + bWasMsg = true; + TranslateMessage(&aMsg); + LRESULT nRet = ImplSalDispatchMessage(&aMsg); + + bWasTimeoutMsg |= (SAL_MSG_TIMER_CALLBACK == aMsg.message) && static_cast<bool>(nRet); + + if (!bHandleAllCurrentEvents) + break; + + if ((aMsg.time > nCurTicks) && (nLastTicks <= nCurTicks || aMsg.time < nLastTicks)) + break; + } + while( true ); + + // 0ms timeouts are handled out-of-bounds to prevent busy-locking the + // event loop with timeout messages. + // We ensure we never handle more than one timeout per call. + // This way we'll always process a normal system message. + if ( !bWasTimeoutMsg && pTimer && pTimer->IsDirectTimeout() ) + { + pTimer->ImplHandleElapsedTimer(); + bWasMsg = true; + } + + nLastTicks = nCurTicks; + + if ( bWait && !bWasMsg ) + { + switch (GetMessageW(&aMsg, nullptr, 0, 0)) + { + case -1: + SAL_WARN("vcl.schedule", "GetMessageW failed: " << WindowsErrorString(GetLastError())); + // should we std::abort() / SalAbort here? + break; + case 0: + SAL_INFO("vcl.schedule", "GetMessageW received WM_QUIT while waiting"); + break; + default: + bWasMsg = true; + TranslateMessage(&aMsg); + ImplSalDispatchMessage(&aMsg); + break; + } + } + + // If we enabled ForceRealTimer mode skipping our direct timeout processing, + // mainly because some Windows API call spawns its own nested message loop, + // switch back to our own processing (like after window resize or move) + if ( pTimer ) + pTimer->SetForceRealTimer( false ); + + return bWasMsg; +} + +bool WinSalInstance::IsMainThread() const +{ + const SalData* pSalData = GetSalData(); + return pSalData->mnAppThreadId == GetCurrentThreadId(); +} + +bool WinSalInstance::DoYield(bool bWait, bool bHandleAllCurrentEvents) +{ + bool bDidWork = false; + SolarMutexReleaser aReleaser; + if ( !IsMainThread() ) + { + bDidWork = SendMessageW( mhComWnd, SAL_MSG_THREADYIELD, + WPARAM(false), static_cast<LPARAM>(bHandleAllCurrentEvents) ); + if ( !bDidWork && bWait ) + { + maWaitingYieldCond.reset(); + maWaitingYieldCond.wait(); + bDidWork = true; + } + } + else + { + bDidWork = ImplSalYield( bWait, bHandleAllCurrentEvents ); + if ( bDidWork ) + maWaitingYieldCond.set(); + } + + return bDidWork; +} + +#define CASE_NOYIELDLOCK( salmsg, function ) \ + case salmsg: \ + if (bIsOtherThreadMessage) \ + { \ + ++pInst->m_nNoYieldLock; \ + function; \ + --pInst->m_nNoYieldLock; \ + } \ + else \ + { \ + DBG_TESTSOLARMUTEX(); \ + function; \ + } \ + break; + +#define CASE_NOYIELDLOCK_RESULT( salmsg, function ) \ + case salmsg: \ + if (bIsOtherThreadMessage) \ + { \ + ++pInst->m_nNoYieldLock; \ + nRet = reinterpret_cast<LRESULT>( function ); \ + --pInst->m_nNoYieldLock; \ + } \ + else \ + { \ + DBG_TESTSOLARMUTEX(); \ + nRet = reinterpret_cast<LRESULT>( function ); \ + } \ + break; + +LRESULT CALLBACK SalComWndProc( HWND, UINT nMsg, WPARAM wParam, LPARAM lParam, bool& rDef ) +{ + const bool bIsOtherThreadMessage = InSendMessage(); + LRESULT nRet = 0; + WinSalInstance *pInst = GetSalData()->mpInstance; + WinSalTimer *const pTimer = static_cast<WinSalTimer*>( ImplGetSVData()->maSchedCtx.mpSalTimer ); + + SAL_INFO("vcl.gdi.wndproc", "SalComWndProc(nMsg=" << nMsg << ", wParam=" << wParam + << ", lParam=" << lParam << "); inSendMsg: " << bIsOtherThreadMessage); + + if (ImplGetSVData()->mbDeInit) + { + SAL_WARN("vcl.gdi.wndproc", "ignoring timer event because we are shutting down"); + return 0; + } + + switch ( nMsg ) + { + case SAL_MSG_THREADYIELD: + assert( !static_cast<bool>(wParam) ); + nRet = static_cast<LRESULT>(ImplSalYield( + false, static_cast<bool>( lParam ) )); + break; + + case SAL_MSG_STARTTIMER: + { + auto const nParam = static_cast<sal_uInt64>( lParam ); + sal_uInt64 nTime = tools::Time::GetSystemTicks(); + if ( nTime < nParam ) + nTime = nParam - nTime; + else + nTime = 0; + assert( pTimer != nullptr ); + pTimer->ImplStart( nTime ); + break; + } + + case SAL_MSG_STOPTIMER: + assert( pTimer != nullptr ); + pTimer->ImplStop(); + break; + + CASE_NOYIELDLOCK_RESULT( SAL_MSG_CREATEFRAME, ImplSalCreateFrame( GetSalData()->mpInstance, + reinterpret_cast<HWND>(lParam), static_cast<SalFrameStyleFlags>(wParam)) ) + CASE_NOYIELDLOCK_RESULT( SAL_MSG_RECREATEHWND, ImplSalReCreateHWND( + reinterpret_cast<HWND>(wParam), reinterpret_cast<HWND>(lParam), false) ) + CASE_NOYIELDLOCK_RESULT( SAL_MSG_RECREATECHILDHWND, ImplSalReCreateHWND( + reinterpret_cast<HWND>(wParam), reinterpret_cast<HWND>(lParam), true) ) + CASE_NOYIELDLOCK( SAL_MSG_DESTROYFRAME, delete reinterpret_cast<SalFrame*>(lParam) ) + + case SAL_MSG_DESTROYHWND: + // We only destroy the native window here. We do NOT destroy the SalFrame contained + // in the structure (GetWindowPtr()). + if (DestroyWindow(reinterpret_cast<HWND>(lParam)) == 0) + { + OSL_FAIL("DestroyWindow failed!"); + // Failure: We remove the SalFrame from the window structure. So we avoid that + // the window structure may contain an invalid pointer, once the SalFrame is deleted. + SetWindowPtr(reinterpret_cast<HWND>(lParam), nullptr); + } + break; + + CASE_NOYIELDLOCK_RESULT( SAL_MSG_CREATEOBJECT, ImplSalCreateObject( + GetSalData()->mpInstance, reinterpret_cast<WinSalFrame*>(lParam)) ) + CASE_NOYIELDLOCK( SAL_MSG_DESTROYOBJECT, delete reinterpret_cast<SalObject*>(lParam) ) + CASE_NOYIELDLOCK_RESULT( SAL_MSG_GETCACHEDDC, GetDCEx( + reinterpret_cast<HWND>(wParam), nullptr, DCX_CACHE) ) + CASE_NOYIELDLOCK( SAL_MSG_RELEASEDC, ReleaseDC( + reinterpret_cast<HWND>(wParam), reinterpret_cast<HDC>(lParam)) ) + + case SAL_MSG_TIMER_CALLBACK: + assert( pTimer != nullptr ); + pTimer->ImplHandleTimerEvent( wParam ); + break; + + case WM_TIMER: + assert( pTimer != nullptr ); + pTimer->ImplHandle_WM_TIMER( wParam ); + break; + + case SAL_MSG_FORCE_REAL_TIMER: + assert(pTimer != nullptr); + pTimer->SetForceRealTimer(true); + break; + + case SAL_MSG_DUMMY: + break; + + default: + rDef = true; + break; + } + + return nRet; +} + +#undef CASE_NOYIELDLOCK +#undef CASE_NOYIELDLOCK_RESULT + +LRESULT CALLBACK SalComWndProcW( HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam ) +{ + bool bDef = false; + LRESULT nRet = 0; + __try + { + nRet = SalComWndProc( hWnd, nMsg, wParam, lParam, bDef ); + } + __except(WinSalInstance::WorkaroundExceptionHandlingInUSER32Lib(GetExceptionCode(), GetExceptionInformation())) + { + } + if ( bDef ) + { + if ( !ImplHandleGlobalMsg( hWnd, nMsg, wParam, lParam, nRet ) ) + nRet = DefWindowProcW( hWnd, nMsg, wParam, lParam ); + } + return nRet; +} + +bool WinSalInstance::AnyInput( VclInputFlags nType ) +{ + if ( nType & VclInputFlags::TIMER ) + { + const WinSalTimer* pTimer = static_cast<WinSalTimer*>( ImplGetSVData()->maSchedCtx.mpSalTimer ); + if ( pTimer && pTimer->HasTimerElapsed() ) + return true; + } + + // Note: Do not use PeekMessage(), despite the name it may dispatch events, + // even with PM_NOREMOVE specified, which may lead to unwanted recursion. + + if ( (nType & VCL_INPUT_ANY) == VCL_INPUT_ANY ) + { + // revert bugfix for #108919# which never reported timeouts when called from the timer handler + // which made the application completely unresponsive during background formatting + if ( GetQueueStatus( QS_ALLEVENTS )) + return true; + } + else + { + UINT flags = 0; + + // This code previously considered modifier keys as OTHER, + // but that makes this hard to do without PeekMessage, + // is inconsistent with the X11 backend, and I see no good reason. + if ( nType & VclInputFlags::KEYBOARD ) + flags |= QS_KEY; + + if ( nType & VclInputFlags::MOUSE ) + flags |= QS_MOUSE; + + if ( nType & VclInputFlags::PAINT ) + flags |= QS_PAINT; + + if ( nType & VclInputFlags::TIMER ) + flags |= QS_TIMER; + + if( nType & VclInputFlags::OTHER ) + flags |= QS_ALLEVENTS & ~QS_KEY & ~QS_MOUSE & ~QS_PAINT & ~QS_TIMER; + + if( GetQueueStatus( flags )) + return true; + } + + return false; +} + +SalFrame* WinSalInstance::CreateChildFrame( SystemParentData* pSystemParentData, SalFrameStyleFlags nSalFrameStyle ) +{ + // to switch to Main-Thread + return reinterpret_cast<SalFrame*>(static_cast<sal_IntPtr>(SendMessageW( mhComWnd, SAL_MSG_CREATEFRAME, static_cast<WPARAM>(nSalFrameStyle), reinterpret_cast<LPARAM>(pSystemParentData->hWnd) ))); +} + +SalFrame* WinSalInstance::CreateFrame( SalFrame* pParent, SalFrameStyleFlags nSalFrameStyle ) +{ + // to switch to Main-Thread + HWND hWndParent; + if ( pParent ) + hWndParent = static_cast<WinSalFrame*>(pParent)->mhWnd; + else + hWndParent = nullptr; + return reinterpret_cast<SalFrame*>(static_cast<sal_IntPtr>(SendMessageW( mhComWnd, SAL_MSG_CREATEFRAME, static_cast<WPARAM>(nSalFrameStyle), reinterpret_cast<LPARAM>(hWndParent) ))); +} + +void WinSalInstance::DestroyFrame( SalFrame* pFrame ) +{ + OpenGLContext::prepareForYield(); + SendMessageW( mhComWnd, SAL_MSG_DESTROYFRAME, 0, reinterpret_cast<LPARAM>(pFrame) ); +} + +SalObject* WinSalInstance::CreateObject( SalFrame* pParent, + SystemWindowData* /*pWindowData*/, // SystemWindowData meaningless on Windows + bool /*bShow*/ ) +{ + // to switch to Main-Thread + return reinterpret_cast<SalObject*>(static_cast<sal_IntPtr>(SendMessageW( mhComWnd, SAL_MSG_CREATEOBJECT, 0, reinterpret_cast<LPARAM>(static_cast<WinSalFrame*>(pParent)) ))); +} + +void WinSalInstance::DestroyObject( SalObject* pObject ) +{ + SendMessageW( mhComWnd, SAL_MSG_DESTROYOBJECT, 0, reinterpret_cast<LPARAM>(pObject) ); +} + +OUString WinSalInstance::GetConnectionIdentifier() +{ + return OUString(); +} + +/** Add a file to the system shells recent document list if there is any. + This function may have no effect under Unix because there is no + standard API among the different desktop managers. + + @param aFileUrl + The file url of the document. +*/ +void WinSalInstance::AddToRecentDocumentList(const OUString& rFileUrl, const OUString& /*rMimeType*/, const OUString& rDocumentService) +{ + if (Application::IsHeadlessModeEnabled()) + return; + + OUString system_path; + osl::FileBase::RC rc = osl::FileBase::getSystemPathFromFileURL(rFileUrl, system_path); + + OSL_ENSURE(osl::FileBase::E_None == rc, "Invalid file url"); + + if (osl::FileBase::E_None == rc) + { + IShellItem* pShellItem = nullptr; + + HRESULT hr = SHCreateItemFromParsingName(o3tl::toW(system_path.getStr()), nullptr, IID_PPV_ARGS(&pShellItem)); + + if ( SUCCEEDED(hr) && pShellItem ) + { + OUString sApplicationName; + + if ( rDocumentService == "com.sun.star.text.TextDocument" || + rDocumentService == "com.sun.star.text.GlobalDocument" || + rDocumentService == "com.sun.star.text.WebDocument" || + rDocumentService == "com.sun.star.xforms.XMLFormDocument" ) + sApplicationName = "Writer"; + else if ( rDocumentService == "com.sun.star.sheet.SpreadsheetDocument" || + rDocumentService == "com.sun.star.chart2.ChartDocument" ) + sApplicationName = "Calc"; + else if ( rDocumentService == "com.sun.star.presentation.PresentationDocument" ) + sApplicationName = "Impress"; + else if ( rDocumentService == "com.sun.star.drawing.DrawingDocument" ) + sApplicationName = "Draw"; + else if ( rDocumentService == "com.sun.star.formula.FormulaProperties" ) + sApplicationName = "Math"; + else if ( rDocumentService == "com.sun.star.sdb.DatabaseDocument" || + rDocumentService == "com.sun.star.sdb.OfficeDatabaseDocument" || + rDocumentService == "com.sun.star.sdb.RelationDesign" || + rDocumentService == "com.sun.star.sdb.QueryDesign" || + rDocumentService == "com.sun.star.sdb.TableDesign" || + rDocumentService == "com.sun.star.sdb.DataSourceBrowser" ) + sApplicationName = "Base"; + + if ( !sApplicationName.isEmpty() ) + { + OUString sApplicationID("TheDocumentFoundation.LibreOffice." + sApplicationName); + + SHARDAPPIDINFO info; + info.psi = pShellItem; + info.pszAppID = o3tl::toW(sApplicationID.getStr()); + + SHAddToRecentDocs ( SHARD_APPIDINFO, &info ); + return; + } + } + // For whatever reason, we could not use the SHARD_APPIDINFO semantics + SHAddToRecentDocs(SHARD_PATHW, system_path.getStr()); + } +} + +SalTimer* WinSalInstance::CreateSalTimer() +{ + return new WinSalTimer(); +} + +std::shared_ptr<SalBitmap> WinSalInstance::CreateSalBitmap() +{ +#if HAVE_FEATURE_SKIA + if (SkiaHelper::isVCLSkiaEnabled()) + return std::make_shared<SkiaSalBitmap>(); + else +#endif + return std::make_shared<WinSalBitmap>(); +} + +int WinSalInstance::WorkaroundExceptionHandlingInUSER32Lib(int, LPEXCEPTION_POINTERS pExceptionInfo) +{ + // Decide if an exception is a c++ (mostly UNO) exception or a process violation. + // Depending on this information we pass process violations directly to our signal handler ... + // and c++ (UNO) exceptions are sended to the following code on the current stack. + // Problem behind: user32.dll sometime consumes exceptions/process violations .-) + // see also #112221# + + static const DWORD EXCEPTION_MSC_CPP_EXCEPTION = 0xE06D7363; + + if (pExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_MSC_CPP_EXCEPTION) + return EXCEPTION_CONTINUE_SEARCH; + + return UnhandledExceptionFilter( pExceptionInfo ); +} + +typedef LONG NTSTATUS; +typedef NTSTATUS(WINAPI* RtlGetVersion_t)(PRTL_OSVERSIONINFOW); +constexpr NTSTATUS STATUS_SUCCESS = 0x00000000; + +OUString WinSalInstance::getOSVersion() +{ + OUStringBuffer aVer(50); // capacity for string like "Windows 6.1 Service Pack 1 build 7601" + aVer.append("Windows "); + // GetVersion(Ex) and VersionHelpers (based on VerifyVersionInfo) API are + // subject to manifest-based behavior since Windows 8.1, so give wrong results. + // Another approach would be to use NetWkstaGetInfo, but that has some small + // reported delays (some milliseconds), and might get slower in domains with + // poor network connections. + // So go with a solution described at https://msdn.microsoft.com/en-us/library/ms724429 + bool bHaveVerFromKernel32 = false; + if (HMODULE h_kernel32 = GetModuleHandleW(L"kernel32.dll")) + { + wchar_t szPath[MAX_PATH]; + DWORD dwCount = GetModuleFileNameW(h_kernel32, szPath, SAL_N_ELEMENTS(szPath)); + if (dwCount != 0 && dwCount < SAL_N_ELEMENTS(szPath)) + { + dwCount = GetFileVersionInfoSizeW(szPath, nullptr); + if (dwCount != 0) + { + std::unique_ptr<char[]> ver(new char[dwCount]); + if (GetFileVersionInfoW(szPath, 0, dwCount, ver.get()) != FALSE) + { + void* pBlock = nullptr; + UINT dwBlockSz = 0; + if (VerQueryValueW(ver.get(), L"\\", &pBlock, &dwBlockSz) != FALSE && dwBlockSz >= sizeof(VS_FIXEDFILEINFO)) + { + VS_FIXEDFILEINFO* vi1 = static_cast<VS_FIXEDFILEINFO*>(pBlock); + aVer.append(OUString::number(HIWORD(vi1->dwProductVersionMS)) + "." + + OUString::number(LOWORD(vi1->dwProductVersionMS))); + bHaveVerFromKernel32 = true; + } + } + } + } + } + // Now use RtlGetVersion (which is not subject to deprecation for GetVersion(Ex) API) + // to get build number and SP info + bool bHaveVerFromRtlGetVersion = false; + if (HMODULE h_ntdll = GetModuleHandleW(L"ntdll.dll")) + { + if (auto RtlGetVersion + = reinterpret_cast<RtlGetVersion_t>(GetProcAddress(h_ntdll, "RtlGetVersion"))) + { + RTL_OSVERSIONINFOW vi2{}; // initialize with zeroes - a better alternative to memset + vi2.dwOSVersionInfoSize = sizeof(vi2); + if (STATUS_SUCCESS == RtlGetVersion(&vi2)) + { + if (!bHaveVerFromKernel32) // we failed above; let's hope this would be useful + aVer.append(OUString::number(vi2.dwMajorVersion) + "." + + OUString::number(vi2.dwMinorVersion)); + aVer.append(" "); + if (vi2.szCSDVersion[0]) + aVer.append(OUString::Concat(o3tl::toU(vi2.szCSDVersion)) + " "); + aVer.append("Build " + OUString::number(vi2.dwBuildNumber)); + bHaveVerFromRtlGetVersion = true; + } + } + } + if (!bHaveVerFromKernel32 && !bHaveVerFromRtlGetVersion) + aVer.append("unknown"); + return aVer.makeStringAndClear(); +} + +void WinSalInstance::BeforeAbort(const OUString&, bool) +{ + ImplFreeSalGDI(); +} + +css::uno::Reference<css::uno::XInterface> WinSalInstance::ImplCreateDragSource(const SystemEnvData* pSysEnv) +{ + return vcl::OleDnDHelper(new DragSource(comphelper::getProcessComponentContext()), + reinterpret_cast<sal_IntPtr>(pSysEnv->hWnd), vcl::DragOrDrop::Drag); +} + +css::uno::Reference<css::uno::XInterface> WinSalInstance::ImplCreateDropTarget(const SystemEnvData* pSysEnv) +{ + return vcl::OleDnDHelper(new DropTarget(comphelper::getProcessComponentContext()), + reinterpret_cast<sal_IntPtr>(pSysEnv->hWnd), vcl::DragOrDrop::Drop); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/win/app/salshl.cxx b/vcl/win/app/salshl.cxx new file mode 100644 index 000000000..5619ece1f --- /dev/null +++ b/vcl/win/app/salshl.cxx @@ -0,0 +1,112 @@ +/* -*- 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 <svsys.h> +#include <sal/log.hxx> +#include <win/saldata.hxx> + +SalShlData aSalShlData; + +extern "C" BOOL WINAPI DllMain(HINSTANCE hInst, DWORD nReason, LPVOID) +{ + if ( nReason == DLL_PROCESS_ATTACH ) + aSalShlData.mhInst = hInst; + return 1; +} + +HCURSOR ImplLoadSalCursor( int nId ) +{ + SAL_WARN_IF( !aSalShlData.mhInst, "vcl", "no DLL instance handle" ); + + HCURSOR hCursor = LoadCursorW( aSalShlData.mhInst, MAKEINTRESOURCEW( nId ) ); + + SAL_WARN_IF( !hCursor, "vcl", "cursor not found in sal resource" ); + + return hCursor; +} + +HBITMAP ImplLoadSalBitmap( int nId ) +{ + SAL_WARN_IF( !aSalShlData.mhInst, "vcl", "no DLL instance handle" ); + + HBITMAP hBitmap = LoadBitmapW( aSalShlData.mhInst, MAKEINTRESOURCEW( nId ) ); + + SAL_WARN_IF( !hBitmap, "vcl", "bitmap not found in sal resource" ); + + return hBitmap; +} + +bool ImplLoadSalIcon( int nId, HICON& rIcon, HICON& rSmallIcon ) +{ + SAL_WARN_IF( !aSalShlData.mhInst, "vcl", "no DLL instance handle" ); + + SalData* pSalData = GetSalData(); + + // check the cache first + SalIcon *pSalIcon = pSalData->mpFirstIcon; + while( pSalIcon ) + { + if( pSalIcon->nId != nId ) + pSalIcon = pSalIcon->pNext; + else + { + rIcon = pSalIcon->hIcon; + rSmallIcon = pSalIcon->hSmallIcon; + return (rSmallIcon != nullptr); + } + } + + // Try at first to load the icons from the application exe file + rIcon = static_cast<HICON>(LoadImageW( pSalData->mhInst, MAKEINTRESOURCEW( nId ), + IMAGE_ICON, GetSystemMetrics( SM_CXICON ), GetSystemMetrics( SM_CYICON ), + LR_DEFAULTCOLOR )); + if ( !rIcon ) + { + // If the application don't provide these icons, then we try + // to load the icon from the VCL resource + rIcon = static_cast<HICON>(LoadImageW( aSalShlData.mhInst, MAKEINTRESOURCEW( nId ), + IMAGE_ICON, GetSystemMetrics( SM_CXICON ), GetSystemMetrics( SM_CYICON ), + LR_DEFAULTCOLOR )); + if ( rIcon ) + { + rSmallIcon = static_cast<HICON>(LoadImageW( aSalShlData.mhInst, MAKEINTRESOURCEW( nId ), + IMAGE_ICON, GetSystemMetrics( SM_CXSMICON ), GetSystemMetrics( SM_CYSMICON ), + LR_DEFAULTCOLOR )); + } + else + rSmallIcon = nullptr; + } + else + { + rSmallIcon = static_cast<HICON>(LoadImageW( pSalData->mhInst, MAKEINTRESOURCEW( nId ), + IMAGE_ICON, GetSystemMetrics( SM_CXSMICON ), GetSystemMetrics( SM_CYSMICON ), + LR_DEFAULTCOLOR )); + } + + if( rIcon ) + { + // add to icon cache + pSalData->mpFirstIcon = new SalIcon{ + nId, rIcon, rSmallIcon, pSalData->mpFirstIcon}; + } + + return (rSmallIcon != nullptr); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/win/app/saltimer.cxx b/vcl/win/app/saltimer.cxx new file mode 100644 index 000000000..1ccab54e9 --- /dev/null +++ b/vcl/win/app/saltimer.cxx @@ -0,0 +1,201 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> +#include <sal/log.hxx> + +#include <tools/time.hxx> + +#include <svsys.h> +#include <win/saldata.hxx> +#include <win/saltimer.h> +#include <win/salinst.h> + +void CALLBACK SalTimerProc(PVOID pParameter, BOOLEAN bTimerOrWaitFired); + +// See http://msdn.microsoft.com/en-us/library/windows/desktop/ms687003%28v=vs.85%29.aspx +// (and related pages) for details about the Timer Queues. + +// in order to prevent concurrent execution of ImplSalStartTimer and double +// deletion of timer (which is extremely likely, given that +// INVALID_HANDLE_VALUE waits for the callback to run on the main thread), +// this must run on the main thread too +void WinSalTimer::ImplStop() +{ + SalData *const pSalData = GetSalData(); + const WinSalInstance *pInst = pSalData->mpInstance; + assert( !pInst || pSalData->mnAppThreadId == GetCurrentThreadId() ); + + if ( m_bSetTimerRunning ) + { + m_bSetTimerRunning = false; + KillTimer( pInst->mhComWnd, m_aWmTimerId ); + } + m_bDirectTimeout = false; + + const HANDLE hTimer = m_nTimerId; + if ( nullptr == hTimer ) + return; + + m_nTimerId = nullptr; + DeleteTimerQueueTimer( nullptr, hTimer, INVALID_HANDLE_VALUE ); + // Keep InvalidateEvent after DeleteTimerQueueTimer, because the event id + // is set in SalTimerProc, which DeleteTimerQueueTimer will finish or cancel. + InvalidateEvent(); +} + +void WinSalTimer::ImplStart( sal_uInt64 nMS ) +{ +#if !defined NDEBUG + SalData* pSalData = GetSalData(); + assert( !pSalData->mpInstance || pSalData->mnAppThreadId == GetCurrentThreadId() ); +#endif + + // DueTime parameter is a DWORD, which is always an unsigned 32bit + if (nMS > SAL_MAX_UINT32) + nMS = SAL_MAX_UINT32; + + // cannot change a one-shot timer, so delete it and create a new one + ImplStop(); + + // directly indicate an elapsed timer + m_bDirectTimeout = ( 0 == nMS ); + // probably WT_EXECUTEONLYONCE is not needed, but it enforces Period + // to be 0 and should not hurt; also see + // https://www.microsoft.com/msj/0499/pooling/pooling.aspx + if ( !m_bDirectTimeout ) + CreateTimerQueueTimer(&m_nTimerId, nullptr, SalTimerProc, this, + nMS, 0, WT_EXECUTEINTIMERTHREAD | WT_EXECUTEONLYONCE); + else if ( m_bForceRealTimer ) + { + // so we don't block the nested message queue in move and resize + // with posted 0ms SAL_MSG_TIMER_CALLBACK messages + SetTimer( GetSalData()->mpInstance->mhComWnd, m_aWmTimerId, + USER_TIMER_MINIMUM, nullptr ); + m_bSetTimerRunning = true; + } + // we don't need any wakeup message, as this code can just run in the + // main thread! +} + +WinSalTimer::WinSalTimer() + : m_nTimerId( nullptr ) + , m_bDirectTimeout( false ) + , m_bForceRealTimer( false ) + , m_bSetTimerRunning( false ) +{ +} + +WinSalTimer::~WinSalTimer() +{ + Stop(); +} + +void WinSalTimer::Start( sal_uInt64 nMS ) +{ + WinSalInstance *pInst = GetSalData()->mpInstance; + if ( pInst && !pInst->IsMainThread() ) + { + bool const ret = PostMessageW(pInst->mhComWnd, + SAL_MSG_STARTTIMER, 0, static_cast<LPARAM>(tools::Time::GetSystemTicks()) + nMS); + SAL_WARN_IF(!ret, "vcl", "ERROR: PostMessage() failed!"); + } + else + ImplStart( nMS ); +} + +void WinSalTimer::Stop() +{ + WinSalInstance *pInst = GetSalData()->mpInstance; + if ( pInst && !pInst->IsMainThread() ) + { + bool const ret = PostMessageW(pInst->mhComWnd, + SAL_MSG_STOPTIMER, 0, 0); + SAL_WARN_IF(!ret, "vcl", "ERROR: PostMessage() failed!"); + } + else + ImplStop(); +} + +/** + * This gets invoked from a Timer Queue thread. + * Don't acquire the SolarMutex to avoid deadlocks. + */ +void CALLBACK SalTimerProc(PVOID data, BOOLEAN) +{ + __try + { + WinSalTimer *pTimer = static_cast<WinSalTimer*>( data ); + bool const ret = PostMessageW( + GetSalData()->mpInstance->mhComWnd, SAL_MSG_TIMER_CALLBACK, + static_cast<WPARAM>(pTimer->GetNextEventVersion()), 0 ); +#if OSL_DEBUG_LEVEL > 0 + if (!ret) // SEH prevents using SAL_WARN here? + fputs("ERROR: PostMessage() failed!\n", stderr); +#endif + } + __except(WinSalInstance::WorkaroundExceptionHandlingInUSER32Lib(GetExceptionCode(), GetExceptionInformation())) + { + } +} + +void WinSalTimer::ImplHandleElapsedTimer() +{ + // Test for MouseLeave + SalTestMouseLeave(); + + m_bDirectTimeout = false; + ImplSalYieldMutexAcquireWithWait(); + CallCallback(); + ImplSalYieldMutexRelease(); +} + +void WinSalTimer::ImplHandleTimerEvent( const WPARAM aWPARAM ) +{ + assert( aWPARAM <= SAL_MAX_INT32 ); + if ( !IsValidEventVersion( static_cast<sal_Int32>( aWPARAM ) ) ) + return; + + ImplHandleElapsedTimer(); +} + +void WinSalTimer::SetForceRealTimer( const bool bVal ) +{ + if ( m_bForceRealTimer == bVal ) + return; + + m_bForceRealTimer = bVal; + + // we need a real timer, as m_bDirectTimeout won't be processed + if ( bVal && m_bDirectTimeout ) + Start( 0 ); +} + +void WinSalTimer::ImplHandle_WM_TIMER( const WPARAM aWPARAM ) +{ + assert( m_aWmTimerId == aWPARAM ); + if ( !(m_aWmTimerId == aWPARAM && m_bSetTimerRunning) ) + return; + + m_bSetTimerRunning = false; + KillTimer( GetSalData()->mpInstance->mhComWnd, m_aWmTimerId ); + ImplHandleElapsedTimer(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |