summaryrefslogtreecommitdiffstats
path: root/desktop/source/app
diff options
context:
space:
mode:
Diffstat (limited to 'desktop/source/app')
-rw-r--r--desktop/source/app/app.cxx2582
-rw-r--r--desktop/source/app/appinit.cxx279
-rw-r--r--desktop/source/app/check_ext_deps.cxx427
-rw-r--r--desktop/source/app/cmdlineargs.cxx786
-rw-r--r--desktop/source/app/cmdlineargs.hxx186
-rw-r--r--desktop/source/app/cmdlinehelp.cxx266
-rw-r--r--desktop/source/app/cmdlinehelp.hxx30
-rw-r--r--desktop/source/app/crashreport.cxx477
-rw-r--r--desktop/source/app/desktopcontext.cxx55
-rw-r--r--desktop/source/app/desktopcontext.hxx40
-rw-r--r--desktop/source/app/dispatchwatcher.cxx863
-rw-r--r--desktop/source/app/dispatchwatcher.hxx86
-rw-r--r--desktop/source/app/langselect.cxx148
-rw-r--r--desktop/source/app/langselect.hxx29
-rw-r--r--desktop/source/app/lockfile2.cxx60
-rw-r--r--desktop/source/app/main.c62
-rw-r--r--desktop/source/app/officeipcthread.cxx1357
-rw-r--r--desktop/source/app/officeipcthread.hxx157
-rw-r--r--desktop/source/app/opencl.cxx257
-rw-r--r--desktop/source/app/sofficemain.cxx104
-rw-r--r--desktop/source/app/sofficemain.h34
-rw-r--r--desktop/source/app/updater.cxx920
-rw-r--r--desktop/source/app/updater.hxx37
-rw-r--r--desktop/source/app/userinstall.cxx176
-rw-r--r--desktop/source/app/userinstall.hxx38
25 files changed, 9456 insertions, 0 deletions
diff --git a/desktop/source/app/app.cxx b/desktop/source/app/app.cxx
new file mode 100644
index 0000000000..0d66a48daa
--- /dev/null
+++ b/desktop/source/app/app.cxx
@@ -0,0 +1,2582 @@
+/* -*- 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 <config_features.h>
+#include <config_feature_desktop.h>
+#include <config_feature_opencl.h>
+#include <config_java.h>
+#include <config_folders.h>
+#include <config_extensions.h>
+#include <config_wasm_strip.h>
+
+#include <sal/config.h>
+
+#include <cstdlib>
+#include <iostream>
+#include <string_view>
+
+#include <app.hxx>
+#include <dp_shared.hxx>
+#include <strings.hrc>
+#include "cmdlineargs.hxx"
+#include <lockfile.hxx>
+#include "userinstall.hxx"
+#include "desktopcontext.hxx"
+#include <migration.hxx>
+#include "officeipcthread.hxx"
+#if HAVE_FEATURE_UPDATE_MAR
+#include "updater.hxx"
+#endif
+
+#include <framework/desktop.hxx>
+#include <i18nlangtag/languagetag.hxx>
+#include <o3tl/char16_t2wchar_t.hxx>
+#include <svl/ctloptions.hxx>
+#include <svtools/javacontext.hxx>
+#include <com/sun/star/beans/XPropertySet.hpp>
+#include <com/sun/star/frame/theAutoRecovery.hpp>
+#include <com/sun/star/frame/theGlobalEventBroadcaster.hpp>
+#include <com/sun/star/frame/SessionListener.hpp>
+#include <com/sun/star/frame/XSynchronousDispatch.hpp>
+#include <com/sun/star/configuration/theDefaultProvider.hpp>
+#include <com/sun/star/util/XFlushable.hpp>
+#include <com/sun/star/system/SystemShellExecuteFlags.hpp>
+#include <com/sun/star/frame/Desktop.hpp>
+#include <com/sun/star/frame/StartModule.hpp>
+#include <com/sun/star/view/XPrintable.hpp>
+#include <com/sun/star/awt/XTopWindow.hpp>
+#include <com/sun/star/util/URLTransformer.hpp>
+#include <com/sun/star/util/XURLTransformer.hpp>
+#include <com/sun/star/lang/ServiceNotRegisteredException.hpp>
+#include <com/sun/star/configuration/MissingBootstrapFileException.hpp>
+#include <com/sun/star/configuration/InvalidBootstrapFileException.hpp>
+#include <com/sun/star/configuration/InstallationIncompleteException.hpp>
+#include <com/sun/star/configuration/backend/BackendSetupException.hpp>
+#include <com/sun/star/configuration/backend/BackendAccessException.hpp>
+#include <com/sun/star/task/theJobExecutor.hpp>
+#include <com/sun/star/task/OfficeRestartManager.hpp>
+#include <com/sun/star/task/XRestartManager.hpp>
+#include <com/sun/star/document/XDocumentEventListener.hpp>
+#include <com/sun/star/office/Quickstart.hpp>
+#include <com/sun/star/system/XSystemShellExecute.hpp>
+#include <com/sun/star/system/SystemShellExecute.hpp>
+#include <com/sun/star/loader/XImplementationLoader.hpp>
+
+#include <desktop/exithelper.h>
+#include <sal/log.hxx>
+#include <toolkit/helper/vclunohelper.hxx>
+#include <comphelper/lok.hxx>
+#include <comphelper/configuration.hxx>
+#include <comphelper/fileurl.hxx>
+#include <comphelper/threadpool.hxx>
+#include <comphelper/processfactory.hxx>
+#include <comphelper/backupfilehelper.hxx>
+#include <uno/current_context.hxx>
+#include <unotools/bootstrap.hxx>
+#include <unotools/configmgr.hxx>
+#include <unotools/moduleoptions.hxx>
+#include <unotools/localfilehelper.hxx>
+#include <unotools/ucbhelper.hxx>
+#include <officecfg/Office/Common.hxx>
+#include <officecfg/Office/Recovery.hxx>
+#include <officecfg/Office/Update.hxx>
+#include <officecfg/Setup.hxx>
+#include <osl/file.hxx>
+#include <osl/process.h>
+#include <rtl/byteseq.hxx>
+#include <unotools/pathoptions.hxx>
+#if !ENABLE_WASM_STRIP_PINGUSER
+#include <unotools/VersionConfig.hxx>
+#endif
+#include <rtl/bootstrap.hxx>
+#include <vcl/test/GraphicsRenderTests.hxx>
+#include <vcl/help.hxx>
+#include <vcl/weld.hxx>
+#include <vcl/settings.hxx>
+#include <sfx2/flatpak.hxx>
+#include <sfx2/sfxsids.hrc>
+#include <sfx2/app.hxx>
+#include <sfx2/safemode.hxx>
+#include <svl/itemset.hxx>
+#include <svl/eitem.hxx>
+#include <basic/sbstar.hxx>
+#include <desktop/crashreport.hxx>
+#include <tools/time.hxx>
+#include <tools/urlobj.hxx>
+#include <comphelper/diagnose_ex.hxx>
+#include <svtools/fontsubstconfig.hxx>
+#include <svtools/accessibilityoptions.hxx>
+#include <svtools/apearcfg.hxx>
+#include <vcl/graphicfilter.hxx>
+#include <vcl/window.hxx>
+#include "langselect.hxx"
+#include <salhelper/thread.hxx>
+
+#if defined MACOSX
+#include <errno.h>
+#include <sys/wait.h>
+#endif
+
+#ifdef _WIN32
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+#include <vcl/fileregistration.hxx>
+#endif
+
+#if defined(_WIN32)
+#include <process.h>
+#define GETPID _getpid
+#else
+#include <unistd.h>
+#define GETPID getpid
+#endif
+
+#include <strings.hxx>
+
+using namespace ::com::sun::star::awt;
+using namespace ::com::sun::star::uno;
+using namespace ::com::sun::star::util;
+using namespace ::com::sun::star::lang;
+using namespace ::com::sun::star::beans;
+using namespace ::com::sun::star::frame;
+using namespace ::com::sun::star::document;
+using namespace ::com::sun::star::view;
+using namespace ::com::sun::star::task;
+using namespace ::com::sun::star::system;
+using namespace ::com::sun::star::ui;
+using namespace ::com::sun::star::ui::dialogs;
+using namespace ::com::sun::star::container;
+
+namespace desktop
+{
+
+static oslSignalHandler pSignalHandler = nullptr;
+
+namespace {
+
+#if HAVE_FEATURE_EXTENSIONS
+
+// Remove any existing UserInstallation's extensions cache data remaining from
+// old installations. This addresses at least two problems:
+//
+// For one, apparently due to the old share/prereg/bundled mechanism (disabled
+// since 5c47e5f63a79a9e72ec4a100786b1bbf65137ed4 "fdo#51252 Disable copying
+// share/prereg/bundled to avoid startup crashes"), the user/extensions/bundled
+// cache could contain corrupted information (like a UNO component registered
+// twice, which got changed from active to passive registration in one LO
+// version, but the version of the corresponding bundled extension only
+// incremented in a later LO version).
+//
+// For another, UserInstallations have been seen in the wild where no extensions
+// were installed per-user (any longer), but user/uno_packages/cache/registry/
+// com.sun.star.comp.deployment.component.PackageRegistryBackend/*.rdb files
+// contained data nevertheless.
+//
+// When a LO upgrade is detected (i.e., no user/extensions/buildid or one
+// containing an old build ID), then user/extensions and
+// user/uno_packages/cache/registry/
+// com.sun.star.comp.deployment.component.PackageRegistryBackend/unorc are
+// removed. That should prevent any problems starting the service manager due
+// to old junk. Later on in Desktop::SynchronizeExtensionRepositories, the
+// removed cache data is recreated.
+//
+// Multiple instances of soffice.bin can execute this code in parallel for a
+// single UserInstallation, as it is called before RequestHandler is set up.
+// Therefore, any errors here only lead to SAL_WARNs.
+//
+// At least in theory, this function could be removed again once no
+// UserInstallation can be poisoned by old junk any more.
+bool cleanExtensionCache() {
+ OUString buildId(
+ "${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE("version") ":buildid}");
+ rtl::Bootstrap::expandMacros(buildId); //TODO: detect failure
+ OUString extDir(
+ "${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE("bootstrap")
+ ":UserInstallation}/user/extensions");
+ rtl::Bootstrap::expandMacros(extDir); //TODO: detect failure
+ OUString buildIdFile(extDir + "/buildid");
+ osl::File fr(buildIdFile);
+ osl::FileBase::RC rc = fr.open(osl_File_OpenFlag_Read);
+ switch (rc) {
+ case osl::FileBase::E_None:
+ {
+ rtl::ByteSequence s1;
+ rc = fr.readLine(s1);
+ osl::FileBase::RC rc2 = fr.close();
+ SAL_WARN_IF(
+ rc2 != osl::FileBase::E_None, "desktop.app",
+ "cannot close " << fr.getURL() << " after reading: " << +rc2);
+ // readLine returns E_AGAIN for a zero-size file:
+ if (rc != osl::FileBase::E_None && rc != osl::FileBase::E_AGAIN) {
+ SAL_WARN( "desktop.app", "cannot read from " << fr.getURL() << ": " << +rc);
+ break;
+ }
+ OUString s2(
+ reinterpret_cast< char const * >(s1.getConstArray()),
+ s1.getLength(), RTL_TEXTENCODING_ISO_8859_1);
+ // using ISO 8859-1 avoids any and all conversion errors; the
+ // content should only be a subset of ASCII, anyway
+ if (s2 == buildId) {
+ return false;
+ }
+ break;
+ }
+ case osl::FileBase::E_NOENT:
+ break;
+ default:
+ SAL_WARN( "desktop.app", "cannot open " << fr.getURL() << " for reading: " << +rc);
+ break;
+ }
+ utl::removeTree(extDir);
+ OUString userRcFile(
+ "$UNO_USER_PACKAGES_CACHE/registry/"
+ "com.sun.star.comp.deployment.component.PackageRegistryBackend/unorc");
+ rtl::Bootstrap::expandMacros(userRcFile); //TODO: detect failure
+ rc = osl::File::remove(userRcFile);
+ SAL_WARN_IF(
+ rc != osl::FileBase::E_None && rc != osl::FileBase::E_NOENT, "desktop.app",
+ "cannot remove file " << userRcFile << ": " << +rc);
+ rc = osl::Directory::createPath(extDir);
+ SAL_WARN_IF(
+ rc != osl::FileBase::E_None && rc != osl::FileBase::E_EXIST, "desktop.app",
+ "cannot create path " << extDir << ": " << +rc);
+ osl::File fw(buildIdFile);
+ rc = fw.open(osl_File_OpenFlag_Write | osl_File_OpenFlag_Create);
+ if (rc != osl::FileBase::E_None) {
+ SAL_WARN( "desktop.app", "cannot open " << fw.getURL() << " for writing: " << +rc);
+ return true;
+ }
+ OString buf(OUStringToOString(buildId, RTL_TEXTENCODING_UTF8));
+ // using UTF-8 avoids almost all conversion errors (and buildid
+ // containing single surrogate halves should never happen, anyway); the
+ // content should only be a subset of ASCII, anyway
+ sal_uInt64 n = 0;
+ rc = fw.write(buf.getStr(), buf.getLength(), n);
+ SAL_WARN_IF(
+ (rc != osl::FileBase::E_None
+ || n != static_cast< sal_uInt32 >(buf.getLength())),
+ "desktop.app",
+ "cannot write to " << fw.getURL() << ": " << +rc << ", " << n);
+ rc = fw.close();
+ SAL_WARN_IF(
+ rc != osl::FileBase::E_None, "desktop.app",
+ "cannot close " << fw.getURL() << " after writing: " << +rc);
+ return true;
+}
+
+#endif
+
+bool shouldLaunchQuickstart()
+{
+ bool bQuickstart = Desktop::GetCommandLineArgs().IsQuickstart();
+ if (!bQuickstart)
+ {
+ SfxItemSetFixed<SID_ATTR_QUICKLAUNCHER, SID_ATTR_QUICKLAUNCHER> aQLSet(SfxGetpApp()->GetPool());
+ SfxApplication::GetOptions(aQLSet);
+ const SfxBoolItem* pLauncherItem = aQLSet.GetItemIfSet(SID_ATTR_QUICKLAUNCHER, false);
+ if (pLauncherItem)
+ bQuickstart = pLauncherItem->GetValue();
+ }
+ return bQuickstart;
+}
+
+void SetRestartState() {
+ try {
+ std::shared_ptr< comphelper::ConfigurationChanges > batch(
+ comphelper::ConfigurationChanges::create());
+ officecfg::Setup::Office::OfficeRestartInProgress::set(true, batch);
+ batch->commit();
+ } catch (css::uno::Exception) {
+ TOOLS_WARN_EXCEPTION("desktop.app", "ignoring");
+ }
+}
+
+void DoRestartActionsIfNecessary(bool quickstart) {
+ if (!quickstart)
+ return;
+
+ try {
+ if (officecfg::Setup::Office::OfficeRestartInProgress::get()) {
+ std::shared_ptr< comphelper::ConfigurationChanges > batch(
+ comphelper::ConfigurationChanges::create());
+ officecfg::Setup::Office::OfficeRestartInProgress::set(
+ false, batch);
+ batch->commit();
+ css::office::Quickstart::createStart(
+ comphelper::getProcessComponentContext(),
+ shouldLaunchQuickstart());
+ }
+ } catch (css::uno::Exception &) {
+ TOOLS_WARN_EXCEPTION("desktop.app", "ignoring");
+ }
+}
+
+void RemoveIconCacheDirectory()
+{
+ // See getIconCacheUrl in vcl/source/image/ImplImageTree.cxx
+ OUString sUrl = "${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER
+ "/" SAL_CONFIGFILE("bootstrap") ":UserInstallation}/cache";
+ rtl::Bootstrap::expandMacros(sUrl);
+ utl::UCBContentHelper::Kill(sUrl);
+}
+
+}
+
+namespace {
+
+void runGraphicsRenderTests()
+{
+ if (comphelper::LibreOfficeKit::isActive())
+ return;
+#if !ENABLE_WASM_STRIP_PINGUSER
+ if (!utl::isProductVersionUpgraded(false))
+ {
+ return;
+ }
+#endif
+ GraphicsRenderTests TestObject;
+ TestObject.run();
+}
+
+
+OUString MakeStartupErrorMessage(std::u16string_view aErrorMessage)
+{
+ return DpResId(STR_BOOTSTRAP_ERR_CANNOT_START) + "\n" + aErrorMessage;
+}
+
+
+// shows a simple error box with the given message ... but exits from these process !
+// Fatal errors can't be solved by the process ... nor any recovery can help.
+// Mostly the installation was damaged and must be repaired manually .. or by calling
+// setup again.
+// On the other side we must make sure that no further actions will be possible within
+// the current office process ! No pipe requests, no menu/toolbar/shortcut actions
+// are allowed. Otherwise we will force a "crash inside a crash".
+// That's why we have to use a special native message box here which does not use yield :-)
+
+void FatalError(const OUString& sMessage)
+{
+ OUString sProductKey = ::utl::Bootstrap::getProductKey();
+ if ( sProductKey.isEmpty())
+ {
+ osl_getExecutableFile( &sProductKey.pData );
+
+ ::sal_uInt32 nLastIndex = sProductKey.lastIndexOf('/');
+ if ( nLastIndex > 0 )
+ sProductKey = sProductKey.copy( nLastIndex+1 );
+ }
+
+ OUString sTitle = sProductKey + " - Fatal Error";
+ Application::ShowNativeErrorBox (sTitle, sMessage);
+ std::cerr << sTitle << ": " << sMessage << std::endl;
+ _exit(EXITHELPER_FATAL_ERROR);
+}
+
+}
+
+CommandLineArgs& Desktop::GetCommandLineArgs()
+{
+ static CommandLineArgs theCommandLineArgs;
+ return theCommandLineArgs;
+}
+
+OUString ReplaceStringHookProc( const OUString& rStr )
+{
+ const static OUString sBuildId(utl::Bootstrap::getBuildIdData("development")),
+ sBrandName(utl::ConfigManager::getProductName()),
+ sVersion(utl::ConfigManager::getProductVersion()),
+ sAboutBoxVersion(utl::ConfigManager::getAboutBoxProductVersion()),
+ sAboutBoxVersionSuffix(utl::ConfigManager::getAboutBoxProductVersionSuffix()),
+ sExtension(utl::ConfigManager::getProductExtension());
+
+ OUString sRet(rStr);
+ if (sRet.indexOf("%PRODUCT") != -1 || sRet.indexOf("%ABOUTBOX") != -1)
+ {
+ sRet = sRet.replaceAll( "%PRODUCTNAME", sBrandName );
+ sRet = sRet.replaceAll( "%PRODUCTVERSION", sVersion );
+ sRet = sRet.replaceAll( "%BUILDID", sBuildId );
+ sRet = sRet.replaceAll( "%ABOUTBOXPRODUCTVERSIONSUFFIX", sAboutBoxVersionSuffix );
+ sRet = sRet.replaceAll( "%ABOUTBOXPRODUCTVERSION", sAboutBoxVersion );
+ sRet = sRet.replaceAll( "%PRODUCTEXTENSION", sExtension );
+ }
+
+ if ( sRet.indexOf( "%OOOVENDOR" ) != -1 )
+ {
+ const static OUString sOOOVendor = utl::ConfigManager::getVendor();
+ sRet = sRet.replaceAll( "%OOOVENDOR", sOOOVendor );
+ }
+
+ return sRet;
+}
+
+Desktop::Desktop()
+ : m_bCleanedExtensionCache(false)
+ , m_bServicesRegistered(false)
+ , m_aBootstrapError(BE_OK)
+ , m_aBootstrapStatus(BS_OK)
+ , m_firstRunTimer( "desktop::Desktop m_firstRunTimer" )
+{
+ m_firstRunTimer.SetTimeout(3000); // 3 sec.
+ m_firstRunTimer.SetInvokeHandler(LINK(this, Desktop, AsyncInitFirstRun));
+}
+
+Desktop::~Desktop()
+{
+}
+
+void Desktop::Init()
+{
+ SetBootstrapStatus(BS_OK);
+
+#if HAVE_FEATURE_EXTENSIONS
+ m_bCleanedExtensionCache = cleanExtensionCache();
+#endif
+
+ // We need to have service factory before going further, but see fdo#37195.
+ // Doing this will mmap common.rdb, making it not overwritable on windows,
+ // so this can't happen before the synchronization above. Lets rework this
+ // so that the above is called *from* CreateApplicationServiceManager or
+ // something to enforce this gotcha
+ try
+ {
+ InitApplicationServiceManager();
+ }
+ catch (css::uno::Exception & e)
+ {
+ HandleBootstrapErrors( BE_UNO_SERVICEMANAGER, e.Message );
+ std::abort();
+ }
+
+ // Check whether safe mode is enabled
+ const CommandLineArgs& rCmdLineArgs = GetCommandLineArgs();
+ // Check if we are restarting from safe mode - in that case we don't want to enter it again
+ if (sfx2::SafeMode::hasRestartFlag())
+ sfx2::SafeMode::removeRestartFlag();
+ else if (rCmdLineArgs.IsSafeMode() || sfx2::SafeMode::hasFlag())
+ Application::EnableSafeMode();
+
+ // When we are in SafeMode we need to do changes before the configuration
+ // gets read (langselect::prepareLocale() by UNO API -> Components::Components)
+ // This may prepare SafeMode or restore from it by moving data in
+ // the UserConfiguration directory
+ comphelper::BackupFileHelper::reactOnSafeMode(Application::IsSafeModeEnabled());
+
+ try
+ {
+ if (!langselect::prepareLocale())
+ {
+ SetBootstrapError( BE_LANGUAGE_MISSING, OUString() );
+ }
+ }
+ catch (css::uno::Exception & e)
+ {
+ SetBootstrapError( BE_OFFICECONFIG_BROKEN, e.Message );
+ }
+
+ // test code for ProfileSafeMode to allow testing the fail
+ // of loading the office configuration initially. To use,
+ // either set to true and compile, or set a breakpoint
+ // in debugger and change the local bool
+ static bool bTryHardOfficeconfigBroken(false); // loplugin:constvars:ignore
+
+ if (bTryHardOfficeconfigBroken)
+ {
+ SetBootstrapError(BE_OFFICECONFIG_BROKEN, OUString());
+ }
+
+ // start ipc thread only for non-remote offices
+ RequestHandler::Status aStatus = RequestHandler::Enable(true);
+ if ( aStatus == RequestHandler::IPC_STATUS_PIPE_ERROR )
+ {
+#if defined(ANDROID) || defined(EMSCRIPTEN)
+ // Ignore crack pipe errors on Android
+#else
+ // Keep using this oddly named BE_PATHINFO_MISSING value
+ // for pipe-related errors on other platforms. Of course
+ // this crack with two (if not more) levels of our own
+ // error codes hiding the actual system error code is
+ // broken, but that is done all over the code, let's leave
+ // reengineering that to another year.
+ SetBootstrapError( BE_PATHINFO_MISSING, OUString() );
+#endif
+ }
+ else if ( aStatus == RequestHandler::IPC_STATUS_BOOTSTRAP_ERROR )
+ {
+ SetBootstrapError( BE_PATHINFO_MISSING, OUString() );
+ }
+ else if ( aStatus == RequestHandler::IPC_STATUS_2ND_OFFICE )
+ {
+ // 2nd office startup should terminate after sending cmdlineargs through pipe
+ if (rCmdLineArgs.IsTextCat() || rCmdLineArgs.IsScriptCat())
+ {
+ HandleBootstrapErrors( BE_2NDOFFICE_WITHCAT, OUString() );
+ }
+ SetBootstrapStatus(BS_TERMINATE);
+ }
+ else if ( !rCmdLineArgs.GetUnknown().isEmpty()
+ || rCmdLineArgs.IsHelp() || rCmdLineArgs.IsVersion() )
+ {
+ // disable IPC thread in an instance that is just showing a help message
+ RequestHandler::Disable();
+ }
+ pSignalHandler = osl_addSignalHandler(SalMainPipeExchangeSignal_impl, nullptr);
+}
+
+void Desktop::InitFinished()
+{
+ CloseSplashScreen();
+}
+
+void Desktop::DeInit()
+{
+ try {
+ // instead of removing of the configManager just let it commit all the changes
+ utl::ConfigManager::storeConfigItems();
+ FlushConfiguration();
+
+ // close splashscreen if it's still open
+ CloseSplashScreen();
+ Reference< XComponent >(
+ comphelper::getProcessComponentContext(), UNO_QUERY_THROW )->
+ dispose();
+ // nobody should get a destroyed service factory...
+ ::comphelper::setProcessServiceFactory( nullptr );
+
+ // clear lockfile
+ m_xLockfile.reset();
+
+ RequestHandler::Disable();
+ if( pSignalHandler )
+ osl_removeSignalHandler( pSignalHandler );
+ } catch (const RuntimeException&) {
+ // someone threw an exception during shutdown
+ // this will leave some garbage behind...
+ TOOLS_WARN_EXCEPTION("desktop.app", "exception throwing during shutdown, will leave some garbage behind");
+ }
+}
+
+bool Desktop::QueryExit()
+{
+ try
+ {
+ utl::ConfigManager::storeConfigItems();
+ }
+ catch ( const RuntimeException& )
+ {
+ }
+
+ static constexpr OUString SUSPEND_QUICKSTARTVETO = u"SuspendQuickstartVeto"_ustr;
+
+ Reference< XDesktop2 > xDesktop = css::frame::Desktop::create( ::comphelper::getProcessComponentContext() );
+ Reference< XPropertySet > xPropertySet(xDesktop, UNO_QUERY_THROW);
+ xPropertySet->setPropertyValue( SUSPEND_QUICKSTARTVETO, Any(true) );
+
+ bool bExit = xDesktop->terminate();
+
+ if ( !bExit )
+ {
+ xPropertySet->setPropertyValue( SUSPEND_QUICKSTARTVETO, Any(false) );
+ }
+ else
+ {
+ FlushConfiguration();
+ try
+ {
+ // it is no problem to call RequestHandler::Disable() more than once
+ // it also looks to be threadsafe
+ RequestHandler::Disable();
+ }
+ catch ( const RuntimeException& )
+ {
+ }
+
+ m_xLockfile.reset();
+
+ }
+
+ return bExit;
+}
+
+void Desktop::Shutdown()
+{
+ framework::getDesktop(::comphelper::getProcessComponentContext())->shutdown();
+}
+
+void Desktop::HandleBootstrapPathErrors( ::utl::Bootstrap::Status aBootstrapStatus, std::u16string_view aDiagnosticMessage )
+{
+ if ( aBootstrapStatus == ::utl::Bootstrap::DATA_OK )
+ return;
+
+ OUString aProductKey;
+ OUString aTemp;
+
+ osl_getExecutableFile( &aProductKey.pData );
+ sal_uInt32 lastIndex = aProductKey.lastIndexOf('/');
+ if ( lastIndex > 0 )
+ aProductKey = aProductKey.copy( lastIndex+1 );
+
+ aTemp = ::utl::Bootstrap::getProductKey( aProductKey );
+ if ( !aTemp.isEmpty() )
+ aProductKey = aTemp;
+
+ OUString const aMessage(OUString::Concat(aDiagnosticMessage) + "\n");
+
+ std::unique_ptr<weld::MessageDialog> xBootstrapFailedBox(Application::CreateMessageDialog(nullptr,
+ VclMessageType::Warning, VclButtonsType::Ok, aMessage));
+ xBootstrapFailedBox->set_title(aProductKey);
+ xBootstrapFailedBox->run();
+}
+
+// Create an error message depending on bootstrap failure code and an optional file url
+OUString Desktop::CreateErrorMsgString(
+ utl::Bootstrap::FailureCode nFailureCode,
+ const OUString& aFileURL )
+{
+ OUString aMsg;
+ bool bFileInfo = true;
+
+ switch ( nFailureCode )
+ {
+ /// the shared installation directory could not be located
+ case ::utl::Bootstrap::MISSING_INSTALL_DIRECTORY:
+ {
+ aMsg = DpResId(STR_BOOTSTRAP_ERR_PATH_INVALID);
+ bFileInfo = false;
+ }
+ break;
+
+ /// the bootstrap INI file could not be found or read
+ case ::utl::Bootstrap::MISSING_BOOTSTRAP_FILE:
+ {
+ aMsg = DpResId(STR_BOOTSTRAP_ERR_FILE_MISSING);
+ }
+ break;
+
+ /// the bootstrap INI is missing a required entry
+ /// the bootstrap INI contains invalid data
+ case ::utl::Bootstrap::MISSING_BOOTSTRAP_FILE_ENTRY:
+ case ::utl::Bootstrap::INVALID_BOOTSTRAP_FILE_ENTRY:
+ {
+ aMsg = DpResId(STR_BOOTSTRAP_ERR_FILE_CORRUPT);
+ }
+ break;
+
+ /// the version locator INI file could not be found or read
+ case ::utl::Bootstrap::MISSING_VERSION_FILE:
+ {
+ aMsg = DpResId(STR_BOOTSTRAP_ERR_FILE_MISSING);
+ }
+ break;
+
+ /// the version locator INI has no entry for this version
+ case ::utl::Bootstrap::MISSING_VERSION_FILE_ENTRY:
+ {
+ aMsg = DpResId(STR_BOOTSTRAP_ERR_NO_SUPPORT);
+ }
+ break;
+
+ /// the user installation directory does not exist
+ case ::utl::Bootstrap::MISSING_USER_DIRECTORY:
+ {
+ aMsg = DpResId(STR_BOOTSTRAP_ERR_DIR_MISSING);
+ }
+ break;
+
+ /// some bootstrap data was invalid in unexpected ways
+ case ::utl::Bootstrap::INVALID_BOOTSTRAP_DATA:
+ {
+ aMsg = DpResId(STR_BOOTSTRAP_ERR_INTERNAL);
+ bFileInfo = false;
+ }
+ break;
+
+ case ::utl::Bootstrap::INVALID_VERSION_FILE_ENTRY:
+ {
+ // This needs to be improved, see #i67575#:
+ aMsg = "Invalid version file entry";
+ bFileInfo = false;
+ }
+ break;
+
+ case ::utl::Bootstrap::NO_FAILURE:
+ {
+ OSL_ASSERT(false);
+ }
+ break;
+ }
+
+ if ( bFileInfo )
+ {
+ OUString aMsgString( aMsg );
+ OUString aFilePath;
+
+ osl::File::getSystemPathFromFileURL( aFileURL, aFilePath );
+
+ aMsgString = aMsgString.replaceFirst( "$1", aFilePath );
+ aMsg = aMsgString;
+ }
+
+ return MakeStartupErrorMessage( aMsg );
+}
+
+void Desktop::HandleBootstrapErrors(
+ BootstrapError aBootstrapError, OUString const & aErrorMessage )
+{
+ if ( aBootstrapError == BE_PATHINFO_MISSING )
+ {
+ OUString aErrorMsg;
+ OUString aBuffer;
+ utl::Bootstrap::Status aBootstrapStatus;
+ utl::Bootstrap::FailureCode nFailureCode;
+
+ aBootstrapStatus = ::utl::Bootstrap::checkBootstrapStatus( aBuffer, nFailureCode );
+ if ( aBootstrapStatus != ::utl::Bootstrap::DATA_OK )
+ {
+ switch ( nFailureCode )
+ {
+ case ::utl::Bootstrap::MISSING_INSTALL_DIRECTORY:
+ case ::utl::Bootstrap::INVALID_BOOTSTRAP_DATA:
+ {
+ aErrorMsg = CreateErrorMsgString( nFailureCode, OUString() );
+ }
+ break;
+
+ /// the bootstrap INI file could not be found or read
+ /// the bootstrap INI is missing a required entry
+ /// the bootstrap INI contains invalid data
+ case ::utl::Bootstrap::MISSING_BOOTSTRAP_FILE_ENTRY:
+ case ::utl::Bootstrap::INVALID_BOOTSTRAP_FILE_ENTRY:
+ case ::utl::Bootstrap::MISSING_BOOTSTRAP_FILE:
+ {
+ OUString aBootstrapFileURL;
+
+ utl::Bootstrap::locateBootstrapFile( aBootstrapFileURL );
+ aErrorMsg = CreateErrorMsgString( nFailureCode, aBootstrapFileURL );
+ }
+ break;
+
+ /// the version locator INI file could not be found or read
+ /// the version locator INI has no entry for this version
+ /// the version locator INI entry is not a valid directory URL
+ case ::utl::Bootstrap::INVALID_VERSION_FILE_ENTRY:
+ case ::utl::Bootstrap::MISSING_VERSION_FILE_ENTRY:
+ case ::utl::Bootstrap::MISSING_VERSION_FILE:
+ {
+ OUString aVersionFileURL;
+
+ utl::Bootstrap::locateVersionFile( aVersionFileURL );
+ aErrorMsg = CreateErrorMsgString( nFailureCode, aVersionFileURL );
+ }
+ break;
+
+ /// the user installation directory does not exist
+ case ::utl::Bootstrap::MISSING_USER_DIRECTORY:
+ {
+ OUString aUserInstallationURL;
+
+ utl::Bootstrap::locateUserInstallation( aUserInstallationURL );
+ aErrorMsg = CreateErrorMsgString( nFailureCode, aUserInstallationURL );
+ }
+ break;
+
+ case ::utl::Bootstrap::NO_FAILURE:
+ {
+ OSL_ASSERT(false);
+ }
+ break;
+ }
+
+ HandleBootstrapPathErrors( aBootstrapStatus, aErrorMsg );
+ }
+ }
+ else if ( aBootstrapError == BE_UNO_SERVICEMANAGER || aBootstrapError == BE_UNO_SERVICE_CONFIG_MISSING )
+ {
+ // UNO service manager is not available. VCL needs a UNO service manager to display a message box!!!
+ // Currently we are not able to display a message box with a service manager due to this limitations inside VCL.
+
+ // When UNO is not properly initialized, all kinds of things can fail
+ // and cause the process to crash. To give the user a hint even if
+ // generating and displaying a message box below crashes, print a
+ // hard-coded message on stderr first:
+ std::cerr
+ << "The application cannot be started.\n"
+ // STR_BOOTSTRAP_ERR_CANNOT_START
+ << (aBootstrapError == BE_UNO_SERVICEMANAGER
+ ? "The component manager is not available.\n"
+ // STR_BOOTSTRAP_ERR_NO_SERVICE
+ : "The configuration service is not available.\n");
+ // STR_BOOTSTRAP_ERR_NO_CFG_SERVICE
+ if ( !aErrorMessage.isEmpty() )
+ {
+ std::cerr << "(\"" << aErrorMessage << "\")\n";
+ }
+
+ // First sentence. We cannot bootstrap office further!
+ OUString aDiagnosticMessage = DpResId(STR_BOOTSTRAP_ERR_NO_CFG_SERVICE) + "\n";
+ if ( !aErrorMessage.isEmpty() )
+ {
+ aDiagnosticMessage += "(\"" + aErrorMessage + "\")\n";
+ }
+
+ // Due to the fact the we haven't a backup applicat.rdb file anymore it is not possible to
+ // repair the installation with the setup executable besides the office executable. Now
+ // we have to ask the user to start the setup on CD/installation directory manually!!
+ aDiagnosticMessage += DpResId(STR_ASK_START_SETUP_MANUALLY);
+
+ FatalError(MakeStartupErrorMessage(aDiagnosticMessage));
+ }
+ else if ( aBootstrapError == BE_OFFICECONFIG_BROKEN )
+ {
+ // set flag at BackupFileHelper to be able to know if _exit was called and
+ // actions are executed after this. This method we are in will not return,
+ // but end up in a _exit() call
+ comphelper::BackupFileHelper::setExitWasCalled();
+
+ // enter safe mode, too
+ sfx2::SafeMode::putFlag();
+
+ OUString msg(DpResId(STR_CONFIG_ERR_ACCESS_GENERAL));
+ if (!aErrorMessage.isEmpty()) {
+ msg += "\n(\"" + aErrorMessage + "\")";
+ }
+ FatalError(MakeStartupErrorMessage(msg));
+ }
+ else if ( aBootstrapError == BE_USERINSTALL_FAILED )
+ {
+ OUString aDiagnosticMessage = DpResId(STR_BOOTSTRAP_ERR_USERINSTALL_FAILED);
+ FatalError(MakeStartupErrorMessage(aDiagnosticMessage));
+ }
+ else if ( aBootstrapError == BE_LANGUAGE_MISSING )
+ {
+ OUString aDiagnosticMessage = DpResId(STR_BOOTSTRAP_ERR_LANGUAGE_MISSING);
+ FatalError(MakeStartupErrorMessage(aDiagnosticMessage));
+ }
+ else if (( aBootstrapError == BE_USERINSTALL_NOTENOUGHDISKSPACE ) ||
+ ( aBootstrapError == BE_USERINSTALL_NOWRITEACCESS ))
+ {
+ OUString aUserInstallationURL;
+ OUString aUserInstallationPath;
+ utl::Bootstrap::locateUserInstallation( aUserInstallationURL );
+ osl::File::getSystemPathFromFileURL( aUserInstallationURL, aUserInstallationPath );
+
+ OUString aDiagnosticMessage;
+ if ( aBootstrapError == BE_USERINSTALL_NOTENOUGHDISKSPACE )
+ aDiagnosticMessage = DpResId(STR_BOOTSTRAP_ERR_NOTENOUGHDISKSPACE);
+ else
+ aDiagnosticMessage = DpResId(STR_BOOTSTRAP_ERR_NOACCESSRIGHTS);
+ aDiagnosticMessage += aUserInstallationPath;
+
+ FatalError(MakeStartupErrorMessage(aDiagnosticMessage));
+ }
+ else if ( aBootstrapError == BE_2NDOFFICE_WITHCAT )
+ {
+ OUString aDiagnosticMessage = DpResId(STR_BOOTSTRAP_ERR_2NDOFFICE_WITHCAT);
+ FatalError(MakeStartupErrorMessage(aDiagnosticMessage));
+ }
+}
+
+
+namespace {
+
+
+#if HAVE_FEATURE_BREAKPAD
+void handleCrashReport()
+{
+ static constexpr OUStringLiteral SERVICENAME_CRASHREPORT = u"com.sun.star.comp.svx.CrashReportUI";
+
+ css::uno::Reference< css::uno::XComponentContext > xContext = ::comphelper::getProcessComponentContext();
+
+ Reference< css::frame::XSynchronousDispatch > xRecoveryUI(
+ xContext->getServiceManager()->createInstanceWithContext(SERVICENAME_CRASHREPORT, xContext),
+ css::uno::UNO_QUERY_THROW);
+
+ Reference< css::util::XURLTransformer > xURLParser =
+ css::util::URLTransformer::create(::comphelper::getProcessComponentContext());
+
+ css::util::URL aURL;
+ css::uno::Any aRet = xRecoveryUI->dispatchWithReturnValue(aURL, css::uno::Sequence< css::beans::PropertyValue >());
+ bool bRet = false;
+ aRet >>= bRet;
+}
+#endif
+
+#if !defined ANDROID
+void handleSafeMode()
+{
+ css::uno::Reference< css::uno::XComponentContext > xContext = ::comphelper::getProcessComponentContext();
+
+ Reference< css::frame::XSynchronousDispatch > xSafeModeUI(
+ xContext->getServiceManager()->createInstanceWithContext("com.sun.star.comp.svx.SafeModeUI", xContext),
+ css::uno::UNO_QUERY_THROW);
+
+ css::util::URL aURL;
+ css::uno::Any aRet = xSafeModeUI->dispatchWithReturnValue(aURL, css::uno::Sequence< css::beans::PropertyValue >());
+ bool bRet = false;
+ aRet >>= bRet;
+}
+#endif
+
+/** @short check if recovery must be started or not.
+
+ @param bCrashed [boolean ... out!]
+ the office crashed last times.
+ But may be there are no recovery data.
+ Useful to trigger the error report tool without
+ showing the recovery UI.
+
+ @param bRecoveryDataExists [boolean ... out!]
+ there exists some recovery data.
+
+ @param bSessionDataExists [boolean ... out!]
+ there exists some session data.
+ Because the user may be logged out last time from its
+ unix session...
+*/
+void impl_checkRecoveryState(bool& bCrashed ,
+ bool& bRecoveryDataExists,
+ bool& bSessionDataExists )
+{
+ bCrashed = officecfg::Office::Recovery::RecoveryInfo::Crashed::get()
+#if HAVE_FEATURE_BREAKPAD
+ || CrashReporter::crashReportInfoExists();
+#else
+ ;
+#endif
+ bool elements = officecfg::Office::Recovery::RecoveryList::get()->
+ hasElements();
+ bool session
+ = officecfg::Office::Recovery::RecoveryInfo::SessionData::get();
+ bRecoveryDataExists = elements && !session;
+ bSessionDataExists = elements && session;
+}
+
+Reference< css::frame::XSynchronousDispatch > g_xRecoveryUI;
+
+template <class Ref>
+struct RefClearGuard
+{
+ Ref& m_Ref;
+ RefClearGuard(Ref& ref) : m_Ref(ref) {}
+ ~RefClearGuard() { m_Ref.clear(); }
+};
+
+/* @short start the recovery wizard.
+
+ @param bEmergencySave
+ differs between EMERGENCY_SAVE and RECOVERY
+*/
+#if !ENABLE_WASM_STRIP_RECOVERYUI
+bool impl_callRecoveryUI(bool bEmergencySave ,
+ bool bExistsRecoveryData)
+{
+ constexpr OUStringLiteral COMMAND_EMERGENCYSAVE = u"vnd.sun.star.autorecovery:/doEmergencySave";
+ constexpr OUStringLiteral COMMAND_RECOVERY = u"vnd.sun.star.autorecovery:/doAutoRecovery";
+
+ css::uno::Reference< css::uno::XComponentContext > xContext = ::comphelper::getProcessComponentContext();
+
+ g_xRecoveryUI.set(
+ xContext->getServiceManager()->createInstanceWithContext("com.sun.star.comp.svx.RecoveryUI", xContext),
+ css::uno::UNO_QUERY_THROW);
+ RefClearGuard<Reference< css::frame::XSynchronousDispatch >> refClearGuard(g_xRecoveryUI);
+
+ Reference< css::util::XURLTransformer > xURLParser =
+ css::util::URLTransformer::create(xContext);
+
+ css::util::URL aURL;
+ if (bEmergencySave)
+ aURL.Complete = COMMAND_EMERGENCYSAVE;
+ else if (bExistsRecoveryData)
+ aURL.Complete = COMMAND_RECOVERY;
+ else
+ return false;
+
+ xURLParser->parseStrict(aURL);
+
+ css::uno::Any aRet = g_xRecoveryUI->dispatchWithReturnValue(aURL, css::uno::Sequence< css::beans::PropertyValue >());
+ bool bRet = false;
+ aRet >>= bRet;
+ return bRet;
+}
+#endif
+
+bool impl_bringToFrontRecoveryUI()
+{
+ Reference< css::frame::XSynchronousDispatch > xRecoveryUI(g_xRecoveryUI);
+ if (!xRecoveryUI.is())
+ return false;
+
+ css::util::URL aURL;
+ aURL.Complete = "vnd.sun.star.autorecovery:/doBringToFront";
+ Reference< css::util::XURLTransformer > xURLParser =
+ css::util::URLTransformer::create(::comphelper::getProcessComponentContext());
+ xURLParser->parseStrict(aURL);
+
+ css::uno::Any aRet = xRecoveryUI->dispatchWithReturnValue(aURL, css::uno::Sequence< css::beans::PropertyValue >());
+ bool bRet = false;
+ aRet >>= bRet;
+ return bRet;
+}
+
+}
+
+namespace {
+
+void restartOnMac(bool passArguments) {
+#if defined MACOSX
+ RequestHandler::Disable();
+#if HAVE_FEATURE_MACOSX_SANDBOX
+ (void) passArguments; // avoid warnings
+ OUString aMessage = DpResId(STR_LO_MUST_BE_RESTARTED);
+
+ std::unique_ptr<weld::MessageDialog> xRestartBox(Application::CreateMessageDialog(nullptr,
+ VclMessageType::Warning, VclButtonsType::Ok, aMessage));
+ xRestartBox->run();
+#else
+ OUString execUrl;
+ OSL_VERIFY(osl_getExecutableFile(&execUrl.pData) == osl_Process_E_None);
+ OUString execPath;
+ OString execPath8;
+ if ((osl::FileBase::getSystemPathFromFileURL(execUrl, execPath)
+ != osl::FileBase::E_None) ||
+ !execPath.convertToString(
+ &execPath8, osl_getThreadTextEncoding(),
+ (RTL_UNICODETOTEXT_FLAGS_UNDEFINED_ERROR |
+ RTL_UNICODETOTEXT_FLAGS_INVALID_ERROR)))
+ {
+ std::abort();
+ }
+ std::vector< OString > args { execPath8 };
+ bool wait = false;
+ if (passArguments) {
+ sal_uInt32 n = osl_getCommandArgCount();
+ for (sal_uInt32 i = 0; i < n; ++i) {
+ OUString arg;
+ osl_getCommandArg(i, &arg.pData);
+ if (arg.match("--accept=")) {
+ wait = true;
+ }
+ OString arg8;
+ if (!arg.convertToString(
+ &arg8, osl_getThreadTextEncoding(),
+ (RTL_UNICODETOTEXT_FLAGS_UNDEFINED_ERROR |
+ RTL_UNICODETOTEXT_FLAGS_INVALID_ERROR)))
+ {
+ std::abort();
+ }
+ args.push_back(arg8);
+ }
+ }
+ std::vector< char const * > argPtrs;
+ for (auto const& elem : args)
+ {
+ argPtrs.push_back(elem.getStr());
+ }
+ argPtrs.push_back(nullptr);
+ execv(execPath8.getStr(), const_cast< char ** >(argPtrs.data()));
+ if (errno == ENOTSUP) { // happens when multithreaded on macOS < 10.6
+ pid_t pid = fork();
+ if (pid == 0) {
+ execv(execPath8.getStr(), const_cast< char ** >(argPtrs.data()));
+ } else if (pid > 0) {
+ // Two simultaneously running soffice processes lead to two dock
+ // icons, so avoid waiting here unless it must be assumed that the
+ // process invoking soffice itself wants to wait for soffice to
+ // finish:
+ if (!wait) {
+ return;
+ }
+ int stat;
+ if (waitpid(pid, &stat, 0) == pid && WIFEXITED(stat)) {
+ _exit(WEXITSTATUS(stat));
+ }
+ }
+ }
+ std::abort();
+#endif
+#else
+ (void) passArguments; // avoid warnings
+#endif
+}
+
+#if HAVE_FEATURE_UPDATE_MAR
+bool isTimeForUpdateCheck()
+{
+ sal_uInt64 nLastUpdate = officecfg::Office::Update::Update::LastUpdateTime::get();
+ sal_uInt64 nNow = tools::Time::GetSystemTicks();
+
+ sal_uInt64 n7DayInMS = 1000 * 60 * 60 * 24 * 7; // 7 days in ms
+ if (nNow - n7DayInMS >= nLastUpdate)
+ return true;
+
+ return false;
+}
+#endif
+
+}
+
+void Desktop::Exception(ExceptionCategory nCategory)
+{
+ // protect against recursive calls
+ static bool bInException = false;
+
+#if HAVE_FEATURE_BREAKPAD
+ CrashReporter::removeExceptionHandler(); // disallow re-entry
+#endif
+
+ SystemWindowFlags nOldMode = Application::GetSystemWindowMode();
+ Application::SetSystemWindowMode( nOldMode & ~SystemWindowFlags::NOAUTOMODE );
+ if ( bInException )
+ {
+ Application::Abort( OUString() );
+ }
+
+ bInException = true;
+ const CommandLineArgs& rArgs = GetCommandLineArgs();
+
+ // save all modified documents ... if it's allowed doing so.
+ bool bRestart = false;
+ bool bAllowRecoveryAndSessionManagement = (
+ ( !rArgs.IsNoRestore() ) && // some use cases of office must work without recovery
+ ( !rArgs.IsHeadless() ) &&
+ ( nCategory != ExceptionCategory::UserInterface ) && // recovery can't work without UI ... but UI layer seems to be the reason for this crash
+ ( Application::IsInExecute() ) // crashes during startup and shutdown should be ignored (they indicate a corrupted installation...)
+ );
+ if ( bAllowRecoveryAndSessionManagement )
+ {
+ // Save all open documents so they will be reopened
+ // the next time the application is started
+ // returns true if at least one document could be saved...
+#if !ENABLE_WASM_STRIP_RECOVERYUI
+ bRestart = impl_callRecoveryUI(
+ true , // force emergency save
+ false);
+#endif
+ }
+
+ FlushConfiguration();
+
+ m_xLockfile.reset();
+
+ if( bRestart )
+ {
+ RequestHandler::Disable();
+ if( pSignalHandler )
+ osl_removeSignalHandler( pSignalHandler );
+
+ restartOnMac(false);
+#if !ENABLE_WASM_STRIP_SPLASH
+ if ( m_rSplashScreen.is() )
+ m_rSplashScreen->reset();
+#endif
+
+ _exit( EXITHELPER_CRASH_WITH_RESTART );
+ }
+ else
+ {
+ Application::Abort( OUString() );
+ }
+
+ OSL_ASSERT(false); // unreachable
+}
+
+void Desktop::AppEvent( const ApplicationEvent& rAppEvent )
+{
+ HandleAppEvent( rAppEvent );
+}
+
+namespace {
+
+class JVMloadThread : public salhelper::Thread {
+public:
+ JVMloadThread() : salhelper::Thread("Preload JVM thread")
+ {
+ }
+
+private:
+ virtual void execute() override final
+ {
+ Reference< XMultiServiceFactory > xSMgr = comphelper::getProcessServiceFactory();
+
+ Reference< css::loader::XImplementationLoader > xJavaComponentLoader(
+ xSMgr->createInstance("com.sun.star.comp.stoc.JavaComponentLoader"),
+ css::uno::UNO_QUERY_THROW);
+
+ if (xJavaComponentLoader.is())
+ {
+ const css::uno::Reference< ::com::sun::star::registry::XRegistryKey > xRegistryKey;
+ try
+ {
+ xJavaComponentLoader->activate("", "", "", xRegistryKey);
+ }
+ catch (...)
+ {
+ SAL_WARN("desktop.app", "Cannot activate factory during JVM preloading");
+ }
+ }
+ }
+};
+
+struct ExecuteGlobals
+{
+ Reference < css::document::XDocumentEventListener > xGlobalBroadcaster;
+ bool bRestartRequested;
+ std::unique_ptr<SvtCTLOptions> pCTLLanguageOptions;
+ std::unique_ptr<SvtPathOptions> pPathOptions;
+ rtl::Reference< JVMloadThread > xJVMloadThread;
+
+ ExecuteGlobals()
+ : bRestartRequested( false )
+ {}
+};
+
+}
+
+static ExecuteGlobals* pExecGlobals = nullptr;
+
+int Desktop::Main()
+{
+ pExecGlobals = new ExecuteGlobals();
+
+ // Remember current context object
+ css::uno::ContextLayer layer( css::uno::getCurrentContext() );
+
+ if ( m_aBootstrapError != BE_OK )
+ {
+ HandleBootstrapErrors( m_aBootstrapError, m_aBootstrapErrorMessage );
+ return EXIT_FAILURE;
+ }
+
+ BootstrapStatus eStatus = GetBootstrapStatus();
+ if (eStatus == BS_TERMINATE) {
+ return EXIT_SUCCESS;
+ }
+
+ // Detect desktop environment - need to do this as early as possible
+ css::uno::setCurrentContext( new DesktopContext( css::uno::getCurrentContext() ) );
+
+ if (officecfg::Office::Common::Misc::PreloadJVM::get() && pExecGlobals)
+ {
+ SAL_INFO("desktop.app", "Preload JVM");
+
+ // pre-load JVM
+ pExecGlobals->xJVMloadThread = new JVMloadThread();
+ pExecGlobals->xJVMloadThread->launch();
+ }
+
+ CommandLineArgs& rCmdLineArgs = GetCommandLineArgs();
+
+ Translate::SetReadStringHook(ReplaceStringHookProc);
+
+ // Startup screen
+#if !ENABLE_WASM_STRIP_SPLASH
+ OpenSplashScreen();
+#endif
+
+ SetSplashScreenProgress(10);
+
+ userinstall::Status inst_fin = userinstall::finalize();
+ if (inst_fin != userinstall::EXISTED && inst_fin != userinstall::CREATED)
+ {
+ SAL_WARN( "desktop.app", "userinstall failed: " << inst_fin);
+ if ( inst_fin == userinstall::ERROR_NO_SPACE )
+ HandleBootstrapErrors(
+ BE_USERINSTALL_NOTENOUGHDISKSPACE, OUString() );
+ else if ( inst_fin == userinstall::ERROR_CANT_WRITE )
+ HandleBootstrapErrors( BE_USERINSTALL_NOWRITEACCESS, OUString() );
+ else
+ HandleBootstrapErrors( BE_USERINSTALL_FAILED, OUString() );
+ return EXIT_FAILURE;
+ }
+ // refresh path information
+ utl::Bootstrap::reloadData();
+ SetSplashScreenProgress(20);
+
+ Reference< XComponentContext > xContext = ::comphelper::getProcessComponentContext();
+
+ Reference< XRestartManager > xRestartManager( OfficeRestartManager::get(xContext) );
+
+ Reference< XDesktop2 > xDesktop;
+
+ RegisterServices();
+
+ SetSplashScreenProgress(25);
+
+#if HAVE_FEATURE_DESKTOP && !defined(EMSCRIPTEN)
+ // check user installation directory for lockfile so we can be sure
+ // there is no other instance using our data files from a remote host
+
+ bool bMustLockProfile = ( getenv( "SAL_NOLOCK_PROFILE" ) == nullptr );
+ if ( bMustLockProfile )
+ {
+ m_xLockfile.reset(new Lockfile);
+
+ if ( !rCmdLineArgs.IsHeadless() && !rCmdLineArgs.IsInvisible() &&
+ !rCmdLineArgs.IsNoLockcheck() && !m_xLockfile->check( Lockfile_execWarning ))
+ {
+ // Lockfile exists, and user clicked 'no'
+ return EXIT_FAILURE;
+ }
+ }
+
+ // check if accessibility is enabled but not working and allow to quit
+ if( Application::GetSettings().GetMiscSettings().GetEnableATToolSupport() )
+ {
+ if( !InitAccessBridge() )
+ return EXIT_FAILURE;
+ }
+#endif
+
+ // terminate if requested...
+ if( rCmdLineArgs.IsTerminateAfterInit() )
+ return EXIT_SUCCESS;
+
+ // Read the common configuration items for optimization purpose
+ if ( !InitializeConfiguration() )
+ return EXIT_FAILURE;
+
+ SetSplashScreenProgress(30);
+
+ // create title string
+ OUString aTitle(ReplaceStringHookProc(RID_APPTITLE));
+#ifdef DBG_UTIL
+ //include buildid in non product builds
+ aTitle += " [" + utl::Bootstrap::getBuildIdData("development") + "]";
+#endif
+
+ SetDisplayName( aTitle );
+ SetSplashScreenProgress(35);
+ pExecGlobals->pPathOptions.reset( new SvtPathOptions);
+ SetSplashScreenProgress(40);
+
+ xDesktop = css::frame::Desktop::create( xContext );
+
+#if HAVE_FEATURE_UPDATE_MAR
+ const char* pUpdaterTestEnable = std::getenv("LIBO_UPDATER_TEST_ENABLE");
+ if (pUpdaterTestEnable || officecfg::Office::Update::Update::Enabled::get())
+ {
+ // check if we just updated
+ const char* pUpdaterRunning = std::getenv("LIBO_UPDATER_TEST_RUNNING");
+ bool bUpdateRunning = officecfg::Office::Update::Update::UpdateRunning::get() || pUpdaterRunning;
+ if (bUpdateRunning)
+ {
+ OUString aSeeAlso = officecfg::Office::Update::Update::SeeAlso::get();
+ OUString aOldBuildID = officecfg::Office::Update::Update::OldBuildID::get();
+
+ OUString aBuildID = Updater::getBuildID();
+ if (aOldBuildID == aBuildID)
+ {
+ Updater::log("Old and new Build ID are the same. No Updating took place.");
+ }
+ else
+ {
+ if (!aSeeAlso.isEmpty())
+ {
+ SAL_INFO("desktop.updater", "See also: " << aSeeAlso);
+ Reference< css::system::XSystemShellExecute > xSystemShell(
+ SystemShellExecute::create(::comphelper::getProcessComponentContext()) );
+
+ xSystemShell->execute( aSeeAlso, OUString(), SystemShellExecuteFlags::URIS_ONLY );
+ }
+ }
+
+ // reset all the configuration values,
+ // all values need to be read before this code
+ std::shared_ptr< comphelper::ConfigurationChanges > batch(
+ comphelper::ConfigurationChanges::create());
+ officecfg::Office::Update::Update::UpdateRunning::set(false, batch);
+ officecfg::Office::Update::Update::SeeAlso::set(OUString(), batch);
+ officecfg::Office::Update::Update::OldBuildID::set(OUString(), batch);
+ batch->commit();
+
+ Updater::removeUpdateFiles();
+ }
+
+ osl::DirectoryItem aUpdateFile;
+ osl::DirectoryItem::get(Updater::getUpdateFileURL(), aUpdateFile);
+
+ const char* pUpdaterTestUpdate = std::getenv("LIBO_UPDATER_TEST_UPDATE");
+ const char* pForcedUpdateCheck = std::getenv("LIBO_UPDATER_TEST_UPDATE_CHECK");
+ if (pUpdaterTestUpdate || aUpdateFile.is())
+ {
+ OUString aBuildID("${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE("version") ":buildid}");
+ rtl::Bootstrap::expandMacros(aBuildID);
+ std::shared_ptr< comphelper::ConfigurationChanges > batch(
+ comphelper::ConfigurationChanges::create());
+ officecfg::Office::Update::Update::OldBuildID::set(aBuildID, batch);
+ officecfg::Office::Update::Update::UpdateRunning::set(true, batch);
+ batch->commit();
+
+ // make sure the change is written to the configuration before we start the update
+ css::uno::Reference<css::util::XFlushable> xFlushable(css::configuration::theDefaultProvider::get(xContext), UNO_QUERY);
+ xFlushable->flush();
+ // avoid the old oosplash staying around
+ CloseSplashScreen();
+ bool bSuccess = update();
+ if (bSuccess)
+ {
+ xDesktop->terminate();
+ return EXIT_SUCCESS;
+ }
+ }
+ else if (isTimeForUpdateCheck() || pForcedUpdateCheck)
+ {
+ sal_uInt64 nNow = tools::Time::GetSystemTicks();
+ Updater::log("Update Check Time: " + OUString::number(nNow));
+ std::shared_ptr< comphelper::ConfigurationChanges > batch(
+ comphelper::ConfigurationChanges::create());
+ officecfg::Office::Update::Update::LastUpdateTime::set(nNow, batch);
+ batch->commit();
+ m_aUpdateThread = std::thread(update_checker);
+ }
+ }
+#endif
+
+ // create service for loading SFX (still needed in startup)
+ pExecGlobals->xGlobalBroadcaster = Reference < css::document::XDocumentEventListener >
+ ( css::frame::theGlobalEventBroadcaster::get(xContext), UNO_SET_THROW );
+
+ /* ensure existence of a default window that messages can be dispatched to
+ This is for the benefit of testtool which uses PostUserEvent extensively
+ and else can deadlock while creating this window from another thread while
+ the main thread is not yet in the event loop.
+ */
+ Application::GetDefaultDevice();
+
+#if HAVE_FEATURE_EXTENSIONS
+ // Check if bundled or shared extensions were added /removed
+ // and process those extensions (has to be done before checking
+ // the extension dependencies!
+ SynchronizeExtensionRepositories(m_bCleanedExtensionCache, this);
+ bool bAbort = CheckExtensionDependencies();
+ if ( bAbort )
+ return EXIT_FAILURE;
+
+ if (inst_fin == userinstall::CREATED)
+ {
+ Migration::migrateSettingsIfNecessary();
+ }
+#endif
+
+ // keep a language options instance...
+ pExecGlobals->pCTLLanguageOptions.reset( new SvtCTLOptions(true));
+
+ css::document::DocumentEvent aEvent;
+ aEvent.EventName = "OnStartApp";
+ pExecGlobals->xGlobalBroadcaster->documentEventOccured(aEvent);
+
+ SetSplashScreenProgress(50);
+
+ // Backing Component
+ bool bCrashed = false;
+ bool bExistsRecoveryData = false;
+ bool bExistsSessionData = false;
+
+ impl_checkRecoveryState(bCrashed, bExistsRecoveryData, bExistsSessionData);
+
+ OUString pidfileName = rCmdLineArgs.GetPidfileName();
+ if ( !pidfileName.isEmpty() )
+ {
+ OUString pidfileURL;
+
+ if ( osl_getFileURLFromSystemPath(pidfileName.pData, &pidfileURL.pData) == osl_File_E_None )
+ {
+ osl::File pidfile( pidfileURL );
+ osl::FileBase::RC rc;
+
+ osl::File::remove( pidfileURL );
+ if ( (rc = pidfile.open( osl_File_OpenFlag_Write | osl_File_OpenFlag_Create ) ) == osl::File::E_None )
+ {
+ OString pid( OString::number( GETPID() ) );
+ sal_uInt64 written = 0;
+ if ( pidfile.write(pid.getStr(), pid.getLength(), written) != osl::File::E_None )
+ {
+ SAL_WARN("desktop.app", "cannot write pidfile " << pidfile.getURL());
+ }
+ pidfile.close();
+ }
+ else
+ {
+ SAL_WARN("desktop.app", "cannot open pidfile " << pidfile.getURL() << rc);
+ }
+ }
+ else
+ {
+ SAL_WARN("desktop.app", "cannot get pidfile URL from path" << pidfileName);
+ }
+ }
+
+ pExecGlobals->bRestartRequested = xRestartManager->isRestartRequested(true);
+ if ( !pExecGlobals->bRestartRequested )
+ {
+ if ((!rCmdLineArgs.WantsToLoadDocument() && !rCmdLineArgs.IsInvisible() && !rCmdLineArgs.IsHeadless() && !rCmdLineArgs.IsQuickstart()) &&
+ (SvtModuleOptions().IsModuleInstalled(SvtModuleOptions::EModule::STARTMODULE)) &&
+ (!bExistsRecoveryData ) &&
+ (!bExistsSessionData ) &&
+ (!Application::AnyInput( VclInputFlags::APPEVENT ) ))
+ {
+ ShowBackingComponent(this);
+ }
+ }
+
+ SetSplashScreenProgress(55);
+
+ svtools::ApplyFontSubstitutionsToVcl();
+
+ SvtTabAppearanceCfg::SetInitialized();
+ SvtTabAppearanceCfg::SetApplicationDefaults( this );
+ SvtAccessibilityOptions::SetVCLSettings();
+ SetSplashScreenProgress(60);
+
+ if ( !pExecGlobals->bRestartRequested )
+ {
+ Application::SetFilterHdl( LINK( this, Desktop, ImplInitFilterHdl ) );
+
+ // Preload function depends on an initialized sfx application!
+ SetSplashScreenProgress(75);
+
+ // use system window dialogs
+ Application::SetSystemWindowMode( SystemWindowFlags::DIALOG );
+
+ SetSplashScreenProgress(80);
+
+ if ( !rCmdLineArgs.IsInvisible() &&
+ !rCmdLineArgs.IsNoQuickstart() )
+ InitializeQuickstartMode( xContext );
+
+ if ( xDesktop.is() )
+ xDesktop->addTerminateListener( new RequestHandlerController );
+ SetSplashScreenProgress(100);
+
+ // FIXME: move this somewhere sensible.
+#if HAVE_FEATURE_OPENCL
+ CheckOpenCLCompute(xDesktop);
+#endif
+
+#if !defined(EMSCRIPTEN)
+ //Running the VCL graphics rendering tests
+ const char * pDisplay = std::getenv("DISPLAY");
+ if (!pDisplay || pDisplay[0] == ':')
+ {
+ runGraphicsRenderTests();
+ }
+#endif
+
+ // Post user event to startup first application component window
+ // We have to send this OpenClients message short before execute() to
+ // minimize the risk that this message overtakes type detection construction!!
+ Application::PostUserEvent( LINK( this, Desktop, OpenClients_Impl ) );
+
+ // Post event to enable acceptors
+ Application::PostUserEvent( LINK( this, Desktop, EnableAcceptors_Impl) );
+
+ // call Application::Execute to process messages in vcl message loop
+#if HAVE_FEATURE_JAVA
+ // The JavaContext contains an interaction handler which is used when
+ // the creation of a Java Virtual Machine fails
+ css::uno::ContextLayer layer2(
+ new svt::JavaContext( css::uno::getCurrentContext() ) );
+#endif
+ // check whether the shutdown is caused by restart just before entering the Execute
+ pExecGlobals->bRestartRequested = pExecGlobals->bRestartRequested ||
+ xRestartManager->isRestartRequested(true);
+
+ if ( !pExecGlobals->bRestartRequested )
+ {
+ // if this run of the office is triggered by restart, some additional actions should be done
+ DoRestartActionsIfNecessary( !rCmdLineArgs.IsInvisible() && !rCmdLineArgs.IsNoQuickstart() );
+
+ Execute();
+ }
+ }
+ else
+ {
+ if (xDesktop.is())
+ xDesktop->terminate();
+ }
+ // CAUTION: you do not necessarily get here e.g. on the Mac.
+ // please put all deinitialization code into doShutdown
+ return doShutdown();
+}
+
+int Desktop::doShutdown()
+{
+ if( ! pExecGlobals )
+ return EXIT_SUCCESS;
+
+ if (m_aUpdateThread.joinable())
+ m_aUpdateThread.join();
+
+ if (pExecGlobals->xJVMloadThread.is())
+ {
+ pExecGlobals->xJVMloadThread->join();
+ pExecGlobals->xJVMloadThread.clear();
+ }
+
+ pExecGlobals->bRestartRequested = pExecGlobals->bRestartRequested ||
+ OfficeRestartManager::get(comphelper::getProcessComponentContext())->
+ isRestartRequested(true);
+ if ( pExecGlobals->bRestartRequested )
+ SetRestartState();
+
+ const CommandLineArgs& rCmdLineArgs = GetCommandLineArgs();
+ OUString pidfileName = rCmdLineArgs.GetPidfileName();
+ if ( !pidfileName.isEmpty() )
+ {
+ OUString pidfileURL;
+
+ if ( osl_getFileURLFromSystemPath(pidfileName.pData, &pidfileURL.pData) == osl_File_E_None )
+ {
+ if ( osl::File::remove( pidfileURL ) != osl::FileBase::E_None )
+ {
+ SAL_WARN("desktop.app", "shutdown: cannot remove pidfile " << pidfileURL);
+ }
+ }
+ else
+ {
+ SAL_WARN("desktop.app", "shutdown: cannot get pidfile URL from path" << pidfileName);
+ }
+ }
+
+ // remove temp directory
+ RemoveTemporaryDirectory();
+ flatpak::removeTemporaryHtmlDirectory();
+
+ // flush evtl. configuration changes so that all config files in user
+ // dir are written
+ FlushConfiguration();
+
+ if (pExecGlobals->bRestartRequested)
+ {
+ // tdf#128523
+ RemoveIconCacheDirectory();
+
+ // a restart is already requested, usually due to a configuration change
+ // that needs a restart to get active. If this is the case, do not try
+ // to use SecureUserConfig to safe this still untested new configuration
+ }
+ else
+ {
+ // Test if SecureUserConfig is active. If yes and we are at this point, regular shutdown
+ // is in progress and the currently used configuration was working. Try to secure this
+ // working configuration for later eventually necessary restores
+ comphelper::BackupFileHelper aBackupFileHelper;
+
+ aBackupFileHelper.tryPush();
+ aBackupFileHelper.tryPushExtensionInfo();
+ }
+
+ // The acceptors in the AcceptorMap must be released (in DeregisterServices)
+ // with the solar mutex unlocked, to avoid deadlock:
+ {
+ SolarMutexReleaser aReleaser;
+ DeregisterServices();
+#if HAVE_FEATURE_SCRIPTING
+ StarBASIC::DetachAllDocBasicItems();
+#endif
+ }
+
+ // be sure that path/language options gets destroyed before
+ // UCB is deinitialized
+ pExecGlobals->pCTLLanguageOptions.reset();
+ pExecGlobals->pPathOptions.reset();
+
+ comphelper::ThreadPool::getSharedOptimalPool().shutdown();
+
+ bool bRR = pExecGlobals->bRestartRequested;
+ delete pExecGlobals;
+ pExecGlobals = nullptr;
+
+ if ( bRR )
+ {
+ restartOnMac(true);
+#if !ENABLE_WASM_STRIP_SPLASH
+ if ( m_rSplashScreen.is() )
+ m_rSplashScreen->reset();
+#endif
+
+ return EXITHELPER_NORMAL_RESTART;
+ }
+ return EXIT_SUCCESS;
+}
+
+IMPL_STATIC_LINK( Desktop, ImplInitFilterHdl, ::ConvertData&, rData, bool )
+{
+ return GraphicFilter::GetGraphicFilter().GetFilterCallback().Call( rData );
+}
+
+bool Desktop::InitializeConfiguration()
+{
+ try
+ {
+ css::configuration::theDefaultProvider::get(
+ comphelper::getProcessComponentContext() );
+ return true;
+ }
+ catch( css::lang::ServiceNotRegisteredException & e )
+ {
+ HandleBootstrapErrors(
+ Desktop::BE_UNO_SERVICE_CONFIG_MISSING, e.Message );
+ }
+ catch( const css::configuration::MissingBootstrapFileException& e )
+ {
+ OUString aMsg( CreateErrorMsgString( utl::Bootstrap::MISSING_BOOTSTRAP_FILE,
+ e.BootstrapFileURL ));
+ HandleBootstrapPathErrors( ::utl::Bootstrap::INVALID_USER_INSTALL, aMsg );
+ }
+ catch( const css::configuration::InvalidBootstrapFileException& e )
+ {
+ OUString aMsg( CreateErrorMsgString( utl::Bootstrap::INVALID_BOOTSTRAP_FILE_ENTRY,
+ e.BootstrapFileURL ));
+ HandleBootstrapPathErrors( ::utl::Bootstrap::INVALID_BASE_INSTALL, aMsg );
+ }
+ catch( const css::configuration::InstallationIncompleteException& )
+ {
+ OUString aVersionFileURL;
+ OUString aMsg;
+ utl::Bootstrap::PathStatus aPathStatus = utl::Bootstrap::locateVersionFile( aVersionFileURL );
+ if ( aPathStatus == utl::Bootstrap::PATH_EXISTS )
+ aMsg = CreateErrorMsgString( utl::Bootstrap::MISSING_VERSION_FILE_ENTRY, aVersionFileURL );
+ else
+ aMsg = CreateErrorMsgString( utl::Bootstrap::MISSING_VERSION_FILE, aVersionFileURL );
+
+ HandleBootstrapPathErrors( ::utl::Bootstrap::MISSING_USER_INSTALL, aMsg );
+ }
+ catch ( const css::configuration::backend::BackendAccessException& exception)
+ {
+ // [cm122549] It is assumed in this case that the message
+ // coming from InitConfiguration (in fact CreateApplicationConf...)
+ // is suitable for display directly.
+ FatalError( MakeStartupErrorMessage( exception.Message ) );
+ }
+ catch ( const css::configuration::backend::BackendSetupException& exception)
+ {
+ // [cm122549] It is assumed in this case that the message
+ // coming from InitConfiguration (in fact CreateApplicationConf...)
+ // is suitable for display directly.
+ FatalError( MakeStartupErrorMessage( exception.Message ) );
+ }
+ catch ( const css::configuration::CannotLoadConfigurationException& )
+ {
+ OUString aMsg( CreateErrorMsgString( utl::Bootstrap::INVALID_BOOTSTRAP_DATA,
+ OUString() ));
+ HandleBootstrapPathErrors( ::utl::Bootstrap::INVALID_BASE_INSTALL, aMsg );
+ }
+ catch( const css::uno::Exception& )
+ {
+ OUString aMsg( CreateErrorMsgString( utl::Bootstrap::INVALID_BOOTSTRAP_DATA,
+ OUString() ));
+ HandleBootstrapPathErrors( ::utl::Bootstrap::INVALID_BASE_INSTALL, aMsg );
+ }
+ return false;
+}
+
+void Desktop::FlushConfiguration()
+{
+ css::uno::Reference< css::util::XFlushable >(
+ css::configuration::theDefaultProvider::get(
+ comphelper::getProcessComponentContext()),
+ css::uno::UNO_QUERY_THROW)->flush();
+}
+
+bool Desktop::InitializeQuickstartMode( const Reference< XComponentContext >& rxContext )
+{
+ try
+ {
+ // the shutdown icon sits in the systray and allows the user to keep
+ // the office instance running for quicker restart
+ // this will only be activated if --quickstart was specified on cmdline
+
+ bool bQuickstart = shouldLaunchQuickstart();
+
+ // Try to instantiate quickstart service. This service is not mandatory, so
+ // do nothing if service is not available
+
+ // #i105753# the following if was invented for performance
+ // unfortunately this broke the Mac behavior which is to always run
+ // in quickstart mode since Mac applications do not usually quit
+ // when the last document closes.
+ // Note that this claim that on macOS we "always run in quickstart mode"
+ // has nothing to do with (quick) *starting* (i.e. starting automatically
+ // when the user logs in), though, but with not quitting when no documents
+ // are open.
+ #ifndef MACOSX
+ if ( bQuickstart )
+ #endif
+ {
+ css::office::Quickstart::createStart(rxContext, bQuickstart);
+ }
+ return true;
+ }
+ catch( const css::uno::Exception& )
+ {
+ return false;
+ }
+}
+
+void Desktop::OverrideSystemSettings( AllSettings& rSettings )
+{
+ if ( !SvtTabAppearanceCfg::IsInitialized () )
+ return;
+
+ StyleSettings hStyleSettings = rSettings.GetStyleSettings();
+ MouseSettings hMouseSettings = rSettings.GetMouseSettings();
+
+ DragFullOptions nDragFullOptions = hStyleSettings.GetDragFullOptions();
+
+ sal_uInt16 nDragMode = officecfg::Office::Common::View::Window::Drag::get();
+ switch ( nDragMode )
+ {
+ case 0: //FullWindow:
+ nDragFullOptions |= DragFullOptions::All;
+ break;
+ case 1: // Frame:
+ nDragFullOptions &= ~DragFullOptions::All;
+ break;
+ case 2: // SystemDep
+ default:
+ break;
+ }
+
+ MouseFollowFlags nFollow = hMouseSettings.GetFollow();
+ bool bMenuFollowMouse = officecfg::Office::Common::View::Menu::FollowMouse::get();
+ hMouseSettings.SetFollow( bMenuFollowMouse ? (nFollow|MouseFollowFlags::Menu) : (nFollow&~MouseFollowFlags::Menu));
+ rSettings.SetMouseSettings(hMouseSettings);
+
+ bool bMenuIcons = officecfg::Office::Common::View::Menu::ShowIconsInMenues::get();
+ bool bSystemMenuIcons = officecfg::Office::Common::View::Menu::IsSystemIconsInMenus::get();
+ TriState eMenuIcons = bSystemMenuIcons ? TRISTATE_INDET : static_cast<TriState>(bMenuIcons);
+ hStyleSettings.SetUseImagesInMenus(eMenuIcons);
+ hStyleSettings.SetContextMenuShortcuts(static_cast<TriState>(officecfg::Office::Common::View::Menu::ShortcutsInContextMenus::get()));
+ hStyleSettings.SetDragFullOptions( nDragFullOptions );
+ rSettings.SetStyleSettings ( hStyleSettings );
+}
+
+namespace {
+
+class ExitTimer : public Timer
+{
+ public:
+ ExitTimer() : Timer("desktop ExitTimer")
+ {
+ SetTimeout(500);
+ Start();
+ }
+ virtual void Invoke() override
+ {
+ _exit(42);
+ }
+};
+
+}
+
+IMPL_LINK_NOARG(Desktop, OpenClients_Impl, void*, void)
+{
+ // #i114963#
+ // Enable IPC thread before OpenClients
+ //
+ // This is because it is possible for another client to connect during the OpenClients() call.
+ // This can happen on Windows when document is printed (not opened) and another client wants to print (when printing multiple documents).
+ // If the IPC thread is enabled after OpenClients, then the client will not be processed because the application will exit after printing. i.e RequestHandler::AreRequestsPending() will always return false
+ //
+ // ALSO:
+ //
+ // Multiple clients may request simultaneous connections.
+ // When this server closes down it attempts to recreate the pipe (in RequestHandler::Disable()).
+ // It's possible that the client has a pending connection request.
+ // When the IPC thread is not running, this connection locks (because maPipe.accept()) is never called
+ RequestHandler::SetReady(true);
+ OpenClients();
+
+ CloseSplashScreen();
+ CheckFirstRun( );
+#ifdef _WIN32
+ bool bDontShowDialogs
+ = Application::IsHeadlessModeEnabled(); // uitest.uicheck fails when the dialog is open
+ for (sal_uInt16 i = 0; !bDontShowDialogs && i < Application::GetCommandLineParamCount(); i++)
+ {
+ if (Application::GetCommandLineParam(i) == "--nologo")
+ bDontShowDialogs = true;
+ }
+ if (!bDontShowDialogs)
+ vcl::fileregistration::CheckFileExtRegistration(SfxGetpApp()->GetTopWindow());
+ // Registers a COM class factory of the service manager with the windows operating system.
+ Reference< XMultiServiceFactory > xSMgr= comphelper::getProcessServiceFactory();
+ xSMgr->createInstance("com.sun.star.bridge.OleApplicationRegistration");
+ xSMgr->createInstance("com.sun.star.comp.ole.EmbedServer");
+#endif
+ const char *pExitPostStartup = getenv ("OOO_EXIT_POST_STARTUP");
+ if (pExitPostStartup && *pExitPostStartup)
+ new ExitTimer();
+}
+
+void Desktop::OpenClients()
+{
+
+ const CommandLineArgs& rArgs = GetCommandLineArgs();
+
+ if (!rArgs.IsQuickstart())
+ {
+ OUString aHelpModule;
+ if (rArgs.IsHelpWriter()) {
+ aHelpModule = "swriter";
+ } else if (rArgs.IsHelpCalc()) {
+ aHelpModule = "scalc";
+ } else if (rArgs.IsHelpDraw()) {
+ aHelpModule = "sdraw";
+ } else if (rArgs.IsHelpImpress()) {
+ aHelpModule = "simpress";
+ } else if (rArgs.IsHelpBase()) {
+ aHelpModule = "sdatabase";
+ } else if (rArgs.IsHelpBasic()) {
+ aHelpModule = "sbasic";
+ } else if (rArgs.IsHelpMath()) {
+ aHelpModule = "smath";
+ }
+ if (!aHelpModule.isEmpty()) {
+ OUString aHelpURL = "vnd.sun.star.help://"
+ + aHelpModule
+ + "/start?Language="
+ + utl::ConfigManager::getUILocale();
+#if defined UNX
+ aHelpURL += "&System=UNX";
+#elif defined _WIN32
+ aHelpURL += "&System=WIN";
+#endif
+ Application::GetHelp()->Start(aHelpURL);
+ return;
+ }
+ }
+
+ // Disable AutoSave feature in case "--norestore" or a similar command line switch is set on the command line.
+ // The reason behind: AutoSave/EmergencySave/AutoRecovery share the same data.
+ // But the require that all documents, which are saved as backup should exists inside
+ // memory. May be this mechanism will be inconsistent if the configuration exists...
+ // but no document inside memory corresponds to this data.
+ // Further it's not acceptable to recover such documents without any UI. It can
+ // need some time, where the user won't see any results and wait for finishing the office startup...
+ bool bAllowRecoveryAndSessionManagement = ( !rArgs.IsNoRestore() ) && ( !rArgs.IsHeadless() );
+
+#if !defined ANDROID
+ // Enter safe mode if requested
+ if (Application::IsSafeModeEnabled()) {
+ handleSafeMode();
+ }
+#endif
+
+#if HAVE_FEATURE_BREAKPAD
+ if (officecfg::Office::Common::Misc::CrashReport::get() && CrashReporter::crashReportInfoExists())
+ handleCrashReport();
+#endif
+
+ if ( ! bAllowRecoveryAndSessionManagement )
+ {
+ try
+ {
+ Reference< XDispatch > xRecovery = css::frame::theAutoRecovery::get( ::comphelper::getProcessComponentContext() );
+ Reference< css::util::XURLTransformer > xParser = css::util::URLTransformer::create( ::comphelper::getProcessComponentContext() );
+
+ css::util::URL aCmd;
+ aCmd.Complete = "vnd.sun.star.autorecovery:/disableRecovery";
+ xParser->parseStrict(aCmd);
+
+ xRecovery->dispatch(aCmd, css::uno::Sequence< css::beans::PropertyValue >());
+ }
+ catch(const css::uno::Exception&)
+ {
+ TOOLS_WARN_EXCEPTION( "desktop.app", "Could not disable AutoRecovery.");
+ }
+ }
+ else
+ {
+ bool bExistsRecoveryData = false;
+#if !ENABLE_WASM_STRIP_RECOVERYUI
+ bool bCrashed = false;
+ bool bExistsSessionData = false;
+ bool const bDisableRecovery
+ = getenv("OOO_DISABLE_RECOVERY") != nullptr
+ || IsOnSystemEventLoop()
+ || !officecfg::Office::Recovery::RecoveryInfo::Enabled::get();
+
+ impl_checkRecoveryState(bCrashed, bExistsRecoveryData, bExistsSessionData);
+
+ if ( !bDisableRecovery &&
+ (
+ bExistsRecoveryData || // => crash with files => recovery
+ bCrashed // => crash without files => error report
+ )
+ )
+ {
+ try
+ {
+ impl_callRecoveryUI(
+ false , // false => force recovery instead of emergency save
+ bExistsRecoveryData);
+ }
+ catch(const css::uno::Exception&)
+ {
+ TOOLS_WARN_EXCEPTION( "desktop.app", "Error during recovery");
+ }
+ }
+#endif
+
+ Reference< XSessionManagerListener2 > xSessionListener;
+ try
+ {
+ // specifies whether the UI-interaction on Session shutdown is allowed
+ bool bUIOnSessionShutdownAllowed = officecfg::Office::Recovery::SessionShutdown::DocumentStoreUIEnabled::get();
+ xSessionListener = SessionListener::createWithOnQuitFlag(
+ ::comphelper::getProcessComponentContext(), bUIOnSessionShutdownAllowed);
+ }
+ catch(const css::uno::Exception&)
+ {
+ TOOLS_WARN_EXCEPTION( "desktop.app", "Registration of session listener failed");
+ }
+
+ if ( !bExistsRecoveryData && xSessionListener.is() )
+ {
+ // session management
+ try
+ {
+ xSessionListener->doRestore();
+ }
+ catch(const css::uno::Exception&)
+ {
+ TOOLS_WARN_EXCEPTION( "desktop.app", "Error in session management");
+ }
+ }
+ }
+
+ // write this information here to avoid depending on vcl in the crash reporter lib
+ CrashReporter::addKeyValue("Language", Application::GetSettings().GetLanguageTag().getBcp47(), CrashReporter::Create);
+
+ RequestHandler::EnableRequests();
+
+ ProcessDocumentsRequest aRequest(rArgs.getCwdUrl());
+ aRequest.aOpenList = rArgs.GetOpenList();
+ aRequest.aViewList = rArgs.GetViewList();
+ aRequest.aStartList = rArgs.GetStartList();
+ aRequest.aPrintList = rArgs.GetPrintList();
+ aRequest.aPrintToList = rArgs.GetPrintToList();
+ aRequest.aPrinterName = rArgs.GetPrinterName();
+ aRequest.aForceOpenList = rArgs.GetForceOpenList();
+ aRequest.aForceNewList = rArgs.GetForceNewList();
+ aRequest.aConversionList = rArgs.GetConversionList();
+ aRequest.aConversionParams = rArgs.GetConversionParams();
+ aRequest.aConversionOut = rArgs.GetConversionOut();
+ aRequest.aImageConversionType = rArgs.GetImageConversionType();
+ aRequest.aInFilter = rArgs.GetInFilter();
+ aRequest.bTextCat = rArgs.IsTextCat();
+ aRequest.bScriptCat = rArgs.IsScriptCat();
+
+ if ( !aRequest.aOpenList.empty() ||
+ !aRequest.aViewList.empty() ||
+ !aRequest.aStartList.empty() ||
+ !aRequest.aPrintList.empty() ||
+ !aRequest.aForceOpenList.empty() ||
+ !aRequest.aForceNewList.empty() ||
+ ( !aRequest.aPrintToList.empty() && !aRequest.aPrinterName.isEmpty() ) ||
+ !aRequest.aConversionList.empty() )
+ {
+ if ( rArgs.HasModuleParam() )
+ {
+ SvtModuleOptions aOpt;
+
+ // Support command line parameters to start a module (as preselection)
+ if ( rArgs.IsWriter() && aOpt.IsModuleInstalled( SvtModuleOptions::EModule::WRITER ) )
+ aRequest.aModule = aOpt.GetFactoryName( SvtModuleOptions::EFactory::WRITER );
+ else if ( rArgs.IsCalc() && aOpt.IsModuleInstalled( SvtModuleOptions::EModule::CALC ) )
+ aRequest.aModule = aOpt.GetFactoryName( SvtModuleOptions::EFactory::CALC );
+ else if ( rArgs.IsImpress() && aOpt.IsModuleInstalled( SvtModuleOptions::EModule::IMPRESS ) )
+ aRequest.aModule= aOpt.GetFactoryName( SvtModuleOptions::EFactory::IMPRESS );
+ else if ( rArgs.IsDraw() && aOpt.IsModuleInstalled( SvtModuleOptions::EModule::DRAW ) )
+ aRequest.aModule= aOpt.GetFactoryName( SvtModuleOptions::EFactory::DRAW );
+ }
+
+ // check for printing disabled
+ if( ( !(aRequest.aPrintList.empty() && aRequest.aPrintToList.empty()) )
+ && Application::GetSettings().GetMiscSettings().GetDisablePrinting() )
+ {
+ aRequest.aPrintList.clear();
+ aRequest.aPrintToList.clear();
+ std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(nullptr,
+ VclMessageType::Warning, VclButtonsType::Ok,
+ DpResId(STR_ERR_PRINTDISABLED)));
+ xBox->run();
+ }
+
+ // Process request
+ if ( RequestHandler::ExecuteCmdLineRequests(aRequest, false) )
+ {
+ // Don't do anything if we have successfully called terminate at desktop:
+ return;
+ }
+ }
+
+ // no default document if a document was loaded by recovery or by command line or if soffice is used as server
+ Reference< XDesktop2 > xDesktop = css::frame::Desktop::create( ::comphelper::getProcessComponentContext() );
+ Reference< XElementAccess > xList( xDesktop->getFrames(), UNO_QUERY_THROW );
+ if ( xList->hasElements() )
+ return;
+
+ if ( rArgs.IsQuickstart() || rArgs.IsInvisible() || Application::AnyInput( VclInputFlags::APPEVENT ) )
+ // soffice was started as tray icon ...
+ return;
+
+ OpenDefault();
+}
+
+void Desktop::OpenDefault()
+{
+ OUString aName;
+ SvtModuleOptions aOpt;
+
+ const CommandLineArgs& rArgs = GetCommandLineArgs();
+ if ( rArgs.IsNoDefault() ) return;
+ if ( rArgs.HasModuleParam() )
+ {
+ // Support new command line parameters to start a module
+ if ( rArgs.IsWriter() && aOpt.IsModuleInstalled( SvtModuleOptions::EModule::WRITER ) )
+ aName = aOpt.GetFactoryEmptyDocumentURL( SvtModuleOptions::EFactory::WRITER );
+ else if ( rArgs.IsCalc() && aOpt.IsModuleInstalled( SvtModuleOptions::EModule::CALC ) )
+ aName = aOpt.GetFactoryEmptyDocumentURL( SvtModuleOptions::EFactory::CALC );
+ else if ( rArgs.IsImpress() && aOpt.IsModuleInstalled( SvtModuleOptions::EModule::IMPRESS ) )
+ aName = aOpt.GetFactoryEmptyDocumentURL( SvtModuleOptions::EFactory::IMPRESS );
+ else if ( rArgs.IsBase() && aOpt.IsModuleInstalled( SvtModuleOptions::EModule::DATABASE ) )
+ aName = aOpt.GetFactoryEmptyDocumentURL( SvtModuleOptions::EFactory::DATABASE );
+ else if ( rArgs.IsDraw() && aOpt.IsModuleInstalled( SvtModuleOptions::EModule::DRAW ) )
+ aName = aOpt.GetFactoryEmptyDocumentURL( SvtModuleOptions::EFactory::DRAW );
+ else if ( rArgs.IsMath() && aOpt.IsModuleInstalled( SvtModuleOptions::EModule::MATH ) )
+ aName = aOpt.GetFactoryEmptyDocumentURL( SvtModuleOptions::EFactory::MATH );
+ else if ( rArgs.IsGlobal() && aOpt.IsModuleInstalled( SvtModuleOptions::EModule::WRITER ) )
+ aName = aOpt.GetFactoryEmptyDocumentURL( SvtModuleOptions::EFactory::WRITERGLOBAL );
+ else if ( rArgs.IsWeb() && aOpt.IsModuleInstalled( SvtModuleOptions::EModule::WRITER ) )
+ aName = aOpt.GetFactoryEmptyDocumentURL( SvtModuleOptions::EFactory::WRITERWEB );
+ }
+
+ if ( aName.isEmpty() )
+ {
+ if (aOpt.IsModuleInstalled(SvtModuleOptions::EModule::STARTMODULE))
+ {
+ ShowBackingComponent(nullptr);
+ return;
+ }
+
+ // Old way to create a default document
+ if ( aOpt.IsModuleInstalled( SvtModuleOptions::EModule::WRITER ) )
+ aName = aOpt.GetFactoryEmptyDocumentURL( SvtModuleOptions::EFactory::WRITER );
+ else if ( aOpt.IsModuleInstalled( SvtModuleOptions::EModule::CALC ) )
+ aName = aOpt.GetFactoryEmptyDocumentURL( SvtModuleOptions::EFactory::CALC );
+ else if ( aOpt.IsModuleInstalled( SvtModuleOptions::EModule::IMPRESS ) )
+ aName = aOpt.GetFactoryEmptyDocumentURL( SvtModuleOptions::EFactory::IMPRESS );
+ else if ( aOpt.IsModuleInstalled( SvtModuleOptions::EModule::DATABASE ) )
+ aName = aOpt.GetFactoryEmptyDocumentURL( SvtModuleOptions::EFactory::DATABASE );
+ else if ( aOpt.IsModuleInstalled( SvtModuleOptions::EModule::DRAW ) )
+ aName = aOpt.GetFactoryEmptyDocumentURL( SvtModuleOptions::EFactory::DRAW );
+ else
+ return;
+ }
+
+ ProcessDocumentsRequest aRequest(rArgs.getCwdUrl());
+ aRequest.aOpenList.push_back(aName);
+ RequestHandler::ExecuteCmdLineRequests(aRequest, false);
+}
+
+
+OUString GetURL_Impl(
+ const OUString& rName, std::optional< OUString > const & cwdUrl )
+{
+ // if rName is a vnd.sun.star.script URL do not attempt to parse it
+ // as INetURLObj does not handle URLs there
+ if (rName.startsWith("vnd.sun.star.script"))
+ {
+ return rName;
+ }
+
+ // don't touch file urls, those should already be in internal form
+ // they won't get better here (#112849#)
+ if (comphelper::isFileUrl(rName))
+ {
+ return rName;
+ }
+
+ if ( rName.startsWith("service:"))
+ {
+ return rName;
+ }
+
+ // Add path separator to these directory and make given URL (rName) absolute by using of current working directory
+ // Attention: "setFinalSlash()" is necessary for calling "smartRel2Abs()"!!!
+ // Otherwise last part will be ignored and wrong result will be returned!!!
+ // "smartRel2Abs()" interpret given URL as file not as path. So he truncate last element to get the base path ...
+ // But if we add a separator - he doesn't do it anymore.
+ INetURLObject aObj;
+ if (cwdUrl) {
+ aObj.SetURL(*cwdUrl);
+ aObj.setFinalSlash();
+ }
+
+ // Use the provided parameters for smartRel2Abs to support the usage of '%' in system paths.
+ // Otherwise this char won't get encoded and we are not able to load such files later,
+ bool bWasAbsolute;
+ INetURLObject aURL = aObj.smartRel2Abs( rName, bWasAbsolute, false, INetURLObject::EncodeMechanism::WasEncoded,
+ RTL_TEXTENCODING_UTF8, true );
+ OUString aFileURL = aURL.GetMainURL(INetURLObject::DecodeMechanism::NONE);
+
+ ::osl::FileStatus aStatus( osl_FileStatus_Mask_FileURL );
+ ::osl::DirectoryItem aItem;
+ if( ::osl::FileBase::E_None == ::osl::DirectoryItem::get( aFileURL, aItem ) &&
+ ::osl::FileBase::E_None == aItem.getFileStatus( aStatus ) )
+ aFileURL = aStatus.getFileURL();
+
+ return aFileURL;
+}
+
+void Desktop::HandleAppEvent( const ApplicationEvent& rAppEvent )
+{
+ switch ( rAppEvent.GetEvent() )
+ {
+ case ApplicationEvent::Type::Accept:
+ // every time an accept parameter is used we create an acceptor
+ // with the corresponding accept-string
+ createAcceptor(rAppEvent.GetStringData());
+ break;
+ case ApplicationEvent::Type::Appear:
+ if ( !GetCommandLineArgs().IsInvisible() && !impl_bringToFrontRecoveryUI() )
+ {
+ Reference< css::uno::XComponentContext > xContext = ::comphelper::getProcessComponentContext();
+
+ // find active task - the active task is always a visible task
+ Reference< css::frame::XDesktop2 > xDesktop = css::frame::Desktop::create( xContext );
+ Reference< css::frame::XFrame > xTask = xDesktop->getActiveFrame();
+ if ( !xTask.is() )
+ {
+ // get any task if there is no active one
+ Reference< css::container::XIndexAccess > xList = xDesktop->getFrames();
+ if ( xList->getCount() > 0 )
+ xList->getByIndex(0) >>= xTask;
+ }
+
+ if ( xTask.is() )
+ {
+ Reference< css::awt::XTopWindow > xTop( xTask->getContainerWindow(), UNO_QUERY );
+ xTop->toFront();
+ }
+ else
+ {
+ // no visible task that could be activated found
+ Reference< css::awt::XWindow > xContainerWindow;
+ Reference< XFrame > xBackingFrame = xDesktop->findFrame( "_blank", 0);
+ if (xBackingFrame.is())
+ xContainerWindow = xBackingFrame->getContainerWindow();
+ if (xContainerWindow.is())
+ {
+ Reference< XController > xStartModule = StartModule::createWithParentWindow(xContext, xContainerWindow);
+ Reference< css::awt::XWindow > xBackingWin(xStartModule, UNO_QUERY);
+ // Attention: You MUST(!) call setComponent() before you call attachFrame().
+ // Because the backing component set the property "IsBackingMode" of the frame
+ // to true inside attachFrame(). But setComponent() reset this state every time ...
+ xBackingFrame->setComponent(xBackingWin, xStartModule);
+ xStartModule->attachFrame(xBackingFrame);
+ xContainerWindow->setVisible(true);
+
+ VclPtr<vcl::Window> pCompWindow = VCLUnoHelper::GetWindow(xBackingFrame->getComponentWindow());
+ if (pCompWindow)
+ pCompWindow->PaintImmediately();
+ }
+ }
+ }
+ break;
+ case ApplicationEvent::Type::Open:
+ {
+ const CommandLineArgs& rCmdLine = GetCommandLineArgs();
+ if ( !rCmdLine.IsInvisible() && !rCmdLine.IsTerminateAfterInit() )
+ {
+ ProcessDocumentsRequest docsRequest(rCmdLine.getCwdUrl());
+ std::vector<OUString> const & data(rAppEvent.GetStringsData());
+ docsRequest.aOpenList.insert(
+ docsRequest.aOpenList.end(), data.begin(), data.end());
+ RequestHandler::ExecuteCmdLineRequests(docsRequest, false);
+ }
+ }
+ break;
+ case ApplicationEvent::Type::OpenHelpUrl:
+ // start help for a specific URL
+ Application::GetHelp()->Start(rAppEvent.GetStringData());
+ break;
+ case ApplicationEvent::Type::Print:
+ {
+ const CommandLineArgs& rCmdLine = GetCommandLineArgs();
+ if ( !rCmdLine.IsInvisible() && !rCmdLine.IsTerminateAfterInit() )
+ {
+ ProcessDocumentsRequest docsRequest(rCmdLine.getCwdUrl());
+ std::vector<OUString> const & data(rAppEvent.GetStringsData());
+ docsRequest.aPrintList.insert(
+ docsRequest.aPrintList.end(), data.begin(), data.end());
+ RequestHandler::ExecuteCmdLineRequests(docsRequest, false);
+ }
+ }
+ break;
+ case ApplicationEvent::Type::PrivateDoShutdown:
+ {
+ Desktop* pD = dynamic_cast<Desktop*>(GetpApp());
+ OSL_ENSURE( pD, "no desktop ?!?" );
+ if( pD )
+ pD->doShutdown();
+ }
+ break;
+ case ApplicationEvent::Type::QuickStart:
+ if ( !GetCommandLineArgs().IsInvisible() )
+ {
+ // If the office has been started the second time its command line arguments are sent through a pipe
+ // connection to the first office. We want to reuse the quickstart option for the first office.
+ // NOTICE: The quickstart service must be initialized inside the "main thread", so we use the
+ // application events to do this (they are executed inside main thread)!!!
+ // Don't start quickstart service if the user specified "--invisible" on the command line!
+ Reference< css::uno::XComponentContext > xContext = ::comphelper::getProcessComponentContext();
+ css::office::Quickstart::createStart(xContext, true/*Quickstart*/);
+ }
+ break;
+ case ApplicationEvent::Type::ShowDialog:
+ // This is only used on macOS, and only for About or Preferences.
+ // Ignore all errors here. It's clicking a menu entry only ...
+ // The user will try it again, in case nothing happens .-)
+ try
+ {
+ Reference< css::uno::XComponentContext > xContext = ::comphelper::getProcessComponentContext();
+
+ Reference< css::frame::XDesktop2 > xDesktop = css::frame::Desktop::create( xContext );
+
+ Reference< css::util::XURLTransformer > xParser = css::util::URLTransformer::create(xContext);
+ css::util::URL aCommand;
+ if( rAppEvent.GetStringData() == "PREFERENCES" )
+ aCommand.Complete = ".uno:OptionsTreeDialog";
+ else if( rAppEvent.GetStringData() == "ABOUT" )
+ aCommand.Complete = ".uno:About";
+ if( !aCommand.Complete.isEmpty() )
+ {
+ xParser->parseStrict(aCommand);
+
+ css::uno::Reference< css::frame::XDispatch > xDispatch = xDesktop->queryDispatch(aCommand, OUString(), 0);
+ if (xDispatch.is())
+ xDispatch->dispatch(aCommand, css::uno::Sequence< css::beans::PropertyValue >());
+ }
+ }
+ catch(const css::uno::Exception&)
+ {
+ TOOLS_WARN_EXCEPTION("desktop.app", "exception thrown by dialog");
+ }
+ break;
+ case ApplicationEvent::Type::Unaccept:
+ // try to remove corresponding acceptor
+ destroyAcceptor(rAppEvent.GetStringData());
+ break;
+ default:
+ SAL_WARN( "desktop.app", "this cannot happen");
+ break;
+ }
+}
+
+#if !ENABLE_WASM_STRIP_SPLASH
+void Desktop::OpenSplashScreen()
+{
+ const CommandLineArgs &rCmdLine = GetCommandLineArgs();
+ // Show intro only if this is normal start (e.g. no server, no quickstart, no printing )
+ if ( !(!rCmdLine.IsInvisible() &&
+ !rCmdLine.IsHeadless() &&
+ !rCmdLine.IsQuickstart() &&
+ !rCmdLine.IsMinimized() &&
+ !rCmdLine.IsNoLogo() &&
+ !rCmdLine.IsTerminateAfterInit() &&
+ rCmdLine.GetPrintList().empty() &&
+ rCmdLine.GetPrintToList().empty() &&
+ rCmdLine.GetConversionList().empty()) )
+ return;
+
+ // Determine application name from command line parameters
+ OUString aAppName;
+ if ( rCmdLine.IsWriter() )
+ aAppName = "writer";
+ else if ( rCmdLine.IsCalc() )
+ aAppName = "calc";
+ else if ( rCmdLine.IsDraw() )
+ aAppName = "draw";
+ else if ( rCmdLine.IsImpress() )
+ aAppName = "impress";
+ else if ( rCmdLine.IsBase() )
+ aAppName = "base";
+ else if ( rCmdLine.IsGlobal() )
+ aAppName = "global";
+ else if ( rCmdLine.IsMath() )
+ aAppName = "math";
+ else if ( rCmdLine.IsWeb() )
+ aAppName = "web";
+
+ // Which splash to use
+ OUString aSplashService( "com.sun.star.office.SplashScreen" );
+ if ( rCmdLine.HasSplashPipe() )
+ aSplashService = "com.sun.star.office.PipeSplashScreen";
+
+ Sequence< Any > aSeq{ Any(true) /* bVisible */, Any(aAppName) };
+ css::uno::Reference< css::uno::XComponentContext > xContext = ::comphelper::getProcessComponentContext();
+ m_rSplashScreen.set(
+ xContext->getServiceManager()->createInstanceWithArgumentsAndContext(aSplashService, aSeq, xContext),
+ UNO_QUERY);
+
+ if(m_rSplashScreen.is())
+ m_rSplashScreen->start("SplashScreen", 100);
+
+}
+#endif
+
+void Desktop::SetSplashScreenProgress(sal_Int32 iProgress)
+{
+#if ENABLE_WASM_STRIP_SPLASH
+ (void) iProgress;
+#else
+ if(m_rSplashScreen.is())
+ {
+ m_rSplashScreen->setValue(iProgress);
+ }
+#endif
+}
+
+void Desktop::SetSplashScreenText( const OUString& rText )
+{
+#if ENABLE_WASM_STRIP_SPLASH
+ (void) rText;
+#else
+ if( m_rSplashScreen.is() )
+ {
+ m_rSplashScreen->setText( rText );
+ }
+#endif
+}
+
+void Desktop::CloseSplashScreen()
+{
+#if !ENABLE_WASM_STRIP_SPLASH
+ if(m_rSplashScreen.is())
+ {
+ SolarMutexGuard ensureSolarMutex;
+ m_rSplashScreen->end();
+ m_rSplashScreen = nullptr;
+ }
+#endif
+}
+
+
+IMPL_STATIC_LINK_NOARG(Desktop, AsyncInitFirstRun, Timer *, void)
+{
+ // does initializations which are necessary for the first run of the office
+ try
+ {
+ Reference< XJobExecutor > xExecutor = theJobExecutor::get( ::comphelper::getProcessComponentContext() );
+ xExecutor->trigger( "onFirstRunInitialization" );
+ }
+ catch(const css::uno::Exception&)
+ {
+ TOOLS_WARN_EXCEPTION( "desktop.app", "Desktop::DoFirstRunInitializations: caught an exception while trigger job executor" );
+ }
+}
+
+void Desktop::ShowBackingComponent(Desktop * progress)
+{
+ if (GetCommandLineArgs().IsNoDefault())
+ {
+ return;
+ }
+ Reference< XComponentContext > xContext = comphelper::getProcessComponentContext();
+ Reference< XDesktop2 > xDesktop = css::frame::Desktop::create(xContext);
+ if (progress != nullptr)
+ {
+ progress->SetSplashScreenProgress(60);
+ }
+ Reference< XFrame > xBackingFrame = xDesktop->findFrame( "_blank", 0);
+ Reference< css::awt::XWindow > xContainerWindow;
+
+ if (xBackingFrame.is())
+ xContainerWindow = xBackingFrame->getContainerWindow();
+ if (!xContainerWindow.is())
+ return;
+
+ // set the WindowExtendedStyle::Document style. Normally, this is done by the TaskCreator service when a "_blank"
+ // frame/window is created. Since we do not use the TaskCreator here, we need to mimic its behavior,
+ // otherwise documents loaded into this frame will later on miss functionality depending on the style.
+ VclPtr<vcl::Window> pContainerWindow = VCLUnoHelper::GetWindow( xContainerWindow );
+ SAL_WARN_IF( !pContainerWindow, "desktop.app", "Desktop::Main: no implementation access to the frame's container window!" );
+ pContainerWindow->SetExtendedStyle( pContainerWindow->GetExtendedStyle() | WindowExtendedStyle::Document );
+ if (progress != nullptr)
+ {
+ progress->SetSplashScreenProgress(75);
+ }
+
+ Reference< XController > xStartModule = StartModule::createWithParentWindow( xContext, xContainerWindow);
+ // Attention: You MUST(!) call setComponent() before you call attachFrame().
+ // Because the backing component set the property "IsBackingMode" of the frame
+ // to true inside attachFrame(). But setComponent() reset this state everytimes ...
+ xBackingFrame->setComponent(Reference< XWindow >(xStartModule, UNO_QUERY), xStartModule);
+ if (progress != nullptr)
+ {
+ progress->SetSplashScreenProgress(100);
+ }
+ xStartModule->attachFrame(xBackingFrame);
+ if (progress != nullptr)
+ {
+ progress->CloseSplashScreen();
+ }
+ xContainerWindow->setVisible(true);
+}
+
+
+void Desktop::CheckFirstRun( )
+{
+ if (!officecfg::Office::Common::Misc::FirstRun::get())
+ return;
+
+ // use VCL timer, which won't trigger during shutdown if the
+ // application exits before timeout
+ m_firstRunTimer.Start();
+
+#ifdef _WIN32
+ // Check if Quickstarter should be started (on Windows only)
+ OUString sRootKey = ReplaceStringHookProc("Software\\%OOOVENDOR\\%PRODUCTNAME\\%PRODUCTVERSION");
+ WCHAR szValue[8192];
+ DWORD nValueSize = sizeof(szValue);
+ HKEY hKey;
+ if (ERROR_SUCCESS == RegOpenKeyW(HKEY_LOCAL_MACHINE, o3tl::toW(sRootKey.getStr()), &hKey))
+ {
+ if ( ERROR_SUCCESS == RegQueryValueExW( hKey, L"RunQuickstartAtFirstStart", nullptr, nullptr, reinterpret_cast<LPBYTE>(szValue), &nValueSize ) )
+ {
+ css::uno::Reference< css::uno::XComponentContext > xContext = ::comphelper::getProcessComponentContext();
+ css::office::Quickstart::createAutoStart(xContext, true/*Quickstart*/, true/*bAutostart*/);
+ RegCloseKey( hKey );
+ }
+ }
+#endif
+
+ std::shared_ptr< comphelper::ConfigurationChanges > batch(
+ comphelper::ConfigurationChanges::create());
+ officecfg::Office::Common::Misc::FirstRun::set(false, batch);
+ batch->commit();
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/app/appinit.cxx b/desktop/source/app/appinit.cxx
new file mode 100644
index 0000000000..51b466c6b9
--- /dev/null
+++ b/desktop/source/app/appinit.cxx
@@ -0,0 +1,279 @@
+/* -*- 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 <algorithm>
+
+#include <app.hxx>
+#include <dp_shared.hxx>
+#include "cmdlineargs.hxx"
+#include <strings.hrc>
+#include <com/sun/star/registry/XSimpleRegistry.hpp>
+#include <com/sun/star/lang/XInitialization.hpp>
+#include <com/sun/star/lang/XMultiServiceFactory.hpp>
+#include <com/sun/star/uno/Exception.hpp>
+#include <com/sun/star/ucb/UniversalContentBroker.hpp>
+#include <cppuhelper/bootstrap.hxx>
+#include <officecfg/Setup.hxx>
+#include <osl/file.hxx>
+#include <rtl/bootstrap.hxx>
+#include <sal/log.hxx>
+#include <comphelper/diagnose_ex.hxx>
+
+#include <comphelper/processfactory.hxx>
+#include <unotools/ucbhelper.hxx>
+#include <unotools/tempfile.hxx>
+#include <vcl/svapp.hxx>
+#include <unotools/pathoptions.hxx>
+
+#include <iostream>
+#include <map>
+
+using namespace ::com::sun::star::uno;
+using namespace ::com::sun::star::lang;
+using namespace ::com::sun::star::beans;
+using namespace ::com::sun::star::registry;
+using namespace ::com::sun::star::ucb;
+
+namespace desktop
+{
+
+
+static void configureUcb()
+{
+ // For backwards compatibility, in case some code still uses plain
+ // createInstance w/o args directly to obtain an instance:
+ UniversalContentBroker::create(comphelper::getProcessComponentContext());
+}
+
+void Desktop::InitApplicationServiceManager()
+{
+ Reference<XMultiServiceFactory> sm;
+#ifdef ANDROID
+ OUString aUnoRc( "file:///assets/program/unorc" );
+ sm.set(
+ cppu::defaultBootstrap_InitialComponentContext( aUnoRc )->getServiceManager(),
+ UNO_QUERY_THROW);
+#elif defined(IOS)
+ OUString uri( "$APP_DATA_DIR" );
+ rtl_bootstrap_expandMacros( &uri.pData );
+ OUString aUnoRc("file://" + uri + "/unorc");
+ sm.set(
+ cppu::defaultBootstrap_InitialComponentContext( aUnoRc )->getServiceManager(),
+ UNO_QUERY_THROW);
+#else
+ sm.set(
+ cppu::defaultBootstrap_InitialComponentContext()->getServiceManager(),
+ UNO_QUERY_THROW);
+#endif
+ comphelper::setProcessServiceFactory(sm);
+}
+
+void Desktop::RegisterServices()
+{
+ if( m_bServicesRegistered )
+ return;
+
+ // interpret command line arguments
+ CommandLineArgs& rCmdLine = GetCommandLineArgs();
+
+ // Headless mode for FAT Office, auto cancels any dialogs that popup
+ if (rCmdLine.IsHeadless())
+ Application::EnableHeadlessMode(false);
+
+ // read accept string from configuration
+ OUString conDcpCfg(
+ officecfg::Setup::Office::ooSetupConnectionURL::get());
+ if (!conDcpCfg.isEmpty()) {
+ createAcceptor(conDcpCfg);
+ }
+
+ std::vector< OUString > const & conDcp = rCmdLine.GetAccept();
+ for (auto const& elem : conDcp)
+ {
+ createAcceptor(elem);
+ }
+
+ configureUcb();
+
+ CreateTemporaryDirectory();
+ m_bServicesRegistered = true;
+}
+
+typedef std::map< OUString, css::uno::Reference<css::lang::XInitialization> > AcceptorMap;
+
+namespace
+{
+ AcceptorMap& acceptorMap()
+ {
+ static AcceptorMap SINGLETON;
+ return SINGLETON;
+ }
+ OUString& CurrentTempURL()
+ {
+ static OUString SINGLETON;
+ return SINGLETON;
+ }
+}
+
+static bool bAccept = false;
+
+void Desktop::createAcceptor(const OUString& aAcceptString)
+{
+ // check whether the requested acceptor already exists
+ AcceptorMap &rMap = acceptorMap();
+ AcceptorMap::const_iterator pIter = rMap.find(aAcceptString);
+ if (pIter != rMap.end() )
+ {
+ // there is already an acceptor with this description
+ SAL_WARN( "desktop.app", "Acceptor already exists.");
+ return;
+ }
+
+ Sequence< Any > aSeq{ Any(aAcceptString), Any(bAccept) };
+ Reference< XComponentContext > xContext = ::comphelper::getProcessComponentContext();
+ Reference<XInitialization> rAcceptor(
+ xContext->getServiceManager()->createInstanceWithContext("com.sun.star.office.Acceptor", xContext),
+ UNO_QUERY );
+ if ( rAcceptor.is() )
+ {
+ try
+ {
+ rAcceptor->initialize( aSeq );
+ rMap.emplace(aAcceptString, rAcceptor);
+ }
+ catch (const css::uno::Exception&)
+ {
+ // no error handling needed...
+ // acceptor just won't come up
+ TOOLS_WARN_EXCEPTION( "desktop.app", "Acceptor could not be created");
+ }
+ }
+ else
+ {
+ ::std::cerr << "UNO Remote Protocol acceptor could not be created, presumably because it has been disabled in configuration." << ::std::endl;
+ }
+}
+
+namespace {
+
+class enable
+{
+ private:
+ Sequence<Any> m_aSeq{ Any(true) };
+ public:
+ enable() = default;
+ void operator() (const AcceptorMap::value_type& val) {
+ if (val.second.is()) {
+ val.second->initialize(m_aSeq);
+ }
+ }
+};
+
+}
+
+// enable acceptors
+IMPL_STATIC_LINK_NOARG(Desktop, EnableAcceptors_Impl, void*, void)
+{
+ if (!bAccept)
+ {
+ // from now on, all new acceptors are enabled
+ bAccept = true;
+ // enable existing acceptors by calling initialize(true)
+ // on all existing acceptors
+ AcceptorMap &rMap = acceptorMap();
+ std::for_each(rMap.begin(), rMap.end(), enable());
+ }
+}
+
+void Desktop::destroyAcceptor(const OUString& aAcceptString)
+{
+ // special case stop all acceptors
+ AcceptorMap &rMap = acceptorMap();
+ if (aAcceptString == "all") {
+ rMap.clear();
+
+ } else {
+ // try to remove acceptor from map
+ AcceptorMap::const_iterator pIter = rMap.find(aAcceptString);
+ if (pIter != rMap.end() ) {
+ // remove reference from map
+ // this is the last reference and the acceptor will be destructed
+ rMap.erase(aAcceptString);
+ } else {
+ SAL_WARN( "desktop.app", "Found no acceptor to remove");
+ }
+ }
+}
+
+
+void Desktop::DeregisterServices()
+{
+ // stop all acceptors by clearing the map
+ acceptorMap().clear();
+}
+
+void Desktop::CreateTemporaryDirectory()
+{
+ OUString aTempBaseURL;
+ try
+ {
+ SvtPathOptions aOpt;
+ aTempBaseURL = aOpt.GetTempPath();
+ }
+ catch (RuntimeException& e)
+ {
+ // Catch runtime exception here: We have to add language dependent info
+ // to the exception message. Fallback solution uses hard coded string.
+ OUString aMsg = DpResId(STR_BOOTSTRAP_ERR_NO_PATHSET_SERVICE);
+ e.Message = aMsg + e.Message;
+ throw;
+ }
+
+ // create new current temporary directory
+ OUString aTempPath = ::utl::SetTempNameBaseDirectory( aTempBaseURL );
+ if ( aTempPath.isEmpty()
+ && ::osl::File::getTempDirURL( aTempBaseURL ) == osl::FileBase::E_None )
+ {
+ aTempPath = ::utl::SetTempNameBaseDirectory( aTempBaseURL );
+ }
+
+ // set new current temporary directory
+ OUString aRet;
+ if (osl::FileBase::getFileURLFromSystemPath( aTempPath, aRet )
+ != osl::FileBase::E_None)
+ {
+ aRet.clear();
+ }
+ CurrentTempURL() = aRet;
+}
+
+void Desktop::RemoveTemporaryDirectory()
+{
+ // remove current temporary directory
+ OUString &rCurrentTempURL = CurrentTempURL();
+ if ( !rCurrentTempURL.isEmpty() )
+ {
+ ::utl::UCBContentHelper::Kill( rCurrentTempURL );
+ }
+}
+
+} // namespace desktop
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/app/check_ext_deps.cxx b/desktop/source/app/check_ext_deps.cxx
new file mode 100644
index 0000000000..4a69a8cf79
--- /dev/null
+++ b/desktop/source/app/check_ext_deps.cxx
@@ -0,0 +1,427 @@
+/* -*- 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 <config_features.h>
+
+#include <rtl/bootstrap.hxx>
+#include <rtl/ustring.hxx>
+#include <sal/log.hxx>
+#include <cppuhelper/implbase.hxx>
+
+#include <comphelper/diagnose_ex.hxx>
+#include <toolkit/helper/vclunohelper.hxx>
+
+#include <comphelper/lok.hxx>
+#include <comphelper/processfactory.hxx>
+#include <cppuhelper/exc_hlp.hxx>
+#include <com/sun/star/lang/WrappedTargetRuntimeException.hpp>
+#include <com/sun/star/ucb/CommandAbortedException.hpp>
+#include <com/sun/star/ucb/CommandFailedException.hpp>
+#include <com/sun/star/ucb/XCommandEnvironment.hpp>
+#include <com/sun/star/beans/XPropertySet.hpp>
+#include <com/sun/star/beans/NamedValue.hpp>
+#include <com/sun/star/configuration/theDefaultProvider.hpp>
+#include <com/sun/star/deployment/DeploymentException.hpp>
+#include <com/sun/star/deployment/XPackage.hpp>
+#include <com/sun/star/deployment/ExtensionManager.hpp>
+#include <com/sun/star/deployment/LicenseException.hpp>
+#include <com/sun/star/deployment/ui/LicenseDialog.hpp>
+#include <com/sun/star/task/OfficeRestartManager.hpp>
+#include <com/sun/star/task/XInteractionApprove.hpp>
+#include <com/sun/star/task/XInteractionAbort.hpp>
+#include <com/sun/star/ui/dialogs/XExecutableDialog.hpp>
+#include <com/sun/star/ui/dialogs/ExecutableDialogResults.hpp>
+#include <com/sun/star/util/XChangesBatch.hpp>
+
+#include <app.hxx>
+#include <utility>
+
+#include <dp_misc.h>
+
+using namespace desktop;
+using namespace com::sun::star;
+using namespace com::sun::star::lang;
+using namespace com::sun::star::task;
+using namespace com::sun::star::uno;
+
+namespace
+{
+//For use with XExtensionManager.synchronize
+class SilentCommandEnv
+ : public ::cppu::WeakImplHelper< ucb::XCommandEnvironment,
+ task::XInteractionHandler,
+ ucb::XProgressHandler >
+{
+ uno::Reference<uno::XComponentContext> mxContext;
+ Desktop *mpDesktop;
+ sal_Int32 mnLevel;
+ sal_Int32 mnProgress;
+
+public:
+ SilentCommandEnv(
+ uno::Reference<uno::XComponentContext> xContext,
+ Desktop* pDesktop );
+ virtual ~SilentCommandEnv() override;
+
+ // XCommandEnvironment
+ virtual uno::Reference<task::XInteractionHandler > SAL_CALL
+ getInteractionHandler() override;
+ virtual uno::Reference<ucb::XProgressHandler >
+ SAL_CALL getProgressHandler() override;
+
+ // XInteractionHandler
+ virtual void SAL_CALL handle(
+ uno::Reference<task::XInteractionRequest > const & xRequest ) override;
+
+ // XProgressHandler
+ virtual void SAL_CALL push( uno::Any const & Status ) override;
+ virtual void SAL_CALL update( uno::Any const & Status ) override;
+ virtual void SAL_CALL pop() override;
+};
+
+
+SilentCommandEnv::SilentCommandEnv(
+ uno::Reference<uno::XComponentContext> xContext,
+ Desktop* pDesktop ):
+ mxContext(std::move( xContext )),
+ mpDesktop( pDesktop ),
+ mnLevel( 0 ),
+ mnProgress( 25 )
+{}
+
+
+SilentCommandEnv::~SilentCommandEnv()
+{
+ if (mpDesktop)
+ mpDesktop->SetSplashScreenText(OUString());
+}
+
+
+Reference<task::XInteractionHandler> SilentCommandEnv::getInteractionHandler()
+{
+ return this;
+}
+
+
+Reference<ucb::XProgressHandler> SilentCommandEnv::getProgressHandler()
+{
+ return this;
+}
+
+
+// XInteractionHandler
+void SilentCommandEnv::handle( Reference< task::XInteractionRequest> const & xRequest )
+{
+ deployment::LicenseException licExc;
+
+ uno::Any request( xRequest->getRequest() );
+ bool bApprove = true;
+
+ if ( request >>= licExc )
+ {
+ uno::Reference< ui::dialogs::XExecutableDialog > xDialog(
+ deployment::ui::LicenseDialog::create(
+ mxContext, VCLUnoHelper::GetInterface( nullptr ),
+ licExc.ExtensionName, licExc.Text ) );
+ sal_Int16 res = xDialog->execute();
+ if ( res == ui::dialogs::ExecutableDialogResults::CANCEL )
+ bApprove = false;
+ else if ( res == ui::dialogs::ExecutableDialogResults::OK )
+ bApprove = true;
+ else
+ {
+ OSL_ASSERT(false);
+ }
+ }
+
+ // We approve everything here
+ uno::Sequence< Reference< task::XInteractionContinuation > > conts( xRequest->getContinuations() );
+ Reference< task::XInteractionContinuation > const * pConts = conts.getConstArray();
+ sal_Int32 len = conts.getLength();
+
+ for ( sal_Int32 pos = 0; pos < len; ++pos )
+ {
+ if ( bApprove )
+ {
+ uno::Reference< task::XInteractionApprove > xInteractionApprove( pConts[ pos ], uno::UNO_QUERY );
+ if ( xInteractionApprove.is() )
+ xInteractionApprove->select();
+ }
+ else
+ {
+ uno::Reference< task::XInteractionAbort > xInteractionAbort( pConts[ pos ], uno::UNO_QUERY );
+ if ( xInteractionAbort.is() )
+ xInteractionAbort->select();
+ }
+ }
+}
+
+
+// XProgressHandler
+void SilentCommandEnv::push( uno::Any const & rStatus )
+{
+ OUString sText;
+ mnLevel += 1;
+
+ if (mpDesktop && rStatus.hasValue() && (rStatus >>= sText))
+ {
+ if ( mnLevel <= 3 )
+ mpDesktop->SetSplashScreenText( sText );
+ else
+ mpDesktop->SetSplashScreenProgress( ++mnProgress );
+ }
+}
+
+
+void SilentCommandEnv::update( uno::Any const & rStatus )
+{
+ OUString sText;
+ if (mpDesktop && rStatus.hasValue() && (rStatus >>= sText))
+ {
+ mpDesktop->SetSplashScreenText( sText );
+ }
+}
+
+
+void SilentCommandEnv::pop()
+{
+ mnLevel -= 1;
+}
+
+} // end namespace
+
+
+constexpr OUString aAccessSrvc = u"com.sun.star.configuration.ConfigurationUpdateAccess"_ustr;
+
+static sal_Int16 impl_showExtensionDialog( uno::Reference< uno::XComponentContext > const &xContext )
+{
+ uno::Reference< uno::XInterface > xService;
+ sal_Int16 nRet = 0;
+
+ uno::Reference< lang::XMultiComponentFactory > xServiceManager( xContext->getServiceManager() );
+ if( !xServiceManager.is() )
+ throw uno::RuntimeException(
+ "impl_showExtensionDialog(): unable to obtain service manager from component context", uno::Reference< uno::XInterface > () );
+
+ xService = xServiceManager->createInstanceWithContext( "com.sun.star.deployment.ui.UpdateRequiredDialog", xContext );
+ uno::Reference< ui::dialogs::XExecutableDialog > xExecutable( xService, uno::UNO_QUERY );
+ if ( xExecutable.is() )
+ nRet = xExecutable->execute();
+
+ return nRet;
+}
+
+
+// Check dependencies of all packages
+
+static bool impl_checkDependencies( const uno::Reference< uno::XComponentContext > &xContext )
+{
+ uno::Sequence< uno::Sequence< uno::Reference< deployment::XPackage > > > xAllPackages;
+ uno::Reference< deployment::XExtensionManager > xExtensionManager = deployment::ExtensionManager::get( xContext );
+
+ if ( !xExtensionManager.is() )
+ {
+ SAL_WARN( "desktop.app", "Could not get the Extension Manager!" );
+ return true;
+ }
+
+ try {
+ xAllPackages = xExtensionManager->getAllExtensions( uno::Reference< task::XAbortChannel >(),
+ uno::Reference< ucb::XCommandEnvironment >() );
+ }
+ catch ( const deployment::DeploymentException & ) { return true; }
+ catch ( const ucb::CommandFailedException & ) { return true; }
+ catch ( const ucb::CommandAbortedException & ) { return true; }
+ catch ( const lang::IllegalArgumentException & e ) {
+ css::uno::Any anyEx = cppu::getCaughtException();
+ throw css::lang::WrappedTargetRuntimeException( e.Message,
+ e.Context, anyEx );
+ }
+
+#ifdef DEBUG
+ sal_Int32 const nMax = 3;
+#else
+ sal_Int32 const nMax = 2;
+#endif
+
+ for ( uno::Sequence< uno::Reference< deployment::XPackage > > const & xPackageList : std::as_const(xAllPackages) )
+ {
+ for ( sal_Int32 j = 0; (j<nMax) && (j < xPackageList.getLength()); ++j )
+ {
+ uno::Reference< deployment::XPackage > xPackage = xPackageList[j];
+ if ( xPackage.is() )
+ {
+ bool bRegistered = false;
+ try {
+ beans::Optional< beans::Ambiguous< sal_Bool > > option( xPackage->isRegistered( uno::Reference< task::XAbortChannel >(),
+ uno::Reference< ucb::XCommandEnvironment >() ) );
+ if ( option.IsPresent )
+ {
+ ::beans::Ambiguous< sal_Bool > const & reg = option.Value;
+ if ( reg.IsAmbiguous )
+ bRegistered = false;
+ else
+ bRegistered = reg.Value;
+ }
+ else
+ bRegistered = false;
+ }
+ catch ( const uno::RuntimeException & ) { throw; }
+ catch (const uno::Exception & ) {
+ TOOLS_WARN_EXCEPTION( "desktop.app", "" );
+ }
+
+ if ( bRegistered )
+ {
+ bool bDependenciesValid = false;
+ try {
+ bDependenciesValid = xPackage->checkDependencies( uno::Reference< ucb::XCommandEnvironment >() );
+ }
+ catch ( const deployment::DeploymentException & ) {}
+ if ( ! bDependenciesValid )
+ {
+ return false;
+ }
+ }
+ }
+ }
+ }
+ return true;
+}
+
+
+// resets the 'check needed' flag (needed, if aborted)
+
+static void impl_setNeedsCompatCheck()
+{
+ try {
+ Reference< XMultiServiceFactory > theConfigProvider(
+ configuration::theDefaultProvider::get(
+ comphelper::getProcessComponentContext() ) );
+
+ beans::NamedValue v( "nodepath",
+ Any( OUString("org.openoffice.Setup/Office") ) );
+ Sequence< Any > theArgs{ Any(v) };
+ Reference< beans::XPropertySet > pset(
+ theConfigProvider->createInstanceWithArguments( aAccessSrvc, theArgs ), UNO_QUERY_THROW );
+
+ Any value( OUString("never") );
+
+ pset->setPropertyValue("LastCompatibilityCheckID", value );
+ Reference< util::XChangesBatch >( pset, UNO_QUERY_THROW )->commitChanges();
+ }
+ catch (const Exception&) {}
+}
+
+
+// to check if we need checking the dependencies of the extensions again, we compare
+// the build id of the office with the one of the last check
+
+static bool impl_needsCompatCheck()
+{
+ bool bNeedsCheck = false;
+ OUString aLastCheckBuildID;
+ OUString aCurrentBuildID( "${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE("version") ":buildid}" );
+ rtl::Bootstrap::expandMacros( aCurrentBuildID );
+
+ try {
+ Reference< XMultiServiceFactory > theConfigProvider(
+ configuration::theDefaultProvider::get(
+ comphelper::getProcessComponentContext() ) );
+
+ beans::NamedValue v( "nodepath",
+ Any( OUString("org.openoffice.Setup/Office") ) );
+ Sequence< Any > theArgs{ Any(v) };
+ Reference< beans::XPropertySet > pset(
+ theConfigProvider->createInstanceWithArguments( aAccessSrvc, theArgs ), UNO_QUERY_THROW );
+
+ Any result = pset->getPropertyValue("LastCompatibilityCheckID");
+
+ result >>= aLastCheckBuildID;
+ if ( aLastCheckBuildID != aCurrentBuildID )
+ {
+ bNeedsCheck = true;
+ result <<= aCurrentBuildID;
+ pset->setPropertyValue("LastCompatibilityCheckID", result );
+ Reference< util::XChangesBatch >( pset, UNO_QUERY_THROW )->commitChanges();
+ }
+#ifdef DEBUG
+ bNeedsCheck = true;
+#endif
+ }
+ catch (const css::uno::Exception&) {}
+
+ return bNeedsCheck;
+}
+
+
+// Do we need to check the dependencies of the extensions?
+// When there are unresolved issues, we can't continue with startup
+bool Desktop::CheckExtensionDependencies()
+{
+ if (!impl_needsCompatCheck())
+ {
+ return false;
+ }
+
+ uno::Reference< uno::XComponentContext > xContext(
+ comphelper::getProcessComponentContext());
+
+ bool bDependenciesValid = impl_checkDependencies( xContext );
+
+ short nRet = 0;
+
+ if ( !bDependenciesValid )
+ nRet = impl_showExtensionDialog( xContext );
+
+ if ( nRet == -1 )
+ {
+ impl_setNeedsCompatCheck();
+ return true;
+ }
+ else
+ return false;
+}
+
+void Desktop::SynchronizeExtensionRepositories(bool bCleanedExtensionCache, Desktop* pDesktop)
+{
+ uno::Reference< uno::XComponentContext > context(
+ comphelper::getProcessComponentContext());
+ uno::Reference< ucb::XCommandEnvironment > silent(
+ new SilentCommandEnv(context, pDesktop));
+ if (bCleanedExtensionCache) {
+ deployment::ExtensionManager::get(context)->reinstallDeployedExtensions(
+ true, "user", Reference<task::XAbortChannel>(), silent);
+#if !HAVE_FEATURE_MACOSX_SANDBOX
+ if (!comphelper::LibreOfficeKit::isActive())
+ task::OfficeRestartManager::get(context)->requestRestart(
+ silent->getInteractionHandler());
+#endif
+ } else {
+ // reinstallDeployedExtensions above already calls syncRepositories internally
+
+ // Force syncing repositories on startup. There are cases where the extension
+ // registration becomes invalid which leads to extensions not starting up, although
+ // installed and active. Syncing extension repos on startup fixes that.
+ dp_misc::syncRepositories(/*force=*/true, silent);
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/app/cmdlineargs.cxx b/desktop/source/app/cmdlineargs.cxx
new file mode 100644
index 0000000000..e7f3152040
--- /dev/null
+++ b/desktop/source/app/cmdlineargs.cxx
@@ -0,0 +1,786 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <config_features.h>
+
+#if HAVE_FEATURE_MACOSX_SANDBOX
+#include <premac.h>
+#include <Foundation/Foundation.h>
+#include <postmac.h>
+#endif
+
+#include "cmdlineargs.hxx"
+#include <osl/thread.hxx>
+#include <tools/stream.hxx>
+#include <tools/urlobj.hxx>
+#include <rtl/ustring.hxx>
+#include <rtl/process.h>
+#include <comphelper/lok.hxx>
+#include <comphelper/processfactory.hxx>
+#include <com/sun/star/uri/ExternalUriReferenceTranslator.hpp>
+#include <unotools/bootstrap.hxx>
+
+#include <rtl/strbuf.hxx>
+#include <osl/file.hxx>
+#include <sal/log.hxx>
+
+#include <mutex>
+
+namespace desktop
+{
+
+namespace {
+
+OUString translateExternalUris(OUString const & input) {
+ OUString t(
+ css::uri::ExternalUriReferenceTranslator::create(
+ comphelper::getProcessComponentContext())->
+ translateToInternal(input));
+ return t.isEmpty() ? input : t;
+}
+
+std::vector< OUString > translateExternalUris(
+ std::vector< OUString > const & input)
+{
+ std::vector< OUString > t;
+ t.reserve(input.size());
+ for (auto const& elem : input)
+ {
+ t.push_back(translateExternalUris(elem));
+ }
+ return t;
+}
+
+class ExtCommandLineSupplier: public CommandLineArgs::Supplier {
+public:
+ explicit ExtCommandLineSupplier():
+ m_count(
+ comphelper::LibreOfficeKit::isActive()
+ ? 0 : rtl_getAppCommandArgCount()),
+ m_index(0)
+ {
+ OUString url;
+ if (utl::Bootstrap::getProcessWorkingDir(url)) {
+ m_cwdUrl = url;
+ }
+ }
+
+ virtual std::optional< OUString > getCwdUrl() override { return m_cwdUrl; }
+
+ virtual bool next(OUString * argument) override {
+ OSL_ASSERT(argument != nullptr);
+ if (m_index < m_count) {
+ rtl_getAppCommandArg(m_index++, &argument->pData);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+private:
+ std::optional< OUString > m_cwdUrl;
+ sal_uInt32 m_count;
+ sal_uInt32 m_index;
+};
+
+enum class CommandLineEvent {
+ Open, Print, View, Start, PrintTo,
+ ForceOpen, ForceNew, Conversion, BatchPrint
+};
+
+// Office URI Schemes: see https://msdn.microsoft.com/en-us/library/dn906146
+// This functions checks if the arg is an Office URI.
+// If applicable, it updates arg to inner URI.
+// If no event argument is explicitly set in command line,
+// then it returns updated command line event,
+// according to Office URI command.
+CommandLineEvent CheckOfficeURI(/* in,out */ OUString& arg, CommandLineEvent curEvt)
+{
+ // 1. Strip the scheme name
+ OUString rest1;
+ bool isOfficeURI = ( arg.startsWithIgnoreAsciiCase("vnd.libreoffice.command:", &rest1) // Proposed extended schema
+ || arg.startsWithIgnoreAsciiCase("ms-word:", &rest1)
+ || arg.startsWithIgnoreAsciiCase("ms-powerpoint:", &rest1)
+ || arg.startsWithIgnoreAsciiCase("ms-excel:", &rest1)
+ || arg.startsWithIgnoreAsciiCase("ms-visio:", &rest1)
+ || arg.startsWithIgnoreAsciiCase("ms-access:", &rest1));
+ if (!isOfficeURI)
+ return curEvt;
+
+ OUString rest2;
+ tools::Long nURIlen = -1;
+
+ // URL might be encoded
+ OUString decoded_rest = rest1.replaceAll("%7C", "|").replaceAll("%7c", "|");
+
+ // 2. Discriminate by command name (incl. 1st command argument descriptor)
+ // Extract URI: everything up to possible next argument
+ if (decoded_rest.startsWith("ofv|u|", &rest2))
+ {
+ // Open for view - override only in default mode
+ if (curEvt == CommandLineEvent::Open)
+ curEvt = CommandLineEvent::View;
+ nURIlen = rest2.indexOf("|");
+ }
+ else if (decoded_rest.startsWith("ofe|u|", &rest2))
+ {
+ // Open for editing - override only in default mode
+ if (curEvt == CommandLineEvent::Open)
+ curEvt = CommandLineEvent::ForceOpen;
+ nURIlen = rest2.indexOf("|");
+ }
+ else if (decoded_rest.startsWith("nft|u|", &rest2))
+ {
+ // New from template - override only in default mode
+ if (curEvt == CommandLineEvent::Open)
+ curEvt = CommandLineEvent::ForceNew;
+ nURIlen = rest2.indexOf("|");
+ // TODO: process optional second argument (default save-to location)
+ // For now, we just ignore it
+ }
+ else
+ {
+ // Abbreviated scheme: <scheme-name>:URI
+ // "ofv|u|" implied
+ // override only in default mode
+ if (curEvt == CommandLineEvent::Open)
+ curEvt = CommandLineEvent::View;
+ rest2 = rest1;
+ }
+ if (nURIlen < 0)
+ nURIlen = rest2.getLength();
+ auto const uri = rest2.subView(0, nURIlen);
+ if (INetURLObject(uri).GetProtocol() == INetProtocol::Macro) {
+ // Let the "Open" machinery process the full command URI (leading to failure, by intention,
+ // as the "Open" machinery does not know about those command URI schemes):
+ curEvt = CommandLineEvent::Open;
+ } else {
+ arg = uri;
+ }
+ return curEvt;
+}
+
+// Skip single newline (be it *NIX LF, MacOS CR, of Win CRLF)
+// Changes the offset, and returns true if moved
+bool SkipNewline(const char* & pStr)
+{
+ if ((*pStr != '\r') && (*pStr != '\n'))
+ return false;
+ if (*pStr == '\r')
+ ++pStr;
+ if (*pStr == '\n')
+ ++pStr;
+ return true;
+}
+
+// Web query: http://support.microsoft.com/kb/157482
+CommandLineEvent CheckWebQuery(/* in,out */ OUString& arg, CommandLineEvent curEvt)
+{
+ // Only handle files with extension .iqy
+ if (!arg.endsWithIgnoreAsciiCase(".iqy"))
+ return curEvt;
+
+ static std::mutex aMutex;
+ std::lock_guard aGuard(aMutex);
+
+ try
+ {
+ OUString sFileURL;
+ // Cannot use translateExternalUris yet, because process service factory is not yet available
+ if (osl::FileBase::getFileURLFromSystemPath(arg, sFileURL) != osl::FileBase::RC::E_None)
+ return curEvt;
+ SvFileStream stream(sFileURL, StreamMode::READ);
+
+ const sal_Int32 nBufLen = 32000;
+ char sBuffer[nBufLen];
+ size_t nRead = stream.ReadBytes(sBuffer, nBufLen);
+ if (nRead < 8) // WEB\n1\n...
+ return curEvt;
+
+ const char* pPos = sBuffer;
+ if (strncmp(pPos, "WEB", 3) != 0)
+ return curEvt;
+ pPos += 3;
+ if (!SkipNewline(pPos))
+ return curEvt;
+ if (*pPos != '1')
+ return curEvt;
+ ++pPos;
+ if (!SkipNewline(pPos))
+ return curEvt;
+
+ OStringBuffer aResult(nRead);
+ do
+ {
+ const char* pPos1 = pPos;
+ const char* pEnd = sBuffer + nRead;
+ while ((pPos1 < pEnd) && (*pPos1 != '\r') && (*pPos1 != '\n'))
+ ++pPos1;
+ aResult.append(pPos, pPos1 - pPos);
+ if (pPos1 < pEnd) // newline
+ break;
+ pPos = sBuffer;
+ } while ((nRead = stream.ReadBytes(sBuffer, nBufLen)) > 0);
+
+ stream.Close();
+
+ arg = OStringToOUString(aResult, osl_getThreadTextEncoding());
+ return CommandLineEvent::ForceNew;
+ }
+ catch (...)
+ {
+ SAL_WARN("desktop.app", "An error processing Web Query file: " << arg);
+ }
+
+ return curEvt;
+}
+
+} // namespace
+
+CommandLineArgs::Supplier::Exception::Exception() {}
+
+CommandLineArgs::Supplier::Exception::Exception(Exception const &) {}
+
+CommandLineArgs::Supplier::Exception &
+CommandLineArgs::Supplier::Exception::operator =(Exception const &)
+{ return *this; }
+
+CommandLineArgs::Supplier::~Supplier() {}
+
+// initialize class with command line parameters from process environment
+CommandLineArgs::CommandLineArgs()
+{
+ InitParamValues();
+ ExtCommandLineSupplier s;
+ ParseCommandLine_Impl( s );
+}
+
+CommandLineArgs::CommandLineArgs( Supplier& supplier )
+{
+ InitParamValues();
+ ParseCommandLine_Impl( supplier );
+}
+
+void CommandLineArgs::ParseCommandLine_Impl( Supplier& supplier )
+{
+ m_cwdUrl = supplier.getCwdUrl();
+ CommandLineEvent eCurrentEvent = CommandLineEvent::Open;
+
+ for (;;)
+ {
+ OUString aArg;
+ if ( !supplier.next( &aArg ) )
+ {
+ break;
+ }
+
+ if ( !aArg.isEmpty() )
+ {
+ m_bEmpty = false;
+ OUString oArg;
+ OUString oDeprecatedArg;
+ if (!aArg.startsWith("--", &oArg) && aArg.startsWith("-", &oArg)
+ && aArg.getLength() > 2) // -h, -?, -n, -o, -p are still valid
+ {
+ oDeprecatedArg = aArg; // save here, since aArg can change later
+ }
+
+ OUString rest;
+ if ( oArg == "minimized" )
+ {
+ m_minimized = true;
+ }
+ else if ( oArg == "invisible" )
+ {
+ m_invisible = true;
+ }
+ else if ( oArg == "norestore" )
+ {
+ m_norestore = true;
+ }
+ else if ( oArg == "nodefault" )
+ {
+ m_nodefault = true;
+ }
+ else if ( oArg == "headless" )
+ {
+ setHeadless();
+ }
+ else if ( oArg == "safe-mode" )
+ {
+ m_safemode = true;
+ }
+ else if ( oArg == "cat" )
+ {
+ m_textcat = true;
+ m_conversionparams = "txt:Text";
+ eCurrentEvent = CommandLineEvent::Conversion;
+ setHeadless();
+ }
+ else if ( oArg == "script-cat" )
+ {
+ m_scriptcat = true;
+ eCurrentEvent = CommandLineEvent::Conversion;
+ setHeadless();
+ }
+ else if ( oArg == "quickstart" )
+ {
+#if defined(ENABLE_QUICKSTART_APPLET)
+ m_quickstart = true;
+#endif
+ m_noquickstart = false;
+ }
+ else if ( oArg == "quickstart=no" )
+ {
+ m_noquickstart = true;
+ m_quickstart = false;
+ }
+ else if ( oArg == "terminate_after_init" )
+ {
+ m_terminateafterinit = true;
+ }
+ else if ( oArg == "nofirststartwizard" )
+ {
+ // Do nothing, accept only for backward compatibility
+ }
+ else if ( oArg == "nologo" )
+ {
+ m_nologo = true;
+ }
+#if HAVE_FEATURE_MULTIUSER_ENVIRONMENT
+ else if ( oArg == "nolockcheck" )
+ {
+ m_nolockcheck = true;
+ }
+#endif
+ else if ( oArg == "help" || aArg == "-h" || aArg == "-?" )
+ {
+ m_help = true;
+ }
+ else if ( oArg == "helpwriter" )
+ {
+ m_helpwriter = true;
+ }
+ else if ( oArg == "helpcalc" )
+ {
+ m_helpcalc = true;
+ }
+ else if ( oArg == "helpdraw" )
+ {
+ m_helpdraw = true;
+ }
+ else if ( oArg == "helpimpress" )
+ {
+ m_helpimpress = true;
+ }
+ else if ( oArg == "helpbase" )
+ {
+ m_helpbase = true;
+ }
+ else if ( oArg == "helpbasic" )
+ {
+ m_helpbasic = true;
+ }
+ else if ( oArg == "helpmath" )
+ {
+ m_helpmath = true;
+ }
+ else if ( oArg == "protector" )
+ {
+ // Not relevant for us here, but can be used in unit tests.
+ // Usually unit tests would not end up here, but e.g. the
+ // LOK Tiled Rendering tests end up running a full soffice
+ // process, and we can't bail on the use of --protector.
+
+ // We specifically need to consume the following 2 arguments
+ // for --protector
+ if ((!supplier.next(&aArg) || !supplier.next(&aArg)) && m_unknown.isEmpty())
+ m_unknown = "--protector must be followed by two arguments";
+ }
+ else if ( oArg == "version" )
+ {
+ m_version = true;
+ }
+ else if ( oArg.startsWith("splash-pipe=") )
+ {
+ m_splashpipe = true;
+ }
+#ifdef MACOSX
+ /* #i84053# ignore -psn on Mac
+ Platform dependent #ifdef here is ugly, however this is currently
+ the only platform dependent parameter. Should more appear
+ we should find a better solution
+ */
+ else if ( aArg.startsWith("-psn") )
+ {
+ oDeprecatedArg.clear();
+ }
+#endif
+#if HAVE_FEATURE_MACOSX_SANDBOX
+ else if ( oArg == "nstemporarydirectory" )
+ {
+ printf("%s\n", [NSTemporaryDirectory() UTF8String]);
+ exit(0);
+ }
+#endif
+#ifdef _WIN32
+ /* fdo#57203 ignore -Embedding on Windows
+ when LibreOffice is launched by COM+
+ */
+ else if ( oArg == "Embedding" )
+ {
+ oDeprecatedArg.clear();
+ }
+#endif
+ else if ( oArg.startsWith("infilter=", &rest))
+ {
+ m_infilter.push_back(rest);
+ }
+ else if ( oArg.startsWith("accept=", &rest))
+ {
+ m_accept.push_back(rest);
+ }
+ else if ( oArg.startsWith("unaccept=", &rest))
+ {
+ m_unaccept.push_back(rest);
+ }
+ else if ( oArg.startsWith("language=", &rest))
+ {
+ m_language = rest;
+ }
+ else if ( oArg.startsWith("pidfile=", &rest))
+ {
+ m_pidfile = rest;
+ }
+ else if ( oArg == "writer" )
+ {
+ m_writer = true;
+ m_bDocumentArgs = true;
+ }
+ else if ( oArg == "calc" )
+ {
+ m_calc = true;
+ m_bDocumentArgs = true;
+ }
+ else if ( oArg == "draw" )
+ {
+ m_draw = true;
+ m_bDocumentArgs = true;
+ }
+ else if ( oArg == "impress" )
+ {
+ m_impress = true;
+ m_bDocumentArgs = true;
+ }
+ else if ( oArg == "base" )
+ {
+ m_base = true;
+ m_bDocumentArgs = true;
+ }
+ else if ( oArg == "global" )
+ {
+ m_global = true;
+ m_bDocumentArgs = true;
+ }
+ else if ( oArg == "math" )
+ {
+ m_math = true;
+ m_bDocumentArgs = true;
+ }
+ else if ( oArg == "web" )
+ {
+ m_web = true;
+ m_bDocumentArgs = true;
+ }
+ else if ( aArg == "-n" )
+ {
+ // force new documents based on the following documents
+ eCurrentEvent = CommandLineEvent::ForceNew;
+ }
+ else if ( aArg == "-o" )
+ {
+ // force open documents regardless if they are templates or not
+ eCurrentEvent = CommandLineEvent::ForceOpen;
+ }
+ else if ( oArg == "pt" )
+ {
+ // Print to special printer
+ eCurrentEvent = CommandLineEvent::PrintTo;
+ // first argument after "-pt" must be the printer name
+ if (supplier.next(&aArg))
+ m_printername = aArg;
+ else if (m_unknown.isEmpty())
+ m_unknown = "--pt must be followed by printername";
+ }
+ else if ( aArg == "-p" )
+ {
+ // Print to default printer
+ eCurrentEvent = CommandLineEvent::Print;
+ }
+ else if ( oArg == "view")
+ {
+ // open in viewmode
+ eCurrentEvent = CommandLineEvent::View;
+ }
+ else if ( oArg == "show" )
+ {
+ // open in viewmode
+ eCurrentEvent = CommandLineEvent::Start;
+ }
+ else if ( oArg == "display" )
+ {
+ // The command line argument following --display should
+ // always be treated as the argument of --display.
+ // --display and its argument are handled "out of line"
+ // in Unix-only desktop/unx/source/splashx.c and vcl/unx/*,
+ // and just ignored here
+ (void)supplier.next(&aArg);
+ }
+ else if ( oArg == "convert-to" )
+ {
+ eCurrentEvent = CommandLineEvent::Conversion;
+ // first argument must be the params
+ if (supplier.next(&aArg))
+ {
+ m_conversionparams = aArg;
+ // It doesn't make sense to use convert-to without headless.
+ setHeadless();
+ }
+ else if (m_unknown.isEmpty())
+ m_unknown = "--convert-to must be followed by output_file_extension[:output_filter_name]";
+ }
+ else if ( oArg == "print-to-file" )
+ {
+ eCurrentEvent = CommandLineEvent::BatchPrint;
+ }
+ else if ( oArg == "printer-name" )
+ {
+ if (eCurrentEvent == CommandLineEvent::BatchPrint)
+ {
+ // first argument is the printer name
+ if (supplier.next(&aArg))
+ m_printername = aArg;
+ else if (m_unknown.isEmpty())
+ m_unknown = "--printer-name must be followed by printername";
+ }
+ else if (m_unknown.isEmpty())
+ {
+ m_unknown = "--printer-name must directly follow --print-to-file";
+ }
+ }
+ else if ( oArg == "outdir" )
+ {
+ if (eCurrentEvent == CommandLineEvent::Conversion ||
+ eCurrentEvent == CommandLineEvent::BatchPrint)
+ {
+ if (supplier.next(&aArg))
+ m_conversionout = aArg;
+ else if (m_unknown.isEmpty())
+ m_unknown = "--outdir must be followed by output directory path";
+ }
+ else if (m_unknown.isEmpty())
+ {
+ m_unknown = "--outdir must directly follow either output filter specification of --convert-to, or --print-to-file or its printer specification";
+ }
+ }
+ else if ( eCurrentEvent == CommandLineEvent::Conversion
+ && oArg == "convert-images-to" )
+ {
+ if (supplier.next(&aArg))
+ m_convertimages = aArg;
+ else if (m_unknown.isEmpty())
+ m_unknown = "--convert-images-to must be followed by an image type";
+ }
+ else if ( aArg.startsWith("-") )
+ {
+ // because it's impossible to filter these options that
+ // are handled in the soffice shell script with the
+ // primitive tools that /bin/sh offers, ignore them here
+ if (
+#if defined UNX
+ oArg != "record" &&
+ oArg != "backtrace" &&
+ oArg != "strace" &&
+ oArg != "valgrind" &&
+ // for X Session Management, handled in
+ // vcl/unx/generic/app/sm.cxx:
+ oArg != "session=" &&
+#endif
+ //ignore additional legacy options that don't do anything anymore
+ oArg != "nocrashreport" &&
+ m_unknown.isEmpty())
+ {
+ m_unknown = aArg;
+ }
+ oDeprecatedArg.clear();
+ }
+ else
+ {
+ // handle this argument as a filename
+
+ // First check if this is an Office URI
+ // This will possibly adjust event for this argument
+ // and put real URI to aArg
+ CommandLineEvent eThisEvent = CheckOfficeURI(aArg, eCurrentEvent);
+
+ // Now check if this is a Web Query file
+ eThisEvent = CheckWebQuery(aArg, eThisEvent);
+
+ switch (eThisEvent)
+ {
+ case CommandLineEvent::Open:
+ m_openlist.push_back(aArg);
+ m_bDocumentArgs = true;
+ break;
+ case CommandLineEvent::View:
+ m_viewlist.push_back(aArg);
+ m_bDocumentArgs = true;
+ break;
+ case CommandLineEvent::Start:
+ m_startlist.push_back(aArg);
+ m_bDocumentArgs = true;
+ break;
+ case CommandLineEvent::Print:
+ m_printlist.push_back(aArg);
+ m_bDocumentArgs = true;
+ break;
+ case CommandLineEvent::PrintTo:
+ m_printtolist.push_back(aArg);
+ m_bDocumentArgs = true;
+ break;
+ case CommandLineEvent::ForceNew:
+ m_forcenewlist.push_back(aArg);
+ m_bDocumentArgs = true;
+ break;
+ case CommandLineEvent::ForceOpen:
+ m_forceopenlist.push_back(aArg);
+ m_bDocumentArgs = true;
+ break;
+ case CommandLineEvent::Conversion:
+ case CommandLineEvent::BatchPrint:
+ m_conversionlist.push_back(aArg);
+ break;
+ }
+ }
+
+ if (!oDeprecatedArg.isEmpty())
+ {
+ OString sArg(OUStringToOString(oDeprecatedArg, osl_getThreadTextEncoding()));
+ fprintf(stderr, "Warning: %s is deprecated. Use -%s instead.\n", sArg.getStr(), sArg.getStr());
+ }
+ }
+ }
+}
+
+void CommandLineArgs::InitParamValues()
+{
+ m_minimized = false;
+ m_norestore = false;
+#if HAVE_FEATURE_UI
+ m_invisible = false;
+ m_headless = false;
+#else
+ m_invisible = true;
+ m_headless = true;
+#endif
+ m_quickstart = false;
+ m_noquickstart = false;
+ m_terminateafterinit = false;
+ m_nologo = false;
+ m_nolockcheck = false;
+ m_nodefault = false;
+ m_help = false;
+ m_writer = false;
+ m_calc = false;
+ m_draw = false;
+ m_impress = false;
+ m_global = false;
+ m_math = false;
+ m_web = false;
+ m_base = false;
+ m_helpwriter = false;
+ m_helpcalc = false;
+ m_helpdraw = false;
+ m_helpbasic = false;
+ m_helpmath = false;
+ m_helpimpress = false;
+ m_helpbase = false;
+ m_version = false;
+ m_splashpipe = false;
+ m_bEmpty = true;
+ m_bDocumentArgs = false;
+ m_textcat = false;
+ m_scriptcat = false;
+ m_safemode = false;
+}
+
+bool CommandLineArgs::HasModuleParam() const
+{
+ return m_writer || m_calc || m_draw || m_impress || m_global || m_math
+ || m_web || m_base;
+}
+
+std::vector< OUString > CommandLineArgs::GetOpenList() const
+{
+ return translateExternalUris(m_openlist);
+}
+
+std::vector< OUString > CommandLineArgs::GetViewList() const
+{
+ return translateExternalUris(m_viewlist);
+}
+
+std::vector< OUString > CommandLineArgs::GetStartList() const
+{
+ return translateExternalUris(m_startlist);
+}
+
+std::vector< OUString > CommandLineArgs::GetForceOpenList() const
+{
+ return translateExternalUris(m_forceopenlist);
+}
+
+std::vector< OUString > CommandLineArgs::GetForceNewList() const
+{
+ return translateExternalUris(m_forcenewlist);
+}
+
+std::vector< OUString > CommandLineArgs::GetPrintList() const
+{
+ return translateExternalUris(m_printlist);
+}
+
+std::vector< OUString > CommandLineArgs::GetPrintToList() const
+{
+ return translateExternalUris(m_printtolist);
+}
+
+std::vector< OUString > CommandLineArgs::GetConversionList() const
+{
+ return translateExternalUris(m_conversionlist);
+}
+
+OUString CommandLineArgs::GetConversionOut() const
+{
+ return translateExternalUris(m_conversionout);
+}
+
+} // namespace desktop
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/app/cmdlineargs.hxx b/desktop/source/app/cmdlineargs.hxx
new file mode 100644
index 0000000000..64a1bcfd0c
--- /dev/null
+++ b/desktop/source/app/cmdlineargs.hxx
@@ -0,0 +1,186 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <sal/config.h>
+
+#include <vector>
+
+#include <rtl/ustring.hxx>
+#include <optional>
+
+namespace desktop
+{
+
+class CommandLineArgs
+{
+ public:
+ struct Supplier
+ {
+ // Thrown from constructors and next:
+ class Exception final
+ {
+ public:
+ Exception();
+ Exception(Exception const &);
+ Exception & operator =(Exception const &);
+ };
+
+ virtual ~Supplier();
+ virtual std::optional< OUString > getCwdUrl() = 0;
+ virtual bool next(OUString * argument) = 0;
+ };
+
+ CommandLineArgs();
+ explicit CommandLineArgs( Supplier& supplier );
+
+ CommandLineArgs(const CommandLineArgs&) = delete;
+ const CommandLineArgs& operator=(const CommandLineArgs&) = delete;
+
+ const std::optional< OUString >& getCwdUrl() const { return m_cwdUrl; }
+
+ // Access to bool parameters
+ bool IsMinimized() const { return m_minimized;}
+ bool IsInvisible() const
+ {
+ return m_invisible || m_headless;
+ }
+ bool IsNoRestore() const { return m_norestore;}
+ bool IsNoDefault() const { return m_nodefault;}
+ bool IsHeadless() const { return m_headless;}
+ bool IsQuickstart() const { return m_quickstart;}
+ bool IsNoQuickstart() const { return m_noquickstart;}
+ bool IsTerminateAfterInit() const { return m_terminateafterinit;}
+ bool IsNoLogo() const { return m_nologo;}
+ bool IsNoLockcheck() const { return m_nolockcheck;}
+ bool IsHelp() const { return m_help;}
+ bool IsHelpWriter() const { return m_helpwriter;}
+ bool IsHelpCalc() const { return m_helpcalc;}
+ bool IsHelpDraw() const { return m_helpdraw;}
+ bool IsHelpImpress() const { return m_helpimpress;}
+ bool IsHelpBase() const { return m_helpbase;}
+ bool IsHelpMath() const { return m_helpmath;}
+ bool IsHelpBasic() const { return m_helpbasic;}
+ bool IsWriter() const { return m_writer;}
+ bool IsCalc() const { return m_calc;}
+ bool IsDraw() const { return m_draw;}
+ bool IsImpress() const { return m_impress;}
+ bool IsBase() const { return m_base;}
+ bool IsGlobal() const { return m_global;}
+ bool IsMath() const { return m_math;}
+ bool IsWeb() const { return m_web;}
+ bool IsVersion() const { return m_version;}
+ bool HasModuleParam() const;
+ bool WantsToLoadDocument() const { return m_bDocumentArgs;}
+ bool IsTextCat() const { return m_textcat;}
+ bool IsScriptCat() const { return m_scriptcat;}
+ bool IsSafeMode() const { return m_safemode; }
+
+ const OUString& GetUnknown() const { return m_unknown;}
+
+ // Access to string parameters
+ bool HasSplashPipe() const { return m_splashpipe;}
+ std::vector< OUString > const & GetAccept() const { return m_accept;}
+ std::vector< OUString > const & GetUnaccept() const { return m_unaccept;}
+ std::vector< OUString > GetOpenList() const;
+ std::vector< OUString > GetViewList() const;
+ std::vector< OUString > GetStartList() const;
+ std::vector< OUString > GetForceOpenList() const;
+ std::vector< OUString > GetForceNewList() const;
+ std::vector< OUString > GetPrintList() const;
+ std::vector< OUString > GetPrintToList() const;
+ const OUString& GetPrinterName() const { return m_printername;}
+ const OUString& GetLanguage() const { return m_language;}
+ std::vector< OUString > const & GetInFilter() const { return m_infilter;}
+ std::vector< OUString > GetConversionList() const;
+ const OUString& GetConversionParams() const { return m_conversionparams;}
+ OUString GetConversionOut() const;
+ OUString const & GetImageConversionType() const { return m_convertimages; }
+ const OUString& GetPidfileName() const { return m_pidfile;}
+
+ // Special analyzed states (does not match directly to a command line parameter!)
+ bool IsEmpty() const { return m_bEmpty;}
+
+ void setHeadless() { m_headless = true; }
+
+ private:
+ void ParseCommandLine_Impl( Supplier& supplier );
+ void InitParamValues();
+
+ std::optional< OUString > m_cwdUrl;
+
+ bool m_minimized;
+ bool m_invisible;
+ bool m_norestore;
+ bool m_headless;
+ bool m_quickstart;
+ bool m_noquickstart;
+ bool m_terminateafterinit;
+ bool m_nologo;
+ bool m_nolockcheck;
+ bool m_nodefault;
+ bool m_help;
+ bool m_writer;
+ bool m_calc;
+ bool m_draw;
+ bool m_impress;
+ bool m_global;
+ bool m_math;
+ bool m_web;
+ bool m_base;
+ bool m_helpwriter;
+ bool m_helpcalc;
+ bool m_helpdraw;
+ bool m_helpbasic;
+ bool m_helpmath;
+ bool m_helpimpress;
+ bool m_helpbase;
+ bool m_version;
+ bool m_splashpipe;
+ bool m_textcat;
+ bool m_scriptcat;
+ bool m_safemode;
+
+ OUString m_unknown;
+
+ bool m_bEmpty; // No Args at all
+ bool m_bDocumentArgs; // A document creation/open/load arg is used
+ std::vector< OUString > m_accept;
+ std::vector< OUString > m_unaccept;
+ std::vector< OUString > m_openlist; // contains external URIs
+ std::vector< OUString > m_viewlist; // contains external URIs
+ std::vector< OUString > m_startlist; // contains external URIs
+ std::vector< OUString > m_forceopenlist; // contains external URIs
+ std::vector< OUString > m_forcenewlist; // contains external URIs
+ std::vector< OUString > m_printlist; // contains external URIs
+ std::vector< OUString > m_printtolist; // contains external URIs
+ OUString m_printername;
+ std::vector< OUString > m_conversionlist; // contains external URIs
+ OUString m_conversionparams;
+ OUString m_conversionout; // contains external URIs
+ OUString m_convertimages; // The format in which images should be converted
+ std::vector< OUString > m_infilter;
+ OUString m_language;
+ OUString m_pidfile;
+};
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/app/cmdlinehelp.cxx b/desktop/source/app/cmdlinehelp.cxx
new file mode 100644
index 0000000000..9c9fd940f2
--- /dev/null
+++ b/desktop/source/app/cmdlinehelp.cxx
@@ -0,0 +1,266 @@
+/* -*- 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 <comphelper/string.hxx>
+#include <app.hxx>
+
+#include "cmdlinehelp.hxx"
+
+#ifdef _WIN32
+#if !defined WIN32_LEAN_AND_MEAN
+# define WIN32_LEAN_AND_MEAN
+#endif
+#include <windows.h>
+#include <io.h>
+#include <fcntl.h>
+#endif
+
+namespace desktop
+{
+ constexpr OUString aCmdLineHelp_version =
+ u"%PRODUCTNAME %PRODUCTVERSION%PRODUCTEXTENSION %BUILDID\n"
+ "\n"_ustr;
+ constexpr OUStringLiteral aCmdLineHelp =
+ u"Usage: %CMDNAME [argument...]\n"
+ " argument - switches, switch parameters and document URIs (filenames). \n\n"
+ "Using without special arguments: \n"
+ "Opens the start center, if it is used without any arguments. \n"
+ " {file} Tries to open the file (files) in the components \n"
+ " suitable for them. \n"
+ " {file} {macro:///Library.Module.MacroName} \n"
+ " Opens the file and runs specified macro from \n"
+ " My Macros container. \n"
+ " {file} {macro://./Library.Module.MacroName} \n"
+ " Opens the file and runs specified macro from \n"
+ " the file. \n\n"
+ "Getting help and information: \n"
+ " --help | -h | -? Shows this help and quits. \n"
+ " --helpwriter Opens built-in or online Help on Writer. \n"
+ " --helpcalc Opens built-in or online Help on Calc. \n"
+ " --helpdraw Opens built-in or online Help on Draw. \n"
+ " --helpimpress Opens built-in or online Help on Impress. \n"
+ " --helpbase Opens built-in or online Help on Base. \n"
+ " --helpbasic Opens built-in or online Help on Basic scripting \n"
+ " language. \n"
+ " --helpmath Opens built-in or online Help on Math. \n"
+ " --version Shows the version and quits. \n"
+ " --nstemporarydirectory \n"
+ " (MacOS X sandbox only) Returns path of the temporary \n"
+ " directory for the current user and exits. Overrides \n"
+ " all other arguments. \n\n"
+ "General arguments: \n"
+ " --quickstart[=no] Activates[Deactivates] the Quickstarter service. \n"
+ " --nolockcheck Disables check for remote instances using one \n"
+ " installation. \n"
+ " --infilter={filter} Force an input filter type if possible. For example: \n"
+ " --infilter=\"Calc Office Open XML\" \n"
+ " --infilter=\"Text (encoded):UTF8,LF,,,\" \n"
+ " --pidfile={file} Store soffice.bin pid to {file}. \n"
+ " --display {display} Sets the DISPLAY environment variable on UNIX-like \n"
+ " platforms to the value {display} (only supported by a \n"
+ " start script). \n\n"
+ "User/programmatic interface control: \n"
+ " --nologo Disables the splash screen at program start. \n"
+ " --minimized Starts minimized. The splash screen is not displayed. \n"
+ " --nodefault Starts without displaying anything except the splash \n"
+ " screen (do not display initial window). \n"
+ " --invisible Starts in invisible mode. Neither the start-up logo nor \n"
+ " the initial program window will be visible. Application \n"
+ " can be controlled, and documents and dialogs can be \n"
+ " controlled and opened via the API. Using the parameter, \n"
+ " the process can only be ended using the taskmanager \n"
+ " (Windows) or the kill command (UNIX-like systems). It \n"
+ " cannot be used in conjunction with --quickstart. \n"
+ " --headless Starts in \"headless mode\" which allows using the \n"
+ " application without GUI. This special mode can be used \n"
+ " when the application is controlled by external clients \n"
+ " via the API. \n"
+ " --norestore Disables restart and file recovery after a system crash.\n"
+ " --safe-mode Starts in a safe mode, i.e. starts temporarily with a \n"
+ " fresh user profile and helps to restore a broken \n"
+ " configuration. \n"
+ " --accept={connect-string} Specifies a UNO connect-string to create a UNO \n"
+ " acceptor through which other programs can connect to \n"
+ " access the API. Note that API access allows execution \n"
+ " of arbitrary commands. \n"
+ " The syntax of the {connect-string} is: \n"
+ " connection-type,params;protocol-name,params \n"
+ " e.g. pipe,name={some name};urp \n"
+ " or socket,host=localhost,port=54321;urp \n"
+ " --unaccept={connect-string} Closes an acceptor that was created with \n"
+ " --accept. Use --unaccept=all to close all acceptors. \n"
+ " --language={lang} Uses specified language, if language is not selected \n"
+ " yet for UI. The lang is a tag of the language in IETF \n"
+ " language tag. \n\n"
+ "Developer arguments: \n"
+ " --terminate_after_init \n"
+ " Exit after initialization complete (no documents loaded)\n"
+ " --eventtesting Exit after loading documents. \n\n"
+ "New document creation arguments: \n"
+ "The arguments create an empty document of specified kind. Only one of them may \n"
+ "be used in one command line. If filenames are specified after an argument, \n"
+ "then it tries to open those files in the specified component. \n"
+ " --writer Creates an empty Writer document. \n"
+ " --calc Creates an empty Calc document. \n"
+ " --draw Creates an empty Draw document. \n"
+ " --impress Creates an empty Impress document. \n"
+ " --base Creates a new database. \n"
+ " --global Creates an empty Writer master (global) document. \n"
+ " --math Creates an empty Math document (formula). \n"
+ " --web Creates an empty HTML document. \n\n"
+ "File open arguments: \n"
+ "The arguments define how following filenames are treated. New treatment begins \n"
+ "after the argument and ends at the next argument. The default treatment is to \n"
+ "open documents for editing, and create new documents from document templates. \n"
+ " -n Treats following files as templates for creation of new \n"
+ " documents. \n"
+ " -o Opens following files for editing, regardless whether \n"
+ " they are templates or not. \n"
+ " --pt {Printername} Prints following files to the printer {Printername}, \n"
+ " after which those files are closed. The splash screen \n"
+ " does not appear. If used multiple times, only last \n"
+ " {Printername} is effective for all documents of all \n"
+ " --pt runs. Also, --printer-name argument of \n"
+ " --print-to-file switch interferes with {Printername}. \n"
+ " -p Prints following files to the default printer, after \n"
+ " which those files are closed. The splash screen does \n"
+ " not appear. If the file name contains spaces, then it \n"
+ " must be enclosed in quotation marks. \n"
+ " --view Opens following files in viewer mode (read-only). \n"
+ " --show Opens and starts the following presentation documents \n"
+ " of each immediately. Files are closed after the showing.\n"
+ " Files other than Impress documents are opened in \n"
+ " default mode , regardless of previous mode. \n"
+ " --convert-to OutputFileExtension[:OutputFilterName] \\ \n"
+ " [--outdir output_dir] [--convert-images-to] \n"
+ " Batch convert files (implies --headless). If --outdir \n"
+ " isn't specified, then current working directory is used \n"
+ " as output_dir. If --convert-images-to is given, its \n"
+ " parameter is taken as the target filter format for *all*\n"
+ " images written to the output format. If --convert-to is \n"
+ " used more than once, the last value of \n"
+ " OutputFileExtension[:OutputFilterName] is effective. If \n"
+ " --outdir is used more than once, only its last value is \n"
+ " effective. For example: \n"
+ " --convert-to pdf *.odt \n"
+ " --convert-to epub *.doc \n"
+ " --convert-to pdf:writer_pdf_Export --outdir /home/user *.doc\n"
+ " --convert-to \"html:XHTML Writer File:UTF8\" \\ \n"
+ " --convert-images-to \"jpg\" *.doc \n"
+ " --convert-to \"txt:Text (encoded):UTF8\" *.doc \n"
+ " --print-to-file [--printer-name printer_name] [--outdir output_dir] \n"
+ " Batch print files to file. If --outdir is not specified,\n"
+ " then current working directory is used as output_dir. \n"
+ " If --printer-name or --outdir used multiple times, only \n"
+ " last value of each is effective. Also, {Printername} of \n"
+ " --pt switch interferes with --printer-name. \n"
+ " --cat Dump text content of the following files to console \n"
+ " (implies --headless). Cannot be used with --convert-to. \n"
+ " --script-cat Dump text content of any scripts embedded in the files \n"
+ " to console (implies --headless). Cannot be used with \n"
+ " --convert-to. \n"
+ " -env:<VAR>[=<VALUE>] Set a bootstrap variable. For example: to set \n"
+ " a non-default user profile path: \n"
+ " -env:UserInstallation=file:///tmp/test \n\n"
+ "Ignored switches: \n"
+ " -psn Ignored (MacOS X only). \n"
+ " -Embedding Ignored (COM+ related; Windows only). \n"
+ " --nofirststartwizard Does nothing, accepted only for backward compatibility.\n"
+ " --protector {arg1} {arg2} \n"
+ " Used only in unit tests and should have two arguments. \n\n";
+#ifdef _WIN32
+ namespace{
+ // This class is only used to create a console when soffice.bin is run without own console
+ // (like using soffice.exe launcher as opposed to soffice.com), and either --version or
+ // --help command line options were specified, or an error in a command line option was
+ // detected, which requires to output strings to user.
+ class lcl_Console {
+ public:
+ explicit lcl_Console(short nBufHeight)
+ : m_bOwnConsole(AllocConsole() != FALSE)
+ {
+ if (m_bOwnConsole)
+ {
+ HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
+
+ // Ensure that console buffer is enough to hold required data
+ CONSOLE_SCREEN_BUFFER_INFO cinfo;
+ GetConsoleScreenBufferInfo(hOut, &cinfo);
+ if (cinfo.dwSize.Y < nBufHeight)
+ {
+ cinfo.dwSize.Y = nBufHeight;
+ SetConsoleScreenBufferSize(hOut, cinfo.dwSize);
+ }
+
+ (void)freopen("CON", "r", stdin);
+ (void)freopen("CON", "w", stdout);
+ (void)freopen("CON", "w", stderr);
+
+ std::ios::sync_with_stdio(true);
+ }
+ }
+
+ ~lcl_Console()
+ {
+ if (m_bOwnConsole)
+ {
+ fflush(stdout);
+ fprintf(stdout, "Press Enter to continue...");
+ fgetc(stdin);
+ FreeConsole();
+ }
+ }
+ private:
+ bool m_bOwnConsole;
+ };
+ }
+#endif
+
+ void displayCmdlineHelp(OUString const & unknown)
+ {
+ OUString aHelpMessage_version = ReplaceStringHookProc(aCmdLineHelp_version);
+ OUString aHelpMessage(OUString(aCmdLineHelp).replaceFirst("%CMDNAME", "soffice"));
+ if (!unknown.isEmpty())
+ {
+ aHelpMessage = "Error in option: " + unknown + "\n\n"
+ + aHelpMessage;
+ }
+#ifdef _WIN32
+ sal_Int32 n = comphelper::string::getTokenCount(aHelpMessage, '\n');
+ lcl_Console aConsole(short(n*2));
+#endif
+ fprintf(stdout, "%s%s",
+ OUStringToOString(aHelpMessage_version, RTL_TEXTENCODING_ASCII_US).getStr(),
+ OUStringToOString(aHelpMessage, RTL_TEXTENCODING_ASCII_US).getStr());
+ }
+
+ void displayVersion()
+ {
+ OUString aVersionMsg(aCmdLineHelp_version);
+ aVersionMsg = ReplaceStringHookProc(aVersionMsg);
+#ifdef _WIN32
+ lcl_Console aConsole(short(10));
+#endif
+ fprintf(stdout, "%s", OUStringToOString(aVersionMsg, RTL_TEXTENCODING_ASCII_US).getStr());
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/app/cmdlinehelp.hxx b/desktop/source/app/cmdlinehelp.hxx
new file mode 100644
index 0000000000..5a32125053
--- /dev/null
+++ b/desktop/source/app/cmdlinehelp.hxx
@@ -0,0 +1,30 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <rtl/ustring.hxx>
+
+namespace desktop
+{
+void displayCmdlineHelp(OUString const& unknown);
+void displayVersion();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/app/crashreport.cxx b/desktop/source/app/crashreport.cxx
new file mode 100644
index 0000000000..b90a04907f
--- /dev/null
+++ b/desktop/source/app/crashreport.cxx
@@ -0,0 +1,477 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include <desktop/crashreport.hxx>
+#include <rtl/bootstrap.hxx>
+#include <osl/file.hxx>
+#include <comphelper/processfactory.hxx>
+#include <ucbhelper/proxydecider.hxx>
+#include <unotools/bootstrap.hxx>
+#include <o3tl/char16_t2wchar_t.hxx>
+#include <desktop/minidump.hxx>
+#include <rtl/ustrbuf.hxx>
+
+#include <config_version.h>
+#include <config_folders.h>
+
+#include <string>
+#include <regex>
+
+
+#if HAVE_FEATURE_BREAKPAD
+
+#include <fstream>
+#if defined( UNX ) && !defined MACOSX && !defined IOS && !defined ANDROID
+#include <client/linux/handler/exception_handler.h>
+#elif defined _WIN32
+#if defined __clang__
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wmicrosoft-enum-value"
+#endif
+#include <client/windows/handler/exception_handler.h>
+#if defined __clang__
+#pragma clang diagnostic pop
+#endif
+#include <locale>
+#include <codecvt>
+#endif
+
+osl::Mutex CrashReporter::maMutex;
+osl::Mutex CrashReporter::maActiveSfxObjectNameMutex;
+osl::Mutex CrashReporter::maUnoLogCmdMutex;
+std::unique_ptr<google_breakpad::ExceptionHandler> CrashReporter::mpExceptionHandler;
+bool CrashReporter::mbInit = false;
+CrashReporter::vmaKeyValues CrashReporter::maKeyValues;
+CrashReporter::vmaloggedUnoCommands CrashReporter::maloggedUnoCommands;
+OUString CrashReporter::msActiveSfxObjectName;
+
+
+#if defined( UNX ) && !defined MACOSX && !defined IOS && !defined ANDROID
+static bool dumpCallback(const google_breakpad::MinidumpDescriptor& descriptor, void* /*context*/, bool succeeded)
+{
+ CrashReporter::addKeyValue("Active-SfxObject",CrashReporter::getActiveSfxObjectName(),CrashReporter::AddItem);
+ CrashReporter::addKeyValue("Last-4-Uno-Commands",CrashReporter::getLoggedUnoCommands(),CrashReporter::AddItem);
+ CrashReporter::addKeyValue("DumpFile", OStringToOUString(descriptor.path(), RTL_TEXTENCODING_UTF8), CrashReporter::Write);
+ SAL_WARN("desktop", "minidump generated: " << descriptor.path());
+
+ return succeeded;
+}
+#elif defined _WIN32
+static bool dumpCallback(const wchar_t* path, const wchar_t* id,
+ void* /*context*/, EXCEPTION_POINTERS* /*exinfo*/,
+ MDRawAssertionInfo* /*assertion*/,
+ bool succeeded)
+{
+ // TODO: moggi: can we avoid this conversion
+#ifdef _MSC_VER
+#pragma warning (disable: 4996)
+#endif
+ std::wstring_convert<std::codecvt_utf8<wchar_t>> conv1;
+ std::string aPath = conv1.to_bytes(std::wstring(path)) + conv1.to_bytes(std::wstring(id)) + ".dmp";
+ CrashReporter::addKeyValue("Active-SfxObject",CrashReporter::getActiveSfxObjectName(),CrashReporter::AddItem);
+ CrashReporter::addKeyValue("Last-4-Uno-Commands",CrashReporter::getLoggedUnoCommands(),CrashReporter::AddItem);
+ CrashReporter::addKeyValue("DumpFile", OStringToOUString(aPath, RTL_TEXTENCODING_UTF8), CrashReporter::AddItem);
+ CrashReporter::addKeyValue("GDIHandles", OUString::number(::GetGuiResources(::GetCurrentProcess(), GR_GDIOBJECTS)), CrashReporter::Write);
+ SAL_WARN("desktop", "minidump generated: " << aPath);
+ return succeeded;
+}
+#endif
+
+
+void CrashReporter::writeToFile(std::ios_base::openmode Openmode)
+{
+#if defined _WIN32
+ const std::string iniPath = getIniFileName();
+ std::wstring iniPathW;
+ const int nChars = MultiByteToWideChar(CP_UTF8, 0, iniPath.c_str(), -1, nullptr, 0);
+ auto buf = std::make_unique<wchar_t[]>(nChars);
+ if (MultiByteToWideChar(CP_UTF8, 0, iniPath.c_str(), -1, buf.get(), nChars) != 0)
+ iniPathW = buf.get();
+
+ std::ofstream ini_file
+ = iniPathW.empty() ? std::ofstream(iniPath, Openmode) : std::ofstream(iniPathW, Openmode);
+#else
+ std::ofstream ini_file(getIniFileName(), Openmode);
+#endif
+
+ for (auto& keyValue : maKeyValues)
+ {
+ ini_file << OUStringToOString(keyValue.first, RTL_TEXTENCODING_UTF8) << "=";
+ ini_file << OUStringToOString(keyValue.second, RTL_TEXTENCODING_UTF8) << "\n";
+ }
+
+ maKeyValues.clear();
+ ini_file.close();
+}
+
+void CrashReporter::addKeyValue(const OUString& rKey, const OUString& rValue, tAddKeyHandling AddKeyHandling)
+{
+ osl::MutexGuard aGuard(maMutex);
+
+ if (IsDumpEnable())
+ {
+ if (!rKey.isEmpty())
+ maKeyValues.push_back(mpair(rKey, rValue));
+
+ if (AddKeyHandling != AddItem)
+ {
+ if (mbInit)
+ writeToFile(std::ios_base::app);
+ else if (AddKeyHandling == Create)
+ writeCommonInfo();
+ }
+ }
+}
+
+void CrashReporter::writeCommonInfo()
+{
+ writeSystemInfo();
+
+ ucbhelper::InternetProxyDecider proxy_decider(::comphelper::getProcessComponentContext());
+
+ static constexpr OUString protocol = u"https"_ustr;
+ static constexpr OUString url = u"crashreport.libreoffice.org"_ustr;
+ const sal_Int32 port = 443;
+
+ const OUString proxy_server = proxy_decider.getProxy(protocol, url, port);
+
+ // save the new Keys
+ vmaKeyValues atlast = maKeyValues;
+ // clear the keys, the following Keys should be at the begin
+ maKeyValues.clear();
+
+ // limit the amount of code that needs to be executed before the crash reporting
+ addKeyValue("ProductName", "LibreOffice", AddItem);
+ addKeyValue("Version", LIBO_VERSION_DOTTED, AddItem);
+ addKeyValue("BuildID", utl::Bootstrap::getBuildIdData(""), AddItem);
+ addKeyValue("URL", protocol + "://" + url + "/submit/", AddItem);
+
+ if (!proxy_server.isEmpty())
+ {
+ addKeyValue("Proxy", proxy_server, AddItem);
+ }
+
+ // write the new keys at the end
+ maKeyValues.insert(maKeyValues.end(), atlast.begin(), atlast.end());
+
+ mbInit = true;
+
+ writeToFile(std::ios_base::trunc);
+
+ updateMinidumpLocation();
+}
+
+void CrashReporter::setActiveSfxObjectName(const OUString& rActiveSfxObjectName)
+{
+ osl::MutexGuard aGuard(maActiveSfxObjectNameMutex);
+ msActiveSfxObjectName = rActiveSfxObjectName;
+}
+
+OUString CrashReporter::getActiveSfxObjectName()
+{
+ osl::MutexGuard aGuard(maActiveSfxObjectNameMutex);
+ return msActiveSfxObjectName;
+}
+
+void CrashReporter::logUnoCommand(const OUString& rUnoCommand)
+{
+ osl::MutexGuard aGuard(maUnoLogCmdMutex);
+
+ if( maloggedUnoCommands.size() == 4 )
+ maloggedUnoCommands.pop_front();
+
+ maloggedUnoCommands.push_back(rUnoCommand);
+}
+
+OUString CrashReporter::getLoggedUnoCommands()
+{
+ osl::MutexGuard aGuard(maUnoLogCmdMutex);
+
+ OUString aCommandSeperator="";
+ OUStringBuffer aUnoCommandBuffer;
+
+ for( auto& unocommand: maloggedUnoCommands)
+ {
+ aUnoCommandBuffer.append(aCommandSeperator + unocommand);
+ aCommandSeperator=",";
+ }
+ return aUnoCommandBuffer.makeStringAndClear();
+}
+
+namespace {
+
+OUString getCrashDirectory()
+{
+ OUString aCrashURL;
+ rtl::Bootstrap::get("CrashDirectory", aCrashURL);
+ // Need to convert to URL in case of user-defined path
+ osl::FileBase::getFileURLFromSystemPath(aCrashURL, aCrashURL);
+
+ if (aCrashURL.isEmpty()) { // Fall back to user profile
+ aCrashURL = "${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE("bootstrap") ":UserInstallation}/crash/";
+ rtl::Bootstrap::expandMacros(aCrashURL);
+ }
+
+ if (!aCrashURL.endsWith("/"))
+ aCrashURL += "/";
+
+ osl::Directory::create(aCrashURL);
+ OUString aCrashPath;
+ osl::FileBase::getSystemPathFromFileURL(aCrashURL, aCrashPath);
+ return aCrashPath;
+}
+
+}
+
+void CrashReporter::updateMinidumpLocation()
+{
+#if defined( UNX ) && !defined MACOSX && !defined IOS && !defined ANDROID
+ OUString aURL = getCrashDirectory();
+ OString aOStringUrl = OUStringToOString(aURL, RTL_TEXTENCODING_UTF8);
+ google_breakpad::MinidumpDescriptor descriptor(std::string{aOStringUrl});
+ mpExceptionHandler->set_minidump_descriptor(descriptor);
+#elif defined _WIN32
+ OUString aURL = getCrashDirectory();
+ mpExceptionHandler->set_dump_path(o3tl::toW(aURL.getStr()));
+#endif
+}
+
+bool CrashReporter::crashReportInfoExists()
+{
+ static const bool InfoExist = crashreport::readConfig(CrashReporter::getIniFileName(), nullptr);
+ return InfoExist;
+}
+
+bool CrashReporter::readSendConfig(std::string& response)
+{
+ return crashreport::readConfig(CrashReporter::getIniFileName(), &response);
+}
+
+void CrashReporter::installExceptionHandler()
+{
+ if (!IsDumpEnable())
+ return;
+#if defined( UNX ) && !defined MACOSX && !defined IOS && !defined ANDROID
+ google_breakpad::MinidumpDescriptor descriptor("/tmp");
+ mpExceptionHandler = std::make_unique<google_breakpad::ExceptionHandler>(descriptor, nullptr, dumpCallback, nullptr, true, -1);
+#elif defined _WIN32
+ mpExceptionHandler = std::make_unique<google_breakpad::ExceptionHandler>(L".", nullptr, dumpCallback, nullptr, google_breakpad::ExceptionHandler::HANDLER_ALL);
+#endif
+}
+
+void CrashReporter::removeExceptionHandler()
+{
+ mpExceptionHandler.reset();
+}
+
+
+
+bool CrashReporter::IsDumpEnable()
+{
+ auto const env = std::getenv("CRASH_DUMP_ENABLE");
+ if (env != nullptr && env[0] != '\0') {
+ return true;
+ }
+ // read configuration item 'CrashDumpEnable' -> bool on/off
+ OUString sToken;
+ if (rtl::Bootstrap::get("CrashDumpEnable", sToken))
+ {
+ return sToken.toBoolean();
+ }
+ return true; // default, always on
+}
+
+
+std::string CrashReporter::getIniFileName()
+{
+ OUString url = getCrashDirectory() + "dump.ini";
+ OString aUrl = OUStringToOString(url, RTL_TEXTENCODING_UTF8);
+ std::string aRet(aUrl);
+ return aRet;
+}
+
+// Write system-specific information such as the CPU name and features.
+// This may allow us to get some statistics for decisions (such as when
+// deciding whether SSE2 can be made a hard-requirement for Windows).
+// Breakpad provides this information poorly or not at all.
+#if defined( UNX ) && !defined MACOSX && !defined IOS && !defined ANDROID
+void CrashReporter::writeSystemInfo()
+{
+ // Get 'model name' and 'flags' from /proc/cpuinfo.
+ if( std::ifstream cpuinfo( "/proc/cpuinfo" ); cpuinfo )
+ {
+ bool haveModel = false;
+ bool haveFlags = false;
+ std::regex modelRegex( "^model name[ \t]*:[ \t]*(.*)$" );
+ std::regex flagsRegex( "^flags[ \t]*:[ \t]*(.*)$" );
+ for( std::string line; std::getline( cpuinfo, line ); )
+ {
+ std::smatch match;
+ if( !haveModel && std::regex_match( line, match, modelRegex ) && match.size() == 2)
+ {
+ addKeyValue("CPUModelName", OUString::fromUtf8( match[ 1 ].str()), AddItem);
+ haveModel = true;
+ }
+ if( !haveFlags && std::regex_match( line, match, flagsRegex ) && match.size() == 2)
+ {
+ addKeyValue("CPUFlags", OUString::fromUtf8( match[ 1 ].str()), AddItem);
+ haveFlags = true;
+ }
+ if( haveModel && haveFlags )
+ break;
+ }
+ }
+ // Get 'MemTotal' from /proc/meminfo.
+ if( std::ifstream meminfo( "/proc/meminfo" ); meminfo )
+ {
+ std::regex memTotalRegex( "^MemTotal[ \t]*:[ \t]*(.*)$" );
+ for( std::string line; std::getline( meminfo, line ); )
+ {
+ std::smatch match;
+ if( std::regex_match( line, match, memTotalRegex ) && match.size() == 2)
+ {
+ addKeyValue("MemoryTotal", OUString::fromUtf8( match[ 1 ].str()), AddItem);
+ break;
+ }
+ }
+ }
+}
+#elif defined _WIN32
+void CrashReporter::writeSystemInfo()
+{
+#if !defined(_ARM64_)
+ // Get CPU model name and flags.
+ // See https://docs.microsoft.com/en-us/cpp/intrinsics/cpuid-cpuidex
+ // and https://en.wikipedia.org/wiki/CPUID .
+ int cpui[ 4 ];
+ __cpuid( cpui, 0x80000000 ); // Get the highest extended ID.
+ unsigned int exIds = cpui[ 0 ];
+ if( exIds >= 0x80000004 )
+ {
+ int brand[ 16 ];
+ __cpuidex( brand, 0x80000002, 0 );
+ __cpuidex( brand + 4, 0x80000003, 0 );
+ __cpuidex( brand + 8, 0x80000004, 0 );
+ brand[ 12 ] = 0;;
+ addKeyValue( "CPUModelName", OUString::fromUtf8( reinterpret_cast< const char* >( brand )),
+ AddItem );
+ }
+ __cpuid( cpui, 0 ); // Get the highest ID.
+ int ids = cpui[ 0 ];
+ unsigned int ecx1 = 0, edx1 = 0, ebx7 = 0, ecx7 = 0, ecx81 = 0, edx81 = 0;
+ if( ids >= 0x1 )
+ {
+ __cpuidex( cpui, 0x1, 0 );
+ ecx1 = cpui[ 2 ];
+ edx1 = cpui[ 3 ];
+ }
+ if( ids >= 0x7 )
+ {
+ __cpuidex( cpui, 0x7, 0 );
+ ebx7 = cpui[ 1 ];
+ ecx7 = cpui[ 2 ];
+ }
+ if( exIds >= 0x80000001 )
+ {
+ __cpuidex( cpui, 0x80000001, 0 );
+ ecx81 = cpui[ 2 ];
+ edx81 = cpui[ 3 ];
+ }
+ struct FlagItem
+ {
+ unsigned int* reg;
+ int bit;
+ const char* name;
+ };
+ const FlagItem flagItems[] =
+ {
+ { &ecx1, 0, "sse3" },
+ { &ecx1, 1, "pclmulqdq" },
+ { &ecx1, 3, "monitor" },
+ { &ecx1, 9, "ssse3" },
+ { &ecx1, 12, "fma" },
+ { &ecx1, 13, "cpmxch16b" },
+ { &ecx1, 19, "sse41" },
+ { &ecx1, 20, "sse42" },
+ { &ecx1, 22, "movbe" },
+ { &ecx1, 23, "popcnt" },
+ { &ecx1, 25, "aes" },
+ { &ecx1, 26, "xsave" },
+ { &ecx1, 27, "osxsave" },
+ { &ecx1, 28, "avx" },
+ { &ecx1, 29, "f16c" },
+ { &ecx1, 30, "rdrand" },
+ { &edx1, 5, "msr" },
+ { &edx1, 8, "cx8" },
+ { &edx1, 11, "sep" },
+ { &edx1, 15, "cmov" },
+ { &edx1, 19, "clfsh" },
+ { &edx1, 23, "mmx" },
+ { &edx1, 24, "fxsr" },
+ { &edx1, 25, "sse" },
+ { &edx1, 26, "sse2" },
+ { &edx1, 28, "ht" },
+ { &ebx7, 0, "fsgsbase" },
+ { &ebx7, 3, "bmi1" },
+ { &ebx7, 4, "hle" },
+ { &ebx7, 5, "avx2" },
+ { &ebx7, 8, "bmi2" },
+ { &ebx7, 9, "erms" },
+ { &ebx7, 10, "invpcid" },
+ { &ebx7, 11, "rtm" },
+ { &ebx7, 16, "avx512f" },
+ { &ebx7, 18, "rdseed" },
+ { &ebx7, 19, "adx" },
+ { &ebx7, 26, "avx512pf" },
+ { &ebx7, 27, "avx512er" },
+ { &ebx7, 28, "avx512cd" },
+ { &ebx7, 29, "sha" },
+ { &ecx7, 0, "prefetchwt1" },
+ { &ecx81, 0, "lahf" },
+ { &ecx81, 5, "abm" },
+ { &ecx81, 6, "sse4a" },
+ { &ecx81, 11, "xop" },
+ { &ecx81, 21, "tbm" },
+ { &edx81, 11, "syscall" },
+ { &edx81, 22, "mmxext" },
+ { &edx81, 27, "rdtscp" },
+ { &edx81, 30, "3dnowext" },
+ { &edx81, 31, "3dnow" }
+ };
+ OUStringBuffer flags;
+ for( const FlagItem& item : flagItems )
+ {
+ if( *item.reg & ( 1U << item.bit ))
+ {
+ if( !flags.isEmpty())
+ flags.append( " " );
+ flags.appendAscii( item.name );
+ }
+ }
+ if( !flags.isEmpty())
+ addKeyValue( "CPUFlags", flags.makeStringAndClear(), AddItem );
+#endif
+ // Get total memory.
+ MEMORYSTATUSEX memoryStatus;
+ memoryStatus.dwLength = sizeof( memoryStatus );
+ if( GlobalMemoryStatusEx( &memoryStatus ))
+ {
+ addKeyValue( "MemoryTotal", OUString::number( int( memoryStatus.ullTotalPhys / 1024 ))
+ + " kB", AddItem );
+ }
+}
+#else
+void CrashReporter::writeSystemInfo()
+{
+}
+#endif
+
+#endif //HAVE_FEATURE_BREAKPAD
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/app/desktopcontext.cxx b/desktop/source/app/desktopcontext.cxx
new file mode 100644
index 0000000000..5d43bb6489
--- /dev/null
+++ b/desktop/source/app/desktopcontext.cxx
@@ -0,0 +1,55 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <config_java.h>
+
+#include "desktopcontext.hxx"
+
+#include <svtools/javainteractionhandler.hxx>
+
+using namespace com::sun::star::uno;
+using namespace com::sun::star::task;
+
+namespace desktop
+{
+DesktopContext::DesktopContext(const Reference<XCurrentContext>& ctx)
+ : m_xNextContext(ctx)
+{
+}
+
+Any SAL_CALL DesktopContext::getValueByName(const OUString& Name)
+{
+ Any retVal;
+
+ if (Name == JAVA_INTERACTION_HANDLER_NAME)
+ {
+#if HAVE_FEATURE_JAVA
+ retVal <<= Reference<XInteractionHandler>(new svt::JavaInteractionHandler());
+#endif
+ }
+ else if (m_xNextContext.is())
+ {
+ // Call next context in chain if found
+ retVal = m_xNextContext->getValueByName(Name);
+ }
+ return retVal;
+}
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/app/desktopcontext.hxx b/desktop/source/app/desktopcontext.hxx
new file mode 100644
index 0000000000..42266a0725
--- /dev/null
+++ b/desktop/source/app/desktopcontext.hxx
@@ -0,0 +1,40 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <cppuhelper/implbase.hxx>
+#include <com/sun/star/uno/XCurrentContext.hpp>
+
+namespace desktop
+{
+ class DesktopContext: public cppu::WeakImplHelper< css::uno::XCurrentContext >
+ {
+ public:
+ explicit DesktopContext( const css::uno::Reference< css::uno::XCurrentContext > & ctx);
+
+ // XCurrentContext
+ virtual css::uno::Any SAL_CALL getValueByName( const OUString& Name ) override;
+
+ private:
+ css::uno::Reference< css::uno::XCurrentContext > m_xNextContext;
+ };
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/app/dispatchwatcher.cxx b/desktop/source/app/dispatchwatcher.cxx
new file mode 100644
index 0000000000..863d246951
--- /dev/null
+++ b/desktop/source/app/dispatchwatcher.cxx
@@ -0,0 +1,863 @@
+/* -*- 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 <sfx2/docfile.hxx>
+#include <sfx2/docfilt.hxx>
+#include <sfx2/fcontnr.hxx>
+#include <svl/fstathelper.hxx>
+
+#include <app.hxx>
+#include "dispatchwatcher.hxx"
+#include "officeipcthread.hxx"
+#include <rtl/ustring.hxx>
+#include <comphelper/processfactory.hxx>
+#include <comphelper/synchronousdispatch.hxx>
+#include <com/sun/star/io/IOException.hpp>
+#include <com/sun/star/util/XCloseable.hpp>
+#include <com/sun/star/util/CloseVetoException.hpp>
+#include <com/sun/star/task/InteractionHandler.hpp>
+#include <com/sun/star/util/URL.hpp>
+#include <com/sun/star/frame/Desktop.hpp>
+#include <com/sun/star/container/XContainerQuery.hpp>
+#include <com/sun/star/container/XEnumeration.hpp>
+#include <com/sun/star/frame/XDispatch.hpp>
+#include <com/sun/star/frame/XNotifyingDispatch.hpp>
+#include <com/sun/star/beans/PropertyValue.hpp>
+#include <com/sun/star/view/XPrintable.hpp>
+#include <com/sun/star/util/URLTransformer.hpp>
+#include <com/sun/star/util/XURLTransformer.hpp>
+#include <com/sun/star/document/MacroExecMode.hpp>
+#include <com/sun/star/document/XTypeDetection.hpp>
+#include <com/sun/star/document/UpdateDocMode.hpp>
+#include <com/sun/star/frame/XStorable.hpp>
+#include <com/sun/star/script/XLibraryContainer2.hpp>
+#include <com/sun/star/document/XEmbeddedScripts.hpp>
+
+#include <comphelper/propertyvalue.hxx>
+#include <comphelper/sequence.hxx>
+#include <comphelper/diagnose_ex.hxx>
+#include <tools/urlobj.hxx>
+#include <unotools/mediadescriptor.hxx>
+#include <unotools/tempfile.hxx>
+
+#include <osl/thread.hxx>
+#include <osl/file.hxx>
+#include <iostream>
+#include <string_view>
+#include <utility>
+
+using namespace ::osl;
+using namespace ::com::sun::star::uno;
+using namespace ::com::sun::star::util;
+using namespace ::com::sun::star::lang;
+using namespace ::com::sun::star::frame;
+using namespace ::com::sun::star::container;
+using namespace ::com::sun::star::beans;
+using namespace ::com::sun::star::view;
+using namespace ::com::sun::star::task;
+using namespace ::com::sun::star::document;
+
+namespace document = ::com::sun::star::document;
+
+namespace desktop
+{
+
+namespace {
+
+struct DispatchHolder
+{
+ DispatchHolder( URL _aURL, Reference< XDispatch > const & rDispatch ) :
+ aURL(std::move( _aURL )), xDispatch( rDispatch ) {}
+
+ URL aURL;
+ Reference< XDispatch > xDispatch;
+};
+
+std::shared_ptr<const SfxFilter> impl_lookupExportFilterForUrl( std::u16string_view rUrl, std::u16string_view rFactory )
+{
+ // create the list of filters
+ OUString sQuery = "getSortedFilterList()"
+ ":module=" +
+ OUString::Concat(rFactory) + // use long name here !
+ ":iflags=" +
+ OUString::number(static_cast<sal_Int32>(SfxFilterFlags::EXPORT)) +
+ ":eflags=" +
+ OUString::number(static_cast<int>(SFX_FILTER_NOTINSTALLED));
+
+ const Reference< XComponentContext > xContext( comphelper::getProcessComponentContext() );
+ const Reference< XContainerQuery > xFilterFactory(
+ xContext->getServiceManager()->createInstanceWithContext( "com.sun.star.document.FilterFactory", xContext ),
+ UNO_QUERY_THROW );
+
+ std::shared_ptr<const SfxFilter> pBestMatch;
+
+ const Reference< XEnumeration > xFilterEnum(
+ xFilterFactory->createSubSetEnumerationByQuery( sQuery ), UNO_SET_THROW );
+ while ( xFilterEnum->hasMoreElements() )
+ {
+ comphelper::SequenceAsHashMap aFilterProps( xFilterEnum->nextElement() );
+ const OUString aName( aFilterProps.getUnpackedValueOrDefault( "Name", OUString() ) );
+ if ( !aName.isEmpty() )
+ {
+ std::shared_ptr<const SfxFilter> pFilter( SfxFilter::GetFilterByName( aName ) );
+ if ( pFilter && pFilter->CanExport() && pFilter->GetWildcard().Matches( rUrl ) )
+ {
+ if ( !pBestMatch || ( SfxFilterFlags::PREFERED & pFilter->GetFilterFlags() ) )
+ pBestMatch = pFilter;
+ }
+ }
+ }
+
+ return pBestMatch;
+}
+
+std::shared_ptr<const SfxFilter> impl_getExportFilterFromUrl(
+ const OUString& rUrl, const OUString& rFactory)
+{
+ try
+ {
+ const Reference< XComponentContext > xContext( comphelper::getProcessComponentContext() );
+ const Reference< document::XTypeDetection > xTypeDetector(
+ xContext->getServiceManager()->createInstanceWithContext( "com.sun.star.document.TypeDetection", xContext ),
+ UNO_QUERY_THROW );
+ const OUString aTypeName( xTypeDetector->queryTypeByURL( rUrl ) );
+
+ std::shared_ptr<const SfxFilter> pFilter( SfxFilterMatcher( rFactory ).GetFilter4EA( aTypeName, SfxFilterFlags::EXPORT ) );
+ if ( !pFilter )
+ pFilter = impl_lookupExportFilterForUrl( rUrl, rFactory );
+ if ( !pFilter )
+ {
+ OUString aTempName;
+ FileBase::getSystemPathFromFileURL( rUrl, aTempName );
+ OString aSource = OUStringToOString ( aTempName, osl_getThreadTextEncoding() );
+ std::cerr << "Error: no export filter for " << aSource << " found, aborting." << std::endl;
+ }
+
+ return pFilter;
+ }
+ catch ( const Exception& )
+ {
+ return nullptr;
+ }
+}
+
+OUString impl_GuessFilter( const OUString& rUrlOut, const OUString& rDocService )
+{
+ OUString aOutFilter;
+ std::shared_ptr<const SfxFilter> pOutFilter = impl_getExportFilterFromUrl( rUrlOut, rDocService );
+ if (pOutFilter)
+ aOutFilter = pOutFilter->GetFilterName();
+
+ return aOutFilter;
+}
+
+/// dump scripts in a document to the console.
+void scriptCat(const Reference< XModel >& xDoc )
+{
+ Reference< XEmbeddedScripts > xScriptAccess( xDoc, UNO_QUERY );
+ if (!xScriptAccess)
+ {
+ std::cout << "No script access\n";
+ return;
+ }
+
+ // ignore xScriptAccess->getDialogLibraries() for now
+ Reference< css::script::XLibraryContainer2 > xLibraries(
+ xScriptAccess->getBasicLibraries() );
+
+ if ( !xLibraries.is() )
+ {
+ std::cout << "No script libraries\n";
+ return;
+ }
+
+ const Sequence< OUString > aLibNames = xLibraries->getElementNames();
+ std::cout << "Libraries: " << aLibNames.getLength() << "\n";
+ for (OUString const & libName : aLibNames)
+ {
+ std::cout << "Library: '" << libName << "' children: ";
+ Reference< XNameContainer > xContainer;
+ try {
+ if (!xLibraries->isLibraryLoaded( libName ))
+ xLibraries->loadLibrary( libName );
+ xContainer = Reference< XNameContainer >(
+ xLibraries->getByName( libName ), UNO_QUERY );
+ }
+ catch (const css::uno::Exception &e)
+ {
+ std::cout << "[" << libName << "] - failed to load library: " << e.Message << "\n";
+ continue;
+ }
+ if( !xContainer.is() )
+ std::cout << "0\n";
+ else
+ {
+ Sequence< OUString > aObjectNames = xContainer->getElementNames();
+
+ std::cout << aObjectNames.getLength() << "\n\n";
+ for ( sal_Int32 j = 0 ; j < aObjectNames.getLength() ; ++j )
+ {
+ const OUString &rObjectName = aObjectNames[j];
+
+ try
+ {
+ Any aCode = xContainer->getByName( rObjectName );
+ OUString aCodeString;
+
+ if (! (aCode >>= aCodeString ) )
+ std::cout << "[" << rObjectName << "] - error fetching code\n";
+ else
+ std::cout << "[" << rObjectName << "]\n"
+ << aCodeString.trim()
+ << "\n[/" << rObjectName << "]\n";
+ }
+ catch (const css::uno::Exception &e)
+ {
+ std::cout << "[" << rObjectName << "] - exception " << e.Message << " fetching code\n";
+ }
+
+ if (j < aObjectNames.getLength() - 1)
+ std::cout << "\n----------------------------------------------------------\n";
+ std::cout << "\n";
+ }
+ }
+ }
+}
+
+// Perform batch print
+void batchPrint( std::u16string_view rPrinterName, const Reference< XPrintable > &xDoc,
+ const INetURLObject &aObj, const OUString &aName )
+{
+ OUString aFilterOut;
+ OUString aPrinterName;
+ size_t nPathIndex = rPrinterName.rfind( ';' );
+ if( nPathIndex != std::u16string_view::npos )
+ aFilterOut=rPrinterName.substr( nPathIndex+1 );
+ if( nPathIndex != 0 )
+ aPrinterName=rPrinterName.substr( 0, nPathIndex );
+
+ INetURLObject aOutFilename( aObj );
+ aOutFilename.SetExtension( u"pdf" );
+ FileBase::getFileURLFromSystemPath( aFilterOut, aFilterOut );
+ OUString aOutFile = aFilterOut + "/" + aOutFilename.getName();
+
+ OUString aTempName;
+ FileBase::getSystemPathFromFileURL( aName, aTempName );
+ OString aSource8 = OUStringToOString ( aTempName, osl_getThreadTextEncoding() );
+ FileBase::getSystemPathFromFileURL( aOutFile, aTempName );
+ OString aTargetURL8 = OUStringToOString(aTempName, osl_getThreadTextEncoding() );
+
+ std::cout << "print " << aSource8 << " -> " << aTargetURL8;
+ std::cout << " using " << (aPrinterName.isEmpty() ? "<default_printer>"_ostr : OUStringToOString( aPrinterName, osl_getThreadTextEncoding() ));
+ std::cout << std::endl;
+
+ // create the custom printer, if given
+ Sequence < PropertyValue > aPrinterArgs;
+ if( !aPrinterName.isEmpty() )
+ {
+ aPrinterArgs = { comphelper::makePropertyValue("Name", aPrinterName) };
+ xDoc->setPrinter( aPrinterArgs );
+ }
+
+ // print ( also without user interaction )
+ aPrinterArgs = { comphelper::makePropertyValue("FileName", aOutFile),
+ comphelper::makePropertyValue("Wait", true) };
+ xDoc->print( aPrinterArgs );
+}
+
+// Get xDoc module name
+OUString getName(const Reference< XInterface > & xDoc)
+{
+ Reference< XModel > xModel( xDoc, UNO_QUERY );
+ if (!xModel)
+ return OUString();
+ utl::MediaDescriptor aMediaDesc( xModel->getArgs() );
+ OUString aDocService = aMediaDesc.getUnpackedValueOrDefault(utl::MediaDescriptor::PROP_DOCUMENTSERVICE, OUString() );
+ if (aDocService == "com.sun.star.text.TextDocument")
+ return "Writer";
+ else if (aDocService == "com.sun.star.text.GlobalDocument")
+ return "Writer master";
+ else if (aDocService == "com.sun.star.text.WebDocument")
+ return "Writer/Web";
+ else if (aDocService == "com.sun.star.drawing.DrawingDocument")
+ return "Draw";
+ else if (aDocService == "com.sun.star.presentation.PresentationDocument")
+ return "Impress";
+ else if (aDocService == "com.sun.star.sheet.SpreadsheetDocument")
+ return "Calc";
+ else if (aDocService == "com.sun.star.script.BasicIDE")
+ return "Basic";
+ else if (aDocService == "com.sun.star.formula.FormulaProperties")
+ return "Math";
+ else if (aDocService == "com.sun.star.sdb.RelationDesign")
+ return "Relation Design";
+ else if (aDocService == "com.sun.star.sdb.QueryDesign")
+ return "Query Design";
+ else if (aDocService == "com.sun.star.sdb.TableDesign")
+ return "Table Design";
+ else if (aDocService == "com.sun.star.sdb.DataSourceBrowser")
+ return "Data Source Browser";
+ else if (aDocService == "com.sun.star.sdb.DatabaseDocument")
+ return "Database";
+
+ return OUString();
+}
+
+} // anonymous namespace
+
+DispatchWatcher::DispatchWatcher()
+ : m_nRequestCount(0)
+{
+}
+
+
+DispatchWatcher::~DispatchWatcher()
+{
+}
+
+
+bool DispatchWatcher::executeDispatchRequests( const std::vector<DispatchRequest>& aDispatchRequestsList, bool bNoTerminate )
+{
+ Reference< XDesktop2 > xDesktop = css::frame::Desktop::create( ::comphelper::getProcessComponentContext() );
+
+ std::vector< DispatchHolder > aDispatches;
+ bool bSetInputFilter = false;
+ OUString aForcedInputFilter;
+
+ for (auto const & aDispatchRequest: aDispatchRequestsList)
+ {
+ // Set Input Filter
+ if ( aDispatchRequest.aRequestType == REQUEST_INFILTER )
+ {
+ bSetInputFilter = true;
+ aForcedInputFilter = aDispatchRequest.aURL;
+ RequestHandler::RequestsCompleted();
+ continue;
+ }
+
+ // create parameter array
+ std::vector<PropertyValue> aArgs;
+
+ // mark request as user interaction from outside
+ aArgs.emplace_back("Referer", 0, Any(OUString("private:OpenEvent")),
+ PropertyState_DIRECT_VALUE);
+
+ OUString aTarget("_default");
+
+ if ( aDispatchRequest.aRequestType == REQUEST_PRINT ||
+ aDispatchRequest.aRequestType == REQUEST_PRINTTO ||
+ aDispatchRequest.aRequestType == REQUEST_BATCHPRINT ||
+ aDispatchRequest.aRequestType == REQUEST_CONVERSION ||
+ aDispatchRequest.aRequestType == REQUEST_CAT ||
+ aDispatchRequest.aRequestType == REQUEST_SCRIPT_CAT)
+ {
+ // documents opened for printing are opened readonly because they must be opened as a
+ // new document and this document could be open already
+ aArgs.emplace_back("ReadOnly", 0, Any(true), PropertyState_DIRECT_VALUE);
+ // always open a new document for printing, because it must be disposed afterwards
+ aArgs.emplace_back("OpenNewView", 0, Any(true), PropertyState_DIRECT_VALUE);
+ // printing is done in a hidden view
+ aArgs.emplace_back("Hidden", 0, Any(true), PropertyState_DIRECT_VALUE);
+ // load document for printing without user interaction
+ aArgs.emplace_back("Silent", 0, Any(true), PropertyState_DIRECT_VALUE);
+
+ // hidden documents should never be put into open tasks
+ aTarget = "_blank";
+ }
+ else
+ {
+ Reference < XInteractionHandler2 > xInteraction(
+ InteractionHandler::createWithParent(::comphelper::getProcessComponentContext(), nullptr) );
+
+ aArgs.emplace_back("InteractionHandler", 0, Any(xInteraction),
+ PropertyState_DIRECT_VALUE);
+
+ aArgs.emplace_back("MacroExecutionMode", 0,
+ Any(css::document::MacroExecMode::USE_CONFIG),
+ PropertyState_DIRECT_VALUE);
+
+ aArgs.emplace_back("UpdateDocMode", 0,
+ Any(css::document::UpdateDocMode::ACCORDING_TO_CONFIG),
+ PropertyState_DIRECT_VALUE);
+ }
+
+ if ( !aDispatchRequest.aPreselectedFactory.isEmpty() )
+ {
+ aArgs.emplace_back(utl::MediaDescriptor::PROP_DOCUMENTSERVICE, 0,
+ Any(aDispatchRequest.aPreselectedFactory),
+ PropertyState_DIRECT_VALUE);
+ }
+
+ OUString aName( GetURL_Impl( aDispatchRequest.aURL, aDispatchRequest.aCwdUrl ) );
+
+ // load the document ... if they are loadable!
+ // Otherwise try to dispatch it ...
+ Reference < XPrintable > xDoc;
+ if(
+ ( aName.startsWith( ".uno" ) ) ||
+ ( aName.startsWith( "slot:" ) ) ||
+ ( aName.startsWith( "macro:" ) ) ||
+ ( aName.startsWith("vnd.sun.star.script") )
+ )
+ {
+ // Attention: URL must be parsed full. Otherwise some detections on it will fail!
+ // It doesn't matter, if parser isn't available. Because; We try loading of URL then ...
+ URL aURL ;
+ aURL.Complete = aName;
+
+ Reference < XDispatch > xDispatcher ;
+ Reference < XURLTransformer > xParser ( URLTransformer::create(::comphelper::getProcessComponentContext()) );
+
+ if( xParser.is() )
+ xParser->parseStrict( aURL );
+
+ xDispatcher = xDesktop->queryDispatch( aURL, OUString(), 0 );
+ SAL_WARN_IF(
+ !xDispatcher.is(), "desktop.app",
+ "unsupported dispatch request <" << aName << ">");
+ if( xDispatcher.is() )
+ {
+ // Remember request so we can find it in statusChanged!
+ m_nRequestCount++;
+
+ // Use local vector to store dispatcher because we have to fill our request container before
+ // we can dispatch. Otherwise it would be possible that statusChanged is called before we dispatched all requests!!
+ aDispatches.emplace_back( aURL, xDispatcher );
+ }
+ }
+ else if ( aName.startsWith( "service:" ) )
+ {
+ // TODO: the dispatch has to be done for loadComponentFromURL as well.
+ URL aURL ;
+ aURL.Complete = aName;
+
+ Reference < XDispatch > xDispatcher ;
+ Reference < XURLTransformer > xParser ( URLTransformer::create(::comphelper::getProcessComponentContext()) );
+
+ if( xParser.is() )
+ xParser->parseStrict( aURL );
+
+ xDispatcher = xDesktop->queryDispatch( aURL, OUString(), 0 );
+
+ if( xDispatcher.is() )
+ {
+ try
+ {
+ // We have to be listener to catch errors during dispatching URLs.
+ // Otherwise it would be possible to have an office running without an open
+ // window!!
+ Sequence < PropertyValue > aArgs2{ comphelper::makePropertyValue("SynchronMode",
+ true) };
+ Reference < XNotifyingDispatch > xDisp( xDispatcher, UNO_QUERY );
+ if ( xDisp.is() )
+ xDisp->dispatchWithNotification( aURL, aArgs2, this );
+ else
+ xDispatcher->dispatch( aURL, aArgs2 );
+ }
+ catch (const css::uno::Exception&)
+ {
+ TOOLS_WARN_EXCEPTION(
+ "desktop.app",
+ "Desktop::OpenDefault() ignoring Exception while calling XNotifyingDispatch");
+ }
+ }
+ }
+ else
+ {
+ INetURLObject aObj( aName );
+ if ( aObj.GetProtocol() == INetProtocol::PrivSoffice )
+ aTarget = "_default";
+
+ // Set "AsTemplate" argument according to request type
+ if ( aDispatchRequest.aRequestType == REQUEST_FORCENEW ||
+ aDispatchRequest.aRequestType == REQUEST_FORCEOPEN )
+ {
+ aArgs.emplace_back("AsTemplate", 0,
+ Any(aDispatchRequest.aRequestType == REQUEST_FORCENEW),
+ PropertyState_DIRECT_VALUE);
+ }
+
+ // if we are called in viewmode, open document read-only
+ if(aDispatchRequest.aRequestType == REQUEST_VIEW) {
+ aArgs.emplace_back("ReadOnly", 0, Any(true), PropertyState_DIRECT_VALUE);
+ }
+
+ // if we are called with --show set Start in mediadescriptor
+ if(aDispatchRequest.aRequestType == REQUEST_START) {
+ aArgs.emplace_back("StartPresentation", 0, Any(true), PropertyState_DIRECT_VALUE);
+ }
+
+ // Force input filter, if possible
+ if( bSetInputFilter )
+ {
+ sal_Int32 nFilterOptionsIndex = 0;
+ aArgs.emplace_back("FilterName", 0,
+ Any(aForcedInputFilter.getToken(0, ':', nFilterOptionsIndex)),
+ PropertyState_DIRECT_VALUE);
+
+ if (0 < nFilterOptionsIndex)
+ {
+ aArgs.emplace_back("FilterOptions", 0,
+ Any(aForcedInputFilter.copy(nFilterOptionsIndex)),
+ PropertyState_DIRECT_VALUE);
+ }
+ }
+
+ // This is a synchron loading of a component so we don't have to deal with our statusChanged listener mechanism.
+ try
+ {
+ xDoc.set(comphelper::SynchronousDispatch::dispatch(
+ xDesktop, aName, aTarget, comphelper::containerToSequence(aArgs)),
+ UNO_QUERY);
+ }
+ catch (const css::lang::IllegalArgumentException&)
+ {
+ TOOLS_WARN_EXCEPTION(
+ "desktop.app",
+ "Dispatchwatcher IllegalArgumentException while calling loadComponentFromURL");
+ }
+ catch (const css::io::IOException&)
+ {
+ TOOLS_WARN_EXCEPTION(
+ "desktop.app",
+ "Dispatchwatcher IOException while calling loadComponentFromURL");
+ }
+ if ( aDispatchRequest.aRequestType == REQUEST_OPEN ||
+ aDispatchRequest.aRequestType == REQUEST_VIEW ||
+ aDispatchRequest.aRequestType == REQUEST_START ||
+ aDispatchRequest.aRequestType == REQUEST_FORCEOPEN ||
+ aDispatchRequest.aRequestType == REQUEST_FORCENEW )
+ {
+ // request is completed
+ RequestHandler::RequestsCompleted();
+ }
+ else if ( aDispatchRequest.aRequestType == REQUEST_PRINT ||
+ aDispatchRequest.aRequestType == REQUEST_PRINTTO ||
+ aDispatchRequest.aRequestType == REQUEST_BATCHPRINT ||
+ aDispatchRequest.aRequestType == REQUEST_CONVERSION ||
+ aDispatchRequest.aRequestType == REQUEST_CAT ||
+ aDispatchRequest.aRequestType == REQUEST_SCRIPT_CAT )
+ {
+ if ( xDoc.is() )
+ {
+ // Do we need to save the document in a different format?
+ if ( aDispatchRequest.aRequestType == REQUEST_CONVERSION ||
+ aDispatchRequest.aRequestType == REQUEST_CAT )
+ {
+// FIXME: factor out into a method ...
+ Reference< XStorable > xStorable( xDoc, UNO_QUERY );
+ if ( xStorable.is() ) {
+ OUString aParam = aDispatchRequest.aPrinterName;
+ sal_Int32 nPathIndex = aParam.lastIndexOf( ';' );
+ sal_Int32 nFilterIndex = aParam.indexOf( ':' );
+ sal_Int32 nImgFilterIndex = aParam.lastIndexOf( '|' );
+ if( nPathIndex < nFilterIndex )
+ nFilterIndex = -1;
+
+ OUString aFilterOut;
+ OUString aImgOut;
+ OUString aFilter;
+ OUString aFilterExt;
+ bool bGuess = false;
+
+ if( nFilterIndex >= 0 )
+ {
+ aFilter = aParam.copy( nFilterIndex+1, nPathIndex-nFilterIndex-1 );
+ aFilterExt = aParam.copy( 0, nFilterIndex );
+ }
+ else
+ {
+ // Guess
+ bGuess = true;
+ aFilterExt = aParam.copy( 0, nPathIndex );
+ }
+
+ if( nImgFilterIndex >= 0 )
+ {
+ aImgOut = aParam.copy( nImgFilterIndex+1 );
+ aFilterOut = aParam.copy( nPathIndex+1, nImgFilterIndex-nPathIndex-1 );
+ }
+ else
+ aFilterOut = aParam.copy( nPathIndex+1 );
+
+ FileBase::getFileURLFromSystemPath( aFilterOut, aFilterOut );
+ INetURLObject aOutFilename(aFilterOut);
+ aOutFilename.Append(aObj.getName(INetURLObject::LAST_SEGMENT, true,
+ INetURLObject::DecodeMechanism::NONE));
+ aOutFilename.SetExtension(aFilterExt);
+ OUString aOutFile
+ = aOutFilename.GetMainURL(INetURLObject::DecodeMechanism::NONE);
+
+ std::unique_ptr<utl::TempFileNamed> fileForCat;
+ if( aDispatchRequest.aRequestType == REQUEST_CAT )
+ {
+ fileForCat = std::make_unique<utl::TempFileNamed>();
+ if (fileForCat->IsValid())
+ fileForCat->EnableKillingFile();
+ else
+ std::cerr << "Error: Cannot create temporary file..." << std::endl ;
+ aOutFile = fileForCat->GetURL();
+ }
+
+ if ( bGuess )
+ {
+ OUString aDocService;
+ Reference< XModel > xModel( xDoc, UNO_QUERY );
+ if ( xModel.is() )
+ {
+ utl::MediaDescriptor aMediaDesc( xModel->getArgs() );
+ aDocService = aMediaDesc.getUnpackedValueOrDefault(utl::MediaDescriptor::PROP_DOCUMENTSERVICE, OUString() );
+ }
+ aFilter = impl_GuessFilter( aOutFile, aDocService );
+ }
+
+ bool bMultiFileTarget = false;
+
+ if (aFilter.isEmpty())
+ {
+ std::cerr << "Error: no export filter" << std::endl;
+ }
+ else
+ {
+ sal_Int32 nFilterOptionsIndex = aFilter.indexOf(':');
+ sal_Int32 nProps = ( 0 < nFilterOptionsIndex ) ? 4 : 3;
+
+ if ( !aImgOut.isEmpty() )
+ nProps +=1;
+ Sequence<PropertyValue> conversionProperties( nProps );
+ auto pconversionProperties = conversionProperties.getArray();
+ pconversionProperties[0].Name = "ConversionRequestOrigin";
+ pconversionProperties[0].Value <<= OUString("CommandLine");
+ pconversionProperties[1].Name = "Overwrite";
+ pconversionProperties[1].Value <<= true;
+
+ pconversionProperties[2].Name = "FilterName";
+ if( 0 < nFilterOptionsIndex )
+ {
+ OUString sFilterName = aFilter.copy(0, nFilterOptionsIndex);
+ OUString sFilterOptions = aFilter.copy(nFilterOptionsIndex + 1);
+
+ if (sFilterName == "Text - txt - csv (StarCalc)")
+ {
+ sal_Int32 nIdx(0);
+ // If the 11th token is '-1' then we export a file
+ // per sheet where the file name is based on the suggested
+ // output filename concatenated with the sheet name, so adjust
+ // the output and overwrite messages
+ // If the 11th token is not present or numeric 0 then the
+ // default sheet is exported with the output filename. If it
+ // is numeric >0 then that sheet (1-based) with the output
+ // filename concatenated with the sheet name. So even if
+ // that is a single file, the multi file target mechanism is
+ // used.
+ const OUString aTok(sFilterOptions.getToken(11, ',', nIdx));
+ // Actual validity is checked in Calc, here just check for
+ // presence of numeric value at start.
+ bMultiFileTarget = (!aTok.isEmpty() && aTok.toInt32() != 0);
+ }
+
+ pconversionProperties[2].Value <<= sFilterName;
+
+ pconversionProperties[3].Name = "FilterOptions";
+ pconversionProperties[3].Value <<= sFilterOptions;
+ }
+ else
+ {
+ pconversionProperties[2].Value <<= aFilter;
+ }
+
+ if ( !aImgOut.isEmpty() )
+ {
+ assert(conversionProperties[nProps-1].Name.isEmpty());
+ pconversionProperties[nProps-1].Name = "ImageFilter";
+ pconversionProperties[nProps-1].Value <<= aImgOut;
+ }
+
+ OUString aTempName;
+ FileBase::getSystemPathFromFileURL(aName, aTempName);
+ OString aSource8 = OUStringToOString(aTempName, osl_getThreadTextEncoding());
+ FileBase::getSystemPathFromFileURL(aOutFile, aTempName);
+ OString aTargetURL8 = OUStringToOString(aTempName, osl_getThreadTextEncoding());
+ if (aDispatchRequest.aRequestType != REQUEST_CAT)
+ {
+ OUString name=getName(xDoc);
+ std::cout << "convert " << aSource8;
+ if (!name.isEmpty())
+ std::cout << " as a " << name <<" document";
+ if (!bMultiFileTarget)
+ std::cout << " -> " << aTargetURL8;
+ std::cout << " using filter : " << OUStringToOString(aFilter, osl_getThreadTextEncoding()) << std::endl;
+ if (!bMultiFileTarget && FStatHelper::IsDocument(aOutFile))
+ std::cout << "Overwriting: " << OUStringToOString(aTempName, osl_getThreadTextEncoding()) << std::endl ;
+ }
+ try
+ {
+ xStorable->storeToURL(aOutFile, conversionProperties);
+ }
+ catch (const Exception& rException)
+ {
+ std::cerr << "Error: Please verify input parameters...";
+ if (!rException.Message.isEmpty())
+ std::cerr << " (" << rException.Message << ")";
+ std::cerr << std::endl;
+ }
+
+ if (fileForCat && fileForCat->IsValid())
+ {
+ SvStream* aStream = fileForCat->GetStream(StreamMode::STD_READ);
+ while (aStream->good())
+ {
+ OString aStr;
+ aStream->ReadLine(aStr, SAL_MAX_INT32);
+ for (sal_Int32 i = 0; i < aStr.getLength(); ++i)
+ {
+ std::cout << aStr[i];
+ }
+ std::cout << std::endl;
+ }
+ }
+ }
+ }
+ }
+ else if ( aDispatchRequest.aRequestType == REQUEST_SCRIPT_CAT )
+ {
+ Reference< XModel > xModel( xDoc, UNO_QUERY );
+ if( xModel.is() )
+ scriptCat( xModel );
+ }
+ else if ( aDispatchRequest.aRequestType == REQUEST_BATCHPRINT )
+ {
+ batchPrint( aDispatchRequest.aPrinterName, xDoc, aObj, aName );
+ }
+ else
+ {
+ if ( aDispatchRequest.aRequestType == REQUEST_PRINTTO )
+ {
+ // create the printer
+ Sequence < PropertyValue > aPrinterArgs{ comphelper::makePropertyValue(
+ "Name", aDispatchRequest.aPrinterName) };
+ xDoc->setPrinter( aPrinterArgs );
+ }
+
+ // print ( also without user interaction )
+ Sequence < PropertyValue > aPrinterArgs{ comphelper::makePropertyValue("Wait",
+ true) };
+ xDoc->print( aPrinterArgs );
+ }
+ }
+ else
+ {
+ std::cerr << "Error: source file could not be loaded" << std::endl;
+ }
+
+ // remove the document
+ try
+ {
+ Reference < XCloseable > xClose( xDoc, UNO_QUERY );
+ if ( xClose.is() )
+ xClose->close( true );
+ else
+ {
+ Reference < XComponent > xComp( xDoc, UNO_QUERY );
+ if ( xComp.is() )
+ xComp->dispose();
+ }
+ }
+ catch (const css::util::CloseVetoException&)
+ {
+ }
+
+ // request is completed
+ RequestHandler::RequestsCompleted();
+ }
+ }
+ }
+
+ if ( !aDispatches.empty() )
+ {
+ // Execute all asynchronous dispatches now after we placed them into our request container!
+ Sequence < PropertyValue > aArgs{
+ comphelper::makePropertyValue("Referer", OUString("private:OpenEvent")),
+ comphelper::makePropertyValue("SynchronMode", true)
+ };
+
+ for (const DispatchHolder & aDispatche : aDispatches)
+ {
+ Reference< XDispatch > xDispatch = aDispatche.xDispatch;
+ Reference < XNotifyingDispatch > xDisp( xDispatch, UNO_QUERY );
+ if ( xDisp.is() )
+ xDisp->dispatchWithNotification( aDispatche.aURL, aArgs, this );
+ else
+ {
+ m_nRequestCount--;
+ xDispatch->dispatch( aDispatche.aURL, aArgs );
+ }
+ }
+ }
+
+ bool bEmpty = (m_nRequestCount == 0);
+
+ // No more asynchronous requests?
+ // The requests are removed from the request container after they called back to this
+ // implementation via statusChanged!!
+ if ( bEmpty && !bNoTerminate /*m_aRequestContainer.empty()*/ )
+ {
+ // We have to check if we have an open task otherwise we have to shutdown the office.
+ Reference< XElementAccess > xList = xDesktop->getFrames();
+
+ if ( !xList->hasElements() )
+ {
+ // We don't have any task open so we have to shutdown ourself!!
+ return xDesktop->terminate();
+ }
+ }
+
+ return false;
+}
+
+
+void SAL_CALL DispatchWatcher::disposing( const css::lang::EventObject& )
+{
+}
+
+
+void SAL_CALL DispatchWatcher::dispatchFinished( const DispatchResultEvent& )
+{
+ int nCount = --m_nRequestCount;
+ RequestHandler::RequestsCompleted();
+ if ( !nCount && !RequestHandler::AreRequestsPending() )
+ {
+ // We have to check if we have an open task otherwise we have to shutdown the office.
+ Reference< XDesktop2 > xDesktop = css::frame::Desktop::create( ::comphelper::getProcessComponentContext() );
+ Reference< XElementAccess > xList = xDesktop->getFrames();
+
+ if ( !xList->hasElements() )
+ {
+ // We don't have any task open so we have to shutdown ourself!!
+ xDesktop->terminate();
+ }
+ }
+}
+
+} // namespace desktop
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/app/dispatchwatcher.hxx b/desktop/source/app/dispatchwatcher.hxx
new file mode 100644
index 0000000000..70a7fd42e6
--- /dev/null
+++ b/desktop/source/app/dispatchwatcher.hxx
@@ -0,0 +1,86 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <cppuhelper/implbase.hxx>
+#include <com/sun/star/frame/XDispatchResultListener.hpp>
+#include <optional>
+#include <atomic>
+#include <vector>
+
+namespace desktop
+{
+
+/*
+ Class for controls dispatching of command URL through office command line. There
+ are "dangerous" command URLs, that can result in a running office without UI. To prevent
+ this situation the implementation monitors all dispatches and looks for an open task if
+ there is arose a problem. If there is none the office will be shutdown to prevent a
+ running office without UI.
+*/
+class DispatchWatcher : public ::cppu::WeakImplHelper< css::frame::XDispatchResultListener >
+{
+ public:
+ enum RequestType
+ {
+ REQUEST_OPEN,
+ REQUEST_VIEW,
+ REQUEST_START,
+ REQUEST_PRINT,
+ REQUEST_PRINTTO,
+ REQUEST_FORCEOPEN,
+ REQUEST_FORCENEW,
+ REQUEST_CONVERSION,
+ REQUEST_INFILTER,
+ REQUEST_BATCHPRINT,
+ REQUEST_CAT,
+ REQUEST_SCRIPT_CAT
+ };
+
+ struct DispatchRequest
+ {
+ RequestType aRequestType;
+ OUString aURL;
+ std::optional< OUString > aCwdUrl;
+ OUString aPrinterName; // also conversion params
+ OUString aPreselectedFactory;
+ };
+
+ DispatchWatcher();
+
+ virtual ~DispatchWatcher() override;
+
+ // XEventListener
+ virtual void SAL_CALL disposing( const css::lang::EventObject& Source ) override;
+
+ // XDispachResultListener
+ virtual void SAL_CALL dispatchFinished( const css::frame::DispatchResultEvent& aEvent ) override;
+
+ // execute new dispatch request
+ bool executeDispatchRequests( const std::vector<DispatchRequest>& aDispatches, bool bNoTerminate );
+
+ private:
+
+ std::atomic<int> m_nRequestCount;
+};
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/app/langselect.cxx b/desktop/source/app/langselect.cxx
new file mode 100644
index 0000000000..5eb2f0636b
--- /dev/null
+++ b/desktop/source/app/langselect.cxx
@@ -0,0 +1,148 @@
+/* -*- 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 <com/sun/star/configuration/theDefaultProvider.hpp>
+#include <com/sun/star/container/XNameAccess.hpp>
+#include <com/sun/star/lang/XLocalizable.hpp>
+#include <com/sun/star/uno/Exception.hpp>
+#include <com/sun/star/uno/Reference.hxx>
+#include <com/sun/star/uno/Sequence.hxx>
+#include <comphelper/configuration.hxx>
+#include <comphelper/processfactory.hxx>
+#include <i18nlangtag/lang.h>
+#include <i18nlangtag/languagetag.hxx>
+#include <i18nlangtag/mslangid.hxx>
+#include <officecfg/Office/Linguistic.hxx>
+#include <officecfg/Setup.hxx>
+#include <officecfg/System.hxx>
+#include <rtl/ustring.hxx>
+#include <svl/languageoptions.hxx>
+#include <svtools/langhelp.hxx>
+#include <comphelper/diagnose_ex.hxx>
+
+#include <app.hxx>
+
+#include "cmdlineargs.hxx"
+#include "langselect.hxx"
+
+namespace desktop::langselect {
+
+namespace {
+
+void setMsLangIdFallback(OUString const & locale) {
+ // #i32939# setting of default document language
+ // See #i42730# for rules for determining source of settings
+ if (locale.isEmpty())
+ return;
+
+ LanguageType type = LanguageTag::convertToLanguageTypeWithFallback(locale);
+ switch (SvtLanguageOptions::GetScriptTypeOfLanguage(type)) {
+ case SvtScriptType::ASIAN:
+ MsLangId::setConfiguredAsianFallback(type);
+ break;
+ case SvtScriptType::COMPLEX:
+ MsLangId::setConfiguredComplexFallback(type);
+ break;
+ default:
+ MsLangId::setConfiguredWesternFallback(type);
+ break;
+ }
+}
+
+}
+
+bool prepareLocale() {
+ // #i42730# Get the windows 16Bit locale, it should be preferred over the UI
+ // locale:
+ setMsLangIdFallback(officecfg::System::L10N::SystemLocale::get());
+ // #i32939# Use system locale to set document default locale:
+ setMsLangIdFallback(officecfg::System::L10N::Locale::get());
+ css::uno::Sequence<OUString> inst(
+ officecfg::Setup::Office::InstalledLocales::get()->getElementNames());
+ OUString locale(officecfg::Office::Linguistic::General::UILocale::get());
+ if (!locale.isEmpty()) {
+ locale = getInstalledLocaleForLanguage(inst, locale);
+ if (locale.isEmpty()) {
+ // Selected language is not/no longer installed:
+ try {
+ std::shared_ptr<comphelper::ConfigurationChanges> batch(
+ comphelper::ConfigurationChanges::create());
+ officecfg::Office::Linguistic::General::UILocale::set(
+ "", batch);
+ batch->commit();
+ } catch (const css::uno::Exception &) {
+ TOOLS_WARN_EXCEPTION("desktop.app", "ignoring");
+ }
+ }
+ }
+ if (locale.isEmpty()) {
+ locale = getInstalledLocaleForLanguage(
+ inst, Desktop::GetCommandLineArgs().GetLanguage());
+ }
+ if (locale.isEmpty()) {
+ locale = getInstalledLocaleForSystemUILanguage(inst, true);
+ }
+ if (locale.isEmpty()) {
+ return false;
+ }
+ LanguageTag tag(locale);
+ // Prepare default config provider by localizing it to the selected
+ // locale this will ensure localized configuration settings to be
+ // selected according to the UI language:
+ css::uno::Reference<css::lang::XLocalizable>(
+ css::configuration::theDefaultProvider::get(
+ comphelper::getProcessComponentContext()),
+ css::uno::UNO_QUERY_THROW)->setLocale(tag.getLocale(false));
+ try {
+ std::shared_ptr<comphelper::ConfigurationChanges> batch(
+ comphelper::ConfigurationChanges::create());
+ officecfg::Setup::L10N::ooLocale::set(locale, batch);
+ batch->commit();
+ } catch (const css::uno::Exception &) {
+ TOOLS_WARN_EXCEPTION("desktop.app", "ignoring");
+ }
+ MsLangId::setConfiguredSystemUILanguage(tag.getLanguageType(false));
+
+ // Note the system language/locale here may or may not be correct before we
+ // actually set it below. It is what could be figured from the system
+ // setting and may only partially match, like "en" for an unsupported
+ // English locale.
+ LanguageTag aSysLocTag( MsLangId::getSystemLanguage());
+ OUString setupSysLoc(officecfg::Setup::L10N::ooSetupSystemLocale::get());
+ if (!setupSysLoc.isEmpty())
+ aSysLocTag.reset( setupSysLoc);
+ // Ensure the system locale is set to a known supported locale.
+ aSysLocTag.makeFallback();
+ LanguageTag::setConfiguredSystemLanguage( aSysLocTag.getLanguageType(false));
+
+ // #i32939# setting of default document locale
+ // #i32939# this should not be based on the UI language
+ // So obtain the system locale now configured just above and pass it on,
+ // resolved of course.
+ LanguageTag docTag(LANGUAGE_SYSTEM);
+ setMsLangIdFallback(docTag.getBcp47());
+
+ return true;
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/app/langselect.hxx b/desktop/source/app/langselect.hxx
new file mode 100644
index 0000000000..496f67570b
--- /dev/null
+++ b/desktop/source/app/langselect.hxx
@@ -0,0 +1,29 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <sal/config.h>
+
+namespace desktop::langselect
+{
+bool prepareLocale();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/app/lockfile2.cxx b/desktop/source/app/lockfile2.cxx
new file mode 100644
index 0000000000..98c2903f94
--- /dev/null
+++ b/desktop/source/app/lockfile2.cxx
@@ -0,0 +1,60 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <vcl/svapp.hxx>
+#include <vcl/weld.hxx>
+#include <dp_shared.hxx>
+#include <strings.hrc>
+#include <tools/config.hxx>
+#include <lockfile.hxx>
+
+namespace desktop {
+
+bool Lockfile_execWarning( Lockfile const * that )
+{
+ // read information from lock
+ OUString aLockname = that->m_aLockname;
+ Config aConfig(aLockname);
+ aConfig.SetGroup( LOCKFILE_GROUP ""_ostr );
+ OString aHost = aConfig.ReadKey( LOCKFILE_HOSTKEY ""_ostr );
+ OString aUser = aConfig.ReadKey( LOCKFILE_USERKEY ""_ostr );
+ OString aTime = aConfig.ReadKey( LOCKFILE_TIMEKEY ""_ostr );
+
+ // display warning and return response
+ std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(nullptr,
+ VclMessageType::Question, VclButtonsType::YesNo, DpResId(STR_QUERY_USERDATALOCKED)));
+ // set box title
+ OUString aTitle = DpResId(STR_TITLE_USERDATALOCKED);
+ xBox->set_title( aTitle );
+ // insert values...
+ OUString aMsgText = xBox->get_primary_text();
+ aMsgText = aMsgText.replaceFirst(
+ "$u", OStringToOUString( aUser, RTL_TEXTENCODING_ASCII_US) );
+ aMsgText = aMsgText.replaceFirst(
+ "$h", OStringToOUString( aHost, RTL_TEXTENCODING_ASCII_US) );
+ aMsgText = aMsgText.replaceFirst(
+ "$t", OStringToOUString( aTime, RTL_TEXTENCODING_ASCII_US) );
+ xBox->set_primary_text(aMsgText);
+ // do it
+ return xBox->run() == RET_YES;
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/app/main.c b/desktop/source/app/main.c
new file mode 100644
index 0000000000..fdd2eb3505
--- /dev/null
+++ b/desktop/source/app/main.c
@@ -0,0 +1,62 @@
+/* -*- 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/main.h>
+
+#include "sofficemain.h"
+
+#ifndef NOTEST_xmlCleanupParser
+#ifdef DBG_UTIL
+#ifdef __gnu_linux__
+#include <stdio.h>
+#include <stdlib.h>
+
+static int g_Exiting = 0;
+
+/* HACK: detect calls to xmlCleanupParser, which causes hard to debug crashes */
+__attribute__((visibility("default"))) void xmlCleanupParser(void)
+{
+ /* there are libraries that register xmlCleanupParser as an atexit handler,
+ which is not entirely sound (another atexit handler could want to
+ use libxml), but not enough of a problem to complain.
+ (example found by llunak: KDE's Strigi library) */
+ if (!g_Exiting)
+ {
+ fprintf(stderr, "\n*** ERROR: DO NOT call xmlCleanupParser()\n\n");
+ abort();
+ }
+}
+#endif
+#endif
+#endif // NOTEST_xmlCleanupParser
+
+SAL_IMPLEMENT_MAIN()
+{
+ int ret = soffice_main();
+#ifndef NOTEST_xmlCleanupParser
+#ifdef DBG_UTIL
+#ifdef __gnu_linux__
+ g_Exiting = 1;
+#endif
+#endif
+#endif
+ return ret;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/app/officeipcthread.cxx b/desktop/source/app/officeipcthread.cxx
new file mode 100644
index 0000000000..9d342bf35a
--- /dev/null
+++ b/desktop/source/app/officeipcthread.cxx
@@ -0,0 +1,1357 @@
+/* -*- 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 <config_dbus.h>
+#include <config_features.h>
+#include <config_feature_desktop.h>
+
+#include <app.hxx>
+#include "officeipcthread.hxx"
+#include "cmdlineargs.hxx"
+#include "dispatchwatcher.hxx"
+#include <com/sun/star/frame/TerminationVetoException.hpp>
+#include <salhelper/thread.hxx>
+#include <sal/log.hxx>
+#include <unotools/bootstrap.hxx>
+#include <utility>
+#include <vcl/svapp.hxx>
+#include <unotools/configmgr.hxx>
+#include <osl/pipe.hxx>
+#include <rtl/digest.h>
+#include <rtl/ustrbuf.hxx>
+#include <osl/conditn.hxx>
+#include <unotools/moduleoptions.hxx>
+#include <rtl/strbuf.hxx>
+#include <cppuhelper/supportsservice.hxx>
+#include <osl/file.hxx>
+#include <rtl/process.h>
+#include <o3tl/string_view.hxx>
+
+#include <cassert>
+#include <cstdlib>
+#include <memory>
+#include <thread>
+
+#if ENABLE_DBUS
+#include <dbus/dbus.h>
+#include <sys/socket.h>
+#endif
+
+using namespace desktop;
+using namespace ::com::sun::star::uno;
+using namespace ::com::sun::star::lang;
+using namespace ::com::sun::star::frame;
+
+namespace {
+
+char const ARGUMENT_PREFIX[] = "InternalIPC::Arguments";
+char const SEND_ARGUMENTS[] = "InternalIPC::SendArguments";
+char const PROCESSING_DONE[] = "InternalIPC::ProcessingDone";
+
+// Receives packets from the pipe until a packet ends in a NUL character (that
+// will not be included in the returned string) or it cannot read anything (due
+// to error or closed pipe, in which case an empty string will be returned to
+// signal failure):
+OString readStringFromPipe(osl::StreamPipe const & pipe) {
+ for (OStringBuffer str;;) {
+ char buf[1024];
+ sal_Int32 n = pipe.recv(buf, std::size(buf));
+ if (n <= 0) {
+ SAL_INFO("desktop.app", "read empty string");
+ return ""_ostr;
+ }
+ bool end = false;
+ if (buf[n - 1] == '\0') {
+ end = true;
+ --n;
+ }
+ str.append(buf, n);
+ //TODO: how does OStringBuffer.append handle overflow?
+ if (end) {
+ auto s = str.makeStringAndClear();
+ SAL_INFO("desktop.app", "read <" << s << ">");
+ return s;
+ }
+ }
+}
+
+}
+
+namespace desktop
+{
+
+namespace {
+
+class Parser: public CommandLineArgs::Supplier {
+public:
+ explicit Parser(OString input): m_input(std::move(input)) {
+ if (!m_input.match(ARGUMENT_PREFIX) ||
+ m_input.getLength() == RTL_CONSTASCII_LENGTH(ARGUMENT_PREFIX))
+ {
+ throw CommandLineArgs::Supplier::Exception();
+ }
+ m_index = RTL_CONSTASCII_LENGTH(ARGUMENT_PREFIX);
+ switch (m_input[m_index++]) {
+ case '0':
+ break;
+ case '1':
+ {
+ OUString url;
+ if (!next(&url, false)) {
+ throw CommandLineArgs::Supplier::Exception();
+ }
+ m_cwdUrl = url;
+ break;
+ }
+ case '2':
+ {
+ OUString path;
+ if (!next(&path, false)) {
+ throw CommandLineArgs::Supplier::Exception();
+ }
+ OUString url;
+ if (osl::FileBase::getFileURLFromSystemPath(path, url) ==
+ osl::FileBase::E_None)
+ {
+ m_cwdUrl = url;
+ }
+ break;
+ }
+ default:
+ throw CommandLineArgs::Supplier::Exception();
+ }
+ }
+
+ virtual std::optional< OUString > getCwdUrl() override { return m_cwdUrl; }
+
+ virtual bool next(OUString * argument) override { return next(argument, true); }
+
+private:
+ bool next(OUString * argument, bool prefix) {
+ OSL_ASSERT(argument != nullptr);
+ if (m_index < m_input.getLength()) {
+ if (prefix) {
+ if (m_input[m_index] != ',') {
+ throw CommandLineArgs::Supplier::Exception();
+ }
+ ++m_index;
+ }
+ OStringBuffer b;
+ while (m_index < m_input.getLength()) {
+ char c = m_input[m_index];
+ if (c == ',') {
+ break;
+ }
+ ++m_index;
+ if (c == '\\') {
+ if (m_index >= m_input.getLength())
+ throw CommandLineArgs::Supplier::Exception();
+ c = m_input[m_index++];
+ switch (c) {
+ case '0':
+ c = '\0';
+ break;
+ case ',':
+ case '\\':
+ break;
+ default:
+ throw CommandLineArgs::Supplier::Exception();
+ }
+ }
+ b.append(c);
+ }
+ OString b2(b.makeStringAndClear());
+ if (!rtl_convertStringToUString(
+ &argument->pData, b2.getStr(), b2.getLength(),
+ RTL_TEXTENCODING_UTF8,
+ (RTL_TEXTTOUNICODE_FLAGS_UNDEFINED_ERROR |
+ RTL_TEXTTOUNICODE_FLAGS_MBUNDEFINED_ERROR |
+ RTL_TEXTTOUNICODE_FLAGS_INVALID_ERROR)))
+ {
+ throw CommandLineArgs::Supplier::Exception();
+ }
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ std::optional< OUString > m_cwdUrl;
+ OString m_input;
+ sal_Int32 m_index;
+};
+
+bool addArgument(OStringBuffer &rArguments, char prefix,
+ const OUString &rArgument)
+{
+ OString utf8;
+ if (!rArgument.convertToString(
+ &utf8, RTL_TEXTENCODING_UTF8,
+ (RTL_UNICODETOTEXT_FLAGS_UNDEFINED_ERROR |
+ RTL_UNICODETOTEXT_FLAGS_INVALID_ERROR)))
+ {
+ return false;
+ }
+ rArguments.append(prefix);
+ for (sal_Int32 i = 0; i < utf8.getLength(); ++i) {
+ char c = utf8[i];
+ switch (c) {
+ case '\0':
+ rArguments.append("\\0");
+ break;
+ case ',':
+ rArguments.append("\\,");
+ break;
+ case '\\':
+ rArguments.append("\\\\");
+ break;
+ default:
+ rArguments.append(c);
+ break;
+ }
+ }
+ return true;
+}
+
+}
+
+rtl::Reference< RequestHandler > RequestHandler::pGlobal;
+
+// Turns a string in aMsg such as file:///home/foo/.libreoffice/3
+// Into a hex string of well known length ff132a86...
+static OUString CreateMD5FromString( const OUString& aMsg )
+{
+ SAL_INFO("desktop.app", "create md5 from '" << aMsg << "'");
+
+ rtlDigest handle = rtl_digest_create( rtl_Digest_AlgorithmMD5 );
+ if ( handle )
+ {
+ const sal_uInt8* pData = reinterpret_cast<const sal_uInt8*>(aMsg.getStr());
+ sal_uInt32 nSize = aMsg.getLength() * sizeof( sal_Unicode );
+ sal_uInt32 nMD5KeyLen = rtl_digest_queryLength( handle );
+ std::unique_ptr<sal_uInt8[]> pMD5KeyBuffer(new sal_uInt8[ nMD5KeyLen ]);
+
+ rtl_digest_init( handle, pData, nSize );
+ rtl_digest_update( handle, pData, nSize );
+ rtl_digest_get( handle, pMD5KeyBuffer.get(), nMD5KeyLen );
+ rtl_digest_destroy( handle );
+
+ // Create hex-value string from the MD5 value to keep the string size minimal
+ OUStringBuffer aBuffer( nMD5KeyLen * 2 + 1 );
+ for ( sal_uInt32 i = 0; i < nMD5KeyLen; i++ )
+ aBuffer.append( static_cast<sal_Int32>(pMD5KeyBuffer[i]), 16 );
+
+ return aBuffer.makeStringAndClear();
+ }
+
+ return OUString();
+}
+
+namespace {
+
+class ProcessEventsClass_Impl
+{
+public:
+ DECL_STATIC_LINK( ProcessEventsClass_Impl, CallEvent, void*, void );
+ DECL_STATIC_LINK( ProcessEventsClass_Impl, ProcessDocumentsEvent, void*, void );
+};
+
+}
+
+IMPL_STATIC_LINK( ProcessEventsClass_Impl, CallEvent, void*, pEvent, void )
+{
+ // Application events are processed by the Desktop::HandleAppEvent implementation.
+ Desktop::HandleAppEvent( *static_cast<ApplicationEvent*>(pEvent) );
+ delete static_cast<ApplicationEvent*>(pEvent);
+}
+
+IMPL_STATIC_LINK( ProcessEventsClass_Impl, ProcessDocumentsEvent, void*, pEvent, void )
+{
+ // Documents requests are processed by the RequestHandler implementation
+ ProcessDocumentsRequest* pDocsRequest = static_cast<ProcessDocumentsRequest*>(pEvent);
+ RequestHandler::ExecuteCmdLineRequests(*pDocsRequest, false);
+ delete pDocsRequest;
+}
+
+static void ImplPostForeignAppEvent( ApplicationEvent* pEvent )
+{
+ Application::PostUserEvent( LINK( nullptr, ProcessEventsClass_Impl, CallEvent ), pEvent );
+}
+
+static void ImplPostProcessDocumentsEvent( std::unique_ptr<ProcessDocumentsRequest> pEvent )
+{
+ Application::PostUserEvent( LINK( nullptr, ProcessEventsClass_Impl, ProcessDocumentsEvent ), pEvent.release() );
+}
+
+oslSignalAction SalMainPipeExchangeSignal_impl(SAL_UNUSED_PARAMETER void* /*pData*/, oslSignalInfo* pInfo)
+{
+ if( pInfo->Signal == osl_Signal_Terminate )
+ RequestHandler::Disable();
+ return osl_Signal_ActCallNextHdl;
+}
+
+
+// The RequestHandlerController implementation is a bookkeeper for all pending requests
+// that were created by the RequestHandler. The requests are waiting to be processed by
+// our framework loadComponentFromURL function (e.g. open/print request).
+// During shutdown the framework is asking RequestHandlerController about pending requests.
+// If there are pending requests framework has to stop the shutdown process. It is waiting
+// for these requests because framework is not able to handle shutdown and open a document
+// concurrently.
+
+
+// XServiceInfo
+OUString SAL_CALL RequestHandlerController::getImplementationName()
+{
+ return "com.sun.star.comp.RequestHandlerController";
+}
+
+sal_Bool RequestHandlerController::supportsService(
+ OUString const & ServiceName)
+{
+ return cppu::supportsService(this, ServiceName);
+}
+
+Sequence< OUString > SAL_CALL RequestHandlerController::getSupportedServiceNames()
+{
+ return { };
+}
+
+// XEventListener
+void SAL_CALL RequestHandlerController::disposing( const EventObject& )
+{
+}
+
+// XTerminateListener
+void SAL_CALL RequestHandlerController::queryTermination( const EventObject& )
+{
+ // Desktop ask about pending request through our office ipc pipe. We have to
+ // be sure that no pending request is waiting because framework is not able to
+ // handle shutdown and open a document concurrently.
+
+ if ( RequestHandler::AreRequestsPending() )
+ throw TerminationVetoException();
+ RequestHandler::SetDowning();
+}
+
+void SAL_CALL RequestHandlerController::notifyTermination( const EventObject& )
+{
+}
+
+class IpcThread: public salhelper::Thread {
+public:
+ void start(RequestHandler * handler) {
+ m_handler = handler;
+ launch();
+ }
+
+ virtual void close() = 0;
+
+protected:
+ explicit IpcThread(char const * name): Thread(name), m_handler(nullptr) {}
+
+ virtual ~IpcThread() override {}
+
+ bool process(OString const & arguments, bool * waitProcessed);
+
+ RequestHandler * m_handler;
+};
+
+class PipeIpcThread: public IpcThread {
+public:
+ static RequestHandler::Status enable(rtl::Reference<IpcThread> * thread);
+
+private:
+ explicit PipeIpcThread(osl::Pipe pipe):
+ IpcThread("PipeIPC"), pipe_(std::move(pipe))
+ {}
+
+ virtual ~PipeIpcThread() override {}
+
+ void execute() override;
+
+ void close() override { pipe_.close(); }
+
+ osl::Pipe pipe_;
+};
+
+#if ENABLE_DBUS
+
+namespace {
+
+struct DbusConnectionHolder {
+ explicit DbusConnectionHolder(DBusConnection * theConnection):
+ connection(theConnection)
+ {}
+
+ DbusConnectionHolder(DbusConnectionHolder && other): connection(nullptr)
+ { std::swap(connection, other.connection); }
+
+ ~DbusConnectionHolder() {
+ if (connection != nullptr) {
+ dbus_connection_close(connection);
+ dbus_connection_unref(connection);
+ }
+ }
+
+ DBusConnection * connection;
+};
+
+struct DbusMessageHolder {
+ explicit DbusMessageHolder(DBusMessage * theMessage): message(theMessage) {}
+
+ ~DbusMessageHolder() { clear(); }
+
+ void clear() {
+ if (message != nullptr) {
+ dbus_message_unref(message);
+ }
+ message = nullptr;
+ }
+
+ DBusMessage * message;
+
+private:
+ DbusMessageHolder(DbusMessageHolder const &) = delete;
+ DbusMessageHolder& operator =(DbusMessageHolder const &) = delete;
+};
+
+}
+
+class DbusIpcThread: public IpcThread {
+public:
+ static RequestHandler::Status enable(rtl::Reference<IpcThread> * thread);
+
+private:
+ explicit DbusIpcThread(DbusConnectionHolder && connection):
+ IpcThread("DbusIPC"), connection_(std::move(connection))
+ {}
+
+ virtual ~DbusIpcThread() override {}
+
+ void execute() override;
+
+ void close() override;
+
+ DbusConnectionHolder connection_;
+};
+
+RequestHandler::Status DbusIpcThread::enable(rtl::Reference<IpcThread> * thread)
+{
+ assert(thread != nullptr);
+ if (!dbus_threads_init_default()) {
+ SAL_WARN("desktop.app", "dbus_threads_init_default failed");
+ return RequestHandler::IPC_STATUS_BOOTSTRAP_ERROR;
+ }
+ DBusError e;
+ dbus_error_init(&e);
+ DbusConnectionHolder con(dbus_bus_get_private(DBUS_BUS_SESSION, &e));
+ assert((con.connection == nullptr) == bool(dbus_error_is_set(&e)));
+ if (con.connection == nullptr) {
+ SAL_WARN(
+ "desktop.app",
+ "dbus_bus_get_private failed with: " << e.name << ": "
+ << e.message);
+ dbus_error_free(&e);
+ return RequestHandler::IPC_STATUS_BOOTSTRAP_ERROR;
+ }
+ for (;;) {
+ int n = dbus_bus_request_name(
+ con.connection, "org.libreoffice.LibreOfficeIpc0",
+ DBUS_NAME_FLAG_DO_NOT_QUEUE, &e);
+ assert((n == -1) == bool(dbus_error_is_set(&e)));
+ switch (n) {
+ case -1:
+ SAL_WARN(
+ "desktop.app",
+ "dbus_bus_request_name failed with: " << e.name << ": "
+ << e.message);
+ dbus_error_free(&e);
+ return RequestHandler::IPC_STATUS_BOOTSTRAP_ERROR;
+ case DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER:
+ *thread = new DbusIpcThread(std::move(con));
+ return RequestHandler::IPC_STATUS_OK;
+ case DBUS_REQUEST_NAME_REPLY_EXISTS:
+ {
+ OStringBuffer buf(ARGUMENT_PREFIX);
+ OUString arg;
+ if (!(utl::Bootstrap::getProcessWorkingDir(arg)
+ && addArgument(buf, '1', arg)))
+ {
+ buf.append('0');
+ }
+ sal_uInt32 narg = rtl_getAppCommandArgCount();
+ for (sal_uInt32 i = 0; i != narg; ++i) {
+ rtl_getAppCommandArg(i, &arg.pData);
+ if (!addArgument(buf, ',', arg)) {
+ return RequestHandler::IPC_STATUS_BOOTSTRAP_ERROR;
+ }
+ }
+ char const * argstr = buf.getStr();
+ DbusMessageHolder msg(
+ dbus_message_new_method_call(
+ "org.libreoffice.LibreOfficeIpc0",
+ "/org/libreoffice/LibreOfficeIpc0",
+ "org.libreoffice.LibreOfficeIpcIfc0", "Execute"));
+ if (msg.message == nullptr) {
+ SAL_WARN(
+ "desktop.app", "dbus_message_new_method_call failed");
+ return RequestHandler::IPC_STATUS_BOOTSTRAP_ERROR;
+ }
+ DBusMessageIter it;
+ dbus_message_iter_init_append(msg.message, &it);
+ if (!dbus_message_iter_append_basic(
+ &it, DBUS_TYPE_STRING, &argstr))
+ {
+ SAL_WARN(
+ "desktop.app", "dbus_message_iter_append_basic failed");
+ return RequestHandler::IPC_STATUS_BOOTSTRAP_ERROR;
+ }
+ DbusMessageHolder repl(
+ dbus_connection_send_with_reply_and_block(
+ con.connection, msg.message, 0x7FFFFFFF, &e));
+ assert(
+ (repl.message == nullptr) == bool(dbus_error_is_set(&e)));
+ if (repl.message == nullptr) {
+ SAL_INFO(
+ "desktop.app",
+ "dbus_connection_send_with_reply_and_block failed"
+ " with: " << e.name << ": " << e.message);
+ dbus_error_free(&e);
+ break;
+ }
+ return RequestHandler::IPC_STATUS_2ND_OFFICE;
+ }
+ case DBUS_REQUEST_NAME_REPLY_IN_QUEUE:
+ case DBUS_REQUEST_NAME_REPLY_ALREADY_OWNER:
+ SAL_WARN(
+ "desktop.app",
+ "dbus_bus_request_name failed with unexpected " << +n);
+ return RequestHandler::IPC_STATUS_BOOTSTRAP_ERROR;
+ default:
+ for (;;) std::abort();
+ }
+ }
+}
+
+void DbusIpcThread::execute()
+{
+ assert(m_handler != nullptr);
+ m_handler->cReady.wait();
+ for (;;) {
+ {
+ osl::MutexGuard g(RequestHandler::GetMutex());
+ if (m_handler->mState == RequestHandler::State::Downing) {
+ break;
+ }
+ }
+ if (!dbus_connection_read_write(connection_.connection, -1)) {
+ break;
+ }
+ for (;;) {
+ DbusMessageHolder msg(
+ dbus_connection_pop_message(connection_.connection));
+ if (msg.message == nullptr) {
+ break;
+ }
+ if (!dbus_message_is_method_call(
+ msg.message, "org.libreoffice.LibreOfficeIpcIfc0",
+ "Execute"))
+ {
+ SAL_INFO("desktop.app", "unknown DBus message ignored");
+ continue;
+ }
+ DBusMessageIter it;
+ if (!dbus_message_iter_init(msg.message, &it)) {
+ SAL_WARN(
+ "desktop.app", "DBus message without argument ignored");
+ continue;
+ }
+ if (dbus_message_iter_get_arg_type(&it) != DBUS_TYPE_STRING) {
+ SAL_WARN(
+ "desktop.app",
+ "DBus message with non-string argument ignored");
+ continue;
+ }
+ char const * argstr;
+ dbus_message_iter_get_basic(&it, &argstr);
+ bool waitProcessed = false;
+ {
+ osl::MutexGuard g(RequestHandler::GetMutex());
+ if (!process(argstr, &waitProcessed)) {
+ continue;
+ }
+ }
+ if (waitProcessed) {
+ m_handler->cProcessed.wait();
+ }
+ DbusMessageHolder repl(dbus_message_new_method_return(msg.message));
+ if (repl.message == nullptr) {
+ SAL_WARN(
+ "desktop.app", "dbus_message_new_method_return failed");
+ continue;
+ }
+ dbus_uint32_t serial = 0;
+ if (!dbus_connection_send(
+ connection_.connection, repl.message, &serial)) {
+ SAL_WARN("desktop.app", "dbus_connection_send failed");
+ continue;
+ }
+ dbus_connection_flush(connection_.connection);
+ }
+ }
+}
+
+void DbusIpcThread::close() {
+ assert(connection_.connection != nullptr);
+ // Make dbus_connection_read_write fall out of internal poll call blocking
+ // on POLLIN:
+ int fd;
+ if (!dbus_connection_get_socket(connection_.connection, &fd)) {
+ SAL_WARN("desktop.app", "dbus_connection_get_socket failed");
+ return;
+ }
+ if (shutdown(fd, SHUT_RD) == -1) {
+ auto const e = errno;
+ SAL_WARN("desktop.app", "shutdown failed with errno " << e);
+ }
+}
+
+#endif
+
+::osl::Mutex& RequestHandler::GetMutex()
+{
+ static ::osl::Mutex theRequestHandlerMutex;
+ return theRequestHandlerMutex;
+}
+
+void RequestHandler::SetDowning()
+{
+ // We have the order to block all incoming requests. Framework
+ // wants to shutdown and we have to make sure that no loading/printing
+ // requests are executed anymore.
+ ::osl::MutexGuard aGuard( GetMutex() );
+
+ if ( pGlobal.is() )
+ pGlobal->mState = State::Downing;
+}
+
+void RequestHandler::EnableRequests()
+{
+ // switch between just queueing the requests and executing them
+ ::osl::MutexGuard aGuard( GetMutex() );
+
+ if ( pGlobal.is() )
+ {
+ if (pGlobal->mState != State::Downing) {
+ pGlobal->mState = State::RequestsEnabled;
+ }
+ ProcessDocumentsRequest aEmptyReq(std::nullopt);
+ // trigger already queued requests
+ RequestHandler::ExecuteCmdLineRequests(aEmptyReq, true);
+ }
+}
+
+bool RequestHandler::AreRequestsPending()
+{
+ // Give info about pending requests
+ ::osl::MutexGuard aGuard( GetMutex() );
+ if ( pGlobal.is() )
+ return ( pGlobal->mnPendingRequests > 0 );
+ else
+ return false;
+}
+
+void RequestHandler::RequestsCompleted()
+{
+ // Remove nCount pending requests from our internal counter
+ ::osl::MutexGuard aGuard( GetMutex() );
+ if ( pGlobal.is() )
+ {
+ if ( pGlobal->mnPendingRequests > 0 )
+ pGlobal->mnPendingRequests --;
+ }
+}
+
+RequestHandler::Status RequestHandler::Enable(bool ipc)
+{
+ ::osl::MutexGuard aGuard( GetMutex() );
+
+ if( pGlobal.is() )
+ return IPC_STATUS_OK;
+
+#if !HAVE_FEATURE_DESKTOP || HAVE_FEATURE_MACOSX_SANDBOX || defined(EMSCRIPTEN)
+ ipc = false;
+#endif
+
+ if (!ipc) {
+ pGlobal = new RequestHandler;
+ return IPC_STATUS_OK;
+ }
+
+ enum class Kind { Pipe, Dbus };
+ Kind kind;
+#if ENABLE_DBUS
+ kind = std::getenv("LIBO_FLATPAK") != nullptr ? Kind::Dbus : Kind::Pipe;
+#else
+ kind = Kind::Pipe;
+#endif
+ rtl::Reference<IpcThread> thread;
+ Status stat = Status(); // silence bogus potentially-uninitialized warnings
+ switch (kind) {
+ case Kind::Pipe:
+ stat = PipeIpcThread::enable(&thread);
+ break;
+ case Kind::Dbus:
+#if ENABLE_DBUS
+ stat = DbusIpcThread::enable(&thread);
+ break;
+#endif
+ default:
+ assert(false);
+ }
+ assert(thread.is() == (stat == IPC_STATUS_OK));
+ if (stat == IPC_STATUS_OK) {
+ pGlobal = new RequestHandler;
+ pGlobal->mIpcThread = thread;
+ pGlobal->mIpcThread->start(pGlobal.get());
+ }
+ return stat;
+}
+
+RequestHandler::Status PipeIpcThread::enable(rtl::Reference<IpcThread> * thread)
+{
+ assert(thread != nullptr);
+
+ // The name of the named pipe is created with the hashcode of the user installation directory (without /user). We have to retrieve
+ // this information from a unotools implementation.
+ OUString aUserInstallPath;
+ ::utl::Bootstrap::PathStatus aLocateResult = ::utl::Bootstrap::locateUserInstallation( aUserInstallPath );
+ if (aLocateResult != utl::Bootstrap::PATH_EXISTS
+ && aLocateResult != utl::Bootstrap::PATH_VALID)
+ {
+ return RequestHandler::IPC_STATUS_BOOTSTRAP_ERROR;
+ }
+
+ // Try to determine if we are the first office or not! This should prevent multiple
+ // access to the user directory !
+ // First we try to create our pipe if this fails we try to connect. We have to do this
+ // in a loop because the other office can crash or shutdown between createPipe
+ // and connectPipe!!
+ auto aUserInstallPathHashCode = CreateMD5FromString(aUserInstallPath);
+
+ // Check result to create a hash code from the user install path
+ if ( aUserInstallPathHashCode.isEmpty() )
+ return RequestHandler::IPC_STATUS_BOOTSTRAP_ERROR; // Something completely broken, we cannot create a valid hash code!
+
+ osl::Pipe pipe;
+ enum PipeMode
+ {
+ PIPEMODE_DONTKNOW,
+ PIPEMODE_CREATED,
+ PIPEMODE_CONNECTED
+ };
+ PipeMode nPipeMode = PIPEMODE_DONTKNOW;
+
+ OUString aPipeIdent( "SingleOfficeIPC_" + aUserInstallPathHashCode );
+ do
+ {
+ osl::Security security;
+
+ // Try to create pipe
+ if ( pipe.create( aPipeIdent, osl_Pipe_CREATE, security ))
+ {
+ // Pipe created
+ nPipeMode = PIPEMODE_CREATED;
+ }
+ else if( pipe.create( aPipeIdent, osl_Pipe_OPEN, security )) // Creation not successful, now we try to connect
+ {
+ osl::StreamPipe aStreamPipe(pipe.getHandle());
+ if (readStringFromPipe(aStreamPipe) == SEND_ARGUMENTS)
+ {
+ // Pipe connected to first office
+ nPipeMode = PIPEMODE_CONNECTED;
+ }
+ else
+ {
+ // Pipe connection failed (other office exited or crashed)
+ std::this_thread::sleep_for( std::chrono::milliseconds(500) );
+ }
+ }
+ else
+ {
+ oslPipeError eReason = pipe.getError();
+ if ((eReason == osl_Pipe_E_ConnectionRefused) || (eReason == osl_Pipe_E_invalidError))
+ return RequestHandler::IPC_STATUS_PIPE_ERROR;
+
+ // Wait for second office to be ready
+ std::this_thread::sleep_for( std::chrono::milliseconds(10) );
+ }
+
+ } while ( nPipeMode == PIPEMODE_DONTKNOW );
+
+ if ( nPipeMode == PIPEMODE_CREATED )
+ {
+ // Seems we are the one and only, so create listening thread
+ *thread = new PipeIpcThread(pipe);
+ return RequestHandler::IPC_STATUS_OK;
+ }
+ else
+ {
+ // Seems another office is running. Pipe arguments to it and self terminate
+ osl::StreamPipe aStreamPipe(pipe.getHandle());
+
+ OStringBuffer aArguments(ARGUMENT_PREFIX);
+ OUString cwdUrl;
+ if (!(utl::Bootstrap::getProcessWorkingDir(cwdUrl) &&
+ addArgument(aArguments, '1', cwdUrl)))
+ {
+ aArguments.append('0');
+ }
+ sal_uInt32 nCount = rtl_getAppCommandArgCount();
+ for( sal_uInt32 i=0; i < nCount; i++ )
+ {
+ rtl_getAppCommandArg( i, &aUserInstallPath.pData );
+ if (!addArgument(aArguments, ',', aUserInstallPath)) {
+ return RequestHandler::IPC_STATUS_BOOTSTRAP_ERROR;
+ }
+ }
+ aArguments.append('\0');
+ // finally, write the string onto the pipe
+ SAL_INFO("desktop.app", "writing <" << aArguments.getStr() << ">");
+ sal_Int32 n = aStreamPipe.write(
+ aArguments.getStr(), aArguments.getLength());
+ if (n != aArguments.getLength()) {
+ SAL_INFO("desktop.app", "short write: " << n);
+ return RequestHandler::IPC_STATUS_BOOTSTRAP_ERROR;
+ }
+
+ if (readStringFromPipe(aStreamPipe) != PROCESSING_DONE)
+ {
+ // something went wrong
+ return RequestHandler::IPC_STATUS_BOOTSTRAP_ERROR;
+ }
+
+ return RequestHandler::IPC_STATUS_2ND_OFFICE;
+ }
+}
+
+void RequestHandler::Disable()
+{
+ osl::ClearableMutexGuard aMutex( GetMutex() );
+
+ if( !pGlobal.is() )
+ return;
+
+ rtl::Reference< RequestHandler > handler(pGlobal);
+ pGlobal.clear();
+
+ handler->mState = State::Downing;
+ if (handler->mIpcThread.is()) {
+ handler->mIpcThread->close();
+ }
+
+ // release mutex to avoid deadlocks
+ aMutex.clear();
+
+ handler->cReady.set();
+
+ // exit gracefully and join
+ if (handler->mIpcThread.is())
+ {
+ handler->mIpcThread->join();
+ handler->mIpcThread.clear();
+ }
+
+ handler->cReady.reset();
+}
+
+RequestHandler::RequestHandler() :
+ mState( State::Starting ),
+ mnPendingRequests( 0 )
+{
+}
+
+RequestHandler::~RequestHandler()
+{
+ assert(!mIpcThread.is());
+}
+
+void RequestHandler::SetReady(bool bIsReady)
+{
+ osl::MutexGuard g(GetMutex());
+ if (pGlobal.is())
+ {
+ if (bIsReady)
+ pGlobal->cReady.set();
+ else
+ pGlobal->cReady.reset();
+ }
+}
+
+void RequestHandler::WaitForReady()
+{
+ rtl::Reference<RequestHandler> t;
+ {
+ osl::MutexGuard g(GetMutex());
+ t = pGlobal;
+ }
+ if (t.is())
+ {
+ t->cReady.wait();
+ }
+}
+
+bool IpcThread::process(OString const & arguments, bool * waitProcessed) {
+ assert(waitProcessed != nullptr);
+
+ std::unique_ptr< CommandLineArgs > aCmdLineArgs;
+ try
+ {
+ Parser p(arguments);
+ aCmdLineArgs.reset( new CommandLineArgs( p ) );
+ }
+ catch ( const CommandLineArgs::Supplier::Exception & )
+ {
+ SAL_WARN("desktop.app", "Error in received command line arguments");
+ return false;
+ }
+
+ bool bDocRequestSent = false;
+
+ OUString aUnknown( aCmdLineArgs->GetUnknown() );
+ if (aUnknown.isEmpty() && !aCmdLineArgs->IsHelp() && !aCmdLineArgs->IsVersion())
+ {
+ const CommandLineArgs &rCurrentCmdLineArgs = Desktop::GetCommandLineArgs();
+
+ if ( aCmdLineArgs->IsQuickstart() )
+ {
+ // we have to use application event, because we have to start quickstart service in main thread!!
+ ApplicationEvent* pAppEvent =
+ new ApplicationEvent(ApplicationEvent::Type::QuickStart);
+ ImplPostForeignAppEvent( pAppEvent );
+ }
+
+ // handle request for acceptor
+ std::vector< OUString > const & accept = aCmdLineArgs->GetAccept();
+ for (auto const& elem : accept)
+ {
+ ApplicationEvent* pAppEvent = new ApplicationEvent(
+ ApplicationEvent::Type::Accept, elem);
+ ImplPostForeignAppEvent( pAppEvent );
+ }
+ // handle acceptor removal
+ std::vector< OUString > const & unaccept = aCmdLineArgs->GetUnaccept();
+ for (auto const& elem : unaccept)
+ {
+ ApplicationEvent* pAppEvent = new ApplicationEvent(
+ ApplicationEvent::Type::Unaccept, elem);
+ ImplPostForeignAppEvent( pAppEvent );
+ }
+
+ std::unique_ptr<ProcessDocumentsRequest> pRequest(new ProcessDocumentsRequest(
+ aCmdLineArgs->getCwdUrl()));
+ m_handler->cProcessed.reset();
+ pRequest->pcProcessed = &m_handler->cProcessed;
+ m_handler->mbSuccess = false;
+ pRequest->mpbSuccess = &m_handler->mbSuccess;
+
+ // Print requests are not dependent on the --invisible cmdline argument as they are
+ // loaded with the "hidden" flag! So they are always checked.
+ pRequest->aPrintList = aCmdLineArgs->GetPrintList();
+ bDocRequestSent |= !pRequest->aPrintList.empty();
+ pRequest->aPrintToList = aCmdLineArgs->GetPrintToList();
+ pRequest->aPrinterName = aCmdLineArgs->GetPrinterName();
+ bDocRequestSent |= !( pRequest->aPrintToList.empty() || pRequest->aPrinterName.isEmpty() );
+ pRequest->aConversionList = aCmdLineArgs->GetConversionList();
+ pRequest->aConversionParams = aCmdLineArgs->GetConversionParams();
+ pRequest->aConversionOut = aCmdLineArgs->GetConversionOut();
+ pRequest->aImageConversionType = aCmdLineArgs->GetImageConversionType();
+ pRequest->aInFilter = aCmdLineArgs->GetInFilter();
+ pRequest->bTextCat = aCmdLineArgs->IsTextCat();
+ pRequest->bScriptCat = aCmdLineArgs->IsScriptCat();
+ bDocRequestSent |= !pRequest->aConversionList.empty();
+
+ if ( !rCurrentCmdLineArgs.IsInvisible() )
+ {
+ // Read cmdline args that can open/create documents. As they would open a window
+ // they are only allowed if the "--invisible" is currently not used!
+ pRequest->aOpenList = aCmdLineArgs->GetOpenList();
+ bDocRequestSent |= !pRequest->aOpenList.empty();
+ pRequest->aViewList = aCmdLineArgs->GetViewList();
+ bDocRequestSent |= !pRequest->aViewList.empty();
+ pRequest->aStartList = aCmdLineArgs->GetStartList();
+ bDocRequestSent |= !pRequest->aStartList.empty();
+ pRequest->aForceOpenList = aCmdLineArgs->GetForceOpenList();
+ bDocRequestSent |= !pRequest->aForceOpenList.empty();
+ pRequest->aForceNewList = aCmdLineArgs->GetForceNewList();
+ bDocRequestSent |= !pRequest->aForceNewList.empty();
+
+ // Special command line args to create an empty document for a given module
+
+ // #i18338# (lo)
+ // we only do this if no document was specified on the command line,
+ // since this would be inconsistent with the behaviour of
+ // the first process, see OpenClients() (call to OpenDefault()) in app.cxx
+ if ( aCmdLineArgs->HasModuleParam() && !bDocRequestSent )
+ {
+ SvtModuleOptions aOpt;
+ SvtModuleOptions::EFactory eFactory = SvtModuleOptions::EFactory::WRITER;
+ if ( aCmdLineArgs->IsWriter() )
+ eFactory = SvtModuleOptions::EFactory::WRITER;
+ else if ( aCmdLineArgs->IsCalc() )
+ eFactory = SvtModuleOptions::EFactory::CALC;
+ else if ( aCmdLineArgs->IsDraw() )
+ eFactory = SvtModuleOptions::EFactory::DRAW;
+ else if ( aCmdLineArgs->IsImpress() )
+ eFactory = SvtModuleOptions::EFactory::IMPRESS;
+ else if ( aCmdLineArgs->IsBase() )
+ eFactory = SvtModuleOptions::EFactory::DATABASE;
+ else if ( aCmdLineArgs->IsMath() )
+ eFactory = SvtModuleOptions::EFactory::MATH;
+ else if ( aCmdLineArgs->IsGlobal() )
+ eFactory = SvtModuleOptions::EFactory::WRITERGLOBAL;
+ else if ( aCmdLineArgs->IsWeb() )
+ eFactory = SvtModuleOptions::EFactory::WRITERWEB;
+
+ if ( !pRequest->aOpenList.empty() )
+ pRequest->aModule = aOpt.GetFactoryName( eFactory );
+ else
+ pRequest->aOpenList.push_back( aOpt.GetFactoryEmptyDocumentURL( eFactory ) );
+ bDocRequestSent = true;
+ }
+ }
+
+ if ( !aCmdLineArgs->IsQuickstart() ) {
+ bool bShowHelp = false;
+ OUStringBuffer aHelpURLBuffer;
+ if (aCmdLineArgs->IsHelpWriter()) {
+ bShowHelp = true;
+ aHelpURLBuffer.append("vnd.sun.star.help://swriter/start");
+ } else if (aCmdLineArgs->IsHelpCalc()) {
+ bShowHelp = true;
+ aHelpURLBuffer.append("vnd.sun.star.help://scalc/start");
+ } else if (aCmdLineArgs->IsHelpDraw()) {
+ bShowHelp = true;
+ aHelpURLBuffer.append("vnd.sun.star.help://sdraw/start");
+ } else if (aCmdLineArgs->IsHelpImpress()) {
+ bShowHelp = true;
+ aHelpURLBuffer.append("vnd.sun.star.help://simpress/start");
+ } else if (aCmdLineArgs->IsHelpBase()) {
+ bShowHelp = true;
+ aHelpURLBuffer.append("vnd.sun.star.help://sdatabase/start");
+ } else if (aCmdLineArgs->IsHelpBasic()) {
+ bShowHelp = true;
+ aHelpURLBuffer.append("vnd.sun.star.help://sbasic/start");
+ } else if (aCmdLineArgs->IsHelpMath()) {
+ bShowHelp = true;
+ aHelpURLBuffer.append("vnd.sun.star.help://smath/start");
+ }
+ if (bShowHelp) {
+ aHelpURLBuffer.append("?Language="
+ + utl::ConfigManager::getUILocale()
+#if defined UNX
+ + "&System=UNX");
+#elif defined _WIN32
+ + "&System=WIN");
+#endif
+ ApplicationEvent* pAppEvent = new ApplicationEvent(
+ ApplicationEvent::Type::OpenHelpUrl,
+ aHelpURLBuffer.makeStringAndClear());
+ ImplPostForeignAppEvent( pAppEvent );
+ }
+ }
+
+ if ( bDocRequestSent )
+ {
+ // Send requests to dispatch watcher if we have at least one. The receiver
+ // is responsible to delete the request after processing it.
+ if ( aCmdLineArgs->HasModuleParam() )
+ {
+ SvtModuleOptions aOpt;
+
+ // Support command line parameters to start a module (as preselection)
+ if ( aCmdLineArgs->IsWriter() && aOpt.IsModuleInstalled( SvtModuleOptions::EModule::WRITER ) )
+ pRequest->aModule = aOpt.GetFactoryName( SvtModuleOptions::EFactory::WRITER );
+ else if ( aCmdLineArgs->IsCalc() && aOpt.IsModuleInstalled( SvtModuleOptions::EModule::CALC ) )
+ pRequest->aModule = aOpt.GetFactoryName( SvtModuleOptions::EFactory::CALC );
+ else if ( aCmdLineArgs->IsImpress() && aOpt.IsModuleInstalled( SvtModuleOptions::EModule::IMPRESS ) )
+ pRequest->aModule= aOpt.GetFactoryName( SvtModuleOptions::EFactory::IMPRESS );
+ else if ( aCmdLineArgs->IsDraw() && aOpt.IsModuleInstalled( SvtModuleOptions::EModule::DRAW ) )
+ pRequest->aModule= aOpt.GetFactoryName( SvtModuleOptions::EFactory::DRAW );
+ }
+
+ ImplPostProcessDocumentsEvent( std::move(pRequest) );
+ }
+ else
+ {
+ // delete not used request again
+ pRequest.reset();
+ }
+ if (aCmdLineArgs->IsEmpty())
+ {
+ // no document was sent, just bring Office to front
+ ApplicationEvent* pAppEvent =
+ new ApplicationEvent(ApplicationEvent::Type::Appear);
+ ImplPostForeignAppEvent( pAppEvent );
+ }
+ }
+ *waitProcessed = bDocRequestSent;
+ return true;
+}
+
+void PipeIpcThread::execute()
+{
+ assert(m_handler != nullptr);
+ do
+ {
+ osl::StreamPipe aStreamPipe;
+ oslPipeError nError = pipe_.accept( aStreamPipe );
+
+
+ if( nError == osl_Pipe_E_None )
+ {
+ // if we receive a request while the office is displaying some dialog or error during
+ // bootstrap, that dialogs event loop might get events that are dispatched by this thread
+ // we have to wait for cReady to be set by the real main loop.
+ // only requests that don't dispatch events may be processed before cReady is set.
+ m_handler->cReady.wait();
+
+ // we might have decided to shutdown while we were sleeping
+ if (!RequestHandler::pGlobal.is()) return;
+
+ // only lock the mutex when processing starts, otherwise we deadlock when the office goes
+ // down during wait
+ osl::ClearableMutexGuard aGuard( RequestHandler::GetMutex() );
+
+ if (m_handler->mState == RequestHandler::State::Downing)
+ {
+ break;
+ }
+
+ // notify client we're ready to process its args:
+ SAL_INFO("desktop.app", "writing <" << SEND_ARGUMENTS << ">");
+ std::size_t n = aStreamPipe.write(
+ SEND_ARGUMENTS, std::size(SEND_ARGUMENTS));
+ // incl. terminating NUL
+ if (n != std::size(SEND_ARGUMENTS)) {
+ SAL_WARN("desktop.app", "short write: " << n);
+ continue;
+ }
+
+ OString aArguments = readStringFromPipe(aStreamPipe);
+
+ // Is this a lookup message from another application? if so, ignore
+ if (aArguments.isEmpty())
+ continue;
+
+ bool waitProcessed = false;
+ if (!process(aArguments, &waitProcessed)) {
+ continue;
+ }
+
+ // we don't need the mutex any longer...
+ aGuard.clear();
+ bool bSuccess = true;
+ // wait for processing to finish
+ if (waitProcessed)
+ {
+ m_handler->cProcessed.wait();
+ bSuccess = m_handler->mbSuccess;
+ }
+ if (bSuccess)
+ {
+ // processing finished, inform the requesting end:
+ SAL_INFO("desktop.app", "writing <" << PROCESSING_DONE << ">");
+ n = aStreamPipe.write(PROCESSING_DONE, std::size(PROCESSING_DONE));
+ // incl. terminating NUL
+ if (n != std::size(PROCESSING_DONE))
+ {
+ SAL_WARN("desktop.app", "short write: " << n);
+ continue;
+ }
+ }
+ }
+ else
+ {
+ {
+ osl::MutexGuard aGuard( RequestHandler::GetMutex() );
+ if (m_handler->mState == RequestHandler::State::Downing)
+ {
+ break;
+ }
+ }
+
+ SAL_WARN( "desktop.app", "Error on accept: " << static_cast<int>(nError));
+ std::this_thread::sleep_for( std::chrono::seconds(1) );
+ }
+ } while( schedule() );
+}
+
+static void AddToDispatchList(
+ std::vector<DispatchWatcher::DispatchRequest>& rDispatchList,
+ std::optional< OUString > const & cwdUrl,
+ std::vector< OUString > const & aRequestList,
+ DispatchWatcher::RequestType nType,
+ const OUString& aParam,
+ const OUString& aFactory )
+{
+ for (auto const& request : aRequestList)
+ {
+ rDispatchList.push_back({nType, request, cwdUrl, aParam, aFactory});
+ }
+}
+
+static void AddConversionsToDispatchList(
+ std::vector<DispatchWatcher::DispatchRequest>& rDispatchList,
+ std::optional< OUString > const & cwdUrl,
+ std::vector< OUString > const & rRequestList,
+ const OUString& rParam,
+ const OUString& rPrinterName,
+ const OUString& rFactory,
+ const OUString& rParamOut,
+ std::u16string_view rImgOut,
+ const bool isTextCat,
+ const bool isScriptCat )
+{
+ DispatchWatcher::RequestType nType;
+ OUString aParam( rParam );
+
+ if( !rParam.isEmpty() )
+ {
+ if ( isTextCat )
+ nType = DispatchWatcher::REQUEST_CAT;
+ else
+ nType = DispatchWatcher::REQUEST_CONVERSION;
+ }
+ else
+ {
+ if ( isScriptCat )
+ nType = DispatchWatcher::REQUEST_SCRIPT_CAT;
+ else
+ {
+ nType = DispatchWatcher::REQUEST_BATCHPRINT;
+ aParam = rPrinterName;
+ }
+ }
+
+ OUString aPWD;
+ if (cwdUrl)
+ {
+ aPWD = *cwdUrl;
+ }
+ else
+ {
+ utl::Bootstrap::getProcessWorkingDir( aPWD );
+ }
+
+ if (OUString aOutDir(rParamOut.trim()); !aOutDir.isEmpty())
+ {
+ if (osl::FileBase::getAbsoluteFileURL(aPWD, rParamOut, aOutDir) == osl::FileBase::E_None)
+ osl::FileBase::getSystemPathFromFileURL(aOutDir, aOutDir);
+ aParam += ";" + aOutDir;
+ }
+ else
+ {
+ ::osl::FileBase::getSystemPathFromFileURL( aPWD, aPWD );
+ aParam += ";" + aPWD;
+ }
+
+ if( !rImgOut.empty() )
+ aParam += OUString::Concat("|") + o3tl::trim(rImgOut);
+
+ for (auto const& request : rRequestList)
+ {
+ rDispatchList.push_back({nType, request, cwdUrl, aParam, rFactory});
+ }
+}
+
+namespace {
+
+struct ConditionSetGuard
+{
+ osl::Condition* m_pCondition;
+ ConditionSetGuard(osl::Condition* pCondition) : m_pCondition(pCondition) {}
+ ~ConditionSetGuard() { if (m_pCondition) m_pCondition->set(); }
+};
+
+}
+
+bool RequestHandler::ExecuteCmdLineRequests(
+ ProcessDocumentsRequest& aRequest, bool noTerminate)
+{
+ // protect the dispatch list
+ osl::ClearableMutexGuard aGuard( GetMutex() );
+
+ // ensure that Processed flag (if exists) is signaled in any outcome
+ ConditionSetGuard aSetGuard(aRequest.pcProcessed);
+
+ static std::vector<DispatchWatcher::DispatchRequest> aDispatchList;
+
+ // Create dispatch list for dispatch watcher
+ AddToDispatchList( aDispatchList, aRequest.aCwdUrl, aRequest.aInFilter, DispatchWatcher::REQUEST_INFILTER, "", aRequest.aModule );
+ AddToDispatchList( aDispatchList, aRequest.aCwdUrl, aRequest.aOpenList, DispatchWatcher::REQUEST_OPEN, "", aRequest.aModule );
+ AddToDispatchList( aDispatchList, aRequest.aCwdUrl, aRequest.aViewList, DispatchWatcher::REQUEST_VIEW, "", aRequest.aModule );
+ AddToDispatchList( aDispatchList, aRequest.aCwdUrl, aRequest.aStartList, DispatchWatcher::REQUEST_START, "", aRequest.aModule );
+ AddToDispatchList( aDispatchList, aRequest.aCwdUrl, aRequest.aPrintList, DispatchWatcher::REQUEST_PRINT, "", aRequest.aModule );
+ AddToDispatchList( aDispatchList, aRequest.aCwdUrl, aRequest.aPrintToList, DispatchWatcher::REQUEST_PRINTTO, aRequest.aPrinterName, aRequest.aModule );
+ AddToDispatchList( aDispatchList, aRequest.aCwdUrl, aRequest.aForceOpenList, DispatchWatcher::REQUEST_FORCEOPEN, "", aRequest.aModule );
+ AddToDispatchList( aDispatchList, aRequest.aCwdUrl, aRequest.aForceNewList, DispatchWatcher::REQUEST_FORCENEW, "", aRequest.aModule );
+ AddConversionsToDispatchList( aDispatchList, aRequest.aCwdUrl, aRequest.aConversionList, aRequest.aConversionParams, aRequest.aPrinterName, aRequest.aModule, aRequest.aConversionOut, aRequest.aImageConversionType, aRequest.bTextCat, aRequest.bScriptCat );
+ bool bShutdown( false );
+
+ if ( pGlobal.is() )
+ {
+ if( ! pGlobal->AreRequestsEnabled() )
+ {
+ // Either starting, or downing - do not process the request, just try to bring Office to front
+ ApplicationEvent* pAppEvent =
+ new ApplicationEvent(ApplicationEvent::Type::Appear);
+ ImplPostForeignAppEvent(pAppEvent);
+ return bShutdown;
+ }
+
+ pGlobal->mnPendingRequests += aDispatchList.size();
+ if ( !pGlobal->mpDispatchWatcher.is() )
+ {
+ pGlobal->mpDispatchWatcher = new DispatchWatcher;
+ }
+ rtl::Reference<DispatchWatcher> dispatchWatcher(
+ pGlobal->mpDispatchWatcher);
+
+ // copy for execute
+ std::vector<DispatchWatcher::DispatchRequest> aTempList;
+ aTempList.swap( aDispatchList );
+
+ aGuard.clear();
+
+ // Execute dispatch requests
+ bShutdown = dispatchWatcher->executeDispatchRequests( aTempList, noTerminate);
+ if (aRequest.mpbSuccess)
+ *aRequest.mpbSuccess = true; // signal that we have actually succeeded
+ }
+
+ return bShutdown;
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/app/officeipcthread.hxx b/desktop/source/app/officeipcthread.hxx
new file mode 100644
index 0000000000..a233c18e01
--- /dev/null
+++ b/desktop/source/app/officeipcthread.hxx
@@ -0,0 +1,157 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <sal/config.h>
+
+#include <utility>
+#include <vector>
+
+#include <com/sun/star/lang/XServiceInfo.hpp>
+#include <com/sun/star/frame/XTerminateListener.hpp>
+#include <osl/signal.h>
+#include <rtl/ref.hxx>
+#include <rtl/ustring.hxx>
+#include <salhelper/simplereferenceobject.hxx>
+#include <cppuhelper/implbase.hxx>
+#include <osl/conditn.hxx>
+#include <optional>
+
+namespace desktop
+{
+
+oslSignalAction SalMainPipeExchangeSignal_impl(void* /*pData*/, oslSignalInfo* pInfo);
+
+// A request for the current office
+// that was given by command line or by IPC pipe communication.
+struct ProcessDocumentsRequest
+{
+ explicit ProcessDocumentsRequest(std::optional< OUString > cwdUrl):
+ aCwdUrl(std::move(cwdUrl)), pcProcessed( nullptr ), bTextCat( false ), bScriptCat( false ) {}
+
+ std::optional< OUString > aCwdUrl;
+ OUString aModule;
+ std::vector< OUString > aOpenList; // Documents that should be opened in the default way
+ std::vector< OUString > aViewList; // Documents that should be opened in viewmode
+ std::vector< OUString > aStartList; // Documents/Presentations that should be started
+ std::vector< OUString > aPrintList; // Documents that should be printed on default printer
+ std::vector< OUString > aForceOpenList; // Documents that should be forced to open for editing (even templates)
+ std::vector< OUString > aForceNewList; // Documents that should be forced to create a new document
+ OUString aPrinterName; // The printer name that should be used for printing
+ std::vector< OUString > aPrintToList; // Documents that should be printed on the given printer
+ std::vector< OUString > aConversionList;
+ OUString aConversionParams;
+ OUString aConversionOut;
+ OUString aImageConversionType;
+ std::vector< OUString > aInFilter;
+ ::osl::Condition *pcProcessed; // pointer condition to be set when the request has been processed
+ bool* mpbSuccess = nullptr; // pointer to boolean receiving if the processing was successful
+ bool bTextCat; // boolean flag indicating whether to dump text content to console
+ bool bScriptCat; // boolean flag indicating whether to dump script content to console
+};
+
+class DispatchWatcher;
+class IpcThread;
+class PipeIpcThread;
+class DbusIpcThread;
+
+class RequestHandler: public salhelper::SimpleReferenceObject
+{
+ friend IpcThread;
+ friend PipeIpcThread;
+ friend DbusIpcThread;
+
+ private:
+ static rtl::Reference< RequestHandler > pGlobal;
+
+ enum class State { Starting, RequestsEnabled, Downing };
+
+ State mState;
+ int mnPendingRequests;
+ rtl::Reference<DispatchWatcher> mpDispatchWatcher;
+ rtl::Reference<IpcThread> mIpcThread;
+
+ /* condition to be set when the request has been processed */
+ ::osl::Condition cProcessed;
+ /* receives if the processing was successful (may be false e.g. when shutting down) */
+ bool mbSuccess = false;
+
+ /* condition to be set when the main event loop is ready
+ otherwise an error dialogs event loop could eat away
+ requests from a 2nd office */
+ ::osl::Condition cReady;
+
+ static ::osl::Mutex& GetMutex();
+
+ RequestHandler();
+
+ virtual ~RequestHandler() override;
+
+ public:
+ enum Status
+ {
+ IPC_STATUS_OK,
+ IPC_STATUS_2ND_OFFICE,
+ IPC_STATUS_PIPE_ERROR,
+ IPC_STATUS_BOOTSTRAP_ERROR
+ };
+
+ // controlling pipe communication during shutdown
+ static void SetDowning();
+ static void EnableRequests();
+ static bool AreRequestsPending();
+ static void RequestsCompleted();
+ static bool ExecuteCmdLineRequests(
+ ProcessDocumentsRequest&, bool noTerminate);
+
+ // return sal_False if second office
+ static Status Enable(bool ipc);
+ static void Disable();
+ // start dispatching events...
+ static void SetReady(bool bIsReady);
+ static void WaitForReady();
+
+ bool AreRequestsEnabled() const { return mState == State::RequestsEnabled; }
+};
+
+
+class RequestHandlerController : public ::cppu::WeakImplHelper<
+ css::lang::XServiceInfo,
+ css::frame::XTerminateListener >
+{
+ public:
+ RequestHandlerController() {}
+
+ // XServiceInfo
+ virtual OUString SAL_CALL getImplementationName() override;
+ virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override;
+ virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames() override;
+
+ // XEventListener
+ virtual void SAL_CALL disposing( const css::lang::EventObject& Source ) override;
+
+ // XTerminateListener
+ virtual void SAL_CALL queryTermination( const css::lang::EventObject& aEvent ) override;
+ virtual void SAL_CALL notifyTermination( const css::lang::EventObject& aEvent ) override;
+};
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/app/opencl.cxx b/desktop/source/app/opencl.cxx
new file mode 100644
index 0000000000..ad3df6bf3f
--- /dev/null
+++ b/desktop/source/app/opencl.cxx
@@ -0,0 +1,257 @@
+/* -*- 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 module exists to validate the OpenCL implementation,
+ * where necessary during startup; and before we load or
+ * calculate using OpenCL.
+ */
+
+#include <app.hxx>
+
+#include <config_version.h>
+#include <config_feature_opencl.h>
+#include <config_folders.h>
+
+#include <rtl/bootstrap.hxx>
+#include <sal/log.hxx>
+
+#include <officecfg/Office/Calc.hxx>
+#include <officecfg/Office/Common.hxx>
+
+#include <comphelper/propertyvalue.hxx>
+#include <svl/documentlockfile.hxx>
+#include <comphelper/diagnose_ex.hxx>
+
+#include <com/sun/star/table/XCell2.hpp>
+#include <com/sun/star/sheet/XCalculatable.hpp>
+#include <com/sun/star/sheet/XSpreadsheet.hpp>
+#include <com/sun/star/sheet/XSpreadsheets.hpp>
+#include <com/sun/star/sheet/XSpreadsheetDocument.hpp>
+
+#if HAVE_FEATURE_OPENCL
+#include <opencl/openclwrapper.hxx>
+#endif
+#include <opencl/OpenCLZone.hxx>
+
+#include <osl/file.hxx>
+#include <osl/process.h>
+
+using namespace ::osl;
+using namespace ::com::sun::star::uno;
+using namespace ::com::sun::star::frame;
+
+namespace desktop {
+
+#if HAVE_FEATURE_OPENCL
+
+static bool testOpenCLDriver()
+{
+ // A simple OpenCL test run in a separate process in order to test
+ // whether the driver crashes (asserts,etc.) when trying to use OpenCL.
+ SAL_INFO("opencl", "Starting CL driver test");
+
+ OUString testerURL("$BRAND_BASE_DIR/" LIBO_BIN_FOLDER "/opencltest");
+ rtl::Bootstrap::expandMacros(testerURL); //TODO: detect failure
+
+ OUString deviceName, platformName;
+ openclwrapper::getOpenCLDeviceName( deviceName, platformName );
+ rtl_uString* args[] = { deviceName.pData, platformName.pData };
+ sal_Int32 numArgs = 2;
+
+ oslProcess process;
+ oslSecurity security = osl_getCurrentSecurity();
+ oslProcessError error = osl_executeProcess(testerURL.pData, args, numArgs,
+ osl_Process_SEARCHPATH | osl_Process_HIDDEN, security,
+ nullptr, nullptr, 0, &process );
+ osl_freeSecurityHandle( security );
+ if( error != osl_Process_E_None )
+ {
+ SAL_WARN( "opencl", "failed to start CL driver test: " << error );
+ return false;
+ }
+ // If the driver takes more than 10 seconds, it's probably broken/useless.
+ TimeValue timeout( 10, 0 );
+ error = osl_joinProcessWithTimeout( process, &timeout );
+ if( error == osl_Process_E_None )
+ {
+ oslProcessInfo info;
+ info.Size = sizeof( info );
+ error = osl_getProcessInfo( process, osl_Process_EXITCODE, &info );
+ if( error == osl_Process_E_None )
+ {
+ if( info.Code == 0 )
+ {
+ SAL_INFO( "opencl", "CL driver test passed" );
+ osl_freeProcessHandle( process );
+ return true;
+ }
+ else
+ {
+ SAL_WARN( "opencl", "CL driver test failed - disabling: " << info.Code );
+ osl_freeProcessHandle( process );
+ return false;
+ }
+ }
+ }
+ SAL_WARN( "opencl", "CL driver test did not finish - disabling: " << error );
+ osl_terminateProcess( process );
+ osl_freeProcessHandle( process );
+ return false;
+}
+
+static bool testOpenCLCompute(const Reference< XDesktop2 > &xDesktop, const OUString &rURL)
+{
+ bool bSuccess = false;
+ css::uno::Reference< css::lang::XComponent > xComponent;
+
+ sal_uInt64 nKernelFailures = openclwrapper::kernelFailures;
+
+ SAL_INFO("opencl", "Starting CL test spreadsheet");
+
+ // A stale lock file would make the loading fail, so make sure to remove it.
+ try {
+ ::svt::DocumentLockFile lockFile( rURL );
+ lockFile.RemoveFileDirectly();
+ }
+ catch (const css::uno::Exception&)
+ {
+ }
+
+ try {
+ css::uno::Reference< css::frame::XComponentLoader > xLoader(xDesktop, css::uno::UNO_QUERY_THROW);
+
+ css::uno::Sequence< css::beans::PropertyValue > aArgs{ comphelper::makePropertyValue("Hidden",
+ true) };
+
+ xComponent.set(xLoader->loadComponentFromURL(rURL, "_blank", 0, aArgs));
+
+ // What an unpleasant API to use.
+ css::uno::Reference< css::sheet::XCalculatable > xCalculatable( xComponent, css::uno::UNO_QUERY_THROW);
+ css::uno::Reference< css::sheet::XSpreadsheetDocument > xSpreadDoc( xComponent, css::uno::UNO_QUERY_THROW );
+ css::uno::Reference< css::sheet::XSpreadsheets > xSheets( xSpreadDoc->getSheets(), css::uno::UNO_SET_THROW );
+ css::uno::Reference< css::container::XIndexAccess > xIndex( xSheets, css::uno::UNO_QUERY_THROW );
+ css::uno::Reference< css::sheet::XSpreadsheet > xSheet( xIndex->getByIndex(0), css::uno::UNO_QUERY_THROW);
+
+ // So we insert our MAX call at the end on a named range.
+ css::uno::Reference< css::table::XCell2 > xThresh( xSheet->getCellByPosition(1,1), css::uno::UNO_QUERY_THROW ); // B2
+ double fThreshold = xThresh->getValue();
+
+ // We need pure OCL formulae all the way through the
+ // dependency chain, or we fall-back.
+ xCalculatable->calculateAll();
+
+ // So we insert our MAX call at the end on a named range.
+ css::uno::Reference< css::table::XCell2 > xCell( xSheet->getCellByPosition(1,0), css::uno::UNO_QUERY_THROW );
+ xCell->setFormula("=MAX(results)");
+ double fResult = xCell->getValue();
+
+ // Ensure the maximum variance is below our tolerance.
+ if (fResult > fThreshold)
+ {
+ SAL_WARN("opencl", "OpenCL results unstable - disabling; result: "
+ << fResult << " vs. " << fThreshold);
+ }
+ else
+ {
+ SAL_INFO("opencl", "calculating smoothly; result: " << fResult);
+ bSuccess = true;
+ }
+ }
+ catch (const css::uno::Exception &)
+ {
+ TOOLS_WARN_EXCEPTION("opencl", "OpenCL testing failed - disabling");
+ }
+
+ if (nKernelFailures != openclwrapper::kernelFailures)
+ {
+ // tdf#100883 - defeat SEH exception handling fallbacks.
+ SAL_WARN("opencl", "OpenCL kernels failed to compile, "
+ "or took SEH exceptions "
+ << nKernelFailures << " != " << openclwrapper::kernelFailures);
+ bSuccess = false;
+ }
+
+ if (!bSuccess)
+ OpenCLZone::hardDisable();
+ if (xComponent.is())
+ xComponent->dispose();
+
+
+ return bSuccess;
+}
+
+void Desktop::CheckOpenCLCompute(const Reference< XDesktop2 > &xDesktop)
+{
+ if (!openclwrapper::canUseOpenCL() || Application::IsSafeModeEnabled())
+ return;
+
+ SAL_INFO("opencl", "Initiating test of OpenCL device");
+ OpenCLZone aZone;
+ OpenCLInitialZone aInitialZone;
+
+ OUString aDevice = officecfg::Office::Calc::Formula::Calculation::OpenCLDevice::get();
+ OUString aSelectedCLDeviceVersionID;
+ if (!openclwrapper::switchOpenCLDevice(
+ aDevice,
+ officecfg::Office::Calc::Formula::Calculation::OpenCLAutoSelect::get(),
+ false /* bForceEvaluation */,
+ aSelectedCLDeviceVersionID))
+ {
+ SAL_WARN("opencl", "Failed to initialize OpenCL for test");
+ OpenCLZone::hardDisable();
+ return;
+ }
+
+ // Append our app version as well.
+ aSelectedCLDeviceVersionID += "--" LIBO_VERSION_DOTTED;
+
+ // Append timestamp of the file.
+ OUString aURL("$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/opencl/cl-test.ods");
+ rtl::Bootstrap::expandMacros(aURL);
+
+ DirectoryItem aItem;
+ (void)DirectoryItem::get( aURL, aItem );
+ FileStatus aFileStatus( osl_FileStatus_Mask_ModifyTime );
+ (void)aItem.getFileStatus( aFileStatus );
+ TimeValue aTimeVal = aFileStatus.getModifyTime();
+ aSelectedCLDeviceVersionID += "--" +
+ OUString::number(aTimeVal.Seconds);
+
+ if (aSelectedCLDeviceVersionID == officecfg::Office::Common::Misc::SelectedOpenCLDeviceIdentifier::get())
+ return;
+
+ // OpenCL device changed - sanity check it and disable if bad.
+
+ sal_Int32 nOrigMinimumSize = officecfg::Office::Calc::Formula::Calculation::OpenCLMinimumDataSize::get();
+ { // set the minimum group size to something small for quick testing.
+ std::shared_ptr<comphelper::ConfigurationChanges> xBatch(comphelper::ConfigurationChanges::create());
+ officecfg::Office::Calc::Formula::Calculation::OpenCLMinimumDataSize::set(3 /* small */, xBatch);
+ xBatch->commit();
+ }
+
+ // Hopefully at least basic functionality always works and broken OpenCL implementations break
+ // only when they are used to compute something. If this assumptions turns out to be not true,
+ // the driver check needs to be moved sooner.
+ bool bSucceeded = testOpenCLDriver() && testOpenCLCompute(xDesktop, aURL);
+
+ { // restore the minimum group size
+ std::shared_ptr<comphelper::ConfigurationChanges> xBatch(comphelper::ConfigurationChanges::create());
+ officecfg::Office::Calc::Formula::Calculation::OpenCLMinimumDataSize::set(nOrigMinimumSize, xBatch);
+ officecfg::Office::Common::Misc::SelectedOpenCLDeviceIdentifier::set(aSelectedCLDeviceVersionID, xBatch);
+ xBatch->commit();
+ }
+
+ if (!bSucceeded)
+ OpenCLZone::hardDisable();
+}
+#endif // HAVE_FEATURE_OPENCL
+
+} // end namespace desktop
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/desktop/source/app/sofficemain.cxx b/desktop/source/app/sofficemain.cxx
new file mode 100644
index 0000000000..0b02155f59
--- /dev/null
+++ b/desktop/source/app/sofficemain.cxx
@@ -0,0 +1,104 @@
+/* -*- 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 <config_features.h>
+
+#include <desktop/dllapi.h>
+
+#include <app.hxx>
+#include "cmdlineargs.hxx"
+#include "cmdlinehelp.hxx"
+
+// needed before sal/main.h to avoid redefinition of macros
+#include <prewin.h>
+
+#include <rtl/bootstrap.hxx>
+#include <sal/log.hxx>
+#include <sal/main.h>
+#include <tools/extendapplicationenvironment.hxx>
+#include <vcl/svmain.hxx>
+
+#if HAVE_FEATURE_BREAKPAD
+#include <desktop/crashreport.hxx>
+#endif
+
+#include <postwin.h>
+
+#ifdef ANDROID
+# include <jni.h>
+# include <android/log.h>
+# include <salhelper/thread.hxx>
+
+# define LOGTAG "LibreOffice/sofficemain"
+# define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, LOGTAG, __VA_ARGS__))
+#endif
+
+extern "C" int DESKTOP_DLLPUBLIC soffice_main()
+{
+ sal_detail_initialize(sal::detail::InitializeSoffice, nullptr);
+
+#if HAVE_FEATURE_BREAKPAD
+ CrashReporter::installExceptionHandler();
+#endif
+
+#if defined ANDROID
+ try {
+ rtl::Bootstrap::setIniFilename("file:///assets/program/lofficerc");
+#endif
+ tools::extendApplicationEnvironment();
+
+ desktop::Desktop aDesktop;
+ // This string is used during initialization of the Gtk+ VCL module
+ Application::SetAppName( "soffice" );
+
+ // handle --version and --help already here, otherwise they would be handled
+ // after VCL initialization that might fail if $DISPLAY is not set
+ const desktop::CommandLineArgs& rCmdLineArgs = desktop::Desktop::GetCommandLineArgs();
+ const OUString& aUnknown( rCmdLineArgs.GetUnknown() );
+ if ( !aUnknown.isEmpty() )
+ {
+ desktop::Desktop::InitApplicationServiceManager();
+ desktop::displayCmdlineHelp( aUnknown );
+ return EXIT_FAILURE;
+ }
+ if ( rCmdLineArgs.IsHelp() )
+ {
+ desktop::Desktop::InitApplicationServiceManager();
+ desktop::displayCmdlineHelp( OUString() );
+ return EXIT_SUCCESS;
+ }
+ if ( rCmdLineArgs.IsVersion() )
+ {
+ desktop::Desktop::InitApplicationServiceManager();
+ desktop::displayVersion();
+ return EXIT_SUCCESS;
+ }
+
+ return SVMain();
+#if defined ANDROID
+ } catch (const css::uno::Exception &e) {
+ LOGI("Unhandled UNO exception: '%s'",
+ OUStringToOString(e.Message, RTL_TEXTENCODING_UTF8).getStr());
+ throw; // to get exception type printed
+ }
+#endif
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/app/sofficemain.h b/desktop/source/app/sofficemain.h
new file mode 100644
index 0000000000..9bc6dafee3
--- /dev/null
+++ b/desktop/source/app/sofficemain.h
@@ -0,0 +1,34 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <desktop/dllapi.h>
+
+#if defined __cplusplus
+extern "C" {
+#endif
+
+int DESKTOP_DLLPUBLIC soffice_main(void);
+
+#if defined __cplusplus
+}
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/app/updater.cxx b/desktop/source/app/updater.cxx
new file mode 100644
index 0000000000..12bb4969a6
--- /dev/null
+++ b/desktop/source/app/updater.cxx
@@ -0,0 +1,920 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include "updater.hxx"
+
+#if UNX
+#include <unistd.h>
+#include <errno.h>
+
+#endif
+
+#ifdef _WIN32
+#include <comphelper/windowsStart.hxx>
+#endif
+
+#include <fstream>
+#include <config_folders.h>
+#include <rtl/bootstrap.hxx>
+
+#include <officecfg/Office/Update.hxx>
+
+#include <rtl/ustring.hxx>
+#include <unotools/tempfile.hxx>
+#include <unotools/configmgr.hxx>
+#include <o3tl/char16_t2wchar_t.hxx>
+#include <osl/file.hxx>
+#include <rtl/process.h>
+#include <sal/log.hxx>
+#include <tools/stream.hxx>
+
+#include <curl/curl.h>
+
+#include <orcus/json_document_tree.hpp>
+#include <orcus/config.hpp>
+
+#include <systools/curlinit.hxx>
+#include <comphelper/hash.hxx>
+
+#include <com/sun/star/container/XNameAccess.hpp>
+
+#include <officecfg/Setup.hxx>
+
+#include <functional>
+#include <memory>
+#include <set>
+#include <string_view>
+
+namespace {
+
+class error_updater : public std::exception
+{
+ OString maStr;
+public:
+
+ error_updater(const OString& rStr):
+ maStr(rStr)
+ {
+ }
+
+ virtual const char* what() const throw() override
+ {
+ return maStr.getStr();
+ }
+};
+
+#ifdef UNX
+static const char kUserAgent[] = "LibreOffice UpdateChecker/1.0 (Linux)";
+#else
+static const char kUserAgent[] = "LibreOffice UpdateChecker/1.0 (unknown platform)";
+#endif
+
+#ifdef UNX
+const char* const pUpdaterName = "updater";
+const char* const pSofficeExeName = "soffice";
+#elif defined(_WIN32)
+const char* pUpdaterName = "updater.exe";
+const char* pSofficeExeName = "soffice.exe";
+#else
+#error "Need implementation"
+#endif
+
+OUString normalizePath(const OUString& rPath)
+{
+ OUString aPath = rPath;
+#if defined WNT
+ aPath = aPath.replace('\\', '/');
+#endif
+
+ aPath = aPath.replaceAll("//", "/");
+
+ // remove final /
+ if (aPath.endsWith("/"))
+ {
+ aPath = aPath.copy(0, aPath.getLength() - 1);
+ }
+
+ while (aPath.indexOf("/..") != -1)
+ {
+ sal_Int32 nIndex = aPath.indexOf("/..");
+ sal_Int32 i = nIndex - 1;
+ for (; i > 0; --i)
+ {
+ if (aPath[i] == '/')
+ break;
+ }
+
+ OUString aTempPath = aPath;
+ aPath = aTempPath.copy(0, i) + aPath.copy(nIndex + 3);
+ }
+
+#if defined WNT
+ aPath = aPath.replace('/', '\\');
+#endif
+ return aPath;
+}
+
+void CopyFileToDir(const OUString& rTempDirURL, const OUString & rFileName, const OUString& rOldDir)
+{
+ OUString aSourceURL = rOldDir + "/" + rFileName;
+ OUString aDestURL = rTempDirURL + "/" + rFileName;
+
+ osl::File::RC eError = osl::File::copy(aSourceURL, aDestURL);
+ if (eError != osl::File::E_None)
+ {
+ SAL_WARN("desktop.updater", "could not copy the file to a temp directory: " << rFileName);
+ throw std::exception();
+ }
+}
+
+OUString getPathFromURL(const OUString& rURL)
+{
+ OUString aPath;
+ osl::FileBase::getSystemPathFromFileURL(rURL, aPath);
+
+ return normalizePath(aPath);
+}
+
+void CopyUpdaterToTempDir(const OUString& rInstallDirURL, const OUString& rTempDirURL)
+{
+ OUString aUpdaterName = OUString::fromUtf8(pUpdaterName);
+ CopyFileToDir(rTempDirURL, aUpdaterName, rInstallDirURL);
+ CopyFileToDir(rTempDirURL, u"updater.ini"_ustr, rInstallDirURL);
+}
+
+#ifdef UNX
+typedef char CharT;
+#define tstrncpy std::strncpy
+char const * toStream(char const * s) { return s; }
+#elif defined(_WIN32)
+typedef wchar_t CharT;
+#define tstrncpy std::wcsncpy
+OUString toStream(wchar_t const * s) { return OUString(o3tl::toU(s)); }
+#else
+#error "Need an implementation"
+#endif
+
+void createStr(const OUString& rStr, CharT** pArgs, size_t i)
+{
+#ifdef UNX
+ OString aStr = OUStringToOString(rStr, RTL_TEXTENCODING_UTF8);
+#elif defined(_WIN32)
+ OUString aStr = rStr;
+#else
+#error "Need an implementation"
+#endif
+ CharT* pStr = new CharT[aStr.getLength() + 1];
+ tstrncpy(pStr, (CharT*)aStr.getStr(), aStr.getLength());
+ pStr[aStr.getLength()] = '\0';
+ pArgs[i] = pStr;
+}
+
+CharT** createCommandLine(OUString const & argv0, int * argc)
+{
+ OUString aInstallDir = Updater::getInstallationPath();
+
+ size_t nCommandLineArgs = rtl_getAppCommandArgCount();
+ size_t nArgs = 8 + nCommandLineArgs;
+ CharT** pArgs = new CharT*[nArgs];
+ createStr(argv0, pArgs, 0);
+ {
+ // directory with the patch log
+ OUString aPatchDir = Updater::getPatchDirURL();
+ rtl::Bootstrap::expandMacros(aPatchDir);
+ OUString aTempDirPath = getPathFromURL(aPatchDir);
+ Updater::log("Patch Dir: " + aTempDirPath);
+ createStr(aTempDirPath, pArgs, 1);
+ }
+ {
+ // the actual update directory
+ Updater::log("Install Dir: " + aInstallDir);
+ createStr(aInstallDir, pArgs, 2);
+ }
+ {
+ // the temporary updated build
+ Updater::log("Working Dir: " + aInstallDir);
+ createStr(aInstallDir, pArgs, 3);
+ }
+ {
+#ifdef UNX
+ OUString aPID("0");
+#elif defined(_WIN32)
+ oslProcessInfo aInfo;
+ aInfo.Size = sizeof(oslProcessInfo);
+ osl_getProcessInfo(nullptr, osl_Process_IDENTIFIER, &aInfo);
+ OUString aPID = OUString::number(aInfo.Ident);
+#else
+#error "Need an implementation"
+#endif
+ createStr(aPID, pArgs, 4);
+ }
+ {
+ OUString aExeDir = Updater::getExecutableDirURL();
+ OUString aSofficePath = getPathFromURL(aExeDir);
+ Updater::log("soffice Path: " + aSofficePath);
+ createStr(aSofficePath, pArgs, 5);
+ }
+ {
+ // the executable to start after the successful update
+ OUString aExeDir = Updater::getExecutableDirURL();
+ OUString aSofficePathURL = aExeDir + OUString::fromUtf8(pSofficeExeName);
+ OUString aSofficePath = getPathFromURL(aSofficePathURL);
+ createStr(aSofficePath, pArgs, 6);
+ }
+
+ // add the command line arguments from the soffice list
+ for (size_t i = 0; i < nCommandLineArgs; ++i)
+ {
+ OUString aCommandLineArg;
+ rtl_getAppCommandArg(i, &aCommandLineArg.pData);
+ createStr(aCommandLineArg, pArgs, 7 + i);
+ }
+
+ pArgs[nArgs - 1] = nullptr;
+
+ *argc = nArgs - 1;
+ return pArgs;
+}
+
+struct update_file
+{
+ OUString aURL;
+ OUString aHash;
+ size_t nSize;
+};
+
+struct language_file
+{
+ update_file aUpdateFile;
+ OUString aLangCode;
+};
+
+struct update_info
+{
+ OUString aFromBuildID;
+ OUString aSeeAlsoURL;
+ OUString aMessage;
+
+ update_file aUpdateFile;
+ std::vector<language_file> aLanguageFiles;
+};
+
+bool isUserWritable(const OUString& rFileURL)
+{
+ osl::FileStatus aStatus(osl_FileStatus_Mask_Attributes);
+ osl::DirectoryItem aDirectoryItem;
+
+ osl::FileBase::RC eRes = osl::DirectoryItem::get(rFileURL, aDirectoryItem);
+ if (eRes != osl::FileBase::E_None)
+ {
+ Updater::log("Could not get the directory item for: " + rFileURL);
+ return false;
+ }
+
+ osl::FileBase::RC eResult = aDirectoryItem.getFileStatus(aStatus);
+ if (eResult != osl::FileBase::E_None)
+ {
+ Updater::log("Could not get the file status for: " + rFileURL);
+ return false;
+ }
+
+ bool bReadOnly = (aStatus.getAttributes() & static_cast<sal_uInt64>(osl_File_Attribute_ReadOnly)) != 0;
+ if (bReadOnly)
+ {
+ Updater::log("Update location as determined by: " + rFileURL + " is read-only.");
+ return false;
+ }
+
+ return true;
+}
+
+}
+
+bool update()
+{
+ utl::TempFileNamed aTempDir(nullptr, true);
+ OUString aTempDirURL = aTempDir.GetURL();
+ CopyUpdaterToTempDir(Updater::getExecutableDirURL(), aTempDirURL);
+
+ OUString aUpdaterPath = getPathFromURL(aTempDirURL + "/" + OUString::fromUtf8(pUpdaterName));
+
+ Updater::log("Calling the updater with parameters: ");
+ int argc;
+ CharT** pArgs = createCommandLine(aUpdaterPath, &argc);
+
+ bool bSuccess = true;
+ const char* pUpdaterTestReplace = std::getenv("LIBO_UPDATER_TEST_REPLACE");
+ if (!pUpdaterTestReplace)
+ {
+#if UNX
+ OString aPath = OUStringToOString(aUpdaterPath, RTL_TEXTENCODING_UTF8);
+ if (execv(aPath.getStr(), pArgs))
+ {
+ printf("execv failed with error %d %s\n",errno,strerror(errno));
+ bSuccess = false;
+ }
+#elif defined(_WIN32)
+ bSuccess = WinLaunchChild((wchar_t*)aUpdaterPath.getStr(), argc, pArgs);
+#endif
+ }
+ else
+ {
+ SAL_WARN("desktop.updater", "Updater executable path: " << aUpdaterPath);
+ for (size_t i = 0; i < 8 + rtl_getAppCommandArgCount(); ++i)
+ {
+ SAL_WARN("desktop.updater", toStream(pArgs[i]));
+ }
+ bSuccess = false;
+ }
+
+ for (size_t i = 0; i < 8 + rtl_getAppCommandArgCount(); ++i)
+ {
+ delete[] pArgs[i];
+ }
+ delete[] pArgs;
+
+ return bSuccess;
+}
+
+namespace {
+
+// Callback to get the response data from server.
+size_t WriteCallback(void *ptr, size_t size,
+ size_t nmemb, void *userp)
+{
+ if (!userp)
+ return 0;
+
+ std::string* response = static_cast<std::string *>(userp);
+ size_t real_size = size * nmemb;
+ response->append(static_cast<char *>(ptr), real_size);
+ return real_size;
+}
+
+
+
+class invalid_update_info : public std::exception
+{
+};
+
+class invalid_hash : public std::exception
+{
+ OString maMessage;
+public:
+
+ invalid_hash(const OUString& rExpectedHash, const OUString& rReceivedHash)
+ : maMessage(
+ OUStringToOString(
+ OUString("Invalid hash found.\nExpected: " + rExpectedHash + ";\nReceived: " + rReceivedHash),
+ RTL_TEXTENCODING_UTF8)
+ )
+ {
+ }
+
+ const char* what() const noexcept override
+ {
+ return maMessage.getStr();
+ }
+};
+
+class invalid_size : public std::exception
+{
+ OString maMessage;
+public:
+
+ invalid_size(const size_t nExpectedSize, const size_t nReceivedSize)
+ : maMessage(
+ OUStringToOString(
+ OUString("Invalid file size found.\nExpected: " + OUString::number(nExpectedSize) + ";\nReceived: " + OUString::number(nReceivedSize)),
+ RTL_TEXTENCODING_UTF8)
+ )
+ {
+ }
+
+ const char* what() const noexcept override
+ {
+ return maMessage.getStr();
+ }
+};
+
+OUString toOUString(const std::string_view& rStr)
+{
+ return OUString::fromUtf8(rStr);
+}
+
+update_file parse_update_file(orcus::json::node& rNode)
+{
+ if (rNode.type() != orcus::json::node_t::object)
+ {
+ SAL_WARN("desktop.updater", "invalid update or language file entry");
+ throw invalid_update_info();
+ }
+
+ if (rNode.child_count() < 4)
+ {
+ SAL_WARN("desktop.updater", "invalid update or language file entry");
+ throw invalid_update_info();
+ }
+
+ orcus::json::node aURLNode = rNode.child("url");
+ orcus::json::node aHashNode = rNode.child("hash");
+ orcus::json::node aHashTypeNode = rNode.child("hash_function");
+ orcus::json::node aSizeNode = rNode.child("size");
+
+ if (aHashTypeNode.string_value() != "sha512")
+ {
+ SAL_WARN("desktop.updater", "invalid hash type");
+ throw invalid_update_info();
+ }
+
+ update_file aUpdateFile;
+ aUpdateFile.aURL = toOUString(aURLNode.string_value());
+
+ if (aUpdateFile.aURL.isEmpty())
+ throw invalid_update_info();
+
+ aUpdateFile.aHash = toOUString(aHashNode.string_value());
+ aUpdateFile.nSize = static_cast<sal_uInt32>(aSizeNode.numeric_value());
+ return aUpdateFile;
+}
+
+update_info parse_response(const std::string& rResponse)
+{
+ orcus::json::document_tree aJsonDoc;
+ orcus::json_config aConfig;
+ aJsonDoc.load(rResponse, aConfig);
+
+ auto aDocumentRoot = aJsonDoc.get_document_root();
+ if (aDocumentRoot.type() != orcus::json::node_t::object)
+ {
+ SAL_WARN("desktop.updater", "invalid root entries: " << rResponse);
+ throw invalid_update_info();
+ }
+
+ auto aRootKeys = aDocumentRoot.keys();
+ if (std::find(aRootKeys.begin(), aRootKeys.end(), "error") != aRootKeys.end())
+ {
+ throw invalid_update_info();
+ }
+ else if (std::find(aRootKeys.begin(), aRootKeys.end(), "response") != aRootKeys.end())
+ {
+ update_info aUpdateInfo;
+ auto aMsgNode = aDocumentRoot.child("response");
+ aUpdateInfo.aMessage = toOUString(aMsgNode.string_value());
+ return aUpdateInfo;
+ }
+
+ orcus::json::node aFromNode = aDocumentRoot.child("from");
+ if (aFromNode.type() != orcus::json::node_t::string)
+ {
+ throw invalid_update_info();
+ }
+
+ orcus::json::node aSeeAlsoNode = aDocumentRoot.child("see also");
+ if (aSeeAlsoNode.type() != orcus::json::node_t::string)
+ {
+ throw invalid_update_info();
+ }
+
+ orcus::json::node aUpdateNode = aDocumentRoot.child("update");
+ if (aUpdateNode.type() != orcus::json::node_t::object)
+ {
+ throw invalid_update_info();
+ }
+
+ orcus::json::node aLanguageNode = aDocumentRoot.child("languages");
+ if (aLanguageNode.type() != orcus::json::node_t::object)
+ {
+ throw invalid_update_info();
+ }
+
+ update_info aUpdateInfo;
+ aUpdateInfo.aFromBuildID = toOUString(aFromNode.string_value());
+ aUpdateInfo.aSeeAlsoURL = toOUString(aSeeAlsoNode.string_value());
+
+ aUpdateInfo.aUpdateFile = parse_update_file(aUpdateNode);
+
+ std::vector<std::string_view> aLanguages = aLanguageNode.keys();
+ for (auto const& language : aLanguages)
+ {
+ language_file aLanguageFile;
+ auto aLangEntry = aLanguageNode.child(language);
+ aLanguageFile.aLangCode = toOUString(language);
+ aLanguageFile.aUpdateFile = parse_update_file(aLangEntry);
+ aUpdateInfo.aLanguageFiles.push_back(aLanguageFile);
+ }
+
+ return aUpdateInfo;
+}
+
+struct WriteDataFile
+{
+ comphelper::Hash maHash;
+ SvStream* mpStream;
+
+ WriteDataFile(SvStream* pStream):
+ maHash(comphelper::HashType::SHA512),
+ mpStream(pStream)
+ {
+ }
+
+ OUString getHash()
+ {
+ auto final_hash = maHash.finalize();
+ std::stringstream aStrm;
+ for (auto& i: final_hash)
+ {
+ aStrm << std::setw(2) << std::setfill('0') << std::hex << (int)i;
+ }
+
+ return toOUString(aStrm.str());
+ }
+};
+
+// Callback to get the response data from server to a file.
+size_t WriteCallbackFile(void *ptr, size_t size,
+ size_t nmemb, void *userp)
+{
+ if (!userp)
+ return 0;
+
+ WriteDataFile* response = static_cast<WriteDataFile *>(userp);
+ size_t real_size = size * nmemb;
+ response->mpStream->WriteBytes(ptr, real_size);
+ response->maHash.update(static_cast<const unsigned char*>(ptr), real_size);
+ return real_size;
+}
+
+std::string download_content(const OString& rURL, bool bFile, OUString& rHash)
+{
+ Updater::log("Download: " + rURL);
+ std::unique_ptr<CURL, std::function<void(CURL *)>> curl(
+ curl_easy_init(), [](CURL * p) { curl_easy_cleanup(p); });
+
+ if (!curl)
+ return std::string();
+
+ ::InitCurl_easy(curl.get());
+
+ curl_easy_setopt(curl.get(), CURLOPT_URL, rURL.getStr());
+ curl_easy_setopt(curl.get(), CURLOPT_USERAGENT, kUserAgent);
+ bool bUseProxy = false;
+ if (bUseProxy)
+ {
+ /*
+ curl_easy_setopt(curl, CURLOPT_PROXY, proxy.c_str());
+ curl_easy_setopt(curl, CURLOPT_PROXYUSERPWD, proxy_user_pwd.c_str());
+ */
+ }
+
+ char buf[] = "Expect:";
+ curl_slist* headerlist = nullptr;
+ headerlist = curl_slist_append(headerlist, buf);
+ curl_easy_setopt(curl.get(), CURLOPT_HTTPHEADER, headerlist);
+ curl_easy_setopt(curl.get(), CURLOPT_FOLLOWLOCATION, 1); // follow redirects
+ // only allow redirect to https://
+#if (LIBCURL_VERSION_MAJOR > 7) || (LIBCURL_VERSION_MAJOR == 7 && LIBCURL_VERSION_MINOR >= 85)
+ curl_easy_setopt(curl.get(), CURLOPT_REDIR_PROTOCOLS_STR, "https");
+#else
+ curl_easy_setopt(curl.get(), CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTPS);
+#endif
+
+ std::string response_body;
+ utl::TempFileNamed aTempFile;
+ WriteDataFile aFile(aTempFile.GetStream(StreamMode::WRITE));
+ if (!bFile)
+ {
+ curl_easy_setopt(curl.get(), CURLOPT_WRITEFUNCTION, WriteCallback);
+ curl_easy_setopt(curl.get(), CURLOPT_WRITEDATA,
+ static_cast<void *>(&response_body));
+
+ aTempFile.EnableKillingFile(true);
+ }
+ else
+ {
+ OUString aTempFileURL = aTempFile.GetURL();
+ OString aTempFileURLOString = OUStringToOString(aTempFileURL, RTL_TEXTENCODING_UTF8);
+ response_body.append(aTempFileURLOString.getStr(), aTempFileURLOString.getLength());
+
+ aTempFile.EnableKillingFile(false);
+
+ curl_easy_setopt(curl.get(), CURLOPT_WRITEFUNCTION, WriteCallbackFile);
+ curl_easy_setopt(curl.get(), CURLOPT_WRITEDATA,
+ static_cast<void *>(&aFile));
+ }
+
+ // Fail if 400+ is returned from the web server.
+ curl_easy_setopt(curl.get(), CURLOPT_FAILONERROR, 1);
+
+ CURLcode cc = curl_easy_perform(curl.get());
+ long http_code = 0;
+ curl_easy_getinfo(curl.get(), CURLINFO_RESPONSE_CODE, &http_code);
+ if (http_code != 200)
+ {
+ SAL_WARN("desktop.updater", "download did not succeed. Error code: " << http_code);
+ throw error_updater("download did not succeed");
+ }
+
+ if (cc != CURLE_OK)
+ {
+ SAL_WARN("desktop.updater", "curl error: " << cc);
+ throw error_updater("curl error");
+ }
+
+ if (bFile)
+ rHash = aFile.getHash();
+
+ return response_body;
+}
+
+void handle_file_error(osl::FileBase::RC eError, const OUString& rMsg)
+{
+ switch (eError)
+ {
+ case osl::FileBase::E_None:
+ break;
+ default:
+ SAL_WARN("desktop.updater", "file error code: " << eError << ", " << rMsg);
+ throw error_updater(OUStringToOString(rMsg, RTL_TEXTENCODING_UTF8));
+ }
+}
+
+void download_file(const OUString& rURL, size_t nFileSize, const OUString& rHash, const OUString& aFileName)
+{
+ Updater::log("Download File: " + rURL + "; FileName: " + aFileName);
+ OString aURL = OUStringToOString(rURL, RTL_TEXTENCODING_UTF8);
+ OUString aHash;
+ std::string temp_file = download_content(aURL, true, aHash);
+ if (temp_file.empty())
+ throw error_updater("empty temp file string");
+
+ OUString aTempFile = OUString::fromUtf8(temp_file.c_str());
+ Updater::log("TempFile: " + aTempFile);
+ osl::File aDownloadedFile(aTempFile);
+ osl::FileBase::RC eError = aDownloadedFile.open(1);
+ handle_file_error(eError, "Could not open the download file: " + aTempFile);
+
+ sal_uInt64 nSize = 0;
+ eError = aDownloadedFile.getSize(nSize);
+ handle_file_error(eError, "Could not get the file size of the downloaded file: " + aTempFile);
+ if (nSize != nFileSize)
+ {
+ SAL_WARN("desktop.updater", "File sizes don't match. File might be corrupted.");
+ throw invalid_size(nFileSize, nSize);
+ }
+
+ if (aHash != rHash)
+ {
+ SAL_WARN("desktop.updater", "File hash don't match. File might be corrupted.");
+ throw invalid_hash(rHash, aHash);
+ }
+
+ OUString aPatchDirURL("${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE("bootstrap") ":UserInstallation}/updates/");
+ rtl::Bootstrap::expandMacros(aPatchDirURL);
+ osl::Directory::create(aPatchDirURL);
+ aPatchDirURL += "0/";
+ osl::Directory::create(aPatchDirURL);
+
+ OUString aDestFile = aPatchDirURL + aFileName;
+ Updater::log("Destination File: " + aDestFile);
+ aDownloadedFile.close();
+ eError = osl::File::move(aTempFile, aDestFile);
+ handle_file_error(eError, "Could not move the file from the Temp directory to the user config: TempFile: " + aTempFile + "; DestFile: " + aDestFile);
+}
+
+}
+
+void update_checker()
+{
+ OUString aBrandBaseDir("${BRAND_BASE_DIR}");
+ rtl::Bootstrap::expandMacros(aBrandBaseDir);
+ bool bUserWritable = isUserWritable(aBrandBaseDir);
+ if (!bUserWritable)
+ {
+ Updater::log("Can't update as the update location is not user writable");
+ return;
+ }
+
+ OUString aDownloadCheckBaseURL = officecfg::Office::Update::Update::URL::get();
+ static const char* pDownloadCheckBaseURLEnv = std::getenv("LIBO_UPDATER_URL");
+ if (pDownloadCheckBaseURLEnv)
+ {
+ aDownloadCheckBaseURL = OUString::createFromAscii(pDownloadCheckBaseURLEnv);
+ }
+
+ OUString aProductName = utl::ConfigManager::getProductName();
+ OUString aBuildID = Updater::getBuildID();
+
+ static const char* pBuildIdEnv = std::getenv("LIBO_UPDATER_BUILD");
+ if (pBuildIdEnv)
+ {
+ aBuildID = OUString::createFromAscii(pBuildIdEnv);
+ }
+
+ OUString aBuildTarget = "${_OS}_${_ARCH}";
+ rtl::Bootstrap::expandMacros(aBuildTarget);
+ OUString aChannel = Updater::getUpdateChannel();
+ static const char* pUpdateChannelEnv = std::getenv("LIBO_UPDATER_CHANNEL");
+ if (pUpdateChannelEnv)
+ {
+ aChannel = OUString::createFromAscii(pUpdateChannelEnv);
+ }
+
+ OUString aDownloadCheckURL = aDownloadCheckBaseURL + "update/check/1/" + aProductName +
+ "/" + aBuildID + "/" + aBuildTarget + "/" + aChannel;
+ OString aURL = OUStringToOString(aDownloadCheckURL, RTL_TEXTENCODING_UTF8);
+ Updater::log("Update check: " + aURL);
+
+ try
+ {
+ OUString aHash;
+ std::string response_body = download_content(aURL, false, aHash);
+ if (!response_body.empty())
+ {
+
+ update_info aUpdateInfo = parse_response(response_body);
+ if (aUpdateInfo.aUpdateFile.aURL.isEmpty())
+ {
+ // No update currently available
+ // add entry to updating.log with the message
+ SAL_WARN("desktop.updater", "Message received from the updater: " << aUpdateInfo.aMessage);
+ Updater::log("Server response: " + aUpdateInfo.aMessage);
+ }
+ else
+ {
+ css::uno::Sequence<OUString> aInstalledLanguages(officecfg::Setup::Office::InstalledLocales::get()->getElementNames());
+ std::set<OUString> aInstalledLanguageSet(std::begin(aInstalledLanguages), std::end(aInstalledLanguages));
+ download_file(aUpdateInfo.aUpdateFile.aURL, aUpdateInfo.aUpdateFile.nSize, aUpdateInfo.aUpdateFile.aHash, "update.mar");
+ for (auto& lang_update : aUpdateInfo.aLanguageFiles)
+ {
+ // only download the language packs for installed languages
+ if (aInstalledLanguageSet.find(lang_update.aLangCode) != aInstalledLanguageSet.end())
+ {
+ OUString aFileName = "update_" + lang_update.aLangCode + ".mar";
+ download_file(lang_update.aUpdateFile.aURL, lang_update.aUpdateFile.nSize, lang_update.aUpdateFile.aHash, aFileName);
+ }
+ }
+ OUString aSeeAlsoURL = aUpdateInfo.aSeeAlsoURL;
+ std::shared_ptr< comphelper::ConfigurationChanges > batch(
+ comphelper::ConfigurationChanges::create());
+ officecfg::Office::Update::Update::SeeAlso::set(aSeeAlsoURL, batch);
+ batch->commit();
+ OUString const statUrl = Updater::getPatchDirURL() + "update.status";
+ SvFileStream stat(statUrl, StreamMode::WRITE | StreamMode::TRUNC);
+ stat.WriteOString("pending-service");
+ stat.Flush();
+ if (auto const e = stat.GetError()) {
+ Updater::log("Writing <" + statUrl + "> failed with " + e.toString());
+ }
+ stat.Close();
+ }
+ }
+ }
+ catch (const invalid_update_info&)
+ {
+ SAL_WARN("desktop.updater", "invalid update information");
+ Updater::log(OString("warning: invalid update info"));
+ }
+ catch (const error_updater& e)
+ {
+ SAL_WARN("desktop.updater", "error during the update check: " << e.what());
+ Updater::log(OString("warning: error by the updater") + e.what());
+ }
+ catch (const invalid_size& e)
+ {
+ SAL_WARN("desktop.updater", e.what());
+ Updater::log(OString("warning: invalid size"));
+ }
+ catch (const invalid_hash& e)
+ {
+ SAL_WARN("desktop.updater", e.what());
+ Updater::log(OString("warning: invalid hash"));
+ }
+ catch (...)
+ {
+ SAL_WARN("desktop.updater", "unknown error during the update check");
+ Updater::log(OString("warning: unknown exception"));
+ }
+}
+
+OUString Updater::getUpdateInfoLog()
+{
+ OUString aUpdateInfoURL("${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE("bootstrap") ":UserInstallation}/updates/updating.log");
+ rtl::Bootstrap::expandMacros(aUpdateInfoURL);
+
+ return aUpdateInfoURL;
+}
+
+OUString Updater::getPatchDirURL()
+{
+ OUString aPatchDirURL("${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE("bootstrap") ":UserInstallation}/updates/0/");
+ rtl::Bootstrap::expandMacros(aPatchDirURL);
+
+ return aPatchDirURL;
+}
+
+OUString Updater::getUpdateFileURL()
+{
+ return getPatchDirURL() + "update.mar";
+}
+
+OUString Updater::getInstallationPath()
+{
+ OUString aInstallDir( "$BRAND_BASE_DIR/");
+ rtl::Bootstrap::expandMacros(aInstallDir);
+
+ return getPathFromURL(aInstallDir);
+}
+
+OUString Updater::getExecutableDirURL()
+{
+ OUString aExeDir( "$BRAND_BASE_DIR/" LIBO_BIN_FOLDER "/" );
+ rtl::Bootstrap::expandMacros(aExeDir);
+
+ return aExeDir;
+}
+
+void Updater::log(const OUString& rMessage)
+{
+ SAL_INFO("desktop.updater", rMessage);
+ OUString aUpdateLog = getUpdateInfoLog();
+ SvFileStream aLog(aUpdateLog, StreamMode::STD_READWRITE);
+ aLog.Seek(aLog.Tell() + aLog.remainingSize()); // make sure we are at the end
+ aLog.WriteLine(OUStringToOString(rMessage, RTL_TEXTENCODING_UTF8));
+}
+
+void Updater::log(const OString& rMessage)
+{
+ SAL_INFO("desktop.updater", rMessage);
+ OUString aUpdateLog = getUpdateInfoLog();
+ SvFileStream aLog(aUpdateLog, StreamMode::STD_READWRITE);
+ aLog.Seek(aLog.Tell() + aLog.remainingSize()); // make sure we are at the end
+ aLog.WriteLine(rMessage);
+}
+
+void Updater::log(const char* pMessage)
+{
+ SAL_INFO("desktop.updater", pMessage);
+ OUString aUpdateLog = getUpdateInfoLog();
+ SvFileStream aLog(aUpdateLog, StreamMode::STD_READWRITE);
+ aLog.Seek(aLog.Tell() + aLog.remainingSize()); // make sure we are at the end
+ aLog.WriteOString(pMessage);
+}
+
+OUString Updater::getBuildID()
+{
+ OUString aBuildID("${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE("version") ":buildid}");
+ rtl::Bootstrap::expandMacros(aBuildID);
+
+ return aBuildID;
+}
+
+OUString Updater::getUpdateChannel()
+{
+ OUString aUpdateChannel("${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE("version") ":UpdateChannel}");
+ rtl::Bootstrap::expandMacros(aUpdateChannel);
+
+ return aUpdateChannel;
+}
+
+void Updater::removeUpdateFiles()
+{
+ Updater::log("Removing: " + getUpdateFileURL());
+ osl::File::remove(getUpdateFileURL());
+
+ OUString aPatchDirURL = getPatchDirURL();
+ osl::Directory aDir(aPatchDirURL);
+ aDir.open();
+
+ osl::FileBase::RC eRC;
+ do
+ {
+ osl::DirectoryItem aItem;
+ eRC = aDir.getNextItem(aItem);
+ if (eRC == osl::FileBase::E_None)
+ {
+ osl::FileStatus aStatus(osl_FileStatus_Mask_All);
+ if (aItem.getFileStatus(aStatus) != osl::FileBase::E_None)
+ continue;
+
+ if (!aStatus.isRegular())
+ continue;
+
+ OUString aURL = aStatus.getFileURL();
+ if (!aURL.endsWith(".mar"))
+ continue;
+
+ Updater::log("Removing. " + aURL);
+ osl::File::remove(aURL);
+ }
+ }
+ while (eRC == osl::FileBase::E_None);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/app/updater.hxx b/desktop/source/app/updater.hxx
new file mode 100644
index 0000000000..7f1ea920fb
--- /dev/null
+++ b/desktop/source/app/updater.hxx
@@ -0,0 +1,37 @@
+/* -*- 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/.
+ */
+
+#pragma once
+
+#include <rtl/ustring.hxx>
+
+bool update();
+
+void update_checker();
+
+class Updater
+{
+public:
+ static OUString getUpdateInfoLog();
+ static OUString getPatchDirURL();
+ static OUString getUpdateFileURL();
+ static OUString getExecutableDirURL();
+ static OUString getInstallationPath();
+
+ static OUString getBuildID();
+ static OUString getUpdateChannel();
+
+ static void log(const OUString& rMessage);
+ static void log(const OString& rMessage);
+ static void log(const char* pMessage);
+
+ static void removeUpdateFiles();
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/app/userinstall.cxx b/desktop/source/app/userinstall.cxx
new file mode 100644
index 0000000000..7243db43e6
--- /dev/null
+++ b/desktop/source/app/userinstall.cxx
@@ -0,0 +1,176 @@
+/* -*- 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/.
+ *
+ * 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 <cassert>
+
+#include <com/sun/star/uno/Exception.hpp>
+#include <comphelper/configuration.hxx>
+#include <config_folders.h>
+#include <officecfg/Setup.hxx>
+#include <osl/file.h>
+#include <osl/file.hxx>
+#if defined ANDROID || defined IOS || defined EMSCRIPTEN
+#include <rtl/bootstrap.hxx>
+#endif
+#include <rtl/ustring.hxx>
+#include <comphelper/diagnose_ex.hxx>
+#include <unotools/bootstrap.hxx>
+
+#include "userinstall.hxx"
+
+namespace desktop::userinstall {
+
+namespace {
+
+#if !(defined ANDROID || defined IOS || defined EMSCRIPTEN)
+osl::FileBase::RC copyRecursive(
+ OUString const & srcUri, OUString const & dstUri)
+{
+ osl::DirectoryItem item;
+ osl::FileBase::RC e = osl::DirectoryItem::get(srcUri, item);
+ if (e != osl::FileBase::E_None) {
+ return e;
+ }
+ osl::FileStatus stat1(osl_FileStatus_Mask_Type);
+ e = item.getFileStatus(stat1);
+ if (e != osl::FileBase::E_None) {
+ return e;
+ }
+ if (stat1.getFileType() == osl::FileStatus::Directory) {
+ e = osl::Directory::create(dstUri);
+ if (e != osl::FileBase::E_None && e != osl::FileBase::E_EXIST) {
+ return e;
+ }
+ osl::Directory dir(srcUri);
+ e = dir.open();
+ if (e != osl::FileBase::E_None) {
+ return e;
+ }
+ for (;;) {
+ e = dir.getNextItem(item);
+ if (e == osl::FileBase::E_NOENT) {
+ break;
+ }
+ if (e != osl::FileBase::E_None) {
+ return e;
+ }
+ osl::FileStatus stat2(
+ osl_FileStatus_Mask_FileName | osl_FileStatus_Mask_FileURL);
+ e = item.getFileStatus(stat2);
+ if (e != osl::FileBase::E_None) {
+ return e;
+ }
+ assert(!dstUri.endsWith("/"));
+ e = copyRecursive(
+ stat2.getFileURL(), dstUri + "/" + stat2.getFileName());
+ // assumes that all files under presets/ have names that can be
+ // copied unencoded into file URLs
+ if (e != osl::FileBase::E_None) {
+ return e;
+ }
+ }
+ e = dir.close();
+ } else {
+ e = osl::File::copy(srcUri, dstUri);
+ if (e == osl::FileBase::E_EXIST) {
+ // Assume an earlier attempt failed half-way through:
+ e = osl::FileBase::E_None;
+ }
+ }
+ return e;
+}
+#endif
+
+Status create(OUString const & uri) {
+ osl::FileBase::RC e = osl::Directory::createPath(uri);
+ if (e != osl::FileBase::E_None && e != osl::FileBase::E_EXIST) {
+ return ERROR_OTHER;
+ }
+#if !(defined ANDROID || defined IOS || defined EMSCRIPTEN)
+#if defined UNIX
+ // Set safer permissions for the user directory by default:
+ osl::File::setAttributes(
+ uri,
+ (osl_File_Attribute_OwnWrite | osl_File_Attribute_OwnRead
+ | osl_File_Attribute_OwnExe));
+#endif
+ // As of now osl_copyFile does not work on Android => don't do this:
+ OUString baseUri;
+ if (utl::Bootstrap::locateBaseInstallation(baseUri)
+ != utl::Bootstrap::PATH_EXISTS)
+ {
+ return ERROR_OTHER;
+ }
+ switch (copyRecursive(
+ baseUri + "/" LIBO_SHARE_PRESETS_FOLDER, uri + "/user"))
+ {
+ case osl::FileBase::E_None:
+ break;
+ case osl::FileBase::E_ACCES:
+ return ERROR_CANT_WRITE;
+ case osl::FileBase::E_NOSPC:
+ return ERROR_NO_SPACE;
+ default:
+ return ERROR_OTHER;
+ }
+#else
+ // On (Android and) iOS, just create the user directory. Later code fails mysteriously if it
+ // doesn't exist.
+ OUString userDir("${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE("bootstrap") ":UserInstallation}/user");
+ rtl::Bootstrap::expandMacros(userDir);
+ osl::Directory::createPath(userDir);
+#endif
+ std::shared_ptr<comphelper::ConfigurationChanges> batch(
+ comphelper::ConfigurationChanges::create());
+ officecfg::Setup::Office::ooSetupInstCompleted::set(true, batch);
+ batch->commit();
+ return CREATED;
+}
+
+bool isCreated() {
+ try {
+ return officecfg::Setup::Office::ooSetupInstCompleted::get();
+ } catch (const css::uno::Exception &) {
+ TOOLS_WARN_EXCEPTION("desktop.app", "ignoring");
+ return false;
+ }
+}
+
+}
+
+Status finalize() {
+ OUString uri;
+ switch (utl::Bootstrap::locateUserInstallation(uri)) {
+ case utl::Bootstrap::PATH_EXISTS:
+ if (isCreated()) {
+ return EXISTED;
+ }
+ [[fallthrough]];
+ case utl::Bootstrap::PATH_VALID:
+ return create(uri);
+ default:
+ return ERROR_OTHER;
+ }
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/app/userinstall.hxx b/desktop/source/app/userinstall.hxx
new file mode 100644
index 0000000000..527df2dcce
--- /dev/null
+++ b/desktop/source/app/userinstall.hxx
@@ -0,0 +1,38 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <sal/config.h>
+
+namespace desktop::userinstall
+{
+enum Status
+{
+ EXISTED,
+ CREATED,
+ ERROR_NO_SPACE,
+ ERROR_CANT_WRITE,
+ ERROR_OTHER
+};
+
+Status finalize();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */