diff options
Diffstat (limited to 'desktop')
694 files changed, 72283 insertions, 0 deletions
diff --git a/desktop/AllLangMoTarget_dkt.mk b/desktop/AllLangMoTarget_dkt.mk new file mode 100644 index 0000000000..b951648dbd --- /dev/null +++ b/desktop/AllLangMoTarget_dkt.mk @@ -0,0 +1,13 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# 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/. + +$(eval $(call gb_AllLangMoTarget_AllLangMoTarget,dkt)) + +$(eval $(call gb_AllLangMoTarget_set_polocation,dkt,desktop)) + +# vim: set noet sw=4 ts=4: diff --git a/desktop/CppunitTest_desktop_app.mk b/desktop/CppunitTest_desktop_app.mk new file mode 100644 index 0000000000..d6159e2813 --- /dev/null +++ b/desktop/CppunitTest_desktop_app.mk @@ -0,0 +1,76 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# 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/. +# + +$(eval $(call gb_CppunitTest_CppunitTest,desktop_app)) + +$(eval $(call gb_CppunitTest_add_exception_objects,desktop_app, \ + desktop/qa/desktop_app/test_desktop_app \ +)) + +$(eval $(call gb_CppunitTest_use_externals,desktop_app, \ + $(if $(ENABLE_BREAKPAD),breakpad) \ + dbus \ + icu_headers \ + icui18n \ + icuuc \ + $(if $(ENABLE_ONLINE_UPDATE_MAR),\ + curl \ + orcus-parser \ + orcus )\ +)) + +$(eval $(call gb_CppunitTest_use_libraries,desktop_app, \ + comphelper \ + cppu \ + cppuhelper \ + $(if $(ENABLE_BREAKPAD),crashreport) \ + deploymentmisc \ + editeng \ + fwk \ + i18nlangtag \ + $(if $(filter OPENCL,$(BUILD_TYPE)),opencl) \ + sal \ + salhelper \ + sb \ + sfx \ + svl \ + svx \ + svxcore \ + svt \ + tk \ + tl \ + ucbhelper \ + utl \ + vcl \ +)) + +ifeq ($(OS),WNT) +$(eval $(call gb_CppunitTest_use_static_libraries,desktop_app,\ + $(if $(ENABLE_ONLINE_UPDATE_MAR),\ + windows_process )\ +)) +endif + +$(eval $(call gb_CppunitTest_use_library_objects,desktop_app, \ + sofficeapp \ +)) + +ifeq ($(ENABLE_MACOSX_SANDBOX),TRUE) + +$(eval $(call gb_CppunitTest_use_system_darwin_frameworks,desktop_app,\ + Foundation \ +)) + +endif + +$(eval $(call gb_CppunitTest_use_external,desktop_app,boost_headers)) + +$(eval $(call gb_CppunitTest_use_sdk_api,desktop_app)) + +# vim: set noet sw=4 ts=4: diff --git a/desktop/CppunitTest_desktop_dialogs_test.mk b/desktop/CppunitTest_desktop_dialogs_test.mk new file mode 100644 index 0000000000..ce26857ab8 --- /dev/null +++ b/desktop/CppunitTest_desktop_dialogs_test.mk @@ -0,0 +1,69 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +#************************************************************************* +# +# 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/. +# +#************************************************************************* + +$(eval $(call gb_CppunitTest_CppunitScreenShot,desktop_dialogs_test)) + +$(eval $(call gb_CppunitTest_add_exception_objects,desktop_dialogs_test, \ + desktop/qa/unit/desktop-dialogs-test \ +)) + +$(eval $(call gb_CppunitTest_use_sdk_api,desktop_dialogs_test)) + +$(eval $(call gb_CppunitTest_set_include,desktop_dialogs_test,\ + -I$(SRCDIR)/desktop/source/inc \ + -I$(SRCDIR)/desktop/inc \ + $$(INCLUDE) \ +)) + +$(eval $(call gb_CppunitTest_use_libraries,desktop_dialogs_test, \ + basegfx \ + comphelper \ + cppu \ + cppuhelper \ + drawinglayer \ + editeng \ + i18nlangtag \ + i18nutil \ + msfilter \ + oox \ + sal \ + salhelper \ + sax \ + sfx \ + sot \ + svl \ + svt \ + test \ + tl \ + tk \ + ucbhelper \ + unotest \ + utl \ + vcl \ + xo \ +)) + +$(eval $(call gb_CppunitTest_use_external,desktop_dialogs_test,boost_headers)) + +$(eval $(call gb_CppunitTest_use_sdk_api,desktop_dialogs_test)) + +$(eval $(call gb_CppunitTest_use_ure,desktop_dialogs_test)) +$(eval $(call gb_CppunitTest_use_vcl_non_headless_with_windows,desktop_dialogs_test)) + +$(eval $(call gb_CppunitTest_use_rdb,desktop_dialogs_test,services)) + +$(eval $(call gb_CppunitTest_use_configuration,desktop_dialogs_test)) + +$(eval $(call gb_CppunitTest_use_uiconfigs,desktop_dialogs_test,\ + desktop \ +)) + +# vim: set noet sw=4 ts=4: diff --git a/desktop/CppunitTest_desktop_lib.mk b/desktop/CppunitTest_desktop_lib.mk new file mode 100644 index 0000000000..878235d4da --- /dev/null +++ b/desktop/CppunitTest_desktop_lib.mk @@ -0,0 +1,94 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +#************************************************************************* +# +# 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/. +# +#************************************************************************* + +$(eval $(call gb_CppunitTest_CppunitTest,desktop_lib)) + +$(eval $(call gb_CppunitTest_add_exception_objects,desktop_lib, \ + desktop/qa/desktop_lib/test_desktop_lib \ +)) + +$(eval $(call gb_CppunitTest_use_libraries,desktop_lib, \ + comphelper \ + cppu \ + cppuhelper \ + i18nlangtag \ + sal \ + sc \ + scfilt \ + sfx \ + sofficeapp \ + subsequenttest \ + svt \ + sw \ + test \ + unotest \ + utl \ + tl \ + vcl \ +)) + +$(eval $(call gb_CppunitTest_use_externals,desktop_lib, \ + boost_headers \ + cairo \ +)) +ifeq ($(TLS),NSS) +$(eval $(call gb_CppunitTest_use_externals,desktop_lib,\ + plc4 \ + nss3 \ +)) +endif + +$(eval $(call gb_CppunitTest_set_include,desktop_lib,\ + -I$(SRCDIR)/desktop/inc \ + $$(INCLUDE) \ +)) + +$(eval $(call gb_CppunitTest_use_sdk_api,desktop_lib)) + +$(eval $(call gb_CppunitTest_use_ure,desktop_lib)) + +$(eval $(call gb_CppunitTest_use_vcl,desktop_lib)) + +$(eval $(call gb_CppunitTest_use_rdb,desktop_lib,services)) + +$(eval $(call gb_CppunitTest_use_configuration,desktop_lib)) + +$(eval $(call gb_CppunitTest_use_more_fonts,desktop_lib)) + +$(eval $(call gb_CppunitTest_use_packages,desktop_lib, \ + scripting_scriptbindinglib \ + wizards_basicshare \ + wizards_basicsrvaccess2base \ + wizards_basicsrvdepot \ + wizards_basicsrveuro \ + wizards_basicsrvform \ + wizards_basicsrvgimmicks \ + wizards_basicsrvimport \ + wizards_basicsrvscriptforge \ + wizards_basicsrvsfdatabases \ + wizards_basicsrvsfdialogs \ + wizards_basicsrvsfdocuments \ + wizards_basicsrvsfunittests \ + wizards_basicsrvsfwidgets \ + wizards_basicsrvtemplate \ + wizards_basicsrvtools \ +)) + +$(eval $(call gb_CppunitTest_use_uiconfigs,desktop_lib, \ + cui \ + modules/swriter \ +)) + +$(eval $(call gb_CppunitTest_use_packages,desktop_lib, \ + postprocess_images \ +)) + +# vim: set noet sw=4 ts=4: diff --git a/desktop/CppunitTest_desktop_lokinit.mk b/desktop/CppunitTest_desktop_lokinit.mk new file mode 100644 index 0000000000..1f648a1ecc --- /dev/null +++ b/desktop/CppunitTest_desktop_lokinit.mk @@ -0,0 +1,48 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +#************************************************************************* +# +# 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/. +# +#************************************************************************* + +$(eval $(call gb_CppunitTest_CppunitTest,desktop_lok_init)) + +$(eval $(call gb_CppunitTest_add_exception_objects,desktop_lok_init, \ + desktop/qa/unit/desktop-lok-init \ +)) + +$(eval $(call gb_CppunitTest_use_external,desktop_lok_init,boost_headers)) + +$(eval $(call gb_CppunitTest_use_sdk_api,desktop_lok_init)) + +$(eval $(call gb_CppunitTest_use_ure,desktop_lok_init)) + +$(eval $(call gb_CppunitTest_set_include,desktop_lok_init,\ + -I$(SRCDIR)/desktop/source/inc \ + -I$(SRCDIR)/desktop/inc \ + $$(INCLUDE) \ +)) + +$(eval $(call gb_CppunitTest_use_libraries,desktop_lok_init, \ + comphelper \ + cppu \ + sal \ + sofficeapp \ + vcl \ + $(gb_UWINAPI) \ +)) + +ifeq ($(OS),LINUX) +$(eval $(call gb_CppunitTest_add_libs,desktop_lok_init,\ + -lm \ + -ldl \ +)) +endif + +$(eval $(call gb_CppunitTest_use_configuration,desktop_lok_init)) + +# vim: set noet sw=4 ts=4: diff --git a/desktop/CppunitTest_desktop_version.mk b/desktop/CppunitTest_desktop_version.mk new file mode 100644 index 0000000000..c200f39a71 --- /dev/null +++ b/desktop/CppunitTest_desktop_version.mk @@ -0,0 +1,23 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# 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/. +# + +$(eval $(call gb_CppunitTest_CppunitTest,desktop_version)) + +$(eval $(call gb_CppunitTest_add_exception_objects,desktop_version, \ + desktop/qa/deployment_misc/test_dp_version \ +)) + +$(eval $(call gb_CppunitTest_use_libraries,desktop_version, \ + deploymentmisc \ + sal \ +)) + +$(eval $(call gb_CppunitTest_use_udk_api,desktop_version)) + +# vim: set noet sw=4 ts=4: diff --git a/desktop/CustomTarget_desktop_unopackages_install.mk b/desktop/CustomTarget_desktop_unopackages_install.mk new file mode 100644 index 0000000000..f16845fe1d --- /dev/null +++ b/desktop/CustomTarget_desktop_unopackages_install.mk @@ -0,0 +1,19 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# 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/. +# + +$(eval $(call gb_CustomTarget_CustomTarget,desktop/unopackages_install)) + +$(call gb_CustomTarget_get_target,desktop/unopackages_install) : | \ + $(call gb_CustomTarget_get_workdir,desktop/unopackages_install)/uno_packages/cache/uno_packages + +$(call gb_CustomTarget_get_workdir,desktop/unopackages_install)/uno_packages/cache/uno_packages : | \ + $(call gb_CustomTarget_get_workdir,desktop/unopackages_install)/.dir + mkdir -p $@ + +# vim: set noet sw=4 ts=4: diff --git a/desktop/CustomTarget_soffice.mk b/desktop/CustomTarget_soffice.mk new file mode 100644 index 0000000000..4df29fc3ca --- /dev/null +++ b/desktop/CustomTarget_soffice.mk @@ -0,0 +1,47 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# 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/. +# + +$(eval $(call gb_CustomTarget_CustomTarget,desktop/soffice)) + +ifeq ($(OS), MACOSX) + +ifeq (,$(ENABLE_RELEASE_BUILD)) + +# Add entitlements if this is a non-release build. Just to be safe, +# this target will always be run and MACOSX_CODESIGNING_IDENTITY is +# set to empty as we don't want to sign $(INSTROOTBASE). Ignore +# failures as it appears that setting only entitlements can fail +# with certain macOS SDKs. +$(call gb_CustomTarget_get_target,desktop/soffice) : \ + $(INSTROOT)/$(LIBO_BIN_FOLDER)/soffice + -MACOSX_CODESIGNING_IDENTITY= $(SRCDIR)/solenv/bin/macosx-codesign-app-bundle $(INSTROOTBASE) + +endif + +else + +$(call gb_CustomTarget_get_target,desktop/soffice) : \ + $(call gb_CustomTarget_get_workdir,desktop/soffice)/soffice.sh + +$(call gb_CustomTarget_get_workdir,desktop/soffice)/soffice.sh : \ + $(SRCDIR)/desktop/scripts/soffice.sh \ + $(BUILDDIR)/config_host.mk \ + | $(call gb_CustomTarget_get_workdir,desktop/soffice)/.dir + $(call gb_Output_announce,$(subst $(WORKDIR)/,,$@),$(true),SED,1) + $(call gb_Trace_StartRange,$(subst $(WORKDIR)/,,$@),SED) +ifneq ($(JITC_PROCESSOR_TYPE),) + sed -e "s/^#@JITC_PROCESSOR_TYPE_EXPORT@/export JITC_PROCESSOR_TYPE=$(JITC_PROCESSOR_TYPE)/" $< > $@ +else + cp $< $@ +endif + $(call gb_Trace_EndRange,$(subst $(WORKDIR)/,,$@),SED) + +endif + +# vim:set shiftwidth=4 tabstop=4 noexpandtab: diff --git a/desktop/Executable_minidump_upload.mk b/desktop/Executable_minidump_upload.mk new file mode 100644 index 0000000000..600c171e26 --- /dev/null +++ b/desktop/Executable_minidump_upload.mk @@ -0,0 +1,25 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# 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/. +# + +$(eval $(call gb_Executable_Executable,minidump_upload)) + +$(eval $(call gb_Executable_use_libraries,minidump_upload,\ + crashreport \ + sal \ +)) + +$(eval $(call gb_Executable_use_external,minidump_upload,curl)) + +$(eval $(call gb_Executable_add_exception_objects,minidump_upload,\ + desktop/source/minidump/minidump_upload \ +)) + +$(eval $(call gb_Executable_add_default_nativeres,minidump_upload)) + +# vim: set noet sw=4 ts=4: diff --git a/desktop/Executable_oosplash.mk b/desktop/Executable_oosplash.mk new file mode 100644 index 0000000000..2808e01135 --- /dev/null +++ b/desktop/Executable_oosplash.mk @@ -0,0 +1,64 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# 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/. +# + +$(eval $(call gb_Executable_Executable,oosplash)) + +$(eval $(call gb_Executable_set_targettype_gui,oosplash,YES)) + +$(eval $(call gb_Executable_use_libraries,oosplash,\ + sal \ +)) + +$(eval $(call gb_Executable_add_cobjects,oosplash,\ + desktop/unx/source/args \ + desktop/unx/source/file_image_unx \ + $(if $(ENABLE_PAGEIN),desktop/unx/source/pagein) \ + desktop/unx/source/splashx \ + desktop/unx/source/start \ +)) + +ifneq ($(USING_X11),) +$(eval $(call gb_Executable_add_libs,oosplash,\ + -lXinerama \ +)) +endif + +ifneq ($(DISABLE_GUI),TRUE) + +ifneq ($(OS),WNT) +$(eval $(call gb_Executable_add_libs,oosplash,\ + -lX11 \ +)) +endif + +$(eval $(call gb_Executable_add_defs,oosplash,\ + -DENABLE_QUICKSTART_LIBPNG \ +)) + +$(eval $(call gb_Executable_use_externals,oosplash,\ + libpng \ +)) + +endif + +ifeq ($(OS),LINUX) +$(eval $(call gb_Executable_add_libs,oosplash,\ + -lm \ +)) +endif + +ifeq ($(OS),SOLARIS) + +$(eval $(call gb_Executable_add_libs,oosplash,\ + -lsocket \ +)) + +endif + +# vim: set ts=4 sw=4 et: diff --git a/desktop/Executable_quickstart.mk b/desktop/Executable_quickstart.mk new file mode 100644 index 0000000000..f0df0bbe7a --- /dev/null +++ b/desktop/Executable_quickstart.mk @@ -0,0 +1,44 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# 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/. +# + +$(eval $(call gb_Executable_Executable,quickstart)) + +$(eval $(call gb_Executable_set_targettype_gui,quickstart,YES)) + +$(eval $(call gb_Executable_use_system_win32_libs,quickstart,\ + comdlg32 \ + gdi32 \ + ole32 \ + oleaut32 \ + shell32 \ +)) + +ifeq ($(COM),GCC) + +$(eval $(call gb_Executable_use_system_win32_libs,quickstart,\ + uuid \ +)) + +else + +$(eval $(call gb_Executable_use_system_win32_libs,quickstart,\ + comsupp \ +)) + +endif + +$(eval $(call gb_Executable_add_exception_objects,quickstart,\ + desktop/win32/source/QuickStart/QuickStart \ +)) + +$(eval $(call gb_Executable_add_nativeres,quickstart,quickstart/QuickStart)) + +$(eval $(call gb_Executable_add_default_nativeres,quickstart)) + +# vim: set ts=4 sw=4 et: diff --git a/desktop/Executable_sbase.mk b/desktop/Executable_sbase.mk new file mode 100644 index 0000000000..9c36165b8b --- /dev/null +++ b/desktop/Executable_sbase.mk @@ -0,0 +1,30 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# 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/. +# + +$(eval $(call gb_Executable_Executable,sbase)) + +$(eval $(call gb_Executable_set_targettype_gui,sbase,YES)) + +$(eval $(call gb_Executable_add_ldflags,sbase,\ + /ENTRY:wWinMainCRTStartup \ +)) + +$(eval $(call gb_Executable_use_static_libraries,sbase,\ + winlauncher \ +)) + +$(eval $(call gb_Executable_add_exception_objects,sbase,\ + desktop/win32/source/applauncher/sbase \ +)) + +$(eval $(call gb_Executable_add_nativeres,sbase,sbase/launcher)) + +$(eval $(call gb_Executable_add_default_nativeres,sbase,$(PRODUCTNAME) Base)) + +# vim: set ts=4 sw=4 et: diff --git a/desktop/Executable_scalc.mk b/desktop/Executable_scalc.mk new file mode 100644 index 0000000000..b6845690df --- /dev/null +++ b/desktop/Executable_scalc.mk @@ -0,0 +1,30 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# 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/. +# + +$(eval $(call gb_Executable_Executable,scalc)) + +$(eval $(call gb_Executable_set_targettype_gui,scalc,YES)) + +$(eval $(call gb_Executable_add_ldflags,scalc,\ + /ENTRY:wWinMainCRTStartup \ +)) + +$(eval $(call gb_Executable_use_static_libraries,scalc,\ + winlauncher \ +)) + +$(eval $(call gb_Executable_add_exception_objects,scalc,\ + desktop/win32/source/applauncher/scalc \ +)) + +$(eval $(call gb_Executable_add_nativeres,scalc,scalc/launcher)) + +$(eval $(call gb_Executable_add_default_nativeres,scalc,$(PRODUCTNAME) Calc)) + +# vim: set ts=4 sw=4 et: diff --git a/desktop/Executable_sdraw.mk b/desktop/Executable_sdraw.mk new file mode 100644 index 0000000000..164b658765 --- /dev/null +++ b/desktop/Executable_sdraw.mk @@ -0,0 +1,30 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# 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/. +# + +$(eval $(call gb_Executable_Executable,sdraw)) + +$(eval $(call gb_Executable_set_targettype_gui,sdraw,YES)) + +$(eval $(call gb_Executable_add_ldflags,sdraw,\ + /ENTRY:wWinMainCRTStartup \ +)) + +$(eval $(call gb_Executable_use_static_libraries,sdraw,\ + winlauncher \ +)) + +$(eval $(call gb_Executable_add_exception_objects,sdraw,\ + desktop/win32/source/applauncher/sdraw \ +)) + +$(eval $(call gb_Executable_add_nativeres,sdraw,sdraw/launcher)) + +$(eval $(call gb_Executable_add_default_nativeres,sdraw,$(PRODUCTNAME) Draw)) + +# vim: set ts=4 sw=4 et: diff --git a/desktop/Executable_simpress.mk b/desktop/Executable_simpress.mk new file mode 100644 index 0000000000..8d3bdd0543 --- /dev/null +++ b/desktop/Executable_simpress.mk @@ -0,0 +1,30 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# 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/. +# + +$(eval $(call gb_Executable_Executable,simpress)) + +$(eval $(call gb_Executable_set_targettype_gui,simpress,YES)) + +$(eval $(call gb_Executable_add_ldflags,simpress,\ + /ENTRY:wWinMainCRTStartup \ +)) + +$(eval $(call gb_Executable_use_static_libraries,simpress,\ + winlauncher \ +)) + +$(eval $(call gb_Executable_add_exception_objects,simpress,\ + desktop/win32/source/applauncher/simpress \ +)) + +$(eval $(call gb_Executable_add_nativeres,simpress,simpress/launcher)) + +$(eval $(call gb_Executable_add_default_nativeres,simpress,$(PRODUCTNAME) Impress)) + +# vim: set ts=4 sw=4 et: diff --git a/desktop/Executable_smath.mk b/desktop/Executable_smath.mk new file mode 100644 index 0000000000..797354d474 --- /dev/null +++ b/desktop/Executable_smath.mk @@ -0,0 +1,30 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# 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/. +# + +$(eval $(call gb_Executable_Executable,smath)) + +$(eval $(call gb_Executable_set_targettype_gui,smath,YES)) + +$(eval $(call gb_Executable_add_ldflags,smath,\ + /ENTRY:wWinMainCRTStartup \ +)) + +$(eval $(call gb_Executable_use_static_libraries,smath,\ + winlauncher \ +)) + +$(eval $(call gb_Executable_add_exception_objects,smath,\ + desktop/win32/source/applauncher/smath \ +)) + +$(eval $(call gb_Executable_add_nativeres,smath,smath/launcher)) + +$(eval $(call gb_Executable_add_default_nativeres,smath,$(PRODUCTNAME) Math)) + +# vim: set ts=4 sw=4 et: diff --git a/desktop/Executable_soffice_bin.mk b/desktop/Executable_soffice_bin.mk new file mode 100644 index 0000000000..c2d811c2e8 --- /dev/null +++ b/desktop/Executable_soffice_bin.mk @@ -0,0 +1,61 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# 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/. +# + +$(eval $(call gb_Executable_Executable,soffice_bin)) + +$(eval $(call gb_Executable_set_include,soffice_bin,\ + $$(INCLUDE) \ + -I$(SRCDIR)/desktop/source/inc \ +)) + +$(eval $(call gb_Executable_add_defs,soffice_bin,\ + $(if $(DISABLE_DYNLOADING),$(if $(SYSTEM_LIBXML),,-DNOTEST_xmlCleanupParser)) \ +)) + +$(eval $(call gb_Executable_use_libraries,soffice_bin,\ + sal \ + sofficeapp \ +)) + +$(eval $(call gb_Executable_add_cobjects,soffice_bin,\ + desktop/source/app/main \ +)) + +$(eval $(call gb_Executable_add_prejs,soffice_bin,$(SRCDIR)/static/emscripten/soffice_args.js)) + +ifeq ($(OS),WNT) + +$(eval $(call gb_Executable_set_targettype_gui,soffice_bin,NO)) + +$(eval $(call gb_Executable_add_nativeres,soffice_bin,sofficebin/officeloader)) + +$(eval $(call gb_Executable_add_default_nativeres,soffice_bin,$(PRODUCTNAME))) + +ifeq ($(COM),MSC) + +$(eval $(call gb_Executable_add_ldflags,soffice_bin,\ + /STACK:10000000 \ +)) + +endif + +endif + +ifeq ($(OS),EMSCRIPTEN) +$(call gb_LinkTarget_get_target,$(call gb_Executable_get_linktarget,soffice_bin)) : $(call gb_StaticLibrary_get_linktarget_target,unoembind) +$(call gb_LinkTarget_get_headers_target,$(call gb_Executable_get_linktarget,soffice_bin)) : $(call gb_StaticLibrary_get_headers_target,unoembind) +$(call gb_LinkTarget__static_lib_dummy_depend,unoembind) + +$(eval $(call gb_Executable_add_ldflags,soffice_bin,\ + -s EXPORTED_FUNCTIONS=["_main"$(COMMA)"_libreofficekit_hook"$(COMMA)"_libreofficekit_hook_2"$(COMMA)"_lok_preinit"$(COMMA)"_lok_preinit_2"] -Wl$(COMMA)--whole-archive $(call gb_StaticLibrary_get_target,unoembind) -Wl$(COMMA)--no-whole-archive \ +)) + +endif + +# vim: set ts=4 sw=4 et: diff --git a/desktop/Executable_soffice_com.mk b/desktop/Executable_soffice_com.mk new file mode 100644 index 0000000000..bc14606344 --- /dev/null +++ b/desktop/Executable_soffice_com.mk @@ -0,0 +1,31 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# 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/. +# + +$(eval $(call gb_Executable_Executable,soffice_com)) + +$(eval $(call gb_Executable_set_targettype_gui,soffice_com,NO)) + +$(eval $(call gb_Executable_use_system_win32_libs,soffice_com,\ + shell32 \ +)) + +$(eval $(call gb_Executable_use_static_libraries,soffice_com,\ + ooopathutils \ + winloader \ +)) + +$(eval $(call gb_Executable_add_exception_objects,soffice_com,\ + desktop/win32/source/officeloader/soffice_com \ +)) + +$(eval $(call gb_Executable_add_nativeres,soffice_com,soffice/launcher)) + +$(eval $(call gb_Executable_add_default_nativeres,soffice_com,$(PRODUCTNAME))) + +# vim: set ts=4 sw=4 et: diff --git a/desktop/Executable_soffice_exe.mk b/desktop/Executable_soffice_exe.mk new file mode 100644 index 0000000000..2803627bcb --- /dev/null +++ b/desktop/Executable_soffice_exe.mk @@ -0,0 +1,31 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# 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/. +# + +$(eval $(call gb_Executable_Executable,soffice_exe)) + +$(eval $(call gb_Executable_set_targettype_gui,soffice_exe,YES)) + +$(eval $(call gb_Executable_use_system_win32_libs,soffice_exe,\ + shell32 \ +)) + +$(eval $(call gb_Executable_use_static_libraries,soffice_exe,\ + ooopathutils \ + winloader \ +)) + +$(eval $(call gb_Executable_add_exception_objects,soffice_exe,\ + desktop/win32/source/officeloader/soffice_exe \ +)) + +$(eval $(call gb_Executable_add_nativeres,soffice_exe,soffice/launcher)) + +$(eval $(call gb_Executable_add_default_nativeres,soffice_exe,$(PRODUCTNAME))) + +# vim: set ts=4 sw=4 et: diff --git a/desktop/Executable_soffice_safe.mk b/desktop/Executable_soffice_safe.mk new file mode 100644 index 0000000000..aa1f6bc820 --- /dev/null +++ b/desktop/Executable_soffice_safe.mk @@ -0,0 +1,30 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# 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/. +# + +$(eval $(call gb_Executable_Executable,soffice_safe)) + +$(eval $(call gb_Executable_set_targettype_gui,soffice_safe,YES)) + +$(eval $(call gb_Executable_add_ldflags,soffice_safe,\ + /ENTRY:wWinMainCRTStartup \ +)) + +$(eval $(call gb_Executable_use_static_libraries,soffice_safe,\ + winlauncher \ +)) + +$(eval $(call gb_Executable_add_exception_objects,soffice_safe,\ + desktop/win32/source/applauncher/soffice_safe \ +)) + +$(eval $(call gb_Executable_add_nativeres,soffice_safe,soffice/launcher)) + +$(eval $(call gb_Executable_add_default_nativeres,soffice_safe,$(PRODUCTNAME))) + +# vim: set ts=4 sw=4 et: diff --git a/desktop/Executable_sweb.mk b/desktop/Executable_sweb.mk new file mode 100644 index 0000000000..44cf701450 --- /dev/null +++ b/desktop/Executable_sweb.mk @@ -0,0 +1,30 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# 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/. +# + +$(eval $(call gb_Executable_Executable,sweb)) + +$(eval $(call gb_Executable_set_targettype_gui,sweb,YES)) + +$(eval $(call gb_Executable_add_ldflags,sweb,\ + /ENTRY:wWinMainCRTStartup \ +)) + +$(eval $(call gb_Executable_use_static_libraries,sweb,\ + winlauncher \ +)) + +$(eval $(call gb_Executable_add_exception_objects,sweb,\ + desktop/win32/source/applauncher/sweb \ +)) + +$(eval $(call gb_Executable_add_nativeres,sweb,sweb/launcher)) + +$(eval $(call gb_Executable_add_default_nativeres,sweb,$(PRODUCTNAME) Writer (Web))) + +# vim: set ts=4 sw=4 et: diff --git a/desktop/Executable_swriter.mk b/desktop/Executable_swriter.mk new file mode 100644 index 0000000000..5ced1b911e --- /dev/null +++ b/desktop/Executable_swriter.mk @@ -0,0 +1,30 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# 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/. +# + +$(eval $(call gb_Executable_Executable,swriter)) + +$(eval $(call gb_Executable_set_targettype_gui,swriter,YES)) + +$(eval $(call gb_Executable_add_ldflags,swriter,\ + /ENTRY:wWinMainCRTStartup \ +)) + +$(eval $(call gb_Executable_use_static_libraries,swriter,\ + winlauncher \ +)) + +$(eval $(call gb_Executable_add_exception_objects,swriter,\ + desktop/win32/source/applauncher/swriter \ +)) + +$(eval $(call gb_Executable_add_nativeres,swriter,swriter/launcher)) + +$(eval $(call gb_Executable_add_default_nativeres,swriter,$(PRODUCTNAME) Writer)) + +# vim: set ts=4 sw=4 et: diff --git a/desktop/Executable_unoinfo.mk b/desktop/Executable_unoinfo.mk new file mode 100644 index 0000000000..77d1778e02 --- /dev/null +++ b/desktop/Executable_unoinfo.mk @@ -0,0 +1,22 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# 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/. +# + +$(eval $(call gb_Executable_Executable,unoinfo)) + +$(eval $(call gb_Executable_use_static_libraries,unoinfo,\ + ooopathutils \ +)) + +$(eval $(call gb_Executable_add_exception_objects,unoinfo,\ + desktop/win32/source/unoinfo \ +)) + +$(eval $(call gb_Executable_add_default_nativeres,unoinfo)) + +# vim: set ts=4 sw=4 et: diff --git a/desktop/Executable_unopkg.mk b/desktop/Executable_unopkg.mk new file mode 100644 index 0000000000..28a753a570 --- /dev/null +++ b/desktop/Executable_unopkg.mk @@ -0,0 +1,29 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# 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/. +# + +$(eval $(call gb_Executable_Executable,unopkg)) + +$(eval $(call gb_Executable_set_targettype_gui,unopkg,YES)) + +$(eval $(call gb_Executable_use_static_libraries,unopkg,\ + ooopathutils \ + winloader \ +)) + +$(eval $(call gb_Executable_use_system_win32_libs,unopkg,\ + shell32 \ +)) + +$(eval $(call gb_Executable_add_exception_objects,unopkg,\ + desktop/win32/source/officeloader/unopkg_exe \ +)) + +$(eval $(call gb_Executable_add_default_nativeres,unopkg)) + +# vim: set ts=4 sw=4 et: diff --git a/desktop/Executable_unopkg_bin.mk b/desktop/Executable_unopkg_bin.mk new file mode 100644 index 0000000000..f66d5ec25e --- /dev/null +++ b/desktop/Executable_unopkg_bin.mk @@ -0,0 +1,32 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# 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/. +# + +$(eval $(call gb_Executable_Executable,unopkg_bin)) + +$(eval $(call gb_Executable_set_targettype_gui,unopkg_bin,NO)) + +$(eval $(call gb_Executable_set_include,unopkg_bin,\ + $$(INCLUDE) \ + -I$(SRCDIR)/desktop/source/inc \ +)) + +$(eval $(call gb_Executable_use_libraries,unopkg_bin,\ + comphelper \ + sal \ + tl \ + unopkgapp \ +)) + +$(eval $(call gb_Executable_add_cobjects,unopkg_bin,\ + desktop/source/pkgchk/unopkg/unopkg_main \ +)) + +$(eval $(call gb_Executable_add_default_nativeres,unopkg_bin)) + +# vim: set ts=4 sw=4 et: diff --git a/desktop/Executable_unopkg_com.mk b/desktop/Executable_unopkg_com.mk new file mode 100644 index 0000000000..022479f05b --- /dev/null +++ b/desktop/Executable_unopkg_com.mk @@ -0,0 +1,29 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# 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/. +# + +$(eval $(call gb_Executable_Executable,unopkg_com)) + +$(eval $(call gb_Executable_set_targettype_gui,unopkg_com,NO)) + +$(eval $(call gb_Executable_use_static_libraries,unopkg_com,\ + ooopathutils \ + winloader \ +)) + +$(eval $(call gb_Executable_use_system_win32_libs,unopkg_com,\ + shell32 \ +)) + +$(eval $(call gb_Executable_add_exception_objects,unopkg_com,\ + desktop/win32/source/officeloader/unopkg_com \ +)) + +$(eval $(call gb_Executable_add_default_nativeres,unopkg_com)) + +# vim: set ts=4 sw=4 et: diff --git a/desktop/Extension_test-active.mk b/desktop/Extension_test-active.mk new file mode 100644 index 0000000000..fbb329f24c --- /dev/null +++ b/desktop/Extension_test-active.mk @@ -0,0 +1,23 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# 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/. +# + +$(eval $(call gb_Extension_Extension,test-active,desktop/test/deployment/active,nodeliver)) + +$(eval $(call gb_Extension_add_files,test-active,, \ + $(SRCDIR)/desktop/test/deployment/active/Addons.xcu \ + $(SRCDIR)/desktop/test/deployment/active/ProtocolHandler.xcu \ + $(SRCDIR)/desktop/test/deployment/active/active_python.py \ + $(call gb_Jar_get_target,active_java) \ +)) + +$(eval $(call gb_Extension_add_libraries,test-active, \ + active_native \ +)) + +# vim: set noet sw=4 ts=4: diff --git a/desktop/Extension_test-crashextension.mk b/desktop/Extension_test-crashextension.mk new file mode 100644 index 0000000000..aa525f72b8 --- /dev/null +++ b/desktop/Extension_test-crashextension.mk @@ -0,0 +1,24 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t; 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/. +# + +$(eval $(call gb_Extension_Extension,test-crashextension,desktop/test/deployment/crashextension,nodeliver)) + +$(eval $(call gb_Extension_add_file,test-crashextension,platform.components,$(call gb_Rdb_get_target,crashextension))) + +$(eval $(call gb_Extension_add_files,test-crashextension,, \ + $(SRCDIR)/desktop/test/deployment/crashextension/Addons.xcu \ + $(SRCDIR)/desktop/test/deployment/crashextension/ProtocolHandler.xcu \ + $(SRCDIR)/desktop/test/deployment/crashextension/crash.png \ +)) + +$(eval $(call gb_Extension_add_libraries,test-crashextension, \ + crashextension \ +)) + +# vim: set noet sw=4 ts=4: diff --git a/desktop/Extension_test-passive.mk b/desktop/Extension_test-passive.mk new file mode 100644 index 0000000000..ef42f3b1b8 --- /dev/null +++ b/desktop/Extension_test-passive.mk @@ -0,0 +1,35 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# 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/. +# + +$(eval $(call gb_Extension_Extension,test-passive,desktop/test/deployment/passive,nodeliver)) + +$(eval $(call gb_Extension_add_file,test-passive,generic.components,$(call gb_Rdb_get_target,passive_generic))) + +$(eval $(call gb_Extension_add_file,test-passive,platform.components,$(call gb_Rdb_get_target,passive_platform))) + +$(eval $(call gb_Extension_add_files,test-passive,, \ + $(SRCDIR)/desktop/test/deployment/passive/Addons.xcu \ + $(SRCDIR)/desktop/test/deployment/passive/ProtocolHandler.xcu \ + $(SRCDIR)/desktop/test/deployment/passive/passive_python.py \ + $(call gb_Jar_get_target,passive_java) \ +)) + +$(eval $(call gb_Extension_add_files,test-passive,help/en, \ + $(SRCDIR)/desktop/test/deployment/passive/help/en/help.tree \ +)) + +$(eval $(call gb_Extension_add_files,test-passive,help/en/org.openoffice%2Fframework%2Fdesktop%2Ftest%2Fdeployment%2Fpassive, \ + $(SRCDIR)/desktop/test/deployment/passive/help/en/main.xhp \ +)) + +$(eval $(call gb_Extension_add_libraries,test-passive, \ + passive_native \ +)) + +# vim: set noet sw=4 ts=4: diff --git a/desktop/GeneratedPackage_desktop_unopackages_install.mk b/desktop/GeneratedPackage_desktop_unopackages_install.mk new file mode 100644 index 0000000000..b3b3cc6fc1 --- /dev/null +++ b/desktop/GeneratedPackage_desktop_unopackages_install.mk @@ -0,0 +1,16 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# 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/. +# + +$(eval $(call gb_GeneratedPackage_GeneratedPackage,desktop_unopackages_install,$(call gb_CustomTarget_get_workdir,desktop/unopackages_install))) + +$(eval $(call gb_GeneratedPackage_use_customtarget,desktop_unopackages_install,desktop/unopackages_install)) + +$(eval $(call gb_GeneratedPackage_add_dir,desktop_unopackages_install,$(INSTROOT)/$(LIBO_SHARE_FOLDER)/uno_packages,uno_packages)) + +# vim: set noet sw=4 ts=4: diff --git a/desktop/IwyuFilter_desktop.yaml b/desktop/IwyuFilter_desktop.yaml new file mode 100644 index 0000000000..cfe94e22cc --- /dev/null +++ b/desktop/IwyuFilter_desktop.yaml @@ -0,0 +1,96 @@ +--- +assumeFilename: desktop/source/lib/init.cxx +excludelist: + desktop/inc/lib/init.hxx: + # Complete type is needed + - boost/property_tree/ptree.hpp + desktop/source/offacc/acceptor.hxx: + # Don't propose hxx -> h change in URE libs + - osl/thread.hxx + desktop/qa/desktop_lib/test_desktop_lib.cxx: + # Actually used + - comphelper/scopeguard.hxx + - rtl/math.hxx + desktop/source/app/app.cxx: + # Needed on WIN32 + - o3tl/char16_t2wchar_t.hxx + # Needed for HAVE_FEATURE_UPDATE_MAR + - com/sun/star/system/XSystemShellExecute.hpp + - com/sun/star/system/SystemShellExecute.hpp + - officecfg/Office/Update.hxx + desktop/source/app/appinit.cxx: + # Needed on IOS + - rtl/bootstrap.hxx + desktop/source/app/cmdlineargs.cxx: + # Don't propose hxx -> h change in URE libs + - osl/thread.hxx + desktop/source/app/cmdlinehelp.cxx: + # Needed on WIN32 + - comphelper/string.hxx + desktop/source/app/crashreport.cxx: + # Needed on WIN32 + - o3tl/char16_t2wchar_t.hxx + desktop/source/app/dispatchwatcher.cxx: + # Don't propose hxx -> h change in URE libs + - osl/thread.hxx + desktop/source/app/sofficemain.cxx: + # Might be needed on WIN32 + - prewin.h + - postwin.h + # Needed on ANDROID + - rtl/bootstrap.hxx + desktop/source/lib/init.cxx: + # Needed for boost::trim_copy + - boost/algorithm/string.hpp + # Actually used + - comphelper/scopeguard.hxx + # Needed on IOS & ANDROID + - officecfg/Office/Impress.hxx + desktop/source/minidump/minidump.cxx: + # Needed for ostringstream + - sstream + desktop/source/deployment/gui/dp_gui_updatedialog.cxx: + # Actually used + - vector + desktop/source/deployment/gui/dp_gui_updateinstalldialog.cxx: + # Actually used + - vector + desktop/source/deployment/manager/dp_properties.cxx: + # Actually used + - com/sun/star/ucb/XCommandEnvironment.hpp + desktop/source/deployment/manager/dp_extensionmanager.cxx: + # Actually used + - com/sun/star/deployment/XPackage.hpp + - com/sun/star/deployment/XPackageManager.hpp + - com/sun/star/uno/XComponentContext.hpp + desktop/source/deployment/misc/lockfile.cxx: + # Needed on WIN32 + - memory + desktop/source/deployment/registry/executable/dp_executablebackenddb.cxx: + # Actually used + - com/sun/star/uno/XComponentContext.hpp + desktop/source/deployment/registry/component/dp_compbackenddb.cxx: + # Actually used + - com/sun/star/uno/XComponentContext.hpp + desktop/source/deployment/registry/script/dp_scriptbackenddb.cxx: + # Actually used + - com/sun/star/uno/XComponentContext.hpp + desktop/source/deployment/registry/package/dp_extbackenddb.cxx: + # Actually used + - com/sun/star/uno/XComponentContext.hpp + desktop/source/deployment/registry/configuration/dp_configurationbackenddb.cxx: + # Actually used + - com/sun/star/uno/XComponentContext.hpp + desktop/source/deployment/registry/help/dp_helpbackenddb.cxx: + # Actually used + - com/sun/star/uno/XComponentContext.hpp + desktop/source/deployment/registry/script/dp_lib_container.cxx: + # Actually used + - com/sun/star/uno/XComponentContext.hpp + - com/sun/star/ucb/XCommandEnvironment.hpp + desktop/source/deployment/registry/help/dp_help.cxx: + # Actually used + - com/sun/star/util/XMacroExpander.hpp + desktop/source/splash/splash.cxx: + # Needed for rtl::math::round + - rtl/math.hxx diff --git a/desktop/Jar_active_java.mk b/desktop/Jar_active_java.mk new file mode 100644 index 0000000000..48fb7425ef --- /dev/null +++ b/desktop/Jar_active_java.mk @@ -0,0 +1,26 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# 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/. +# + +$(eval $(call gb_Jar_Jar,active_java)) + +$(eval $(call gb_Jar_add_sourcefiles,active_java, \ + desktop/test/deployment/active/com/sun/star/comp/test/deployment/Dispatch \ + desktop/test/deployment/active/com/sun/star/comp/test/deployment/Provider \ + desktop/test/deployment/active/com/sun/star/comp/test/deployment/Services \ +)) + +$(eval $(call gb_Jar_set_manifest,active_java,$(SRCDIR)/desktop/test/deployment/active/MANIFEST.MF)) + +$(eval $(call gb_Jar_set_packageroot,active_java,com)) + +$(eval $(call gb_Jar_use_jars,active_java, \ + libreoffice \ +)) + +# vim: set noet sw=4 ts=4: diff --git a/desktop/Jar_passive_java.mk b/desktop/Jar_passive_java.mk new file mode 100644 index 0000000000..0c8c4af5bc --- /dev/null +++ b/desktop/Jar_passive_java.mk @@ -0,0 +1,28 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# 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/. +# + +$(eval $(call gb_Jar_Jar,passive_java)) + +$(eval $(call gb_Jar_add_sourcefiles,passive_java, \ + desktop/test/deployment/passive/com/sun/star/comp/test/deployment/Dispatch \ + desktop/test/deployment/passive/com/sun/star/comp/test/deployment/Provider \ + desktop/test/deployment/passive/com/sun/star/comp/test/deployment/Services \ +)) + +$(eval $(call gb_Jar_set_componentfile,passive_java,desktop/test/deployment/passive/passive_java,OXT,passive_generic)) + +$(eval $(call gb_Jar_set_manifest,passive_java,$(SRCDIR)/desktop/test/deployment/passive/MANIFEST.MF)) + +$(eval $(call gb_Jar_set_packageroot,passive_java,com)) + +$(eval $(call gb_Jar_use_jars,passive_java, \ + libreoffice \ +)) + +# vim: set noet sw=4 ts=4: diff --git a/desktop/Library_active_native.mk b/desktop/Library_active_native.mk new file mode 100644 index 0000000000..ef4815f1e9 --- /dev/null +++ b/desktop/Library_active_native.mk @@ -0,0 +1,30 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# 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/. +# + +$(eval $(call gb_Library_Library,active_native)) + +$(eval $(call gb_Library_add_exception_objects,active_native, \ + desktop/test/deployment/active/active_native \ +)) + +$(eval $(call gb_Library_set_external_code,active_native)) + +$(eval $(call gb_Library_use_externals,active_native, \ + boost_headers \ +)) + +$(eval $(call gb_Library_use_libraries,active_native, \ + cppu \ + cppuhelper \ + sal \ +)) + +$(eval $(call gb_Library_use_sdk_api,active_native)) + +# vim: set noet sw=4 ts=4: diff --git a/desktop/Library_crashextension.mk b/desktop/Library_crashextension.mk new file mode 100644 index 0000000000..ae18412688 --- /dev/null +++ b/desktop/Library_crashextension.mk @@ -0,0 +1,28 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t; 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/. +# + +$(eval $(call gb_Library_Library,crashextension)) + +$(eval $(call gb_Library_add_exception_objects,crashextension, \ + desktop/test/deployment/crashextension/crashextension \ +)) + +$(eval $(call gb_Library_set_componentfile,crashextension,desktop/test/deployment/crashextension/crashextension,crashextension)) + +$(eval $(call gb_Library_set_external_code,crashextension)) + +$(eval $(call gb_Library_use_libraries,crashextension, \ + cppu \ + cppuhelper \ + sal \ +)) + +$(eval $(call gb_Library_use_sdk_api,crashextension)) + +# vim: set noet sw=4 ts=4: diff --git a/desktop/Library_crashreport.mk b/desktop/Library_crashreport.mk new file mode 100644 index 0000000000..c00814c82c --- /dev/null +++ b/desktop/Library_crashreport.mk @@ -0,0 +1,54 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# 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/. +# + +$(eval $(call gb_Library_Library,crashreport)) + +$(eval $(call gb_Library_set_include,crashreport,\ + $$(INCLUDE) \ + -I$(SRCDIR)/desktop/inc \ +)) + +$(eval $(call gb_Library_use_externals,crashreport,\ + breakpad \ + curl \ +)) + +$(eval $(call gb_Library_add_defs,crashreport,\ + -DCRASHREPORT_DLLIMPLEMENTATION \ +)) + +$(eval $(call gb_Library_add_libs,crashreport,\ + $(if $(filter LINUX %BSD SOLARIS, $(OS)), \ + $(UNIX_DLAPI_LIBS) \ + ) \ +)) + +$(eval $(call gb_Library_use_sdk_api,crashreport)) + +$(eval $(call gb_Library_use_custom_headers,crashreport,\ + officecfg/registry \ +)) + +$(eval $(call gb_Library_use_libraries,crashreport,\ + comphelper \ + cppu \ + cppuhelper \ + sal \ + salhelper \ + ucbhelper \ + utl \ +)) + +$(eval $(call gb_Library_add_exception_objects,crashreport,\ + desktop/source/app/crashreport \ + desktop/source/minidump/minidump \ +)) + + +# vim: set ts=4 sw=4 et: diff --git a/desktop/Library_deployment.mk b/desktop/Library_deployment.mk new file mode 100644 index 0000000000..faab6887a2 --- /dev/null +++ b/desktop/Library_deployment.mk @@ -0,0 +1,81 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# 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/. +# + +$(eval $(call gb_Library_Library,deployment)) + +$(eval $(call gb_Library_set_include,deployment,\ + $$(INCLUDE) \ + -I$(SRCDIR)/desktop/inc \ + -I$(SRCDIR)/desktop/source/deployment/inc \ + -I$(SRCDIR)/desktop/source/deployment/registry/inc \ +)) + +$(eval $(call gb_Library_use_external,deployment,boost_headers)) + +$(eval $(call gb_Library_set_precompiled_header,deployment,desktop/inc/pch/precompiled_deployment)) + +$(eval $(call gb_Library_use_sdk_api,deployment)) + +$(eval $(call gb_Library_use_libraries,deployment,\ + comphelper \ + cppu \ + cppuhelper \ + deploymentmisc \ + sal \ + svl \ + tl \ + ucbhelper \ + utl \ + vcl \ + xmlscript \ + i18nlangtag \ +)) + +$(eval $(call gb_Library_set_componentfile,deployment,desktop/source/deployment/deployment,services)) + +$(eval $(call gb_Library_add_exception_objects,deployment,\ + desktop/source/deployment/dp_log \ + desktop/source/deployment/dp_persmap \ + desktop/source/deployment/dp_xml \ + desktop/source/deployment/manager/dp_activepackages \ + desktop/source/deployment/manager/dp_commandenvironments \ + desktop/source/deployment/manager/dp_extensionmanager \ + desktop/source/deployment/manager/dp_informationprovider \ + desktop/source/deployment/manager/dp_manager \ + desktop/source/deployment/manager/dp_managerfac \ + desktop/source/deployment/manager/dp_properties \ + desktop/source/deployment/registry/component/dp_compbackenddb \ + desktop/source/deployment/registry/component/dp_component \ + desktop/source/deployment/registry/configuration/dp_configuration \ + desktop/source/deployment/registry/configuration/dp_configurationbackenddb \ + desktop/source/deployment/registry/dp_backend \ + desktop/source/deployment/registry/dp_backenddb \ + desktop/source/deployment/registry/dp_registry \ + desktop/source/deployment/registry/executable/dp_executable \ + desktop/source/deployment/registry/executable/dp_executablebackenddb \ + desktop/source/deployment/registry/help/dp_help \ + desktop/source/deployment/registry/help/dp_helpbackenddb \ + desktop/source/deployment/registry/package/dp_extbackenddb \ + desktop/source/deployment/registry/package/dp_package \ + desktop/source/deployment/registry/script/dp_lib_container \ + desktop/source/deployment/registry/script/dp_script \ + desktop/source/deployment/registry/script/dp_scriptbackenddb \ + desktop/source/deployment/registry/sfwk/dp_parceldesc \ + desktop/source/deployment/registry/sfwk/dp_sfwk \ +)) + +ifneq (,$(filter XMLHELP,$(BUILD_TYPE))) + +$(eval $(call gb_Library_use_libraries,deployment,\ + helplinker \ +)) + +endif + +# vim: set ts=4 sw=4 et: diff --git a/desktop/Library_deploymentgui.mk b/desktop/Library_deploymentgui.mk new file mode 100644 index 0000000000..325a29b559 --- /dev/null +++ b/desktop/Library_deploymentgui.mk @@ -0,0 +1,71 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# 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/. +# + +$(eval $(call gb_Library_Library,deploymentgui)) + +$(eval $(call gb_Library_set_include,deploymentgui,\ + $$(INCLUDE) \ + -I$(SRCDIR)/desktop/inc \ + -I$(SRCDIR)/desktop/source/deployment/inc \ + -I$(SRCDIR)/desktop/source/inc \ +)) + +$(eval $(call gb_Library_use_external,deploymentgui,boost_headers)) + +$(eval $(call gb_Library_set_precompiled_header,deploymentgui,desktop/inc/pch/precompiled_deploymentgui)) + +$(eval $(call gb_Library_use_custom_headers,deploymentgui,\ + officecfg/registry \ +)) + +$(eval $(call gb_Library_use_sdk_api,deploymentgui)) + +$(eval $(call gb_Library_use_libraries,deploymentgui,\ + comphelper \ + cppu \ + cppuhelper \ + deploymentmisc \ + i18nlangtag \ + sal \ + salhelper \ + sfx \ + svl \ + svt \ + svxcore \ + tk \ + tl \ + ucbhelper \ + utl \ + vcl \ +)) + +ifeq ($(OS),WNT) + +$(eval $(call gb_Library_use_system_win32_libs,deploymentgui,\ + ole32 \ +)) + +endif + + +$(eval $(call gb_Library_set_componentfile,deploymentgui,desktop/source/deployment/gui/deploymentgui,services)) + +$(eval $(call gb_Library_add_exception_objects,deploymentgui,\ + desktop/source/deployment/gui/dp_gui_dependencydialog \ + desktop/source/deployment/gui/dp_gui_dialog2 \ + desktop/source/deployment/gui/dp_gui_extensioncmdqueue \ + desktop/source/deployment/gui/dp_gui_extlistbox \ + desktop/source/deployment/gui/dp_gui_service \ + desktop/source/deployment/gui/dp_gui_theextmgr \ + desktop/source/deployment/gui/dp_gui_updatedialog \ + desktop/source/deployment/gui/dp_gui_updateinstalldialog \ + desktop/source/deployment/gui/license_dialog \ +)) + +# vim: set ts=4 sw=4 et: diff --git a/desktop/Library_deploymentmisc.mk b/desktop/Library_deploymentmisc.mk new file mode 100644 index 0000000000..b7f4c09e1b --- /dev/null +++ b/desktop/Library_deploymentmisc.mk @@ -0,0 +1,55 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# 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/. +# + +$(eval $(call gb_Library_Library,deploymentmisc)) + +$(eval $(call gb_Library_set_include,deploymentmisc,\ + $$(INCLUDE) \ + -I$(SRCDIR)/desktop/inc \ + -I$(SRCDIR)/desktop/source/deployment/inc \ +)) + +$(eval $(call gb_Library_use_external,deploymentmisc,boost_headers)) + +$(eval $(call gb_Library_use_sdk_api,deploymentmisc)) + +$(eval $(call gb_Library_add_defs,deploymentmisc,\ + -DDESKTOP_DEPLOYMENTMISC_DLLIMPLEMENTATION \ +)) + +$(eval $(call gb_Library_set_precompiled_header,deploymentmisc,desktop/inc/pch/precompiled_deploymentmisc)) + +$(eval $(call gb_Library_use_libraries,deploymentmisc,\ + comphelper \ + cppu \ + cppuhelper \ + sal \ + tl \ + ucbhelper \ + utl \ + vcl \ + xmlscript \ + i18nlangtag \ +)) + +$(eval $(call gb_Library_add_exception_objects,deploymentmisc,\ + desktop/source/deployment/misc/dp_dependencies \ + desktop/source/deployment/misc/dp_descriptioninfoset \ + desktop/source/deployment/misc/dp_identifier \ + desktop/source/deployment/misc/dp_interact \ + desktop/source/deployment/misc/dp_misc \ + desktop/source/deployment/misc/dp_platform \ + desktop/source/deployment/misc/dp_resource \ + desktop/source/deployment/misc/dp_ucb \ + desktop/source/deployment/misc/dp_update \ + desktop/source/deployment/misc/dp_version \ + desktop/source/deployment/misc/lockfile \ +)) + +# vim: set ts=4 sw=4 et: diff --git a/desktop/Library_migrationoo2.mk b/desktop/Library_migrationoo2.mk new file mode 100644 index 0000000000..2314d56976 --- /dev/null +++ b/desktop/Library_migrationoo2.mk @@ -0,0 +1,36 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# 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/. +# + +$(eval $(call gb_Library_Library,migrationoo2)) + +$(eval $(call gb_Library_use_external,migrationoo2,boost_headers)) + +$(eval $(call gb_Library_use_sdk_api,migrationoo2)) + +$(eval $(call gb_Library_use_libraries,migrationoo2,\ + cppu \ + cppuhelper \ + i18nlangtag \ + $(if $(ENABLE_JAVA), \ + jvmfwk) \ + sal \ + tl \ + utl \ +)) + +$(eval $(call gb_Library_set_componentfile,migrationoo2,desktop/source/migration/services/migrationoo2,services)) + +$(eval $(call gb_Library_add_exception_objects,migrationoo2,\ + desktop/source/migration/services/basicmigration \ + $(if $(ENABLE_JAVA), \ + desktop/source/migration/services/jvmfwk) \ + desktop/source/migration/services/wordbookmigration \ +)) + +# vim: set ts=4 sw=4 et: diff --git a/desktop/Library_migrationoo3.mk b/desktop/Library_migrationoo3.mk new file mode 100644 index 0000000000..b18f77ebf6 --- /dev/null +++ b/desktop/Library_migrationoo3.mk @@ -0,0 +1,28 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# 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/. +# + +$(eval $(call gb_Library_Library,migrationoo3)) + +$(eval $(call gb_Library_use_sdk_api,migrationoo3)) + +$(eval $(call gb_Library_use_libraries,migrationoo3,\ + comphelper \ + cppu \ + cppuhelper \ + sal \ + utl \ +)) + +$(eval $(call gb_Library_set_componentfile,migrationoo3,desktop/source/migration/services/migrationoo3,services)) + +$(eval $(call gb_Library_add_exception_objects,migrationoo3,\ + desktop/source/migration/services/oo3extensionmigration \ +)) + +# vim: set ts=4 sw=4 et: diff --git a/desktop/Library_offacc.mk b/desktop/Library_offacc.mk new file mode 100644 index 0000000000..fb1a162b2b --- /dev/null +++ b/desktop/Library_offacc.mk @@ -0,0 +1,32 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# 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/. +# + +$(eval $(call gb_Library_Library,offacc)) + +$(eval $(call gb_Library_use_sdk_api,offacc)) + +$(eval $(call gb_Library_use_custom_headers,offacc,\ + officecfg/registry \ +)) + +$(eval $(call gb_Library_use_libraries,offacc,\ + comphelper \ + cppu \ + cppuhelper \ + sal \ + tl \ +)) + +$(eval $(call gb_Library_set_componentfile,offacc,desktop/source/offacc/offacc,services)) + +$(eval $(call gb_Library_add_exception_objects,offacc,\ + desktop/source/offacc/acceptor \ +)) + +# vim: set ts=4 sw=4 et: diff --git a/desktop/Library_passive_native.mk b/desktop/Library_passive_native.mk new file mode 100644 index 0000000000..c09743edc9 --- /dev/null +++ b/desktop/Library_passive_native.mk @@ -0,0 +1,32 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# 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/. +# + +$(eval $(call gb_Library_Library,passive_native)) + +$(eval $(call gb_Library_add_exception_objects,passive_native, \ + desktop/test/deployment/passive/passive_native \ +)) + +$(eval $(call gb_Library_set_componentfile,passive_native,desktop/test/deployment/passive/passive_native,passive_platform)) + +$(eval $(call gb_Library_set_external_code,passive_native)) + +$(eval $(call gb_Library_use_externals,passive_native, \ + boost_headers \ +)) + +$(eval $(call gb_Library_use_libraries,passive_native, \ + cppu \ + cppuhelper \ + sal \ +)) + +$(eval $(call gb_Library_use_sdk_api,passive_native)) + +# vim: set noet sw=4 ts=4: diff --git a/desktop/Library_sofficeapp.mk b/desktop/Library_sofficeapp.mk new file mode 100644 index 0000000000..641efda7ed --- /dev/null +++ b/desktop/Library_sofficeapp.mk @@ -0,0 +1,153 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# 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/. +# + +$(eval $(call gb_Library_Library,sofficeapp)) + +$(eval $(call gb_Library_set_include,sofficeapp,\ + $$(INCLUDE) \ + -I$(SRCDIR)/desktop/inc \ + -I$(SRCDIR)/desktop/source/inc \ + -I$(SRCDIR)/desktop/source/deployment/inc \ +)) + +$(eval $(call gb_Library_use_externals,sofficeapp, \ + $(if $(ENABLE_BREAKPAD),breakpad) \ + $(if $(filter OPENCL,$(BUILD_TYPE)),clew) \ + boost_headers \ + dbus \ + icu_headers \ + icui18n \ + icuuc \ + $(if $(ENABLE_CURL), \ + $(if $(filter-out EMSCRIPTEN iOS,$(OS)), \ + curl \ + ))\ + $(if $(ENABLE_ONLINE_UPDATE_MAR),\ + orcus-parser \ + orcus )\ +)) + +$(eval $(call gb_Library_use_custom_headers,sofficeapp,\ + officecfg/registry \ +)) + +$(eval $(call gb_Library_use_api,sofficeapp,\ + udkapi \ + offapi \ +)) + +$(eval $(call gb_Library_add_defs,sofficeapp,\ + -DDESKTOP_DLLIMPLEMENTATION \ + $(if $(filter WNT,$(OS)),-DENABLE_QUICKSTART_APPLET) \ + $(if $(filter MACOSX,$(OS)),-DENABLE_QUICKSTART_APPLET) \ +)) + +$(eval $(call gb_Library_set_precompiled_header,sofficeapp,desktop/inc/pch/precompiled_sofficeapp)) + +$(eval $(call gb_Library_use_libraries,sofficeapp,\ + comphelper \ + cppu \ + cppuhelper \ + $(if $(ENABLE_BREAKPAD), \ + crashreport \ + ) \ + deploymentmisc \ + editeng \ + fwk \ + i18nlangtag \ + $(if $(filter OPENCL,$(BUILD_TYPE)),opencl) \ + sal \ + salhelper \ + sb \ + sfx \ + svl \ + svx \ + svxcore \ + svt \ + tk \ + tl \ + ucbhelper \ + utl \ + vcl \ +)) + +ifeq ($(OS),WNT) +$(eval $(call gb_Library_use_static_libraries,sofficeapp,\ + $(if $(ENABLE_ONLINE_UPDATE_MAR),\ + windows_process )\ +)) +endif + +ifeq ($(OS),MACOSX) + +$(eval $(call gb_Library_add_cxxflags,sofficeapp,\ + $(gb_OBJCXXFLAGS) \ +)) + +$(eval $(call gb_Library_use_system_darwin_frameworks,sofficeapp,\ + Foundation \ +)) + +endif + +ifeq ($(OS),iOS) + +$(eval $(call gb_Library_add_cflags,sofficeapp,\ + $(gb_OBJCFLAGS) \ +)) + +$(eval $(call gb_Library_add_cxxflags,sofficeapp,\ + $(gb_OBJCXXFLAGS) \ +)) + +endif + +$(eval $(call gb_Library_add_exception_objects,sofficeapp,\ + desktop/source/app/app \ + desktop/source/app/appinit \ + desktop/source/app/check_ext_deps \ + desktop/source/app/cmdlineargs \ + desktop/source/app/cmdlinehelp \ + desktop/source/app/desktopcontext \ + desktop/source/app/dispatchwatcher \ + desktop/source/app/langselect \ + desktop/source/app/lockfile2 \ + desktop/source/app/officeipcthread \ + desktop/source/app/opencl \ + desktop/source/app/sofficemain \ + $(if $(ENABLE_ONLINE_UPDATE_MAR),\ + desktop/source/app/updater )\ + desktop/source/app/userinstall \ + desktop/source/migration/migration \ +)) + +# LibreOfficeKit bits +ifneq ($(filter $(OS),ANDROID iOS MACOSX WNT),) +$(eval $(call gb_Library_add_exception_objects,sofficeapp,\ + desktop/source/lib/init \ + desktop/source/lib/lokinteractionhandler \ + $(if $(filter-out $(OS),iOS), \ + desktop/source/lib/lokclipboard) \ + $(if $(filter $(OS),ANDROID), \ + desktop/source/lib/lokandroid) \ +)) +$(if $(filter-out $(OS),IOS), \ + $(eval $(call gb_Library_set_componentfile,sofficeapp,desktop/lokclipboard,services))) +else +ifneq ($(filter TRUE,$(USING_X11) $(DISABLE_GUI))($filter EMSCRIPTEN,$(OS)),) +$(eval $(call gb_Library_add_exception_objects,sofficeapp,\ + desktop/source/lib/init \ + desktop/source/lib/lokinteractionhandler \ + desktop/source/lib/lokclipboard \ +)) +$(eval $(call gb_Library_set_componentfile,sofficeapp,desktop/lokclipboard,services)) +endif +endif + +# vim: set ts=4 sw=4 et: diff --git a/desktop/Library_spl.mk b/desktop/Library_spl.mk new file mode 100644 index 0000000000..ec43e6326c --- /dev/null +++ b/desktop/Library_spl.mk @@ -0,0 +1,39 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# 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/. +# + +$(eval $(call gb_Library_Library,spl)) + +$(eval $(call gb_Library_set_include,spl,\ + $$(INCLUDE) \ + -I$(SRCDIR)/desktop/inc \ +)) + +$(eval $(call gb_Library_use_external,spl,boost_headers)) + +$(eval $(call gb_Library_use_sdk_api,spl)) + +$(eval $(call gb_Library_use_libraries,spl,\ + comphelper \ + cppu \ + cppuhelper \ + sal \ + tl \ + ucbhelper \ + utl \ + vcl \ +)) + +$(eval $(call gb_Library_set_componentfile,spl,desktop/source/splash/spl,services)) + +$(eval $(call gb_Library_add_exception_objects,spl,\ + desktop/source/splash/splash \ + desktop/source/splash/unxsplash \ +)) + +# vim: set ts=4 sw=4 et: diff --git a/desktop/Library_unopkgapp.mk b/desktop/Library_unopkgapp.mk new file mode 100644 index 0000000000..b8978625c3 --- /dev/null +++ b/desktop/Library_unopkgapp.mk @@ -0,0 +1,46 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# 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/. +# + +$(eval $(call gb_Library_Library,unopkgapp)) + +$(eval $(call gb_Library_set_include,unopkgapp,\ + $$(INCLUDE) \ + -I$(SRCDIR)/desktop/inc \ + -I$(SRCDIR)/desktop/source/deployment/inc \ + -I$(SRCDIR)/desktop/source/inc \ +)) + +$(eval $(call gb_Library_use_external,unopkgapp,boost_headers)) + +$(eval $(call gb_Library_use_sdk_api,unopkgapp)) + +$(eval $(call gb_Library_add_defs,unopkgapp,\ + -DDESKTOP_DLLIMPLEMENTATION \ +)) + +$(eval $(call gb_Library_use_libraries,unopkgapp,\ + comphelper \ + cppu \ + cppuhelper \ + deploymentmisc \ + sal \ + tl \ + ucbhelper \ + utl \ + vcl \ + i18nlangtag \ +)) + +$(eval $(call gb_Library_add_exception_objects,unopkgapp,\ + desktop/source/pkgchk/unopkg/unopkg_app \ + desktop/source/pkgchk/unopkg/unopkg_cmdenv \ + desktop/source/pkgchk/unopkg/unopkg_misc \ +)) + +# vim: set ts=4 sw=4 et: diff --git a/desktop/Makefile b/desktop/Makefile new file mode 100644 index 0000000000..0997e62848 --- /dev/null +++ b/desktop/Makefile @@ -0,0 +1,14 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# 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/. +# + +module_directory:=$(dir $(realpath $(firstword $(MAKEFILE_LIST)))) + +include $(module_directory)/../solenv/gbuild/partial_build.mk + +# vim: set noet sw=4 ts=4: diff --git a/desktop/Module_desktop.mk b/desktop/Module_desktop.mk new file mode 100644 index 0000000000..de56f3bd13 --- /dev/null +++ b/desktop/Module_desktop.mk @@ -0,0 +1,152 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# 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/. +# + +$(eval $(call gb_Module_Module,desktop)) + +$(eval $(call gb_Module_add_targets,desktop,\ + CustomTarget_desktop_unopackages_install \ + GeneratedPackage_desktop_unopackages_install \ + Library_deployment \ + Library_deploymentgui \ + Library_deploymentmisc \ + Library_offacc \ + Library_sofficeapp \ + $(if $(ENABLE_BREAKPAD), \ + Library_crashreport \ + ) \ + $(if $(or $(DISABLE_GUI),$(ENABLE_WASM_STRIP_SPLASH)),,Library_spl) \ + Package_branding \ + $(if $(CUSTOM_BRAND_DIR),Package_branding_custom) \ + UIConfig_deployment \ +)) + +$(eval $(call gb_Module_add_l10n_targets,desktop,\ + AllLangMoTarget_dkt \ +)) + +ifneq (,$(filter DESKTOP,$(BUILD_TYPE))) +$(eval $(call gb_Module_add_targets,desktop,\ + Executable_soffice_bin \ + $(call gb_CondExeUnopkg,Executable_unopkg_bin) \ + $(if $(ENABLE_BREAKPAD),Executable_minidump_upload) \ + Library_migrationoo2 \ + Library_migrationoo3 \ + $(call gb_CondExeUnopkg,Library_unopkgapp) \ + Package_scripts \ + $(if $(ENABLE_PAGEIN), \ + Pagein_calc \ + Pagein_common \ + Pagein_draw \ + Pagein_impress \ + Pagein_writer \ + ) \ + $(if $(filter-out WNT,$(OS)),CustomTarget_soffice) \ +)) + +ifeq ($(USING_X11),TRUE) +$(eval $(call gb_Module_add_targets,desktop,\ + Package_sbase_sh \ + Package_scalc_sh \ + Package_sdraw_sh \ + Package_simpress_sh \ + Package_smath_sh \ + Package_swriter_sh \ + Package_soffice_sh \ +)) +endif +endif # DESKTOP + +ifeq ($(OS),WNT) + +$(eval $(call gb_Module_add_targets,desktop,\ + StaticLibrary_winloader \ + StaticLibrary_winlauncher \ + Executable_quickstart \ + Executable_sbase \ + Executable_scalc \ + Executable_sdraw \ + Executable_simpress \ + Executable_smath \ + Executable_soffice_exe \ + Executable_soffice_com \ + Executable_soffice_safe \ + Executable_sweb \ + Executable_swriter \ + Executable_unoinfo \ + $(call gb_CondExeUnopkg, \ + Executable_unopkg \ + Executable_unopkg_com \ + ) \ + WinResTarget_quickstart \ + WinResTarget_sbase \ + WinResTarget_scalc \ + WinResTarget_sdraw \ + WinResTarget_simpress \ + WinResTarget_soffice \ + WinResTarget_sofficebin \ + WinResTarget_smath \ + WinResTarget_sweb \ + WinResTarget_swriter \ +)) + +else ifeq (,$(filter MACOSX ANDROID iOS HAIKU EMSCRIPTEN,$(OS))) + +ifeq (,$(filter FUZZERS,$(BUILD_TYPE))) +$(eval $(call gb_Module_add_targets,desktop,\ + Executable_oosplash \ +)) +endif + +endif # $(OS) + +ifneq (,$(filter Extension_test-active,$(MAKECMDGOALS))) +$(eval $(call gb_Module_add_targets,desktop, \ + Extension_test-active \ + Jar_active_java \ + Library_active_native \ +)) +endif + +ifneq (,$(filter Extension_test-passive,$(MAKECMDGOALS))) +$(eval $(call gb_Module_add_targets,desktop, \ + Extension_test-passive \ + Jar_passive_java \ + Library_passive_native \ + Pyuno_passive_python \ + Rdb_passive_generic \ + Rdb_passive_platform \ +)) +endif + +ifneq (,$(filter Extension_test-crashextension,$(MAKECMDGOALS))) +$(eval $(call gb_Module_add_targets,desktop, \ + Extension_test-crashextension \ + Library_crashextension \ + Rdb_crashextension \ +)) +endif + +$(eval $(call gb_Module_add_check_targets,desktop, \ + CppunitTest_desktop_app \ + CppunitTest_desktop_version \ +)) + +ifeq ($(OS),LINUX) +$(eval $(call gb_Module_add_check_targets,desktop, \ + CppunitTest_desktop_lib \ + CppunitTest_desktop_lokinit \ +)) +endif + +# screenshots +$(eval $(call gb_Module_add_screenshot_targets,desktop,\ + CppunitTest_desktop_dialogs_test \ +)) + +# vim: set ts=4 sw=4 et: diff --git a/desktop/Package_branding.mk b/desktop/Package_branding.mk new file mode 100644 index 0000000000..bca87bab32 --- /dev/null +++ b/desktop/Package_branding.mk @@ -0,0 +1,25 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# 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/. +# + +$(eval $(call gb_Package_Package,desktop_branding,$(SRCDIR)/icon-themes/colibre)) + +$(eval $(call gb_Package_add_files,desktop_branding,$(LIBO_ETC_FOLDER),\ + $(foreach image,$(filter $(BRAND_INTRO_IMAGES),$(DEFAULT_BRAND_IMAGES)),\ + $(if $(filter intro.png,$(image)),\ + $(if $(ENABLE_RELEASE_BUILD),brand,brand_dev)/$(image),\ + brand/$(image) \ + ) \ + ) \ +)) + +$(eval $(call gb_Package_add_files,desktop_branding,$(LIBO_ETC_FOLDER)/shell,\ + $(addprefix brand/shell/,$(filter-out $(BRAND_INTRO_IMAGES),$(DEFAULT_BRAND_IMAGES))) \ +)) + +# vim: set noet sw=4 ts=4: diff --git a/desktop/Package_branding_custom.mk b/desktop/Package_branding_custom.mk new file mode 100644 index 0000000000..c2b6181f9c --- /dev/null +++ b/desktop/Package_branding_custom.mk @@ -0,0 +1,20 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# 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/. +# + +$(eval $(call gb_Package_Package,desktop_branding_custom,$(CUSTOM_BRAND_DIR))) + +$(eval $(call gb_Package_add_files,desktop_branding_custom,$(LIBO_ETC_FOLDER),\ + $(filter $(BRAND_INTRO_IMAGES),$(CUSTOM_BRAND_IMAGES)) \ +)) + +$(eval $(call gb_Package_add_files,desktop_branding_custom,$(LIBO_ETC_FOLDER)/shell,\ + $(filter-out $(BRAND_INTRO_IMAGES),$(CUSTOM_BRAND_IMAGES)) \ +)) + +# vim: set noet sw=4 ts=4: diff --git a/desktop/Package_sbase_sh.mk b/desktop/Package_sbase_sh.mk new file mode 100644 index 0000000000..ada63e2d5f --- /dev/null +++ b/desktop/Package_sbase_sh.mk @@ -0,0 +1,14 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# 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/. +# + +$(eval $(call gb_Package_Package,desktop_sbase_sh,$(SRCDIR)/desktop/scripts)) + +$(eval $(call gb_Package_add_file,desktop_sbase_sh,$(LIBO_BIN_FOLDER)/sbase,sbase.sh)) + +# vim: set ts=4 sw=4 noet: diff --git a/desktop/Package_scalc_sh.mk b/desktop/Package_scalc_sh.mk new file mode 100644 index 0000000000..5d29e16258 --- /dev/null +++ b/desktop/Package_scalc_sh.mk @@ -0,0 +1,14 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# 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/. +# + +$(eval $(call gb_Package_Package,desktop_scalc_sh,$(SRCDIR)/desktop/scripts)) + +$(eval $(call gb_Package_add_file,desktop_scalc_sh,$(LIBO_BIN_FOLDER)/scalc,scalc.sh)) + +# vim: set ts=4 sw=4 noet: diff --git a/desktop/Package_scripts.mk b/desktop/Package_scripts.mk new file mode 100644 index 0000000000..248665595e --- /dev/null +++ b/desktop/Package_scripts.mk @@ -0,0 +1,31 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# 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/. +# + +$(eval $(call gb_Package_Package,desktop_scripts_install,$(SRCDIR)/desktop/scripts)) + +ifeq (,$(filter MACOSX WNT,$(OS))) + +$(eval $(call gb_Package_add_file,desktop_scripts_install,$(LIBO_BIN_FOLDER)/gdbtrace,gdbtrace)) +ifneq (,$(call gb_CondExeUnopkg,$(true))) +$(eval $(call gb_Package_add_file,desktop_scripts_install,$(LIBO_BIN_FOLDER)/unopkg,unopkg.sh)) +endif + +endif + +ifeq ($(OS), MACOSX) +# only mach-o binaries allowed in bin folder (signing scripts would require extended attributes) +# so install it into Resources folder and use a symlink instead +# see https://developer.apple.com/library/archive/technotes/tn2206/_index.html +$(eval $(call gb_Package_add_file,desktop_scripts_install,$(LIBO_SHARE_FOLDER)/unoinfo,unoinfo-mac.sh)) +$(eval $(call gb_Package_add_symbolic_link,desktop_scripts_install,$(LIBO_BIN_FOLDER)/unoinfo,../$(LIBO_SHARE_FOLDER)/unoinfo)) +else ifneq ($(OS),WNT) +$(eval $(call gb_Package_add_file,desktop_scripts_install,$(LIBO_BIN_FOLDER)/unoinfo,unoinfo.sh)) +endif + +# vim: set ts=4 sw=4 noet: diff --git a/desktop/Package_sdraw_sh.mk b/desktop/Package_sdraw_sh.mk new file mode 100644 index 0000000000..58ae0f5fd3 --- /dev/null +++ b/desktop/Package_sdraw_sh.mk @@ -0,0 +1,14 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# 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/. +# + +$(eval $(call gb_Package_Package,desktop_sdraw_sh,$(SRCDIR)/desktop/scripts)) + +$(eval $(call gb_Package_add_file,desktop_sdraw_sh,$(LIBO_BIN_FOLDER)/sdraw,sdraw.sh)) + +# vim: set ts=4 sw=4 noet: diff --git a/desktop/Package_simpress_sh.mk b/desktop/Package_simpress_sh.mk new file mode 100644 index 0000000000..bb315aa774 --- /dev/null +++ b/desktop/Package_simpress_sh.mk @@ -0,0 +1,14 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# 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/. +# + +$(eval $(call gb_Package_Package,desktop_simpress_sh,$(SRCDIR)/desktop/scripts)) + +$(eval $(call gb_Package_add_file,desktop_simpress_sh,$(LIBO_BIN_FOLDER)/simpress,simpress.sh)) + +# vim: set ts=4 sw=4 noet: diff --git a/desktop/Package_smath_sh.mk b/desktop/Package_smath_sh.mk new file mode 100644 index 0000000000..67c113a206 --- /dev/null +++ b/desktop/Package_smath_sh.mk @@ -0,0 +1,14 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# 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/. +# + +$(eval $(call gb_Package_Package,desktop_smath_sh,$(SRCDIR)/desktop/scripts)) + +$(eval $(call gb_Package_add_file,desktop_smath_sh,$(LIBO_BIN_FOLDER)/smath,smath.sh)) + +# vim: set ts=4 sw=4 noet: diff --git a/desktop/Package_soffice_sh.mk b/desktop/Package_soffice_sh.mk new file mode 100644 index 0000000000..046c95f459 --- /dev/null +++ b/desktop/Package_soffice_sh.mk @@ -0,0 +1,14 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# 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/. +# + +$(eval $(call gb_Package_Package,desktop_soffice_sh,$(call gb_CustomTarget_get_workdir,desktop/soffice))) + +$(eval $(call gb_Package_add_file,desktop_soffice_sh,$(LIBO_BIN_FOLDER)/soffice,soffice.sh)) + +# vim:set noet sw=4 ts=4: diff --git a/desktop/Package_swriter_sh.mk b/desktop/Package_swriter_sh.mk new file mode 100644 index 0000000000..88720a9326 --- /dev/null +++ b/desktop/Package_swriter_sh.mk @@ -0,0 +1,14 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# 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/. +# + +$(eval $(call gb_Package_Package,desktop_swriter_sh,$(SRCDIR)/desktop/scripts)) + +$(eval $(call gb_Package_add_file,desktop_swriter_sh,$(LIBO_BIN_FOLDER)/swriter,swriter.sh)) + +# vim: set ts=4 sw=4 noet: diff --git a/desktop/Pagein_calc.mk b/desktop/Pagein_calc.mk new file mode 100644 index 0000000000..9d7ac0a6c4 --- /dev/null +++ b/desktop/Pagein_calc.mk @@ -0,0 +1,19 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# 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/. +# + +$(eval $(call gb_Pagein_Pagein,calc)) + +$(eval $(call gb_Pagein_add_objects,calc,\ + sc \ + scui \ + svx \ + svxcore \ +)) + +# vim: set ts=4 sw=4 et: diff --git a/desktop/Pagein_common.mk b/desktop/Pagein_common.mk new file mode 100644 index 0000000000..d0a0e689c2 --- /dev/null +++ b/desktop/Pagein_common.mk @@ -0,0 +1,69 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# 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/. +# + +$(eval $(call gb_Pagein_Pagein,common)) + +# sorted in approx. reverse load order (ld.so.1) +$(eval $(call gb_Pagein_add_objects,common,\ + $(if $(MERGELIBS),merged) \ + i18nlangtag \ + $(if $(SYSTEM_ICU),,\ + libicui18n$(gb_Library_DLLEXT).$(ICU_MAJOR) \ + libicuuc$(gb_Library_DLLEXT).$(ICU_MAJOR) \ + ) \ + lng \ + xo \ + fwk \ + package2 \ + ucpfile1 \ + ucb1 \ + configmgr \ + vclplug_gen \ + $(if $(findstring TRUE,$(ENABLE_GTK3)),vclplug_gtk3) \ + basegfx \ + sot \ + xmlscript \ + sb \ + stocservices \ + bootstrap \ + reg \ + store \ + reflection \ + cppuhelper \ + cppu \ + sal \ + ucbhelper \ + comphelper \ + tl \ + utl \ + svl \ + vcl \ + tk \ + types.rdb \ + services/services.rdb \ + types/oovbaapi.rdb \ + deployment \ + deploymentmisc \ + xstor \ + filterconfig \ + uui \ + svt \ + spl \ + avmedia \ + helplinker \ + sax \ + fsstorage \ + desktopbe1 \ + localebe1 \ + ucpexpand1 \ + sfx \ + sofficeapp \ +)) + +# vim: set ts=4 sw=4 et: diff --git a/desktop/Pagein_draw.mk b/desktop/Pagein_draw.mk new file mode 100644 index 0000000000..2fd0ef6ba4 --- /dev/null +++ b/desktop/Pagein_draw.mk @@ -0,0 +1,19 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# 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/. +# + +$(eval $(call gb_Pagein_Pagein,draw)) + +$(eval $(call gb_Pagein_add_objects,draw,\ + sd \ + sdui \ + svx \ + svxcore \ +)) + +# vim: set ts=4 sw=4 et: diff --git a/desktop/Pagein_impress.mk b/desktop/Pagein_impress.mk new file mode 100644 index 0000000000..f3e4ff7b6d --- /dev/null +++ b/desktop/Pagein_impress.mk @@ -0,0 +1,19 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# 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/. +# + +$(eval $(call gb_Pagein_Pagein,impress)) + +$(eval $(call gb_Pagein_add_objects,impress,\ + sd \ + sdui \ + svx \ + svxcore \ +)) + +# vim: set ts=4 sw=4 et: diff --git a/desktop/Pagein_writer.mk b/desktop/Pagein_writer.mk new file mode 100644 index 0000000000..eed7403f4f --- /dev/null +++ b/desktop/Pagein_writer.mk @@ -0,0 +1,19 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# 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/. +# + +$(eval $(call gb_Pagein_Pagein,writer)) + +$(eval $(call gb_Pagein_add_objects,writer,\ + sw \ + swui \ + svx \ + svxcore \ +)) + +# vim: set ts=4 sw=4 et: diff --git a/desktop/Pyuno_passive_python.mk b/desktop/Pyuno_passive_python.mk new file mode 100644 index 0000000000..c6a56db06a --- /dev/null +++ b/desktop/Pyuno_passive_python.mk @@ -0,0 +1,18 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# 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/. +# + +$(eval $(call gb_Pyuno_Pyuno,passive_python,$(SRCDIR)/desktop/test/deployment/passive)) + +$(eval $(call gb_Pyuno_add_files,passive_python,,\ + passive_python.py \ +)) + +$(eval $(call gb_Pyuno_set_componentfile_full,passive_python,desktop/test/deployment/passive/passive_python,./,passive_python.py,passive_generic)) + +# vim: set noet sw=4 ts=4: diff --git a/desktop/README.md b/desktop/README.md new file mode 100644 index 0000000000..88bed96583 --- /dev/null +++ b/desktop/README.md @@ -0,0 +1,56 @@ +# LibreOffice Binary + +Code for the LibreOffice main binary (`soffice`) resides here. The `soffice_main` +function for the `soffice` binary can be found here. + + +## Stable Interface + +Some of the artifacts built here are part of a LibreOffice installation set's +stable interface, which (programmatic) clients can depend on. Among them are: + +### soffice + +In the `program` directory (`program/` on Linux and Windows, `Contents/MacOS/` +on macOS). + +### unoinfo + +In the `program` directory (`program/` on Linux and Windows, `Contents/MacOS/` +on macOS). + +When called with a sole argument of `c++`, it prints to stdout an absolute +pathname denoting the directory where the public URE libraries are found. + +When called with a sole argument of `java`, it prints to stdout a marker +character (either an ASCII '0' or '1') followed by a sequence of zero or more +absolute pathnames denoting jars or directories that need to be included in a +class loader's search locations. + +If the marker character is '0' (on Linux and macOS), the pathnames are +encoded as bytes, and any two pathnames in the sequence are separated from each +other by NUL bytes. + +If the marker character is '1' (on Windows), the pathnames are encoded as +UTF-16-LE two-byte code units, and any two pathnames in the sequence are +separated from each other by two-byte `NUL` code units. + +## Other Binaries + +### oosplash +Splash screen for the LibreOffice `soffice` binary. + + +## Extensions + +The directory `test/deployment` contains some extensions to be used for testing: + +* `test/deployment/crashextension`: C++ extension to make LibreOffice crash. Useful for testing Crashreporter. + * Build with `Extension_test-crashextension`. + * Extension can be found in `workdir/Extension/test-crashextension.oxt` +* `test/deployment/passive`: C++, Java and Python extension samples with passive registration. + * Build with `make Extension_test-passive`. + * Extension can be found in `workdir/Extension/test-passive.oxt` +* `test/deployment/active`: C++, Java and Python extension samples with active registration. + * Build with `make Extension_test-active`. + * Extension can be found in `workdir/Extension/test-active.oxt` diff --git a/desktop/README.vars b/desktop/README.vars new file mode 100644 index 0000000000..67c6056981 --- /dev/null +++ b/desktop/README.vars @@ -0,0 +1,15 @@ +Environment variables in desktop: + +General +------- + +DISPLAY - X11 display to use. +OOO_DISABLE_RECOVERY - Disables the recovery dialog. +OOO_EXIT_POST_STARTUP - Exit right after startup, for profiling purposes. +SAL_DISABLE_USERMIGRATION - Disable automatic conversion of old user configurations. +SAL_USE_VCLPLUGIN - Which VCL plugin to use instead of the auto-detected one. + +LibreOfficeKit +-------------- + +LOK_DEBUG - Draw a small red rectangle in the top left corner so that it's easy to see where a new tile begins. diff --git a/desktop/Rdb_crashextension.mk b/desktop/Rdb_crashextension.mk new file mode 100644 index 0000000000..9229fe4b90 --- /dev/null +++ b/desktop/Rdb_crashextension.mk @@ -0,0 +1,12 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t; 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/. +# + +$(eval $(call gb_Rdb_Rdb,crashextension)) + +# vim: set noet sw=4 ts=4: diff --git a/desktop/Rdb_passive_generic.mk b/desktop/Rdb_passive_generic.mk new file mode 100644 index 0000000000..70b17947dd --- /dev/null +++ b/desktop/Rdb_passive_generic.mk @@ -0,0 +1,12 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# 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/. +# + +$(eval $(call gb_Rdb_Rdb,passive_generic)) + +# vim: set noet sw=4 ts=4: diff --git a/desktop/Rdb_passive_platform.mk b/desktop/Rdb_passive_platform.mk new file mode 100644 index 0000000000..51d0928df6 --- /dev/null +++ b/desktop/Rdb_passive_platform.mk @@ -0,0 +1,12 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# 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/. +# + +$(eval $(call gb_Rdb_Rdb,passive_platform)) + +# vim: set noet sw=4 ts=4: diff --git a/desktop/StaticLibrary_winlauncher.mk b/desktop/StaticLibrary_winlauncher.mk new file mode 100644 index 0000000000..1fed934f1b --- /dev/null +++ b/desktop/StaticLibrary_winlauncher.mk @@ -0,0 +1,17 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# +# 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/. +# + +$(eval $(call gb_StaticLibrary_StaticLibrary,winlauncher)) + +$(eval $(call gb_StaticLibrary_add_exception_objects,winlauncher,\ + desktop/win32/source/applauncher/launcher \ +)) + +# vim:set noet sw=4 ts=4: diff --git a/desktop/StaticLibrary_winloader.mk b/desktop/StaticLibrary_winloader.mk new file mode 100644 index 0000000000..abee2aa3ee --- /dev/null +++ b/desktop/StaticLibrary_winloader.mk @@ -0,0 +1,21 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# +# 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/. +# + +$(eval $(call gb_StaticLibrary_StaticLibrary,winloader)) + +$(eval $(call gb_StaticLibrary_use_externals,winloader,\ + boost_headers \ +)) + +$(eval $(call gb_StaticLibrary_add_exception_objects,winloader,\ + desktop/win32/source/loader \ +)) + +# vim:set noet sw=4 ts=4: diff --git a/desktop/UIConfig_deployment.mk b/desktop/UIConfig_deployment.mk new file mode 100644 index 0000000000..80e5e96c12 --- /dev/null +++ b/desktop/UIConfig_deployment.mk @@ -0,0 +1,24 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# 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/. +# + +$(eval $(call gb_UIConfig_UIConfig,desktop)) + +$(eval $(call gb_UIConfig_add_uifiles,desktop,\ + desktop/uiconfig/ui/dependenciesdialog \ + desktop/uiconfig/ui/extensionmanager \ + desktop/uiconfig/ui/extensionmenu \ + desktop/uiconfig/ui/installforalldialog \ + desktop/uiconfig/ui/licensedialog \ + desktop/uiconfig/ui/showlicensedialog \ + desktop/uiconfig/ui/updatedialog \ + desktop/uiconfig/ui/updateinstalldialog \ + desktop/uiconfig/ui/updaterequireddialog \ +)) + +# vim: set noet sw=4 ts=4: diff --git a/desktop/WinResTarget_quickstart.mk b/desktop/WinResTarget_quickstart.mk new file mode 100644 index 0000000000..0c06f59903 --- /dev/null +++ b/desktop/WinResTarget_quickstart.mk @@ -0,0 +1,24 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# 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/. +# + +$(eval $(call gb_WinResTarget_WinResTarget,quickstart/QuickStart)) + +$(eval $(call gb_WinResTarget_set_include,quickstart/QuickStart,\ + $$(INCLUDE) \ + -I$(SRCDIR)/desktop/win32/source/QuickStart \ + -I$(SRCDIR)/sysui/desktop \ +)) + +$(eval $(call gb_WinResTarget_add_dependencies,quickstart/QuickStart,\ + sysui/desktop/icons/soffice.ico \ +)) + +$(eval $(call gb_WinResTarget_set_rcfile,quickstart/QuickStart,desktop/win32/source/QuickStart/QuickStart)) + +# vim: set ts=4 sw=4 et: diff --git a/desktop/WinResTarget_sbase.mk b/desktop/WinResTarget_sbase.mk new file mode 100644 index 0000000000..8ff57b91a4 --- /dev/null +++ b/desktop/WinResTarget_sbase.mk @@ -0,0 +1,27 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# 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/. +# + +$(eval $(call gb_WinResTarget_WinResTarget,sbase/launcher)) + +$(eval $(call gb_WinResTarget_set_include,sbase/launcher,\ + $$(INCLUDE) \ + -I$(SRCDIR)/sysui/desktop \ +)) + +$(eval $(call gb_WinResTarget_add_defs,sbase/launcher,\ + -DRES_APP_ICON=icons/base_app.ico \ +)) + +$(eval $(call gb_WinResTarget_add_dependencies,sbase/launcher,\ + sysui/desktop/icons/base_app.ico \ +)) + +$(eval $(call gb_WinResTarget_set_rcfile,sbase/launcher,desktop/win32/source/applauncher/launcher)) + +# vim: set ts=4 sw=4 et: diff --git a/desktop/WinResTarget_scalc.mk b/desktop/WinResTarget_scalc.mk new file mode 100644 index 0000000000..7060dcb775 --- /dev/null +++ b/desktop/WinResTarget_scalc.mk @@ -0,0 +1,27 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# 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/. +# + +$(eval $(call gb_WinResTarget_WinResTarget,scalc/launcher)) + +$(eval $(call gb_WinResTarget_set_include,scalc/launcher,\ + $$(INCLUDE) \ + -I$(SRCDIR)/sysui/desktop \ +)) + +$(eval $(call gb_WinResTarget_add_defs,scalc/launcher,\ + -DRES_APP_ICON=icons/calc_app.ico \ +)) + +$(eval $(call gb_WinResTarget_add_dependencies,scalc/launcher,\ + sysui/desktop/icons/calc_app.ico \ +)) + +$(eval $(call gb_WinResTarget_set_rcfile,scalc/launcher,desktop/win32/source/applauncher/launcher)) + +# vim: set ts=4 sw=4 et: diff --git a/desktop/WinResTarget_sdraw.mk b/desktop/WinResTarget_sdraw.mk new file mode 100644 index 0000000000..920a4625b6 --- /dev/null +++ b/desktop/WinResTarget_sdraw.mk @@ -0,0 +1,28 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# 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/. +# + +$(eval $(call gb_WinResTarget_WinResTarget,sdraw/launcher)) + +$(eval $(call gb_WinResTarget_set_include,sdraw/launcher,\ + $$(INCLUDE) \ + -I$(SRCDIR)/sysui/desktop \ +)) + +$(eval $(call gb_WinResTarget_add_defs,sdraw/launcher,\ + -DRES_APP_ICON=icons/draw_app.ico \ +)) + +$(eval $(call gb_WinResTarget_add_dependencies,sdraw/launcher,\ + sysui/desktop/icons/draw_app.ico \ +)) + +$(eval $(call gb_WinResTarget_set_rcfile,sdraw/launcher,desktop/win32/source/applauncher/launcher)) + +# vim: set ts=4 sw=4 et: + diff --git a/desktop/WinResTarget_simpress.mk b/desktop/WinResTarget_simpress.mk new file mode 100644 index 0000000000..f5d11a6a41 --- /dev/null +++ b/desktop/WinResTarget_simpress.mk @@ -0,0 +1,27 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# 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/. +# + +$(eval $(call gb_WinResTarget_WinResTarget,simpress/launcher)) + +$(eval $(call gb_WinResTarget_set_include,simpress/launcher,\ + $$(INCLUDE) \ + -I$(SRCDIR)/sysui/desktop \ +)) + +$(eval $(call gb_WinResTarget_add_defs,simpress/launcher,\ + -DRES_APP_ICON=icons/impress_app.ico \ +)) + +$(eval $(call gb_WinResTarget_add_dependencies,simpress/launcher,\ + sysui/desktop/icons/impress_app.ico \ +)) + +$(eval $(call gb_WinResTarget_set_rcfile,simpress/launcher,desktop/win32/source/applauncher/launcher)) + +# vim: set ts=4 sw=4 et: diff --git a/desktop/WinResTarget_smath.mk b/desktop/WinResTarget_smath.mk new file mode 100644 index 0000000000..0ad3ee938d --- /dev/null +++ b/desktop/WinResTarget_smath.mk @@ -0,0 +1,27 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# 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/. +# + +$(eval $(call gb_WinResTarget_WinResTarget,smath/launcher)) + +$(eval $(call gb_WinResTarget_set_include,smath/launcher,\ + $$(INCLUDE) \ + -I$(SRCDIR)/sysui/desktop \ +)) + +$(eval $(call gb_WinResTarget_add_defs,smath/launcher,\ + -DRES_APP_ICON=icons/math_app.ico \ +)) + +$(eval $(call gb_WinResTarget_add_dependencies,smath/launcher,\ + sysui/desktop/icons/math_app.ico \ +)) + +$(eval $(call gb_WinResTarget_set_rcfile,smath/launcher,desktop/win32/source/applauncher/launcher)) + +# vim: set ts=4 sw=4 et: diff --git a/desktop/WinResTarget_soffice.mk b/desktop/WinResTarget_soffice.mk new file mode 100644 index 0000000000..990eb5c98d --- /dev/null +++ b/desktop/WinResTarget_soffice.mk @@ -0,0 +1,27 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# 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/. +# + +$(eval $(call gb_WinResTarget_WinResTarget,soffice/launcher)) + +$(eval $(call gb_WinResTarget_set_include,soffice/launcher,\ + $$(INCLUDE) \ + -I$(SRCDIR)/sysui/desktop \ +)) + +$(eval $(call gb_WinResTarget_add_defs,soffice/launcher,\ + -DRES_APP_ICON=icons/soffice.ico \ +)) + +$(eval $(call gb_WinResTarget_add_dependencies,soffice/launcher,\ + sysui/desktop/icons/soffice.ico \ +)) + +$(eval $(call gb_WinResTarget_set_rcfile,soffice/launcher,desktop/win32/source/applauncher/launcher)) + +# vim: set ts=4 sw=4 et: diff --git a/desktop/WinResTarget_sofficebin.mk b/desktop/WinResTarget_sofficebin.mk new file mode 100644 index 0000000000..139de96449 --- /dev/null +++ b/desktop/WinResTarget_sofficebin.mk @@ -0,0 +1,51 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# 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/. +# + +$(eval $(call gb_WinResTarget_WinResTarget,sofficebin/officeloader)) + +$(eval $(call gb_WinResTarget_set_include,sofficebin/officeloader,\ + $$(INCLUDE) \ + -I$(SRCDIR)/sysui/desktop \ +)) + +$(eval $(call gb_WinResTarget_add_defs,sofficebin/officeloader,\ + -DRES_APP_ICON=icons/soffice.ico \ +)) + +$(eval $(call gb_WinResTarget_add_dependencies,sofficebin/officeloader,\ + sysui/desktop/icons/soffice.ico \ + sysui/desktop/icons/oasis-database.ico \ + sysui/desktop/icons/oasis-drawing-template.ico \ + sysui/desktop/icons/oasis-drawing.ico \ + sysui/desktop/icons/oasis-formula.ico \ + sysui/desktop/icons/oasis-master-document.ico \ + sysui/desktop/icons/oasis-presentation-template.ico \ + sysui/desktop/icons/oasis-presentation.ico \ + sysui/desktop/icons/oasis-spreadsheet-template.ico \ + sysui/desktop/icons/oasis-spreadsheet.ico \ + sysui/desktop/icons/oasis-text-template.ico \ + sysui/desktop/icons/oasis-text.ico \ + sysui/desktop/icons/oasis-web-template.ico \ + sysui/desktop/icons/database.ico \ + sysui/desktop/icons/drawing-template.ico \ + sysui/desktop/icons/drawing.ico \ + sysui/desktop/icons/formula.ico \ + sysui/desktop/icons/master-document.ico \ + sysui/desktop/icons/presentation-template.ico \ + sysui/desktop/icons/presentation.ico \ + sysui/desktop/icons/spreadsheet-template.ico \ + sysui/desktop/icons/spreadsheet.ico \ + sysui/desktop/icons/text-template.ico \ + sysui/desktop/icons/text.ico \ + sysui/desktop/icons/oxt-extension.ico \ +)) + +$(eval $(call gb_WinResTarget_set_rcfile,sofficebin/officeloader,desktop/util/officeloader)) + +# vim: set ts=4 sw=4 et: diff --git a/desktop/WinResTarget_sweb.mk b/desktop/WinResTarget_sweb.mk new file mode 100644 index 0000000000..e6fd283431 --- /dev/null +++ b/desktop/WinResTarget_sweb.mk @@ -0,0 +1,27 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# 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/. +# + +$(eval $(call gb_WinResTarget_WinResTarget,sweb/launcher)) + +$(eval $(call gb_WinResTarget_set_include,sweb/launcher,\ + $$(INCLUDE) \ + -I$(SRCDIR)/sysui/desktop \ +)) + +$(eval $(call gb_WinResTarget_add_defs,sweb/launcher,\ + -DRES_APP_ICON=icons/writer_app.ico \ +)) + +$(eval $(call gb_WinResTarget_add_dependencies,sweb/launcher,\ + sysui/desktop/icons/writer_app.ico \ +)) + +$(eval $(call gb_WinResTarget_set_rcfile,sweb/launcher,desktop/win32/source/applauncher/launcher)) + +# vim: set ts=4 sw=4 et: diff --git a/desktop/WinResTarget_swriter.mk b/desktop/WinResTarget_swriter.mk new file mode 100644 index 0000000000..1136c61bb8 --- /dev/null +++ b/desktop/WinResTarget_swriter.mk @@ -0,0 +1,27 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# 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/. +# + +$(eval $(call gb_WinResTarget_WinResTarget,swriter/launcher)) + +$(eval $(call gb_WinResTarget_set_include,swriter/launcher,\ + $$(INCLUDE) \ + -I$(SRCDIR)/sysui/desktop \ +)) + +$(eval $(call gb_WinResTarget_add_defs,swriter/launcher,\ + -DRES_APP_ICON=icons/writer_app.ico \ +)) + +$(eval $(call gb_WinResTarget_add_dependencies,swriter/launcher,\ + sysui/desktop/icons/writer_app.ico \ +)) + +$(eval $(call gb_WinResTarget_set_rcfile,swriter/launcher,desktop/win32/source/applauncher/launcher)) + +# vim: set ts=4 sw=4 et: diff --git a/desktop/inc/app.hxx b/desktop/inc/app.hxx new file mode 100644 index 0000000000..9d6ac9864a --- /dev/null +++ b/desktop/inc/app.hxx @@ -0,0 +1,181 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#pragma once + +#include <optional> +#include <sal/log.hxx> +#include <vcl/svapp.hxx> +#include <vcl/timer.hxx> +#include <unotools/bootstrap.hxx> +#include <com/sun/star/frame/XDesktop2.hpp> +#include <com/sun/star/task/XStatusIndicator.hpp> +#include <com/sun/star/uno/Reference.h> + +#include <memory> +#include <string_view> +#include <thread> + +namespace com::sun::star::uno { class XComponentContext; } + +namespace desktop +{ + +/*-------------------------------------------------------------------- + Description: Application-class + --------------------------------------------------------------------*/ +class CommandLineArgs; +class Lockfile; +struct ConvertData; +class Desktop final : public Application +{ + int doShutdown(); + + public: + enum BootstrapError + { + BE_OK, + BE_UNO_SERVICEMANAGER, + BE_UNO_SERVICE_CONFIG_MISSING, + BE_PATHINFO_MISSING, + BE_USERINSTALL_FAILED, + BE_LANGUAGE_MISSING, + BE_USERINSTALL_NOTENOUGHDISKSPACE, + BE_USERINSTALL_NOWRITEACCESS, + BE_OFFICECONFIG_BROKEN, + BE_2NDOFFICE_WITHCAT, + }; + enum BootstrapStatus + { + BS_OK, + BS_TERMINATE + }; + + Desktop(); + virtual ~Desktop() override; + virtual int Main( ) override; + virtual void Init() override; + virtual void InitFinished() override; + virtual void DeInit() override; + virtual bool QueryExit() override; + virtual void Shutdown() override; + virtual void Exception(ExceptionCategory nCategory) override; + virtual void OverrideSystemSettings( AllSettings& rSettings ) override; + virtual void AppEvent( const ApplicationEvent& rAppEvent ) override; + + DECL_LINK( OpenClients_Impl, void*, void ); + + static void OpenClients(); + static void OpenDefault(); + static void CheckOpenCLCompute(const css::uno::Reference<css::frame::XDesktop2> &); + + DECL_STATIC_LINK( Desktop, EnableAcceptors_Impl, void*, void); + + static void HandleAppEvent( const ApplicationEvent& rAppEvent ); + static CommandLineArgs& GetCommandLineArgs(); + + static void HandleBootstrapErrors( + BootstrapError nError, OUString const & aMessage ); + void SetBootstrapError( + BootstrapError nError, OUString const & aMessage ) + { + if ( m_aBootstrapError == BE_OK ) + { + SAL_INFO("desktop.app", "SetBootstrapError: " << nError << " '" << aMessage << "'"); + m_aBootstrapError = nError; + m_aBootstrapErrorMessage = aMessage; + } + } + + void SetBootstrapStatus( BootstrapStatus nStatus ) + { + m_aBootstrapStatus = nStatus; + } + BootstrapStatus GetBootstrapStatus() const + { + return m_aBootstrapStatus; + } + + // first-start (ever) related methods + static bool CheckExtensionDependencies(); + + static void SynchronizeExtensionRepositories(bool bCleanedExtensionCache, Desktop* pDesktop = nullptr); + void SetSplashScreenText( const OUString& rText ); + void SetSplashScreenProgress( sal_Int32 ); + + // Bootstrap methods + static void InitApplicationServiceManager(); + // throws an exception upon failure + + private: + void RegisterServices(); + static void DeregisterServices(); + + public: + static void CreateTemporaryDirectory(); + static void RemoveTemporaryDirectory(); + + private: + static bool InitializeConfiguration(); + static void FlushConfiguration(); + static bool InitializeQuickstartMode( const css::uno::Reference< css::uno::XComponentContext >& rxContext ); + + static void HandleBootstrapPathErrors( ::utl::Bootstrap::Status, std::u16string_view aMsg ); + + // Create an error message depending on bootstrap failure code and an optional file url + static OUString CreateErrorMsgString( utl::Bootstrap::FailureCode nFailureCode, + const OUString& aFileURL ); + + css::uno::Reference<css::task::XStatusIndicator> m_rSplashScreen; + void OpenSplashScreen(); + void CloseSplashScreen(); + + DECL_STATIC_LINK( Desktop, ImplInitFilterHdl, ::ConvertData&, bool ); + DECL_STATIC_LINK( Desktop, AsyncInitFirstRun, Timer*, void ); + /** checks if the office is run the first time + <p>If so, <method>DoFirstRunInitializations</method> is called (asynchronously and delayed) and the + respective flag in the configuration is reset.</p> + */ + void CheckFirstRun( ); + + static void ShowBackingComponent(Desktop * progress); + + // on-demand acceptors + static void createAcceptor(const OUString& aDescription); + static void destroyAcceptor(const OUString& aDescription); + + bool m_bCleanedExtensionCache; + bool m_bServicesRegistered; + BootstrapError m_aBootstrapError; + OUString m_aBootstrapErrorMessage; + BootstrapStatus m_aBootstrapStatus; + + std::unique_ptr<Lockfile> m_xLockfile; + Timer m_firstRunTimer; + std::thread m_aUpdateThread; +}; + +OUString GetURL_Impl( + const OUString& rName, std::optional< OUString > const & cwdUrl ); + +OUString ReplaceStringHookProc(const OUString& rStr); + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/inc/bitmaps.hlst b/desktop/inc/bitmaps.hlst new file mode 100644 index 0000000000..1f565b47dd --- /dev/null +++ b/desktop/inc/bitmaps.hlst @@ -0,0 +1,17 @@ +/* -*- 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/. + */ + +#pragma once + +inline constexpr OUString RID_BMP_WARNING = u"desktop/res/caution_16.png"_ustr; +inline constexpr OUString RID_BMP_LOCKED = u"desktop/res/lock_16.png"_ustr; +inline constexpr OUString RID_BMP_SHARED = u"desktop/res/shared_16.png"_ustr; +inline constexpr OUString RID_BMP_EXTENSION = u"desktop/res/extension_32.png"_ustr; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/desktop/inc/dp_misc.h b/desktop/inc/dp_misc.h new file mode 100644 index 0000000000..f9ac5687e4 --- /dev/null +++ b/desktop/inc/dp_misc.h @@ -0,0 +1,147 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#pragma once + +#include <sal/config.h> + +#include <string_view> + +#include <osl/process.h> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <com/sun/star/lang/XComponent.hpp> +#include <com/sun/star/ucb/XCommandEnvironment.hpp> +#include <dp_misc_api.hxx> + +namespace dp_misc { + +const char CR = 0x0d; +const char LF = 0x0a; + + +inline void try_dispose( css::uno::Reference< css::uno::XInterface> const & x ) +{ + css::uno::Reference< css::lang::XComponent> xComp( x, css::uno::UNO_QUERY ); + if (xComp.is()) + xComp->dispose(); +} + + + + +DESKTOP_DEPLOYMENTMISC_DLLPUBLIC +OUString expandUnoRcTerm( OUString const & term ); + +DESKTOP_DEPLOYMENTMISC_DLLPUBLIC +OUString makeRcTerm( OUString const & url ); + + +DESKTOP_DEPLOYMENTMISC_DLLPUBLIC +OUString expandUnoRcUrl( OUString const & url ); + + + +/** appends a relative path to a url. + + The relative path must already be correctly encoded for use in a URL. + If the URL starts with vnd.sun.star.expand then the relative path will + be again encoded for use in an "expand" URL. + */ +DESKTOP_DEPLOYMENTMISC_DLLPUBLIC OUString makeURL( + std::u16string_view baseURL, OUString const & relPath ); + + +/** appends a relative path to a url. + + This is the same as makeURL, but the relative Path must me a segment + of an system path. + */ +DESKTOP_DEPLOYMENTMISC_DLLPUBLIC OUString makeURLAppendSysPathSegment( + std::u16string_view baseURL, OUString const & relPath ); + + +DESKTOP_DEPLOYMENTMISC_DLLPUBLIC OUString generateRandomPipeId(); + +class AbortChannel; + +DESKTOP_DEPLOYMENTMISC_DLLPUBLIC +css::uno::Reference< css::uno::XInterface> resolveUnoURL( + OUString const & connectString, + css::uno::Reference< css::uno::XComponentContext> const & xLocalContext, + AbortChannel const * abortChannel = nullptr ); + + +DESKTOP_DEPLOYMENTMISC_DLLPUBLIC bool office_is_running(); + + +DESKTOP_DEPLOYMENTMISC_DLLPUBLIC +oslProcess raiseProcess( OUString const & appURL, + css::uno::Sequence< OUString > const & args ); + + + +/** writes the argument string to the console. + It converts the UTF16 string to an ANSI string using osl_getThreadTextEncoding() + as target encoding. +*/ +DESKTOP_DEPLOYMENTMISC_DLLPUBLIC +void writeConsole(std::u16string_view sText); + +/** writes the argument to the console using the error stream. + Otherwise the same as writeConsole. +*/ +DESKTOP_DEPLOYMENTMISC_DLLPUBLIC +void writeConsoleError(std::u16string_view sText); + + +/** reads from the console. + It uses fgets to read char values and converts them to OUString using + osl_getThreadTextEncoding as target encoding. The returned string has a maximum size of + 1024 and does NOT include leading and trailing white space(applied OUString::trim()) +*/ +DESKTOP_DEPLOYMENTMISC_DLLPUBLIC +OUString readConsole(); + +/** print the text to the console in a debug build. + The argument is forwarded to writeConsole. The function does not add new line. + The code is only executed if OSL_DEBUG_LEVEL > 1 +*/ +DESKTOP_DEPLOYMENTMISC_DLLPUBLIC +void TRACE(OUString const & sText); + +/** registers or revokes shared or bundled extensions which have been + recently added or removed. +*/ +DESKTOP_DEPLOYMENTMISC_DLLPUBLIC +void syncRepositories( + bool force, + css::uno::Reference< + css::ucb::XCommandEnvironment> const & xCmdEnv); + +/** workaround: for some reason the bridge threads which communicate with the + uno.exe process are not released on time +*/ +DESKTOP_DEPLOYMENTMISC_DLLPUBLIC +void disposeBridges( + css::uno::Reference< css::uno::XComponentContext > + const & ctx); + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/inc/dp_shared.hxx b/desktop/inc/dp_shared.hxx new file mode 100644 index 0000000000..dbd695c31b --- /dev/null +++ b/desktop/inc/dp_shared.hxx @@ -0,0 +1,27 @@ +/* -*- 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 <unotools/resmgr.hxx> +#include <dp_misc_api.hxx> + +DESKTOP_DEPLOYMENTMISC_DLLPUBLIC OUString DpResId(TranslateId aId); + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/inc/lib/init.hxx b/desktop/inc/lib/init.hxx new file mode 100644 index 0000000000..323a508098 --- /dev/null +++ b/desktop/inc/lib/init.hxx @@ -0,0 +1,291 @@ +/* -*- 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 <map> +#include <unordered_map> +#include <memory> +#include <mutex> +#include <set> +#include <string_view> + +#include <boost/property_tree/ptree.hpp> +#include <boost/variant.hpp> +#include <boost/container/flat_map.hpp> + +#include <osl/thread.h> +#include <rtl/ref.hxx> +#include <rtl/strbuf.hxx> +#include <vcl/idle.hxx> +#include <LibreOfficeKit/LibreOfficeKit.h> +#include <LibreOfficeKit/LibreOfficeKitEnums.h> +#include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/lang/XComponent.hpp> +#include <tools/gen.hxx> +#include <sfx2/lokcallback.hxx> +#include <sfx2/lokhelper.hxx> + +#include <desktop/dllapi.h> + +class LOKInteractionHandler; + +namespace desktop { + + /// Represents an invalidated rectangle inside a given document part. + struct RectangleAndPart + { + tools::Rectangle m_aRectangle; + int m_nPart; + int m_nMode; + + // This is the "EMPTY" rectangle, which somewhat confusingly actually means + // to drop all rectangles (see LOK_CALLBACK_INVALIDATE_TILES documentation), + // and so it is actually an infinite rectangle and not an empty one. + constexpr static tools::Rectangle emptyAllRectangle = {0, 0, SfxLokHelper::MaxTwips, SfxLokHelper::MaxTwips}; + + RectangleAndPart() + : m_nPart(INT_MIN) // -1 is reserved to mean "all parts". + , m_nMode(0) + { + } + + RectangleAndPart(const tools::Rectangle* pRect, int nPart, int nMode) + : m_aRectangle( pRect ? SanitizedRectangle(*pRect) : emptyAllRectangle) + , m_nPart(nPart) + , m_nMode(nMode) + { + } + + OString toString() const + { + if (m_nPart >= -1) + return (isInfinite() ? "EMPTY"_ostr : m_aRectangle.toString()) + + ", " + OString::number(m_nPart) + ", " + OString::number(m_nMode); + else + return (isInfinite() ? "EMPTY"_ostr : m_aRectangle.toString()); + } + + /// Infinite Rectangle is both sides are + /// equal or longer than SfxLokHelper::MaxTwips. + bool isInfinite() const + { + return m_aRectangle.GetWidth() >= SfxLokHelper::MaxTwips && + m_aRectangle.GetHeight() >= SfxLokHelper::MaxTwips; + } + + /// Empty Rectangle is when it has zero dimensions. + bool isEmpty() const + { + return m_aRectangle.IsEmpty(); + } + + static RectangleAndPart Create(const OString& rPayload); + /// Makes sure a rectangle is valid (apparently some code does not like negative coordinates for example). + static tools::Rectangle SanitizedRectangle(tools::Long nLeft, tools::Long nTop, tools::Long nWidth, tools::Long nHeight); + static tools::Rectangle SanitizedRectangle(const tools::Rectangle& rect); + }; + + /// One instance of this per view, handles flushing callbacks + class DESKTOP_DLLPUBLIC CallbackFlushHandler final : public Idle, public SfxLokCallbackInterface + { + public: + explicit CallbackFlushHandler(LibreOfficeKitDocument* pDocument, LibreOfficeKitCallback pCallback, void* pData); + virtual ~CallbackFlushHandler() override; + virtual void Invoke() override; + // TODO This should be dropped and the binary libreOfficeKitViewCallback() variants should be called? + void queue(const int type, const OString& data); + + /// Disables callbacks on this handler. Must match with identical count + /// of enableCallbacks. Used during painting and changing views. + void disableCallbacks() { ++m_nDisableCallbacks; } + /// Enables callbacks on this handler. Must match with identical count + /// of disableCallbacks. Used during painting and changing views. + void enableCallbacks() { --m_nDisableCallbacks; } + /// Returns true iff callbacks are disabled. + bool callbacksDisabled() const { return m_nDisableCallbacks != 0; } + + void addViewStates(int viewId); + void removeViewStates(int viewId); + + void setViewId( int viewId ) { m_viewId = viewId; } + + // SfxLockCallbackInterface + virtual void libreOfficeKitViewCallback(int nType, const OString& pPayload) override; + virtual void libreOfficeKitViewCallbackWithViewId(int nType, const OString& pPayload, int nViewId) override; + virtual void libreOfficeKitViewInvalidateTilesCallback(const tools::Rectangle* pRect, int nPart, int nMode) override; + virtual void libreOfficeKitViewUpdatedCallback(int nType) override; + virtual void libreOfficeKitViewUpdatedCallbackPerViewId(int nType, int nViewId, int nSourceViewId) override; + virtual void libreOfficeKitViewAddPendingInvalidateTiles() override; + virtual void dumpState(rtl::OStringBuffer &rState) override; + + private: + struct CallbackData + { + CallbackData(OString payload) + : PayloadString(payload) + { + } + + CallbackData(OString payload, int viewId) + : PayloadString(payload) + , PayloadObject(viewId) + { + } + + CallbackData(const tools::Rectangle* pRect, int viewId) + : PayloadObject(RectangleAndPart(pRect, viewId, 0)) + { // PayloadString will be done on demand + } + + CallbackData(const tools::Rectangle* pRect, int part, int mode) + : PayloadObject(RectangleAndPart(pRect, part, mode)) + { // PayloadString will be done on demand + } + + const OString& getPayload() const; + /// Update a RectangleAndPart object and update PayloadString if necessary. + void updateRectangleAndPart(const RectangleAndPart& rRectAndPart); + /// Return the parsed RectangleAndPart instance. + const RectangleAndPart& getRectangleAndPart() const; + /// Parse and set the JSON object and return it. Clobbers PayloadString. + boost::property_tree::ptree& setJson(const std::string& payload); + /// Set a Json object and update PayloadString. + void setJson(const boost::property_tree::ptree& rTree); + /// Return the parsed JSON instance. + const boost::property_tree::ptree& getJson() const; + + int getViewId() const; + + bool isEmpty() const + { + return PayloadString.isEmpty() && PayloadObject.which() == 0; + } + void clear() + { + PayloadString.clear(); + PayloadObject = boost::blank(); + } + + /// Validate that the payload and parsed object match. + bool validate() const; + + /// Returns true iff there is cached data. + bool isCached() const { return PayloadObject.which() != 0; } + + private: + mutable OString PayloadString; + + /// The parsed payload cache. Update validate() when changing this. + mutable boost::variant<boost::blank, RectangleAndPart, boost::property_tree::ptree, int> PayloadObject; + }; + + typedef std::vector<int> queue_type1; + typedef std::vector<CallbackData> queue_type2; + + void startTimer(); + bool removeAll(int type); + bool removeAll(int type, const std::function<bool (const CallbackData&)>& rTestFunc); + bool processInvalidateTilesEvent(int type, CallbackData& aCallbackData); + bool processWindowEvent(int type, CallbackData& aCallbackData); + queue_type2::iterator toQueue2(queue_type1::iterator); + queue_type2::reverse_iterator toQueue2(queue_type1::reverse_iterator); + void queue(const int type, CallbackData& data); + void enqueueUpdatedTypes(); + void enqueueUpdatedType( int type, const SfxViewShell* sourceViewShell, int viewId ); + + /** we frequently want to scan the queue, and mostly when we do so, we only care about the element type + so we split the queue in 2 to make the scanning cache friendly. */ + queue_type1 m_queue1; + queue_type2 m_queue2; + std::map<int, OString> m_states; + std::unordered_map<OString, OString> m_lastStateChange; + std::unordered_map<int, std::unordered_map<int, OString>> m_viewStates; + + // For some types only the last message matters (see isUpdatedType()) or only the last message + // per each viewId value matters (see isUpdatedTypePerViewId()), so instead of using push model + // where we'd get flooded by repeated messages (which might be costly to generate and process), + // the preferred way is that libreOfficeKitViewUpdatedCallback() + // or libreOfficeKitViewUpdatedCallbackPerViewId() get called to notify about such a message being + // needed, and we'll set a flag here to fetch the actual message before flushing. + void setUpdatedType( int nType, bool value ); + void setUpdatedTypePerViewId( int nType, int nViewId, int nSourceViewId, bool value ); + void resetUpdatedType( int nType); + void resetUpdatedTypePerViewId( int nType, int nViewId ); + std::vector<bool> m_updatedTypes; // index is type, value is if set + struct PerViewIdData + { + bool set = false; // value is if set + int sourceViewId; + }; + // Flat_map is used in preference to unordered_map because the map is accessed very often. + boost::container::flat_map<int, std::vector<PerViewIdData>> m_updatedTypesPerViewId; // key is view, index is type + + LibreOfficeKitDocument* m_pDocument; + int m_viewId = -1; // view id of the associated SfxViewShell + LibreOfficeKitCallback m_pCallback; + void *m_pData; + int m_nDisableCallbacks; + std::recursive_mutex m_mutex; + class TimeoutIdle : public Timer + { + public: + TimeoutIdle( CallbackFlushHandler* handler ); + virtual void Invoke() override; + private: + CallbackFlushHandler* mHandler; + }; + TimeoutIdle m_TimeoutIdle; + }; + + struct DESKTOP_DLLPUBLIC LibLODocument_Impl : public _LibreOfficeKitDocument + { + css::uno::Reference<css::lang::XComponent> mxComponent; + std::shared_ptr< LibreOfficeKitDocumentClass > m_pDocumentClass; + std::map<size_t, std::shared_ptr<CallbackFlushHandler>> mpCallbackFlushHandlers; + const int mnDocumentId; + std::set<OUString> maFontsMissing; + + explicit LibLODocument_Impl(css::uno::Reference<css::lang::XComponent> xComponent, + int nDocumentId); + ~LibLODocument_Impl(); + }; + + struct DESKTOP_DLLPUBLIC LibLibreOffice_Impl : public _LibreOfficeKit + { + OUString maLastExceptionMsg; + std::shared_ptr< LibreOfficeKitClass > m_pOfficeClass; + oslThread maThread; + LibreOfficeKitCallback mpCallback; + void *mpCallbackData; + int64_t mOptionalFeatures; + std::map<OString, rtl::Reference<LOKInteractionHandler>> mInteractionMap; + + LibLibreOffice_Impl(); + ~LibLibreOffice_Impl(); + + bool hasOptionalFeature(LibreOfficeKitOptionalFeatures const feature) + { + return (mOptionalFeatures & feature) != 0; + } + + void dumpState(rtl::OStringBuffer &aState); + }; + + /// Helper function to extract the value from parameters delimited by + /// comma, like: Name1=Value1,Name2=Value2,Name3=Value3. + /// @param rOptions When extracted, the Param=Value is removed from it. + DESKTOP_DLLPUBLIC OUString extractParameter(OUString& aOptions, std::u16string_view rName); + + /// Helper function to convert JSON to a vector of PropertyValues. + /// Public to be unit-test-able. + DESKTOP_DLLPUBLIC std::vector<com::sun::star::beans::PropertyValue> jsonToPropertyValuesVector(const char* pJSON); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/inc/migration.hxx b/desktop/inc/migration.hxx new file mode 100644 index 0000000000..bbe420f0c3 --- /dev/null +++ b/desktop/inc/migration.hxx @@ -0,0 +1,31 @@ +/* -*- 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 + +namespace desktop +{ +class Migration +{ +public: + static void migrateSettingsIfNecessary(); +}; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/inc/pch/precompiled_deployment.cxx b/desktop/inc/pch/precompiled_deployment.cxx new file mode 100644 index 0000000000..4a35415dfc --- /dev/null +++ b/desktop/inc/pch/precompiled_deployment.cxx @@ -0,0 +1,12 @@ +/* -*- 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 "precompiled_deployment.hxx" + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/inc/pch/precompiled_deployment.hxx b/desktop/inc/pch/precompiled_deployment.hxx new file mode 100644 index 0000000000..bc0dd95fd4 --- /dev/null +++ b/desktop/inc/pch/precompiled_deployment.hxx @@ -0,0 +1,94 @@ +/* -*- 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 has been autogenerated by update_pch.sh. It is possible to edit it + manually (such as when an include file has been moved/renamed/removed). All such + manual changes will be rewritten by the next run of update_pch.sh (which presumably + also fixes all possible problems, so it's usually better to use it). + + Generated on 2021-03-08 13:13:30 using: + ./bin/update_pch desktop deployment --cutoff=3 --exclude:system --exclude:module --exclude:local + + If after updating build fails, use the following command to locate conflicting headers: + ./bin/update_pch_bisect ./desktop/inc/pch/precompiled_deployment.hxx "make desktop.build" --find-conflicts +*/ + +#include <sal/config.h> +#if PCH_LEVEL >= 1 +#include <algorithm> +#include <cstddef> +#include <memory> +#include <optional> +#include <ostream> +#include <string_view> +#include <unordered_map> +#include <vector> +#endif // PCH_LEVEL >= 1 +#if PCH_LEVEL >= 2 +#include <osl/diagnose.h> +#include <osl/file.hxx> +#include <osl/security.hxx> +#include <rtl/bootstrap.hxx> +#include <rtl/byteseq.hxx> +#include <rtl/strbuf.hxx> +#include <rtl/string.hxx> +#include <rtl/textenc.h> +#include <rtl/uri.hxx> +#include <rtl/ustrbuf.hxx> +#include <rtl/ustring.hxx> +#include <sal/log.hxx> +#endif // PCH_LEVEL >= 2 +#if PCH_LEVEL >= 3 +#include <com/sun/star/deployment/DeploymentException.hpp> +#include <com/sun/star/deployment/ExtensionRemovedException.hpp> +#include <com/sun/star/io/XInputStream.hpp> +#include <com/sun/star/lang/IllegalArgumentException.hpp> +#include <com/sun/star/lang/WrappedTargetRuntimeException.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/sdbc/XResultSet.hpp> +#include <com/sun/star/sdbc/XRow.hpp> +#include <com/sun/star/task/XInteractionApprove.hpp> +#include <com/sun/star/ucb/CommandAbortedException.hpp> +#include <com/sun/star/ucb/CommandFailedException.hpp> +#include <com/sun/star/ucb/ContentCreationException.hpp> +#include <com/sun/star/ucb/NameClash.hpp> +#include <com/sun/star/ucb/XCommandEnvironment.hpp> +#include <com/sun/star/uno/Any.hxx> +#include <com/sun/star/uno/Reference.h> +#include <com/sun/star/uno/Reference.hxx> +#include <com/sun/star/uno/RuntimeException.hpp> +#include <com/sun/star/uno/Sequence.hxx> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <com/sun/star/uno/XInterface.hpp> +#include <com/sun/star/util/XUpdatable.hpp> +#include <comphelper/comphelperdllapi.h> +#include <comphelper/sequence.hxx> +#include <cppu/cppudllapi.h> +#include <cppuhelper/compbase.hxx> +#include <cppuhelper/exc_hlp.hxx> +#include <cppuhelper/implbase.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <svl/inettype.hxx> +#include <comphelper/diagnose_ex.hxx> +#include <ucbhelper/content.hxx> +#include <unotools/unotoolsdllapi.h> +#include <xmlscript/xml_helper.hxx> +#endif // PCH_LEVEL >= 3 +#if PCH_LEVEL >= 4 +#include <dp_backend.h> +#include <dp_descriptioninfoset.hxx> +#include <dp_identifier.hxx> +#include <dp_interact.h> +#include <dp_misc_api.hxx> +#include <dp_platform.hxx> +#include <dp_ucb.h> +#endif // PCH_LEVEL >= 4 + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/inc/pch/precompiled_deploymentgui.cxx b/desktop/inc/pch/precompiled_deploymentgui.cxx new file mode 100644 index 0000000000..6a5ec88ebd --- /dev/null +++ b/desktop/inc/pch/precompiled_deploymentgui.cxx @@ -0,0 +1,12 @@ +/* -*- 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 "precompiled_deploymentgui.hxx" + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/inc/pch/precompiled_deploymentgui.hxx b/desktop/inc/pch/precompiled_deploymentgui.hxx new file mode 100644 index 0000000000..fe7d402e7e --- /dev/null +++ b/desktop/inc/pch/precompiled_deploymentgui.hxx @@ -0,0 +1,110 @@ +/* -*- 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 has been autogenerated by update_pch.sh. It is possible to edit it + manually (such as when an include file has been moved/renamed/removed). All such + manual changes will be rewritten by the next run of update_pch.sh (which presumably + also fixes all possible problems, so it's usually better to use it). + + Generated on 2021-03-08 13:13:39 using: + ./bin/update_pch desktop deploymentgui --cutoff=3 --exclude:system --exclude:module --exclude:local + + If after updating build fails, use the following command to locate conflicting headers: + ./bin/update_pch_bisect ./desktop/inc/pch/precompiled_deploymentgui.hxx "make desktop.build" --find-conflicts +*/ + +#include <sal/config.h> +#if PCH_LEVEL >= 1 +#include <algorithm> +#include <cassert> +#include <cstddef> +#include <cstdlib> +#include <initializer_list> +#include <iomanip> +#include <limits.h> +#include <memory> +#include <new> +#include <optional> +#include <ostream> +#include <sstream> +#include <stddef.h> +#include <string> +#include <string_view> +#include <utility> +#include <vector> +#endif // PCH_LEVEL >= 1 +#if PCH_LEVEL >= 2 +#include <osl/conditn.hxx> +#include <osl/diagnose.h> +#include <osl/file.hxx> +#include <osl/interlck.h> +#include <osl/mutex.hxx> +#include <osl/time.h> +#include <rtl/alloc.h> +#include <rtl/locale.h> +#include <rtl/ref.hxx> +#include <rtl/ustrbuf.hxx> +#include <rtl/ustring.hxx> +#include <sal/detail/log.h> +#include <sal/log.hxx> +#include <sal/macros.h> +#include <sal/saldllapi.h> +#include <sal/types.h> +#include <sal/typesizes.h> +#include <vcl/dllapi.h> +#include <vcl/svapp.hxx> +#include <vcl/weld.hxx> +#endif // PCH_LEVEL >= 2 +#if PCH_LEVEL >= 3 +#include <com/sun/star/beans/NamedValue.hpp> +#include <com/sun/star/deployment/DeploymentException.hpp> +#include <com/sun/star/deployment/ExtensionManager.hpp> +#include <com/sun/star/lang/IllegalArgumentException.hpp> +#include <com/sun/star/ucb/CommandFailedException.hpp> +#include <com/sun/star/ucb/XCommandEnvironment.hpp> +#include <com/sun/star/uno/Any.h> +#include <com/sun/star/uno/Any.hxx> +#include <com/sun/star/uno/Reference.h> +#include <com/sun/star/uno/Reference.hxx> +#include <com/sun/star/uno/RuntimeException.hpp> +#include <com/sun/star/uno/Sequence.h> +#include <com/sun/star/uno/Sequence.hxx> +#include <com/sun/star/uno/Type.h> +#include <com/sun/star/uno/Type.hxx> +#include <com/sun/star/uno/XInterface.hpp> +#include <com/sun/star/uno/genfunc.hxx> +#include <comphelper/anytostring.hxx> +#include <comphelper/processfactory.hxx> +#include <cppu/cppudllapi.h> +#include <cppu/unotype.hxx> +#include <cppuhelper/exc_hlp.hxx> +#include <cppuhelper/implbase.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <o3tl/typed_flags_set.hxx> +#include <salhelper/thread.hxx> +#include <tools/degree.hxx> +#include <tools/gen.hxx> +#include <tools/link.hxx> +#include <tools/long.hxx> +#include <tools/solar.h> +#include <tools/toolsdllapi.h> +#include <typelib/typedescription.h> +#include <uno/data.h> +#include <uno/sequence2.h> +#include <unotools/configmgr.hxx> +#endif // PCH_LEVEL >= 3 +#if PCH_LEVEL >= 4 +#include <dp_dependencies.hxx> +#include <dp_identifier.hxx> +#include <dp_misc_api.hxx> +#include <dp_update.hxx> +#endif // PCH_LEVEL >= 4 + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/inc/pch/precompiled_deploymentmisc.cxx b/desktop/inc/pch/precompiled_deploymentmisc.cxx new file mode 100644 index 0000000000..1a2225a936 --- /dev/null +++ b/desktop/inc/pch/precompiled_deploymentmisc.cxx @@ -0,0 +1,12 @@ +/* -*- 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 "precompiled_deploymentmisc.hxx" + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/inc/pch/precompiled_deploymentmisc.hxx b/desktop/inc/pch/precompiled_deploymentmisc.hxx new file mode 100644 index 0000000000..4732ac5720 --- /dev/null +++ b/desktop/inc/pch/precompiled_deploymentmisc.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 has been autogenerated by update_pch.sh. It is possible to edit it + manually (such as when an include file has been moved/renamed/removed). All such + manual changes will be rewritten by the next run of update_pch.sh (which presumably + also fixes all possible problems, so it's usually better to use it). + + Generated on 2021-04-11 19:47:50 using: + ./bin/update_pch desktop deploymentmisc --cutoff=3 --exclude:system --exclude:module --exclude:local + + If after updating build fails, use the following command to locate conflicting headers: + ./bin/update_pch_bisect ./desktop/inc/pch/precompiled_deploymentmisc.hxx "make desktop.build" --find-conflicts +*/ + +#include <sal/config.h> +#if PCH_LEVEL >= 1 +#include <cassert> +#include <cstddef> +#include <cstdlib> +#include <memory> +#include <sstream> +#include <string> +#include <string_view> +#include <utility> +#include <vector> +#endif // PCH_LEVEL >= 1 +#if PCH_LEVEL >= 2 +#include <osl/diagnose.h> +#include <osl/doublecheckedlocking.h> +#include <osl/getglobalmutex.hxx> +#include <osl/interlck.h> +#include <osl/pipe.hxx> +#include <osl/security.hxx> +#include <osl/thread.hxx> +#include <osl/time.h> +#include <rtl/alloc.h> +#include <rtl/bootstrap.hxx> +#include <rtl/digest.h> +#include <rtl/instance.hxx> +#include <rtl/locale.h> +#include <rtl/random.h> +#include <rtl/ref.hxx> +#include <rtl/uri.hxx> +#include <rtl/ustrbuf.hxx> +#include <rtl/ustring.hxx> +#include <sal/detail/log.h> +#include <sal/log.hxx> +#include <sal/saldllapi.h> +#include <sal/types.h> +#endif // PCH_LEVEL >= 2 +#if PCH_LEVEL >= 3 +#include <com/sun/star/lang/XTypeProvider.hpp> +#include <com/sun/star/ucb/XCommandEnvironment.hpp> +#include <com/sun/star/uno/Any.h> +#include <com/sun/star/uno/Reference.hxx> +#include <com/sun/star/uno/Sequence.h> +#include <com/sun/star/uno/Sequence.hxx> +#include <com/sun/star/uno/Type.h> +#include <com/sun/star/uno/XWeak.hpp> +#include <com/sun/star/uno/genfunc.hxx> +#include <comphelper/comphelperdllapi.h> +#include <comphelper/processfactory.hxx> +#include <cppu/unotype.hxx> +#include <cppuhelper/cppuhelperdllapi.h> +#include <cppuhelper/implbase.hxx> +#include <cppuhelper/implbase_ex.hxx> +#include <cppuhelper/weak.hxx> +#include <salhelper/linkhelper.hxx> +#include <typelib/typedescription.h> +#include <uno/data.h> +#endif // PCH_LEVEL >= 3 +#if PCH_LEVEL >= 4 +#include <dp_descriptioninfoset.hxx> +#include <dp_misc_api.hxx> +#include <dp_version.hxx> +#endif // PCH_LEVEL >= 4 + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/inc/pch/precompiled_sofficeapp.cxx b/desktop/inc/pch/precompiled_sofficeapp.cxx new file mode 100644 index 0000000000..4ac33f45ee --- /dev/null +++ b/desktop/inc/pch/precompiled_sofficeapp.cxx @@ -0,0 +1,12 @@ +/* -*- 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 "precompiled_sofficeapp.hxx" + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/inc/pch/precompiled_sofficeapp.hxx b/desktop/inc/pch/precompiled_sofficeapp.hxx new file mode 100644 index 0000000000..3635613937 --- /dev/null +++ b/desktop/inc/pch/precompiled_sofficeapp.hxx @@ -0,0 +1,197 @@ +/* -*- 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 has been autogenerated by update_pch.sh. It is possible to edit it + manually (such as when an include file has been moved/renamed/removed). All such + manual changes will be rewritten by the next run of update_pch.sh (which presumably + also fixes all possible problems, so it's usually better to use it). + + Generated on 2021-04-08 13:55:49 using: + ./bin/update_pch desktop sofficeapp --cutoff=6 --exclude:system --include:module --include:local + + If after updating build fails, use the following command to locate conflicting headers: + ./bin/update_pch_bisect ./desktop/inc/pch/precompiled_sofficeapp.hxx "make desktop.build" --find-conflicts +*/ + +#include <sal/config.h> +#if PCH_LEVEL >= 1 +#include <algorithm> +#include <cassert> +#include <chrono> +#include <cmath> +#include <cstddef> +#include <cstdlib> +#include <cstring> +#include <deque> +#include <float.h> +#include <functional> +#include <initializer_list> +#include <iomanip> +#include <limits.h> +#include <limits> +#include <map> +#include <math.h> +#include <memory> +#include <new> +#include <optional> +#include <ostream> +#include <stddef.h> +#include <string.h> +#include <string> +#include <string_view> +#include <type_traits> +#include <utility> +#include <vector> +#include <boost/property_tree/ptree_fwd.hpp> +#endif // PCH_LEVEL >= 1 +#if PCH_LEVEL >= 2 +#include <osl/conditn.hxx> +#include <osl/diagnose.h> +#include <osl/doublecheckedlocking.h> +#include <osl/endian.h> +#include <osl/file.hxx> +#include <osl/getglobalmutex.hxx> +#include <osl/interlck.h> +#include <osl/mutex.h> +#include <osl/mutex.hxx> +#include <osl/pipe.h> +#include <osl/pipe.hxx> +#include <osl/security.h> +#include <osl/security.hxx> +#include <osl/thread.h> +#include <osl/time.h> +#include <rtl/alloc.h> +#include <rtl/bootstrap.hxx> +#include <rtl/byteseq.hxx> +#include <rtl/character.hxx> +#include <rtl/digest.h> +#include <rtl/instance.hxx> +#include <rtl/math.h> +#include <rtl/process.h> +#include <rtl/ref.hxx> +#include <rtl/strbuf.hxx> +#include <rtl/string.h> +#include <rtl/string.hxx> +#include <rtl/stringconcat.hxx> +#include <rtl/stringutils.hxx> +#include <rtl/textcvt.h> +#include <rtl/textenc.h> +#include <rtl/uri.hxx> +#include <rtl/ustrbuf.h> +#include <rtl/ustrbuf.hxx> +#include <rtl/ustring.h> +#include <rtl/ustring.hxx> +#include <sal/backtrace.hxx> +#include <sal/detail/log.h> +#include <sal/log.hxx> +#include <sal/macros.h> +#include <sal/saldllapi.h> +#include <sal/types.h> +#include <sal/typesizes.h> +#include <vcl/Scanline.hxx> +#include <vcl/alpha.hxx> +#include <vcl/bitmap.hxx> +#include <vcl/bitmap/BitmapTypes.hxx> +#include <vcl/bitmapex.hxx> +#include <vcl/checksum.hxx> +#include <vcl/dllapi.h> +#include <comphelper/errcode.hxx> +#include <vcl/fntstyle.hxx> +#include <vcl/font.hxx> +#include <vcl/mapmod.hxx> +#include <vcl/region.hxx> +#include <vcl/vclenum.hxx> +#include <vcl/vclptr.hxx> +#endif // PCH_LEVEL >= 2 +#if PCH_LEVEL >= 3 +#include <basegfx/basegfxdllapi.h> +#include <basegfx/color/bcolor.hxx> +#include <basegfx/numeric/ftools.hxx> +#include <basegfx/point/b2dpoint.hxx> +#include <basegfx/point/b2ipoint.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <basegfx/polygon/b2dpolypolygon.hxx> +#include <basegfx/range/b2drange.hxx> +#include <basegfx/range/basicrange.hxx> +#include <basegfx/tuple/b2dtuple.hxx> +#include <basegfx/tuple/b2ituple.hxx> +#include <basegfx/tuple/b3dtuple.hxx> +#include <basegfx/utils/common.hxx> +#include <basegfx/vector/b2dvector.hxx> +#include <basegfx/vector/b2enums.hxx> +#include <basegfx/vector/b2ivector.hxx> +#include <basic/basicdllapi.h> +#include <basic/sbxcore.hxx> +#include <basic/sbxdef.hxx> +#include <com/sun/star/lang/DisposedException.hpp> +#include <com/sun/star/lang/EventObject.hpp> +#include <com/sun/star/lang/XTypeProvider.hpp> +#include <com/sun/star/uno/Any.h> +#include <com/sun/star/uno/Any.hxx> +#include <com/sun/star/uno/Reference.h> +#include <com/sun/star/uno/Reference.hxx> +#include <com/sun/star/uno/RuntimeException.hpp> +#include <com/sun/star/uno/Sequence.h> +#include <com/sun/star/uno/Sequence.hxx> +#include <com/sun/star/uno/Type.h> +#include <com/sun/star/uno/Type.hxx> +#include <com/sun/star/uno/TypeClass.hdl> +#include <com/sun/star/uno/XInterface.hpp> +#include <com/sun/star/uno/XWeak.hpp> +#include <com/sun/star/uno/genfunc.h> +#include <com/sun/star/uno/genfunc.hxx> +#include <comphelper/comphelperdllapi.h> +#include <comphelper/processfactory.hxx> +#include <cppu/cppudllapi.h> +#include <cppu/unotype.hxx> +#include <cppuhelper/cppuhelperdllapi.h> +#include <cppuhelper/implbase.hxx> +#include <cppuhelper/implbase_ex.hxx> +#include <cppuhelper/implbase_ex_post.hxx> +#include <cppuhelper/implbase_ex_pre.hxx> +#include <cppuhelper/weak.hxx> +#include <i18nlangtag/lang.h> +#include <o3tl/cow_wrapper.hxx> +#include <o3tl/strong_int.hxx> +#include <o3tl/typed_flags_set.hxx> +#include <o3tl/underlyingenumvalue.hxx> +#include <salhelper/thread.hxx> +#include <sfx2/dllapi.h> +#include <svl/hint.hxx> +#include <svl/poolitem.hxx> +#include <svl/svldllapi.h> +#include <svl/typedwhich.hxx> +#include <svtools/svtdllapi.h> +#include <tools/color.hxx> +#include <tools/degree.hxx> +#include <comphelper/diagnose_ex.hxx> +#include <tools/fontenum.hxx> +#include <tools/gen.hxx> +#include <tools/link.hxx> +#include <tools/long.hxx> +#include <tools/mapunit.hxx> +#include <tools/poly.hxx> +#include <tools/ref.hxx> +#include <tools/solar.h> +#include <tools/toolsdllapi.h> +#include <typelib/typeclass.h> +#include <typelib/typedescription.h> +#include <typelib/uik.h> +#include <uno/any2.h> +#include <uno/data.h> +#include <uno/sequence2.h> +#include <unotools/options.hxx> +#include <unotools/unotoolsdllapi.h> +#endif // PCH_LEVEL >= 3 +#if PCH_LEVEL >= 4 +#include <app.hxx> +#endif // PCH_LEVEL >= 4 + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/inc/strings.hrc b/desktop/inc/strings.hrc new file mode 100644 index 0000000000..eb4e238d1a --- /dev/null +++ b/desktop/inc/strings.hrc @@ -0,0 +1,192 @@ +/* -*- 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 + +#define NC_(Context, String) TranslateId(Context, u8##String) + +#define RID_STR_COPYING_PACKAGE NC_("RID_STR_COPYING_PACKAGE", "Copying: ") +#define RID_STR_ERROR_WHILE_ADDING NC_("RID_STR_ERROR_WHILE_ADDING", "Error while adding: ") +#define RID_STR_ERROR_WHILE_REMOVING NC_("RID_STR_ERROR_WHILE_REMOVING", "Error while removing: ") +#define RID_STR_PACKAGE_ALREADY_ADDED NC_("RID_STR_PACKAGE_ALREADY_ADDED", "Extension has already been added: ") +#define RID_STR_NO_SUCH_PACKAGE NC_("RID_STR_NO_SUCH_PACKAGE", "There is no such extension deployed: ") +#define RID_STR_SYNCHRONIZING_REPOSITORY NC_("RID_STR_SYNCHRONIZING_REPOSITORY", "Synchronizing repository for %NAME extensions") + +#define RID_STR_REGISTERING_PACKAGE NC_("RID_STR_REGISTERING_PACKAGE", "Enabling: ") +#define RID_STR_REVOKING_PACKAGE NC_("RID_STR_REVOKING_PACKAGE", "Disabling: ") +#define RID_STR_CANNOT_DETECT_MEDIA_TYPE NC_("RID_STR_CANNOT_DETECT_MEDIA_TYPE", "Cannot detect media-type: ") +#define RID_STR_UNSUPPORTED_MEDIA_TYPE NC_("RID_STR_UNSUPPORTED_MEDIA_TYPE", "This media-type is not supported: ") +#define RID_STR_ERROR_WHILE_REGISTERING NC_("RID_STR_ERROR_WHILE_REGISTERING", "An error occurred while enabling: ") +#define RID_STR_ERROR_WHILE_REVOKING NC_("RID_STR_ERROR_WHILE_REVOKING", "An error occurred while disabling: ") + +#define RID_STR_CONF_SCHEMA NC_("RID_STR_CONF_SCHEMA", "Configuration Schema") +#define RID_STR_CONF_DATA NC_("RID_STR_CONF_DATA", "Configuration Data") + +#define RID_STR_BASIC_LIB NC_("RID_STR_BASIC_LIB", "Basic Library") +#define RID_STR_DIALOG_LIB NC_("RID_STR_DIALOG_LIB", "Dialog Library") +#define RID_STR_CANNOT_DETERMINE_LIBNAME NC_("RID_STR_CANNOT_DETERMINE_LIBNAME", "The library name could not be determined.") + +#define RID_STR_PACKAGE_BUNDLE NC_("RID_STR_PACKAGE_BUNDLE", "Extension") +#define RID_STR_ALL_SUPPORTED NC_("RID_STR_PACKAGE_BUNDLE", "All supported files") + +#define RID_STR_DYN_COMPONENT NC_("RID_STR_DYN_COMPONENT", "UNO Dynamic Library Component") +#define RID_STR_JAVA_COMPONENT NC_("RID_STR_JAVA_COMPONENT", "UNO Java Component") +#define RID_STR_PYTHON_COMPONENT NC_("RID_STR_PYTHON_COMPONENT", "UNO Python Component") +#define RID_STR_COMPONENTS NC_("RID_STR_COMPONENTS", "UNO Components") +#define RID_STR_RDB_TYPELIB NC_("RID_STR_RDB_TYPELIB", "UNO RDB Type Library") +#define RID_STR_JAVA_TYPELIB NC_("RID_STR_JAVA_TYPELIB", "UNO Java Type Library") + +#define RID_STR_SFWK_LIB NC_("RID_STR_SFWK_LIB", "%MACROLANG Library") + +#define RID_STR_HELP NC_("RID_STR_HELP", "Help") +#define RID_STR_HELPPROCESSING_GENERAL_ERROR NC_("RID_STR_HELPPROCESSING_GENERAL_ERROR", "The extension cannot be installed because:\n") +#define RID_STR_HELPPROCESSING_XMLPARSING_ERROR NC_("RID_STR_HELPPROCESSING_XMLPARSING_ERROR", "The extension will not be installed because an error occurred in the Help files:\n") + +#define RID_STR_ADD_PACKAGES NC_("RID_STR_ADD_PACKAGES", "Add Extension(s)") +#define RID_CTX_ITEM_REMOVE NC_("RID_CTX_ITEM_REMOVE", "~Remove") +#define RID_CTX_ITEM_ENABLE NC_("RID_CTX_ITEM_ENABLE", "~Enable") +#define RID_CTX_ITEM_DISABLE NC_("RID_CTX_ITEM_DISABLE", "~Disable") +#define RID_CTX_ITEM_CHECK_UPDATE NC_("RID_CTX_ITEM_CHECK_UPDATE", "~Update...") +#define RID_STR_ADDING_PACKAGES NC_("RID_STR_ADDING_PACKAGES", "Adding %EXTENSION_NAME") +#define RID_STR_REMOVING_PACKAGES NC_("RID_STR_REMOVING_PACKAGES", "Removing %EXTENSION_NAME") +#define RID_STR_ENABLING_PACKAGES NC_("RID_STR_ENABLING_PACKAGES", "Enabling %EXTENSION_NAME") +#define RID_STR_DISABLING_PACKAGES NC_("RID_STR_DISABLING_PACKAGES", "Disabling %EXTENSION_NAME") +#define RID_STR_ACCEPT_LICENSE NC_("RID_STR_ACCEPT_LICENSE", "Accept license for %EXTENSION_NAME") +#define RID_STR_ERROR_UNKNOWN_STATUS NC_("RID_STR_ERROR_UNKNOWN_STATUS", "Error: The status of this extension is unknown") +#define RID_STR_CLOSE_BTN NC_("RID_STR_CLOSE_BTN", "Close") +#define RID_STR_EXIT_BTN NC_("RID_STR_EXIT_BTN", "Quit") +#define RID_STR_NO_ADMIN_PRIVILEGE NC_("RID_STR_NO_ADMIN_PRIVILEGE", "%PRODUCTNAME has been updated to a new version. " \ + "Some shared %PRODUCTNAME extensions are not compatible with this version and need to be updated before %PRODUCTNAME can be started.\n\n" \ + "Updating of shared extension requires administrator privileges. Contact your system administrator to update the following shared extensions:") +#define RID_STR_ERROR_MISSING_DEPENDENCIES NC_("RID_STR_ERROR_MISSING_DEPENDENCIES", "The extension cannot be enabled as the following system dependencies are not fulfilled:") +#define RID_STR_ERROR_MISSING_LICENSE NC_("RID_STR_ERROR_MISSING_LICENSE", "This extension is disabled because you haven't accepted the license yet.\n") +#define RID_STR_SHOW_LICENSE_CMD NC_("RID_STR_SHOW_LICENSE_CMD", "Show license") +#define RID_STR_WARNING_INSTALL_EXTENSION NC_("RID_STR_WARNING_INSTALL_EXTENSION", "You are about to install the extension '%NAME'.\n" \ + "Click 'OK' to proceed with the installation.\n" \ + "Click 'Cancel' to stop the installation.") +#define RID_STR_WARNING_INSTALL_EXTENSION_DISABLED NC_("RID_STR_WARNING_INSTALL_EXTENSION_DISABLED", "Extension installation is currently disabled. " \ + "Please consult your system administrator for more information.") +#define RID_STR_WARNING_REMOVE_EXTENSION_DISABLED NC_("RID_STR_WARNING_REMOVE_EXTENSION_DISABLED", "Extension removal is currently disabled. " \ + "Please consult your system administrator for more information.") +#define RID_STR_WARNING_REMOVE_EXTENSION NC_("RID_STR_WARNING_REMOVE_EXTENSION", "You are about to remove the extension '%NAME'.\n" \ + "Click 'OK' to remove the extension.\n" \ + "Click 'Cancel' to stop removing the extension.") +#define RID_STR_WARNING_REMOVE_SHARED_EXTENSION NC_("RID_STR_WARNING_REMOVE_SHARED_EXTENSION", "Make sure that no further users are working with the same " \ + "%PRODUCTNAME, when changing shared extensions in a multi user environment.\n" \ + "Click 'OK' to remove the extension.\n" \ + "Click 'Cancel' to stop removing the extension.") +#define RID_STR_WARNING_ENABLE_SHARED_EXTENSION NC_("RID_STR_WARNING_ENABLE_SHARED_EXTENSION", "Make sure that no further users are working with the same " \ + "%PRODUCTNAME, when changing shared extensions in a multi user environment.\n" \ + "Click 'OK' to enable the extension.\n" \ + "Click 'Cancel' to stop enabling the extension.") +#define RID_STR_WARNING_DISABLE_SHARED_EXTENSION NC_("RID_STR_WARNING_DISABLE_SHARED_EXTENSION", "Make sure that no further users are working with the same " \ + "%PRODUCTNAME, when changing shared extensions in a multi user environment.\n" \ + "Click 'OK' to disable the extension.\n" \ + "Click 'Cancel' to stop disabling the extension.") +#define RID_STR_UNSUPPORTED_PLATFORM NC_("RID_STR_UNSUPPORTED_PLATFORM", "The extension '%Name' does not work on this computer.") + +#define RID_DLG_UPDATE_INSTALL_INSTALLING NC_("RID_DLG_UPDATE_INSTALL_INSTALLING", "Installing extensions...") +#define RID_DLG_UPDATE_INSTALL_FINISHED NC_("RID_DLG_UPDATE_INSTALL_FINISHED", "Installation finished") +#define RID_DLG_UPDATE_INSTALL_NO_ERRORS NC_("RID_DLG_UPDATE_INSTALL_NO_ERRORS", "No errors.") +#define RID_DLG_UPDATE_INSTALL_ERROR_DOWNLOAD NC_("RID_DLG_UPDATE_INSTALL_ERROR_DOWNLOAD", "Error while downloading extension %NAME. ") +#define RID_DLG_UPDATE_INSTALL_THIS_ERROR_OCCURRED NC_("RID_DLG_UPDATE_INSTALL_THIS_ERROR_OCCURRED", "The error message is: ") +#define RID_DLG_UPDATE_INSTALL_ERROR_INSTALLATION NC_("RID_DLG_UPDATE_INSTALL_ERROR_INSTALLATION", "Error while installing extension %NAME. ") +#define RID_DLG_UPDATE_INSTALL_ERROR_LIC_DECLINED NC_("RID_DLG_UPDATE_INSTALL_ERROR_LIC_DECLINED", "The license agreement for extension %NAME was refused. ") +#define RID_DLG_UPDATE_INSTALL_EXTENSION_NOINSTALL NC_("RID_DLG_UPDATE_INSTALL_EXTENSION_NOINSTALL", "The extension will not be installed.") + +#define RID_DEPLOYMENT_DEPENDENCIES_UNKNOWN NC_("RID_DEPLOYMENT_DEPENDENCIES_UNKNOWN", "Unknown") +#define RID_DEPLOYMENT_DEPENDENCIES_OOO_MIN NC_("RID_DEPLOYMENT_DEPENDENCIES_OOO_MIN", "Extension requires at least OpenOffice.org reference version %VERSION") +#define RID_DEPLOYMENT_DEPENDENCIES_OOO_MAX NC_("RID_DEPLOYMENT_DEPENDENCIES_OOO_MAX", "Extension does not support OpenOffice.org reference versions greater than %VERSION") +#define RID_DEPLOYMENT_DEPENDENCIES_LO_MIN NC_("RID_DEPLOYMENT_DEPENDENCIES_LO_MIN", "Extension requires at least %PRODUCTNAME version %VERSION") +#define RID_DEPLOYMENT_DEPENDENCIES_LO_MAX NC_("RID_DEPLOYMENT_DEPENDENCIES_LO_MAX", "Extension does not support %PRODUCTNAME versions greater than %VERSION") + +#define RID_STR_WARNING_VERSION_LESS NC_("RID_STR_WARNING_VERSION_LESS", "You are about to install version $NEW of the extension '$NAME'.\n" \ + "The newer version $DEPLOYED is already installed.\n"\ + "Click 'OK' to replace the installed extension.\n"\ + "Click 'Cancel' to stop the installation.") +#define RID_STR_WARNINGBOX_VERSION_LESS_DIFFERENT_NAMES NC_("RID_STR_WARNINGBOX_VERSION_LESS_DIFFERENT_NAMES", "You are about to install version $NEW of the extension '$NAME'.\n" \ + "The newer version $DEPLOYED, named '$OLDNAME', is already installed.\n" \ + "Click 'OK' to replace the installed extension.\n" \ + "Click 'Cancel' to stop the installation.") +#define RID_STR_WARNING_VERSION_EQUAL NC_("RID_STR_WARNING_VERSION_EQUAL", "You are about to install version $NEW of the extension '$NAME'.\n" \ + "That version is already installed.\n" \ + "Click 'OK' to replace the installed extension.\n" \ + "Click 'Cancel' to stop the installation.") +#define RID_STR_WARNINGBOX_VERSION_EQUAL_DIFFERENT_NAMES NC_("RID_STR_WARNINGBOX_VERSION_EQUAL_DIFFERENT_NAMES", "You are about to install version $NEW of the extension '$NAME'.\n" \ + "That version, named '$OLDNAME', is already installed.\n" \ + "Click 'OK' to replace the installed extension.\n" \ + "Click 'Cancel' to stop the installation.") +#define RID_STR_WARNING_VERSION_GREATER NC_("RID_STR_WARNING_VERSION_GREATER", "You are about to install version $NEW of the extension '$NAME'.\n" \ + "The older version $DEPLOYED is already installed.\n" \ + "Click 'OK' to replace the installed extension.\n" \ + "Click 'Cancel' to stop the installation.") +#define RID_STR_WARNINGBOX_VERSION_GREATER_DIFFERENT_NAMES NC_("RID_STR_WARNINGBOX_VERSION_GREATER_DIFFERENT_NAMES", "You are about to install version $NEW of the extension '$NAME'.\n" \ + "The older version $DEPLOYED, named '$OLDNAME', is already installed.\n" \ + "Click 'OK' to replace the installed extension.\n" \ + "Click 'Cancel' to stop the installation.") + +#define RID_DLG_UPDATE_NONE NC_("RID_DLG_UPDATE_NONE", "No new updates are available.") +#define RID_DLG_UPDATE_NOINSTALLABLE NC_("RID_DLG_UPDATE_NOINSTALLABLE", "No installable updates are available. To see ignored or disabled updates, mark the check box 'Show all updates'.") +#define RID_DLG_UPDATE_FAILURE NC_("RID_DLG_UPDATE_FAILURE", "An error occurred:") +#define RID_DLG_UPDATE_UNKNOWNERROR NC_("RID_DLG_UPDATE_UNKNOWNERROR", "Unknown error.") +#define RID_DLG_UPDATE_NODESCRIPTION NC_("RID_DLG_UPDATE_NODESCRIPTION", "No more details are available for this update.") +#define RID_DLG_UPDATE_NOINSTALL NC_("RID_DLG_UPDATE_NOINSTALL", "The extension cannot be updated because:") +#define RID_DLG_UPDATE_NODEPENDENCY NC_("RID_DLG_UPDATE_NODEPENDENCY", "Required %PRODUCTNAME version doesn't match:") +#define RID_DLG_UPDATE_NODEPENDENCY_CUR_VER NC_("RID_DLG_UPDATE_NODEPENDENCY_CUR_VER", "You have %PRODUCTNAME %VERSION") +#define RID_DLG_UPDATE_BROWSERBASED NC_("RID_DLG_UPDATE_BROWSERBASED", "browser based update") +#define RID_DLG_UPDATE_VERSION NC_("RID_DLG_UPDATE_VERSION", "Version") +#define RID_DLG_UPDATE_IGNORED_UPDATE NC_("RID_DLG_UPDATE_IGNORED_UPDATE", "This update will be ignored.\n") + +#define STR_BOOTSTRAP_ERR_CANNOT_START NC_("STR_BOOTSTRAP_ERR_CANNOT_START", "The application cannot be started. ") +#define STR_BOOTSTRAP_ERR_DIR_MISSING NC_("STR_BOOTSTRAP_ERR_DIR_MISSING", "The configuration directory \"$1\" could not be found.") +#define STR_BOOTSTRAP_ERR_PATH_INVALID NC_("STR_BOOTSTRAP_ERR_PATH_INVALID", "The installation path is invalid.") +#define STR_BOOTSTRAP_ERR_INTERNAL NC_("STR_BOOTSTRAP_ERR_INTERNAL", "An internal error occurred.") +#define STR_BOOTSTRAP_ERR_FILE_CORRUPT NC_("STR_BOOTSTRAP_ERR_FILE_CORRUPT", "The configuration file \"$1\" is corrupt.") +#define STR_BOOTSTRAP_ERR_FILE_MISSING NC_("STR_BOOTSTRAP_ERR_FILE_MISSING", "The configuration file \"$1\" was not found.") +#define STR_BOOTSTRAP_ERR_NO_SUPPORT NC_("STR_BOOTSTRAP_ERR_NO_SUPPORT", "The configuration file \"$1\" does not support the current version.") +#define STR_BOOTSTRAP_ERR_LANGUAGE_MISSING NC_("STR_BOOTSTRAP_ERR_LANGUAGE_MISSING", "The user interface language cannot be determined.") +#define STR_BOOTSTRAP_ERR_USERINSTALL_FAILED NC_("STR_BOOTSTRAP_ERR_USERINSTALL_FAILED", "User installation could not be completed. ") +#define STR_BOOTSTRAP_ERR_NO_CFG_SERVICE NC_("STR_BOOTSTRAP_ERR_NO_CFG_SERVICE", "The configuration service is not available.") +#define STR_BOOTSTRAP_ERR_2NDOFFICE_WITHCAT NC_("STR_BOOTSTRAP_ERR_2NDOFFICE_WITHCAT", "There is already another %PRODUCTNAME instance running. Please close all %PRODUCTNAME processes before running with the '--cat' or '--script-cat' option.") +#define STR_ASK_START_SETUP_MANUALLY NC_("STR_ASK_START_SETUP_MANUALLY", "Start the setup application to repair the installation from the CD or the folder containing the installation packages.") +#define STR_CONFIG_ERR_ACCESS_GENERAL NC_("STR_CONFIG_ERR_ACCESS_GENERAL", "A general error occurred while accessing your central configuration. ") +#define STR_LO_MUST_BE_RESTARTED NC_("STR_LO_MUST_BE_RESTARTED", "%PRODUCTNAME must unfortunately be manually restarted once after installation or update." ) +#define STR_QUERY_USERDATALOCKED NC_("STR_QUERY_USERDATALOCKED", "Either another instance of %PRODUCTNAME is accessing your personal settings or your personal settings are locked.\nSimultaneous access can lead to inconsistencies in your personal settings. Before continuing, you should make sure user '$u' closes %PRODUCTNAME on host '$h'.\n\nDo you really want to continue?") +#define STR_TITLE_USERDATALOCKED NC_("STR_TITLE_USERDATALOCKED", "%PRODUCTNAME %PRODUCTVERSION") +#define STR_ERR_PRINTDISABLED NC_("STR_ERR_PRINTDISABLED", "Printing is disabled. No documents can be printed.") +#define STR_BOOTSTRAP_ERR_NO_PATHSET_SERVICE NC_("STR_BOOTSTRAP_ERR_NO_PATHSET_SERVICE", "The path manager is not available.\n") +#define STR_BOOTSTRAP_ERR_NOTENOUGHDISKSPACE NC_("STR_BOOTSTRAP_ERR_NOTENOUGHDISKSPACE", "%PRODUCTNAME user installation could not be completed due to insufficient free disk space. Please free more disc space at the following location and restart %PRODUCTNAME:\n\n") +#define STR_BOOTSTRAP_ERR_NOACCESSRIGHTS NC_("STR_BOOTSTRAP_ERR_NOACCESSRIGHTS", "%PRODUCTNAME user installation could not be processed due to missing access rights. Please make sure that you have sufficient access rights for the following location and restart %PRODUCTNAME:\n\n") + +#define RID_STR_UNOPKG_ACCEPT_LIC_1 NC_("RID_STR_UNOPKG_ACCEPT_LIC_1", "Extension Software License Agreement of $NAME:") +#define RID_STR_UNOPKG_ACCEPT_LIC_2 NC_("RID_STR_UNOPKG_ACCEPT_LIC_2", "Read the complete License Agreement displayed above. " \ + "Accept the License Agreement by typing \"yes\" on the console " \ + "then press the Return key. Type \"no\" to decline and to abort the " \ + "extension setup.") +#define RID_STR_UNOPKG_ACCEPT_LIC_3 NC_("RID_STR_UNOPKG_ACCEPT_LIC_3", "[Enter \"yes\" or \"no\"]:") +#define RID_STR_UNOPKG_ACCEPT_LIC_4 NC_("RID_STR_UNOPKG_ACCEPT_LIC_4", "Your input was not correct. Please enter \"yes\" or \"no\":") +#define RID_STR_UNOPKG_ACCEPT_LIC_YES NC_("RID_STR_UNOPKG_ACCEPT_LIC_YES", "YES") +#define RID_STR_UNOPKG_ACCEPT_LIC_Y NC_("RID_STR_UNOPKG_ACCEPT_LIC_Y", "Y") +#define RID_STR_UNOPKG_ACCEPT_LIC_NO NC_("RID_STR_UNOPKG_ACCEPT_LIC_NO", "NO") +#define RID_STR_UNOPKG_ACCEPT_LIC_N NC_("RID_STR_UNOPKG_ACCEPT_LIC_N", "N") +#define RID_STR_CONCURRENTINSTANCE NC_("RID_STR_CONCURRENTINSTANCE", "unopkg cannot be started. The lock file indicates it is already running. " \ + "If this does not apply, delete the lock file at:") +#define RID_STR_UNOPKG_ERROR NC_("RID_STR_UNOPKG_ERROR", "ERROR: ") + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/inc/strings.hxx b/desktop/inc/strings.hxx new file mode 100644 index 0000000000..09582c6c5e --- /dev/null +++ b/desktop/inc/strings.hxx @@ -0,0 +1,16 @@ +/* -*- 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/. + */ + +#pragma once + +#include <rtl/ustring.hxx> + +inline constexpr OUString RID_APPTITLE = u"%PRODUCTNAME %PRODUCTVERSION%PRODUCTEXTENSION"_ustr; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/desktop/lokclipboard.component b/desktop/lokclipboard.component new file mode 100644 index 0000000000..a2c7c63284 --- /dev/null +++ b/desktop/lokclipboard.component @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + * 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 . +--> + +<component loader="com.sun.star.loader.SharedLibrary" environment="@CPPU_ENV@" + xmlns="http://openoffice.org/2010/uno-components"> + <implementation name="com.sun.star.datatransfer.LOKClipboard" + constructor="desktop_LOKClipboard_get_implementation"> + <service name="com.sun.star.datatransfer.clipboard.LokClipboard"/> + </implementation> +</component> diff --git a/desktop/qa/data/2slides.odp b/desktop/qa/data/2slides.odp Binary files differnew file mode 100644 index 0000000000..0e3f8758ff --- /dev/null +++ b/desktop/qa/data/2slides.odp diff --git a/desktop/qa/data/3page.odg b/desktop/qa/data/3page.odg Binary files differnew file mode 100644 index 0000000000..1fad913e04 --- /dev/null +++ b/desktop/qa/data/3page.odg diff --git a/desktop/qa/data/BlankDrawDocument.odg b/desktop/qa/data/BlankDrawDocument.odg Binary files differnew file mode 100644 index 0000000000..19ae49d63b --- /dev/null +++ b/desktop/qa/data/BlankDrawDocument.odg diff --git a/desktop/qa/data/SearchIndexResultShapeTest.odt b/desktop/qa/data/SearchIndexResultShapeTest.odt Binary files differnew file mode 100644 index 0000000000..4298eb8ad0 --- /dev/null +++ b/desktop/qa/data/SearchIndexResultShapeTest.odt diff --git a/desktop/qa/data/SearchIndexResultTest.odt b/desktop/qa/data/SearchIndexResultTest.odt Binary files differnew file mode 100644 index 0000000000..58ed3a0f54 --- /dev/null +++ b/desktop/qa/data/SearchIndexResultTest.odt diff --git a/desktop/qa/data/ThemeDocument.docx b/desktop/qa/data/ThemeDocument.docx Binary files differnew file mode 100644 index 0000000000..4dbba883d9 --- /dev/null +++ b/desktop/qa/data/ThemeDocument.docx diff --git a/desktop/qa/data/blank_presentation.odp b/desktop/qa/data/blank_presentation.odp Binary files differnew file mode 100644 index 0000000000..a7d57a48e4 --- /dev/null +++ b/desktop/qa/data/blank_presentation.odp diff --git a/desktop/qa/data/blank_text.docx b/desktop/qa/data/blank_text.docx Binary files differnew file mode 100644 index 0000000000..028a35b6ca --- /dev/null +++ b/desktop/qa/data/blank_text.docx diff --git a/desktop/qa/data/blank_text.odt b/desktop/qa/data/blank_text.odt Binary files differnew file mode 100644 index 0000000000..00b92d785a --- /dev/null +++ b/desktop/qa/data/blank_text.odt diff --git a/desktop/qa/data/certificate.der b/desktop/qa/data/certificate.der Binary files differnew file mode 100644 index 0000000000..10e3ade13e --- /dev/null +++ b/desktop/qa/data/certificate.der diff --git a/desktop/qa/data/certificatePrivateKey.der b/desktop/qa/data/certificatePrivateKey.der Binary files differnew file mode 100644 index 0000000000..7a5599c825 --- /dev/null +++ b/desktop/qa/data/certificatePrivateKey.der diff --git a/desktop/qa/data/comments.odt b/desktop/qa/data/comments.odt Binary files differnew file mode 100644 index 0000000000..1bcdcc0385 --- /dev/null +++ b/desktop/qa/data/comments.odt diff --git a/desktop/qa/data/hidden-row.ods b/desktop/qa/data/hidden-row.ods Binary files differnew file mode 100644 index 0000000000..25fe89865d --- /dev/null +++ b/desktop/qa/data/hidden-row.ods diff --git a/desktop/qa/data/intermediateRootCA.der b/desktop/qa/data/intermediateRootCA.der Binary files differnew file mode 100644 index 0000000000..9adf7f82e5 --- /dev/null +++ b/desktop/qa/data/intermediateRootCA.der diff --git a/desktop/qa/data/objects.odt b/desktop/qa/data/objects.odt Binary files differnew file mode 100644 index 0000000000..45c2b39cc1 --- /dev/null +++ b/desktop/qa/data/objects.odt diff --git a/desktop/qa/data/paste.jpg b/desktop/qa/data/paste.jpg Binary files differnew file mode 100644 index 0000000000..ca9183e9d3 --- /dev/null +++ b/desktop/qa/data/paste.jpg diff --git a/desktop/qa/data/rootCA.der b/desktop/qa/data/rootCA.der Binary files differnew file mode 100644 index 0000000000..30fc66e26f --- /dev/null +++ b/desktop/qa/data/rootCA.der diff --git a/desktop/qa/data/search.ods b/desktop/qa/data/search.ods Binary files differnew file mode 100644 index 0000000000..ea1d731538 --- /dev/null +++ b/desktop/qa/data/search.ods diff --git a/desktop/qa/data/sheet_with_image.ods b/desktop/qa/data/sheet_with_image.ods Binary files differnew file mode 100644 index 0000000000..00c0019cb8 --- /dev/null +++ b/desktop/qa/data/sheet_with_image.ods diff --git a/desktop/qa/data/sheets.ods b/desktop/qa/data/sheets.ods Binary files differnew file mode 100644 index 0000000000..3f43fa3a3d --- /dev/null +++ b/desktop/qa/data/sheets.ods diff --git a/desktop/qa/data/signed.odt b/desktop/qa/data/signed.odt Binary files differnew file mode 100644 index 0000000000..49bd9dd240 --- /dev/null +++ b/desktop/qa/data/signed.odt diff --git a/desktop/qa/data/table-selection.odt b/desktop/qa/data/table-selection.odt Binary files differnew file mode 100644 index 0000000000..c19f8c79fc --- /dev/null +++ b/desktop/qa/data/table-selection.odt diff --git a/desktop/qa/data/test-PK-signing.pem b/desktop/qa/data/test-PK-signing.pem new file mode 100644 index 0000000000..eabbaae18c --- /dev/null +++ b/desktop/qa/data/test-PK-signing.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQC/x1dyX2PgJs6o +e/BcBT5XTJXtipme29mtI/FmTq5vuAFopqvAEg084hNM7Q++wKyKG8F0ABF2pUOY +vo6Pq6rXQqMbxUaO47NNefh4v/f6hBoMfSYmXtEPji8NjIcIrQ3U8yTWquaHi13h +coG/cVf16Doem5AxUdQ6a9e/jA1VU2MmSZxthkABKPWdw1pfFJDLWhFrCjhePKrB +xw2ArmNx66fdzpi5XK0fC2TbHD2NVc8cbYOzF4h3knGZo2sq4/MBevyVhzXZNn2P +jf3PbOxp1O8CLx86MuDTLDkSVjmP0yXBf6rLFMWtEfv1Jmf3I61meIuOcw6wvXRK +zPnAKpLRAgMBAAECggEAFQU1JooiGQg9OpEV0ArwbFfZTxy+qH+Rz70oJn3qohWE +bK4SwTi5TrpmQdZHatxqN7EXIS8out0ebaLlXrCtnG6SEOcsoVjVPGFpb1ggnCAt +TWp3lgO/3SPz2wOo1rXxKtguaivNR39qc4g+LyJYm9GFHU8RHPbKe6TPvw+2HmtB +Qr2dfwgN+JJ/lBTIE0lUVuGcBcCRxbM6aG3WVpVrWcsGwRekcuuy9xrv+6fd1p2E +7zdM0/18+tnCWF9kCO6ot+spKJGTKqiuaKrSB54gFnDEgjQKIW7lUuOcXKI8vOZb +yO3owLE4mlNbE9sv8gPKoMXf2d6wzMRA6wBdaqOlKQKBgQD1XhyNDTpCoqcO82KY +YCJOZ6d1jH27XhHplfmrYDlQuWiuJ/b3ckq5Pqw5J5pRO19tvb4RSonYtmOcLajR +CkQtHapeH8mUzGsE/tgauB4KoZ1sVhzsag0Ill44P5/5oBlzhRrvJ7L80qlHn8xC +a4Tzk5t0tWbCS6K3/rqvil0hXwKBgQDIFsMoujc8hW5jyC6rO41elWEDVmPs5P+5 +RYxH5+uVeByyz2R86CAFmHUn/1nD47KKouNhwU4Anf5lA8JHh9rNVF07a2dAfH1o +yfkBGz8d3xb5hq3ahVGg9WyMRyfczGGA19uJrQ19dY4G4B4FPPz0J6oOnNkTFaQ9 +Bks/k/fJzwKBgGSBqVZJzcyPzbh9D6z06/iL0vd+ld4TGWlCKqP9ZVzgpbV431va +sCsTNf6vbzHJDTzplRqGGtLvWvwVY+pEt0p3tVqa0LqnxUqljSXct0mJi+9dkrlw +c2hKF8wYm9Hnt6UvJ6pA67tOG1MgbM3kNvCDTRFQYQhDbSLLL/NJzP4nAoGAJqhv +MFE6FtFY0KJ+kcrBt4J46eIpED32Ql9ziPkABTLdqJZ1PcTDWxFnoUCuoTA+8JYk +BGEKpwfffLjLMnLHDWC9WpuXqVfkCvjqyRHwkd7mW3Nv54ZWjRidzkR5KSm7tN7/ +pYvvzUuHE0D9y9lKrglzy7r2Hb/SqY+rvi7icvUCgYADV2kkky++OmCVLkEg3WUf +SJkF6jUAVMqlMdjTbySEfCJbxpVwRAiUWWlDD07c5HCBEASi6/NSn06MDb9Fvxo0 +a3m24Aa2c+K4ENj+bj453gdxhtvpeyfSRK+gBEP64iBG92UFJjcwHz5kFCzppPuP +p+ZtA6JAnV6QPT1EixAOCA== +-----END PRIVATE KEY----- diff --git a/desktop/qa/data/test-cert-chain-1.pem b/desktop/qa/data/test-cert-chain-1.pem new file mode 100644 index 0000000000..3a3407caf6 --- /dev/null +++ b/desktop/qa/data/test-cert-chain-1.pem @@ -0,0 +1,24 @@ +-----BEGIN CERTIFICATE----- +MIID+zCCAuOgAwIBAgIUaS0j9S5ZbREDcPMsLlfFE0+oUE0wDQYJKoZIhvcNAQEL +BQAwajELMAkGA1UEBhMCQ0gxDDAKBgNVBAgMA1p1ZzEMMAoGA1UEBwwDWnVnMRMw +EQYDVQQKDApWZXJlaWduIEFHMRUwEwYDVQQLDAxCdXNzaW5lcyBEZXAxEzARBgNV +BAMMCnZlcmVpZ24tY2EwHhcNMTgxMTI0MDAwMDAwWhcNMjMxMjE4MjM1OTU5WjCB +jzELMAkGA1UEBhMCQ0gxDDAKBgNVBAgTA1p1ZzEMMAoGA1UEBxMDWnVnMRMwEQYD +VQQKEwpWZXJlaWduIEFHMRUwEwYDVQQLEwxCdXNpbmVzcyBEZXAxODA2BgNVBAMT +L2YxZmUxZGJhLWZiMDUtNDk5Ni04Zjk1LWE4MTQyMjE5OGRiOS1zZXJ2ZXJzaWRl +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzE7WuIkjvhWceJGwLoYN +2+huR47jlqqb0ez5VMGuZqK8qn1uf0PB6j/OW9B0cdBs65nBTL2CrkZncKov5g/i +yZADuiezqBtEUr6J74oRBVPWV+e9iNr3M2FMtG/lkpYDpkvNziibMLE4kyhgbbRd +Q0OHz55mk4Wn3CYAA/a7zhMgCjvT+wPuLXJjLje+2bB4rMv/USlnTN3DINx4i/Vt +klNNtK5NSaxlkYf1QuyxXeHEJUufVuFY7sG4xZBhh75yUF7Z7836Oi1++DNeuWc/ +YOmsrfu1lqDfYNjb5IpOMz9x2HtmG6V3ETeKQX8GIs34qhG6zA9Up3JkSQsd9qTP +1QIDAQABo3MwcTAOBgNVHQ8BAf8EBAMCAbYwDwYDVR0TAQH/BAUwAwEB/zAfBgNV +HSMEGDAWgBQlN+K7lesKXsDZYQUu4zkqtNBwrjAaBgNVHREEEzARgg93d3cudmVy +ZWlnbi5jb20wEQYDVR0gBAowCDAGBgRVHSAAMA0GCSqGSIb3DQEBCwUAA4IBAQAx +86wXlw779hT3Jad4EfuozQycLw4lXLMiGdHuLdRGNdg6faK2p5qaJXFEd13pE/Qn +AI2z5SpuYr2G3NYJsT3deXi8Yh58AsBSF5UY61xCgITXOW2NaB2gb6L7sL8Uau8i +BE5yn0r0V6wD3gxG7yJRNPgH7ksELfN1b++BHdvkcodC1H4vHzl0mAJvYaNlPBhd +LDnGU3GKhZ1r2pF+eXIW7n3BbAi/a7226Or4eTWEjc0footKJeJLfsG4HOcSj+Bi +LKAeftebGRRsZzuL6tLTzoSiRsEn6Y6GOdxRM21Uu1rvtWmfDyNRuCcKZU8Secqy +0s1uSGkNr+6wgcWqA+VQ +-----END CERTIFICATE----- diff --git a/desktop/qa/data/test-cert-chain-2.pem b/desktop/qa/data/test-cert-chain-2.pem new file mode 100644 index 0000000000..a31db3f657 --- /dev/null +++ b/desktop/qa/data/test-cert-chain-2.pem @@ -0,0 +1,26 @@ +-----BEGIN CERTIFICATE----- +MIIEdjCCA16gAwIBAgIEW7HqGDANBgkqhkiG9w0BAQsFADBqMQswCQYDVQQGEwJD +SDEMMAoGA1UECAwDWnVnMQwwCgYDVQQHDANadWcxEzARBgNVBAoMClZlcmVpZ24g +QUcxFTATBgNVBAsMDEJ1c3NpbmVzIERlcDETMBEGA1UEAwwKdmVyZWlnbi1jYTAe +Fw0xODEwMDEwOTM0MTZaFw0xOTEwMDEwOTM0MTZaMGoxCzAJBgNVBAYTAkNIMQww +CgYDVQQIDANadWcxDDAKBgNVBAcMA1p1ZzETMBEGA1UECgwKVmVyZWlnbiBBRzEV +MBMGA1UECwwMQnVzc2luZXMgRGVwMRMwEQYDVQQDDAp2ZXJlaWduLWNhMIIBIjAN +BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsfpPjrblQuxrHiSLAAyyDgRd66gY +PRo7lgKZH5NYcBO9VhJNwnvV+fBIVeJI49b+a12TPHjRzJYrkaBAcUxMM8FkZ01A +mv6JSG4o2ZXV+GWpnWzEJzt9ZXmNZ1MSUlqIGzVZ/eUlXIj4gy57+SZoJURcQGhs +jpoRgUpYnFsDJk2x77jiOa5ym/N+8HKsOabASMU6VkbIFvUqf62RXWpnQlOhFjGo +0jvheRGBWbaYKHM3/d+u78w4tmvHqGVDDbsuOluZ39p2jCic9S7CnDkauZB0Afd/ +xgQ0CglpAgY8g4cfMl2zwRmm616PtutqjcE/NoA2JEVN5vP9QZsuXeRpJwIDAQAB +o4IBIjCCAR4wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwLwYDVR0R +BCgwJoIPd3d3LnZlcmVpZ24uY29tgRNjb250YWN0QHZlcmVpZ24uY29tMBEGA1Ud +IAQKMAgwBgYEVR0gADCBlwYDVR0jBIGPMIGMgBQlN+K7lesKXsDZYQUu4zkqtNBw +rqFupGwwajELMAkGA1UEBhMCQ0gxDDAKBgNVBAgMA1p1ZzEMMAoGA1UEBwwDWnVn +MRMwEQYDVQQKDApWZXJlaWduIEFHMRUwEwYDVQQLDAxCdXNzaW5lcyBEZXAxEzAR +BgNVBAMMCnZlcmVpZ24tY2GCBFux6hgwHQYDVR0OBBYEFCU34ruV6wpewNlhBS7j +OSq00HCuMA0GCSqGSIb3DQEBCwUAA4IBAQCG3tf8/tuCNJXby4B7decDNE6bff40 +1ybO17kzekrKj0IO2TatFIG+UDlxDfm2iydEQVoPuRTAgmJD1aq5g4C0ZLyUqmOg +75Dve6W9+zzxbdI711WKxH+uSj4mTRkFD4Tb7r3VZ1ZyZYnCOMIGB4/lqUK6Ok3a +2v8XaFcxHt5XhrQtgqd5bBGokQfwYPNVZW9FwXf/8cd59prEOnqlMbZJ7copgwYO +97abhpy2FUoRWtvDjDLLfdiFQhVY8meDcS/h5mw2aEugew8hnfSEaD5ZcbOf0ZQe +MOVxKbIzSeUDAFyRY6BPpGVPuJD6QAXRMW6KIWiGoF1taKp5G/nzbzJC +-----END CERTIFICATE----- diff --git a/desktop/qa/data/test-cert-chain-3.pem b/desktop/qa/data/test-cert-chain-3.pem new file mode 100644 index 0000000000..d02dbe0f6a --- /dev/null +++ b/desktop/qa/data/test-cert-chain-3.pem @@ -0,0 +1,23 @@ +-----BEGIN CERTIFICATE----- +MIID5zCCAs+gAwIBAgIU0Ar2t+Zp1ZC0Jx1An2HK+e+ghR0wDQYJKoZIhvcNAQEL +BQAwgY8xCzAJBgNVBAYTAkNIMQwwCgYDVQQIEwNadWcxDDAKBgNVBAcTA1p1ZzET +MBEGA1UEChMKVmVyZWlnbiBBRzEVMBMGA1UECxMMQnVzaW5lc3MgRGVwMTgwNgYD +VQQDEy9mMWZlMWRiYS1mYjA1LTQ5OTYtOGY5NS1hODE0MjIxOThkYjktc2VydmVy +c2lkZTAeFw0xODExMjMyMzAwMDBaFw0yMzExMjQyMjU5NTlaMIGGMYGDMDYGA1UE +AxMvZjFmZTFkYmEtZmIwNS00OTk2LThmOTUtYTgxNDIyMTk4ZGI5LXVzZXJkZXZp +Y2UwCQYDVQQGEwJDSDAKBgNVBAcTA1p1ZzAKBgNVBAgTA1p1ZzARBgNVBAoTClZl +cmVpZ24gQUcwEwYDVQQLEwxCdXNpbmVzcyBEZXAwggEiMA0GCSqGSIb3DQEBAQUA +A4IBDwAwggEKAoIBAQDFQ+2fHCdE9ksa/h0aMK/DHPdq3iGWHx6eFsG3yI7RSU/7 +IA/DPht7A4U/a4qw/8PqT9Df9CvNeURXDmIG1S4ZVn7oTlEua9Da5A+HByA6M4Vk +4meDo/tY6hE8NZy25Q3l0dZvH34eocRe0xhjGajoUo6vGfzzQ+pQPerM1VivraVf +EZva4b3rCN7XrZkzkBPjEZijLvlk1wKcG5R1teXlEMHiHKiIeECDXmBD406ngWlt +KzlH2bXZYkfdBSoYXW2eyM/z0kJcY4M/75re0YGMRUPK6Cp1C4F+jIIv8np09bKS +6lvr/niemJz0BPXapsLOWXsc0Gg9sPiMpwdNbjflAgMBAAGjQjBAMA4GA1UdDwEB +/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSdM4fK3wQY6/nXpgZL +5Nl5udSGGjANBgkqhkiG9w0BAQsFAAOCAQEAe/6URI5GLBee3OJo00iHXCYMxOoX +Bi2V6IC1zS6mQlRYDtzbUQAKJrxRVns2+wjeCqKetqSuZ00hM/ipCrnxwb/X0CKY +4az9Mf9BycYnkmGNKzzskefIlUciaThdc0Ju6RVlCgFXX8vP9iMO3iQ9ET7JS4jB +oRNFnMeyy4+HG1RwYivi1YjaPNkp7xd8Lqq56VULGY4dKZUieJceFXtYQZHuVgWo +woAulOZH0IYqTv16tHxLovWGJaWpoMwgWo/c8sk8CfYF5vv9SMxcFnWwNooCRtWI +HJzs/1zxXIFy9D49PcH0gwkPM66F8bPS8TMpHcyjRDF/TQFzu1JwVyFY0g== +-----END CERTIFICATE----- diff --git a/desktop/qa/data/test-cert-signing.pem b/desktop/qa/data/test-cert-signing.pem new file mode 100644 index 0000000000..8f5c788b53 --- /dev/null +++ b/desktop/qa/data/test-cert-signing.pem @@ -0,0 +1,23 @@ +-----BEGIN CERTIFICATE----- +MIID3zCCAsmgAwIBAgIUV/4FfTrYMHCb7AxAeI/zAHowUjQwCwYJKoZIhvcNAQEL +MIGGMYGDMDYGA1UEAxMvZjFmZTFkYmEtZmIwNS00OTk2LThmOTUtYTgxNDIyMTk4 +ZGI5LXVzZXJkZXZpY2UwCQYDVQQGEwJDSDAKBgNVBAcTA1p1ZzAKBgNVBAgTA1p1 +ZzARBgNVBAoTClZlcmVpZ24gQUcwEwYDVQQLEwxCdXNpbmVzcyBEZXAwHhcNMTgx +MTIzMjMwMDAwWhcNMjMxMTI0MjI1OTU5WjCBjjGBizA+BgNVBAMTNzNub2JKdk9n +ZkstZjFmZTFkYmEtZmIwNS00OTk2LThmOTUtYTgxNDIyMTk4ZGI5LW9uZXRpbWUw +CQYDVQQGEwJDSDAKBgNVBAcTA1p1ZzAKBgNVBAgTA1p1ZzARBgNVBAoTClZlcmVp +Z24gQUcwEwYDVQQLEwxCdXNpbmVzcyBEZXAwggEiMA0GCSqGSIb3DQEBAQUAA4IB +DwAwggEKAoIBAQC/x1dyX2PgJs6oe/BcBT5XTJXtipme29mtI/FmTq5vuAFopqvA +Eg084hNM7Q++wKyKG8F0ABF2pUOYvo6Pq6rXQqMbxUaO47NNefh4v/f6hBoMfSYm +XtEPji8NjIcIrQ3U8yTWquaHi13hcoG/cVf16Doem5AxUdQ6a9e/jA1VU2MmSZxt +hkABKPWdw1pfFJDLWhFrCjhePKrBxw2ArmNx66fdzpi5XK0fC2TbHD2NVc8cbYOz +F4h3knGZo2sq4/MBevyVhzXZNn2Pjf3PbOxp1O8CLx86MuDTLDkSVjmP0yXBf6rL +FMWtEfv1Jmf3I61meIuOcw6wvXRKzPnAKpLRAgMBAAGjPzA9MAwGA1UdEwEB/wQC +MAAwDgYDVR0PAQH/BAQDAgDwMB0GA1UdDgQWBBSjYCO+w2VQgpdkCvoHgdgZzOPz +BTALBgkqhkiG9w0BAQsDggEBAD6MAP/l4jkJjIJinJ1uPuA6wOVBff1Beb6JV2jo +ay1099VkFVHe/tEeyDcx2j+a2CSKsLdOoG/RjcBup3XsumxlqQS6r9GjZLwtNkxI +nFNLqwv2PbXesSLTR1mjm/6BdcjGEgcxllkST2uYb8DMwbASNQT+rvmGy85b3D4m +tk3scetFWozkfs+lRUql1dWf2aZTcN+IIZlSpxEZu+w1w8yMoDvVV0pCz49d7WIA +RIXDE8QJpiAqszuCbqsNdAbr/pAyYzIAIfKKuteWaViesdY26pZfCe2JsfUTDWSi +PDrFAlkbtB5LGYd4Tfliuvud6I/87GZsuWcCYoS4wXDkRS4= +-----END CERTIFICATE----- diff --git a/desktop/qa/deployment_misc/test_dp_version.cxx b/desktop/qa/deployment_misc/test_dp_version.cxx new file mode 100644 index 0000000000..1b8fb90867 --- /dev/null +++ b/desktop/qa/deployment_misc/test_dp_version.cxx @@ -0,0 +1,82 @@ +/* -*- 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 <cstddef> +#include <sal/types.h> + +#include <cppunit/TestAssert.h> +#include <cppunit/TestFixture.h> +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/plugin/TestPlugIn.h> +#include <rtl/ustring.hxx> + +#include "../../source/deployment/inc/dp_version.hxx" + +namespace { + +class Test: public ::CppUnit::TestFixture { +public: + void test(); + + CPPUNIT_TEST_SUITE(Test); + CPPUNIT_TEST(test); + CPPUNIT_TEST_SUITE_END(); +}; + +void Test::test() { + struct Data { + OUString version1; + OUString version2; + ::dp_misc::Order order; + }; + static Data const data[] = { + { OUString(), + OUString("0.0000.00.0"), + ::dp_misc::EQUAL }, + { OUString(".01"), + OUString("0.1"), + ::dp_misc::EQUAL }, + { OUString("10"), + OUString("2"), + ::dp_misc::GREATER }, + { OUString("9223372036854775808"), + // 2^63 + OUString("9223372036854775807"), + ::dp_misc::GREATER } + }; + for (std::size_t i = 0; i < std::size(data); ++i) { + CPPUNIT_ASSERT_EQUAL( + data[i].order, + ::dp_misc::compareVersions(data[i].version1, data[i].version2)); + static ::dp_misc::Order const reverse[3] = { + ::dp_misc::GREATER, ::dp_misc::EQUAL, ::dp_misc::LESS + }; + CPPUNIT_ASSERT_EQUAL( + reverse[data[i].order], + ::dp_misc::compareVersions(data[i].version2, data[i].version1)); + } +} + +CPPUNIT_TEST_SUITE_REGISTRATION(Test); + +} + +CPPUNIT_PLUGIN_IMPLEMENT(); + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/qa/desktop_app/test_desktop_app.cxx b/desktop/qa/desktop_app/test_desktop_app.cxx new file mode 100644 index 0000000000..0e86368e63 --- /dev/null +++ b/desktop/qa/desktop_app/test_desktop_app.cxx @@ -0,0 +1,142 @@ +/* -*- 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/types.h> + +#include <cppunit/TestAssert.h> +#include <cppunit/TestFixture.h> +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/plugin/TestPlugIn.h> +#include <rtl/ustring.hxx> +#include <cppuhelper/bootstrap.hxx> +#include <com/sun/star/uno/Reference.hxx> +#include <com/sun/star/lang/XMultiComponentFactory.hpp> +#include <com/sun/star/lang/XMultiServiceFactory.hpp> +#include <comphelper/processfactory.hxx> + +#include "../../source/app/cmdlineargs.hxx" + +namespace { + +class Test: public ::CppUnit::TestFixture { +public: + void testTdf100837(); + + CPPUNIT_TEST_SUITE(Test); + CPPUNIT_TEST(testTdf100837); + CPPUNIT_TEST_SUITE_END(); +}; + +class TestSupplier : public desktop::CommandLineArgs::Supplier { +public: + explicit TestSupplier(const std::initializer_list<OUString>& args) : m_args(args) {} + + virtual std::optional< OUString > getCwdUrl() override { return std::optional< OUString >(); } + virtual bool next(OUString * argument) override { + CPPUNIT_ASSERT(argument != nullptr); + if (m_index < m_args.size()) { + *argument = m_args[m_index++]; + return true; + } + else { + return false; + } + } +private: + std::vector< OUString > m_args; + std::vector< OUString >::size_type m_index = 0; +}; + +// Test Office URI Schemes support +void Test::testTdf100837() { + auto xContext = ::cppu::defaultBootstrap_InitialComponentContext(); + ::css::uno::Reference<::css::lang::XMultiComponentFactory> xFactory(xContext->getServiceManager()); + ::css::uno::Reference<::css::lang::XMultiServiceFactory> xSM(xFactory, ::css::uno::UNO_QUERY_THROW); + // Without this we're crashing because callees are using getProcessServiceFactory + ::comphelper::setProcessServiceFactory(xSM); + + { + // 1. Test default behaviour: Office URIs define open mode + TestSupplier supplier{ "foo", "ms-word:ofe|u|bar1", "ms-word:ofv|u|bar2", "ms-word:nft|u|bar3", "baz" }; + desktop::CommandLineArgs args(supplier); + auto vOpenList = args.GetOpenList(); + auto vForceOpenList = args.GetForceOpenList(); + auto vViewList = args.GetViewList(); + auto vForceNewList = args.GetForceNewList(); + // 2 documents go to Open list: foo; baz + CPPUNIT_ASSERT_EQUAL(decltype(vOpenList.size())(2), vOpenList.size()); + CPPUNIT_ASSERT_EQUAL(OUString("foo"), vOpenList[0]); + CPPUNIT_ASSERT_EQUAL(OUString("baz"), vOpenList[1]); + // 1 document goes to ForceOpen list: bar1 + CPPUNIT_ASSERT_EQUAL(decltype(vForceOpenList.size())(1), vForceOpenList.size()); + CPPUNIT_ASSERT_EQUAL(OUString("bar1"), vForceOpenList[0]); + // 1 document goes to View list: bar2 + CPPUNIT_ASSERT_EQUAL(decltype(vViewList.size())(1), vViewList.size()); + CPPUNIT_ASSERT_EQUAL(OUString("bar2"), vViewList[0]); + // 1 document goes to ForceNew list: bar3 + CPPUNIT_ASSERT_EQUAL(decltype(vForceNewList.size())(1), vForceNewList.size()); + CPPUNIT_ASSERT_EQUAL(OUString("bar3"), vForceNewList[0]); + } + + { + // 2. Test explicit open mode arguments. Office URI commands should have no effect + TestSupplier supplier{ "--view", "ms-word:ofe|u|foo", "-o", "ms-word:ofv|u|bar", "ms-word:nft|u|baz" }; + desktop::CommandLineArgs args(supplier); + auto vViewList = args.GetViewList(); + auto vForceOpenList = args.GetForceOpenList(); + // 1 document goes to View list: foo + CPPUNIT_ASSERT_EQUAL(decltype(vViewList.size())(1), vViewList.size()); + CPPUNIT_ASSERT_EQUAL(OUString("foo"), vViewList[0]); + // 2 documents go to ForceOpen list: bar, baz + CPPUNIT_ASSERT_EQUAL(decltype(vForceOpenList.size())(2), vForceOpenList.size()); + CPPUNIT_ASSERT_EQUAL(OUString("bar"), vForceOpenList[0]); + CPPUNIT_ASSERT_EQUAL(OUString("baz"), vForceOpenList[1]); + } + + { + // 3. Test encoded URLs + TestSupplier supplier{ "foo", "ms-word:ofe%7Cu%7cbar1", "ms-word:ofv%7cu%7Cbar2", "ms-word:nft%7Cu%7cbar3", "baz" }; + desktop::CommandLineArgs args(supplier); + auto vOpenList = args.GetOpenList(); + auto vForceOpenList = args.GetForceOpenList(); + auto vViewList = args.GetViewList(); + auto vForceNewList = args.GetForceNewList(); + // 2 documents go to Open list: foo; baz + CPPUNIT_ASSERT_EQUAL(decltype(vOpenList.size())(2), vOpenList.size()); + CPPUNIT_ASSERT_EQUAL(OUString("foo"), vOpenList[0]); + CPPUNIT_ASSERT_EQUAL(OUString("baz"), vOpenList[1]); + // 1 document goes to ForceOpen list: bar1 + CPPUNIT_ASSERT_EQUAL(decltype(vForceOpenList.size())(1), vForceOpenList.size()); + CPPUNIT_ASSERT_EQUAL(OUString("bar1"), vForceOpenList[0]); + // 1 document goes to View list: bar2 + CPPUNIT_ASSERT_EQUAL(decltype(vViewList.size())(1), vViewList.size()); + CPPUNIT_ASSERT_EQUAL(OUString("bar2"), vViewList[0]); + // 1 document goes to ForceNew list: bar3 + CPPUNIT_ASSERT_EQUAL(decltype(vForceNewList.size())(1), vForceNewList.size()); + CPPUNIT_ASSERT_EQUAL(OUString("bar3"), vForceNewList[0]); + } +} + +CPPUNIT_TEST_SUITE_REGISTRATION(Test); + +} + +CPPUNIT_PLUGIN_IMPLEMENT(); + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/qa/desktop_lib/test_desktop_lib.cxx b/desktop/qa/desktop_lib/test_desktop_lib.cxx new file mode 100644 index 0000000000..b3410bd8eb --- /dev/null +++ b/desktop/qa/desktop_lib/test_desktop_lib.cxx @@ -0,0 +1,3713 @@ +/* -*- 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 <config_oox.h> +#include <memory> +#include <string_view> + +#include <com/sun/star/frame/Desktop.hpp> +#include <com/sun/star/lang/XComponent.hpp> +#include <com/sun/star/text/XTextDocument.hpp> +#include <com/sun/star/awt/Key.hpp> +#include <com/sun/star/awt/XReschedule.hpp> +#include <com/sun/star/awt/Toolkit.hpp> +#include <com/sun/star/drawing/XDrawPageSupplier.hpp> +#include <com/sun/star/text/TextContentAnchorType.hpp> +#include <boost/property_tree/json_parser.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/util/XCloseable.hpp> + +#include <vcl/scheduler.hxx> +#include <vcl/svapp.hxx> +#include <vcl/syswin.hxx> +#include <vcl/window.hxx> +#include <vcl/ctrl.hxx> +#include <vcl/uitest/uiobject.hxx> +#include <comphelper/processfactory.hxx> +#include <rtl/math.hxx> +#include <rtl/uri.hxx> +#include <sfx2/app.hxx> +#include <sfx2/childwin.hxx> +#include <sfx2/lokhelper.hxx> +#include <test/unoapi_test.hxx> +#include <comphelper/lok.hxx> +#include <comphelper/propertysequence.hxx> +#include <osl/conditn.hxx> +#include <svl/srchitem.hxx> +#include <LibreOfficeKit/LibreOfficeKitEnums.h> +#include <unotools/tempfile.hxx> +#include <sfx2/viewsh.hxx> +#include <sfx2/viewfrm.hxx> +#include <sfx2/bindings.hxx> +#include <sfx2/sidebar/SidebarController.hxx> +#include <sfx2/sidebar/SidebarDockingWindow.hxx> +#include <unotools/datetime.hxx> +#include <unotools/syslocaleoptions.hxx> +#include <comphelper/string.hxx> +#include <comphelper/scopeguard.hxx> +#include <cairo.h> +#include <config_features.h> +#include <config_fonts.h> +#include <config_mpl.h> +#include <tools/json_writer.hxx> +#include <o3tl/unit_conversion.hxx> +#include <o3tl/string_view.hxx> + +#include <lib/init.hxx> +#include <svx/svxids.hrc> + +#include <cppunit/TestAssert.h> +#include <vcl/BitmapTools.hxx> +#include <vcl/filter/PngImageWriter.hxx> +#include <vcl/filter/PDFiumLibrary.hxx> +#include <svtools/colorcfg.hxx> +#include <sal/types.h> + +#if USE_TLS_NSS +#include <nss.h> +#endif + +using namespace com::sun::star; +using namespace desktop; + +static LibreOfficeKitDocumentType getDocumentTypeFromName(const char* pName) +{ + CPPUNIT_ASSERT_MESSAGE("Document name must be valid.", pName != nullptr); + + const std::string name(pName); + CPPUNIT_ASSERT_MESSAGE("Document name must include extension.", name.size() > 4); + + const auto it = name.rfind('.'); + if (it != std::string::npos) + { + const std::string ext = name.substr(it); + + if (ext == ".ods") + return LOK_DOCTYPE_SPREADSHEET; + + if (ext == ".odp") + return LOK_DOCTYPE_PRESENTATION; + } + + CPPUNIT_ASSERT_MESSAGE("Document name must include extension.", it != std::string::npos); + return LOK_DOCTYPE_TEXT; +} + +class DesktopLOKTest : public UnoApiTest +{ +public: + DesktopLOKTest() : UnoApiTest("/desktop/qa/data/"), + m_nSelectionBeforeSearchResult(0), + m_nSelectionAfterSearchResult(0), + m_bModified(false), + m_nTrackChanges(0) + { + } + ~DesktopLOKTest(); + + void readFileIntoByteVector( + std::u16string_view sFilename, std::vector<sal_uInt8> & rByteVector); + + virtual void setUp() override + { + comphelper::LibreOfficeKit::setActive(true); + + UnoApiTest::setUp(); + } + + virtual void tearDown() override + { + closeDoc(); + + // documents are already closed, no need to call UnoApiTest::tearDown + test::BootstrapFixture::tearDown(); + + comphelper::LibreOfficeKit::setActive(false); + } + + std::unique_ptr<LibLODocument_Impl> + loadDocImpl(const char* pName, LibreOfficeKitDocumentType eType); + +private: + std::unique_ptr<LibLODocument_Impl> + loadDocImpl(const char* pName); + +public: + std::unique_ptr<LibLODocument_Impl> + loadDocUrlImpl(const OUString& rFileURL, LibreOfficeKitDocumentType eType); + + LibLODocument_Impl* loadDocUrl(const OUString& rFileURL, LibreOfficeKitDocumentType eType); + LibLODocument_Impl* loadDoc(const char* pName, LibreOfficeKitDocumentType eType); + LibLODocument_Impl* loadDoc(const char* pName) + { + return loadDoc(pName, getDocumentTypeFromName(pName)); + } + + void closeDoc(std::unique_ptr<LibLODocument_Impl>& loDocument); + void closeDoc() { closeDoc(m_pDocument); } + static void callback(int nType, const char* pPayload, void* pData); + void callbackImpl(int nType, const char* pPayload); + + void testGetStyles(); + void testGetFonts(); + void testCreateView(); + void testGetFilterTypes(); + void testGetPartPageRectangles(); + void testSearchCalc(); + void testSearchAllNotificationsCalc(); + void testPaintTile(); + void testSaveAs(); + void testSaveAsJsonOptions(); + void testSaveAsCalc(); + void testPasteWriter(); + void testPasteWriterJPEG(); + void testUndoWriter(); + void testRowColumnHeaders(); + void testHiddenRowHeaders(); + void testCellCursor(); + void testCommandResult(); + void testWriterComments(); + void testSheetOperations(); + void testSheetSelections(); + void testSheetDragDrop(); + void testContextMenuCalc(); + void testContextMenuWriter(); + void testContextMenuImpress(); + void testNotificationCompression(); + void testTileInvalidationCompression(); + void testPartInInvalidation(); + void testBinaryCallback(); + void testInput(); + void testRedlineWriter(); + void testTrackChanges(); + void testRedlineCalc(); + void testPaintPartTile(); + void testPaintPartTileDifferentSchemes(); +#if HAVE_MORE_FONTS + void testGetFontSubset(); +#endif + void testCommentsWriter(); + void testCommentsCalc(); + void testCommentsImpress(); + void testCommentsCallbacksWriter(); + void testCommentsAddEditDeleteDraw(); + void testRunMacro(); + void testExtractParameter(); + void testGetSignatureState_NonSigned(); + void testGetSignatureState_Signed(); +#if 0 // broken with system nss on RHEL 7 + void testInsertCertificate_DER_ODT(); + void testInsertCertificate_PEM_ODT(); + void testInsertCertificate_PEM_DOCX(); +#endif + void testSignDocument_PEM_PDF(); + void testTextSelectionHandles(); + void testComplexSelection(); + void testSpellcheckerMultiView(); + void testDialogPaste(); + void testCalcSaveAs(); + void testControlState(); + void testMetricField(); + void testMultiDocuments(); + void testJumpCursor(); + void testRenderSearchResult_WriterNode(); + void testRenderSearchResult_CommonNode(); + void testNoDuplicateTableSelection(); + void testMultiViewTableSelection(); + void testColorPaletteCallback(); + void testABI(); + + CPPUNIT_TEST_SUITE(DesktopLOKTest); + CPPUNIT_TEST(testGetStyles); + CPPUNIT_TEST(testGetFonts); + CPPUNIT_TEST(testCreateView); + CPPUNIT_TEST(testGetFilterTypes); + CPPUNIT_TEST(testGetPartPageRectangles); + CPPUNIT_TEST(testSearchCalc); + CPPUNIT_TEST(testSearchAllNotificationsCalc); + CPPUNIT_TEST(testPaintTile); + CPPUNIT_TEST(testSaveAs); + CPPUNIT_TEST(testSaveAsJsonOptions); + CPPUNIT_TEST(testSaveAsCalc); + CPPUNIT_TEST(testPasteWriter); + CPPUNIT_TEST(testPasteWriterJPEG); + CPPUNIT_TEST(testUndoWriter); + CPPUNIT_TEST(testRowColumnHeaders); + CPPUNIT_TEST(testHiddenRowHeaders); + CPPUNIT_TEST(testCellCursor); + CPPUNIT_TEST(testCommandResult); + CPPUNIT_TEST(testWriterComments); + CPPUNIT_TEST(testSheetOperations); + CPPUNIT_TEST(testSheetSelections); + CPPUNIT_TEST(testSheetDragDrop); + CPPUNIT_TEST(testContextMenuCalc); + CPPUNIT_TEST(testContextMenuWriter); + CPPUNIT_TEST(testContextMenuImpress); + CPPUNIT_TEST(testNotificationCompression); + CPPUNIT_TEST(testTileInvalidationCompression); + CPPUNIT_TEST(testPartInInvalidation); + CPPUNIT_TEST(testBinaryCallback); + CPPUNIT_TEST(testInput); + CPPUNIT_TEST(testRedlineWriter); + CPPUNIT_TEST(testTrackChanges); + CPPUNIT_TEST(testRedlineCalc); + CPPUNIT_TEST(testPaintPartTile); + CPPUNIT_TEST(testPaintPartTileDifferentSchemes); +#if HAVE_MORE_FONTS + CPPUNIT_TEST(testGetFontSubset); +#endif + CPPUNIT_TEST(testCommentsWriter); + CPPUNIT_TEST(testCommentsCalc); + CPPUNIT_TEST(testCommentsImpress); + CPPUNIT_TEST(testCommentsCallbacksWriter); + CPPUNIT_TEST(testCommentsAddEditDeleteDraw); + CPPUNIT_TEST(testRunMacro); + CPPUNIT_TEST(testExtractParameter); + CPPUNIT_TEST(testGetSignatureState_Signed); + CPPUNIT_TEST(testGetSignatureState_NonSigned); +#if !MPL_HAVE_SUBSET +#if 0 // broken with system nss on RHEL 7 + CPPUNIT_TEST(testInsertCertificate_DER_ODT); + CPPUNIT_TEST(testInsertCertificate_PEM_ODT); + CPPUNIT_TEST(testInsertCertificate_PEM_DOCX); +#endif + CPPUNIT_TEST(testSignDocument_PEM_PDF); +#endif + CPPUNIT_TEST(testTextSelectionHandles); + CPPUNIT_TEST(testComplexSelection); + CPPUNIT_TEST(testSpellcheckerMultiView); + CPPUNIT_TEST(testDialogPaste); + CPPUNIT_TEST(testCalcSaveAs); + CPPUNIT_TEST(testControlState); + CPPUNIT_TEST(testMetricField); + CPPUNIT_TEST(testMultiDocuments); + CPPUNIT_TEST(testJumpCursor); + CPPUNIT_TEST(testRenderSearchResult_WriterNode); + CPPUNIT_TEST(testRenderSearchResult_CommonNode); + CPPUNIT_TEST(testNoDuplicateTableSelection); + CPPUNIT_TEST(testMultiViewTableSelection); + CPPUNIT_TEST(testColorPaletteCallback); + CPPUNIT_TEST(testABI); + CPPUNIT_TEST_SUITE_END(); + + OString m_aTextSelection; + OString m_aTextSelectionStart; + OString m_aTextSelectionEnd; + std::vector<OString> m_aSearchResultSelection; + std::vector<int> m_aSearchResultPart; + int m_nSelectionBeforeSearchResult; + int m_nSelectionAfterSearchResult; + + // for testCommandResult + osl::Condition m_aCommandResultCondition; + OString m_aCommandResult; + + // for testModifiedStatus + osl::Condition m_aStateChangedCondition; + bool m_bModified; + int m_nTrackChanges; + + // for testContextMenu{Calc, Writer} + osl::Condition m_aContextMenuCondition; + boost::property_tree::ptree m_aContextMenuResult; + + std::unique_ptr<LibLODocument_Impl> m_pDocument; +}; + +DesktopLOKTest::~DesktopLOKTest() +{ +#if USE_TLS_NSS + NSS_Shutdown(); +#endif +} + +static Control* GetFocusControl(vcl::Window const * pParent) +{ + sal_uInt16 nChildren = pParent->GetChildCount(); + for (sal_uInt16 nChild = 0; nChild < nChildren; ++nChild) + { + vcl::Window* pChild = pParent->GetChild( nChild ); + Control* pCtrl = dynamic_cast<Control*>(pChild); + if (pCtrl && pCtrl->HasControlFocus()) + return pCtrl; + + Control* pSubCtrl = GetFocusControl( pChild ); + if (pSubCtrl) + return pSubCtrl; + } + return nullptr; +} + +std::unique_ptr<LibLODocument_Impl> +DesktopLOKTest::loadDocUrlImpl(const OUString& rFileURL, LibreOfficeKitDocumentType eType) +{ + OUString aService; + switch (eType) + { + case LOK_DOCTYPE_TEXT: + aService = "com.sun.star.text.TextDocument"; + break; + case LOK_DOCTYPE_SPREADSHEET: + aService = "com.sun.star.sheet.SpreadsheetDocument"; + break; + case LOK_DOCTYPE_PRESENTATION: + aService = "com.sun.star.presentation.PresentationDocument"; + break; + default: + CPPUNIT_ASSERT(false); + break; + } + + static int nDocumentIdCounter = 0; + SfxViewShell::SetCurrentDocId(ViewShellDocId(nDocumentIdCounter)); + mxComponent = loadFromDesktop(rFileURL, aService); + + std::unique_ptr<LibLODocument_Impl> pDocument(new LibLODocument_Impl(mxComponent, nDocumentIdCounter)); + ++nDocumentIdCounter; + + return pDocument; +} + +std::unique_ptr<LibLODocument_Impl> +DesktopLOKTest::loadDocImpl(const char* pName, LibreOfficeKitDocumentType eType) +{ + OUString aFileURL = createFileURL(OUString::createFromAscii(pName)); + return loadDocUrlImpl(aFileURL, eType); +} + +std::unique_ptr<LibLODocument_Impl> +DesktopLOKTest::loadDocImpl(const char* pName) +{ + return loadDocImpl(pName, getDocumentTypeFromName(pName)); +} + +LibLODocument_Impl* DesktopLOKTest::loadDocUrl(const OUString& rFileURL, LibreOfficeKitDocumentType eType) +{ + m_pDocument = loadDocUrlImpl(rFileURL, eType); + return m_pDocument.get(); +} + +LibLODocument_Impl* DesktopLOKTest::loadDoc(const char* pName, LibreOfficeKitDocumentType eType) +{ + m_pDocument = loadDocImpl(pName, eType); + return m_pDocument.get(); +} + +void DesktopLOKTest::closeDoc(std::unique_ptr<LibLODocument_Impl>& pDocument) +{ + if (pDocument) + { + pDocument->pClass->registerCallback(pDocument.get(), nullptr, nullptr); + pDocument.reset(); + } + + if (mxComponent.is()) + { + css::uno::Reference<util::XCloseable> xCloseable(mxComponent, css::uno::UNO_QUERY_THROW); + xCloseable->close(false); + mxComponent.clear(); + } +} + +void DesktopLOKTest::callback(int nType, const char* pPayload, void* pData) +{ + static_cast<DesktopLOKTest*>(pData)->callbackImpl(nType, pPayload); +} + +void DesktopLOKTest::callbackImpl(int nType, const char* pPayload) +{ + switch (nType) + { + case LOK_CALLBACK_TEXT_SELECTION: + { + m_aTextSelection = pPayload; + if (m_aSearchResultSelection.empty()) + ++m_nSelectionBeforeSearchResult; + else + ++m_nSelectionAfterSearchResult; + } + break; + case LOK_CALLBACK_TEXT_SELECTION_START: + m_aTextSelectionStart = pPayload; + break; + case LOK_CALLBACK_TEXT_SELECTION_END: + m_aTextSelectionEnd = pPayload; + break; + case LOK_CALLBACK_SEARCH_RESULT_SELECTION: + { + m_aSearchResultSelection.clear(); + boost::property_tree::ptree aTree; + std::stringstream aStream(pPayload); + boost::property_tree::read_json(aStream, aTree); + for (const boost::property_tree::ptree::value_type& rValue : aTree.get_child("searchResultSelection")) + { + m_aSearchResultSelection.emplace_back(rValue.second.get<std::string>("rectangles").c_str()); + m_aSearchResultPart.push_back(std::atoi(rValue.second.get<std::string>("part").c_str())); + } + } + break; + case LOK_CALLBACK_UNO_COMMAND_RESULT: + { + m_aCommandResult = pPayload; + m_aCommandResultCondition.set(); + } + break; + case LOK_CALLBACK_STATE_CHANGED: + { + OString aPayload(pPayload); + OString aPrefix(".uno:ModifiedStatus="_ostr); + if (aPayload.startsWith(aPrefix)) + { + m_bModified = aPayload.copy(aPrefix.getLength()).toBoolean(); + m_aStateChangedCondition.set(); + } + else if (aPayload.startsWith(".uno:TrackChanges=") && aPayload.endsWith("=true")) + ++m_nTrackChanges; + } + break; + case LOK_CALLBACK_CONTEXT_MENU: + { + m_aContextMenuResult.clear(); + std::stringstream aStream(pPayload); + boost::property_tree::read_json(aStream, m_aContextMenuResult); + m_aContextMenuCondition.set(); + } + break; + } +} + +void DesktopLOKTest::testGetStyles() +{ + LibLODocument_Impl* pDocument = loadDoc("blank_text.odt"); + boost::property_tree::ptree aTree; + char* pJSON = pDocument->m_pDocumentClass->getCommandValues(pDocument, ".uno:StyleApply"); + std::stringstream aStream(pJSON); + boost::property_tree::read_json(aStream, aTree); + CPPUNIT_ASSERT( !aTree.empty() ); + CPPUNIT_ASSERT_EQUAL( std::string(".uno:StyleApply"), aTree.get_child("commandName").get_value<std::string>() ); + + boost::property_tree::ptree aValues = aTree.get_child("commandValues"); + CPPUNIT_ASSERT( !aValues.empty() ); + for (const auto& rPair : aValues) + { + if( rPair.first != "ClearStyle") + { + CPPUNIT_ASSERT( !rPair.second.empty()); + } + if (rPair.first != "CharacterStyles" && + rPair.first != "ParagraphStyles" && + rPair.first != "FrameStyles" && + rPair.first != "PageStyles" && + rPair.first != "NumberingStyles" && + rPair.first != "CellStyles" && + rPair.first != "ShapeStyles" && + rPair.first != "TableStyles" && + rPair.first != "HeaderFooter" && + rPair.first != "Commands") + { + CPPUNIT_FAIL("Unknown style family: " + rPair.first); + } + } + free(pJSON); +} + +void DesktopLOKTest::testGetFonts() +{ + LibLODocument_Impl* pDocument = loadDoc("blank_presentation.odp"); + boost::property_tree::ptree aTree; + char* pJSON = pDocument->m_pDocumentClass->getCommandValues(pDocument, ".uno:CharFontName"); + std::stringstream aStream(pJSON); + boost::property_tree::read_json(aStream, aTree); + CPPUNIT_ASSERT( !aTree.empty() ); + CPPUNIT_ASSERT_EQUAL( std::string(".uno:CharFontName"), aTree.get_child("commandName").get_value<std::string>() ); + + boost::property_tree::ptree aValues = aTree.get_child("commandValues"); + CPPUNIT_ASSERT( !aValues.empty() ); + for (const auto& rPair : aValues) + { + // check that we have font sizes available for each font + CPPUNIT_ASSERT( !rPair.second.empty()); + } + free(pJSON); +} + +void DesktopLOKTest::testCreateView() +{ + LibLODocument_Impl* pDocument = loadDoc("blank_text.odt"); + CPPUNIT_ASSERT_EQUAL(1, pDocument->m_pDocumentClass->getViewsCount(pDocument)); + + int nId0 = pDocument->m_pDocumentClass->getView(pDocument); + int nId1 = pDocument->m_pDocumentClass->createView(pDocument); + CPPUNIT_ASSERT_EQUAL(2, pDocument->m_pDocumentClass->getViewsCount(pDocument)); + + // Test getViewIds(). + std::vector<int> aViewIds(2); + CPPUNIT_ASSERT(pDocument->m_pDocumentClass->getViewIds(pDocument, aViewIds.data(), aViewIds.size())); + CPPUNIT_ASSERT_EQUAL(nId0, aViewIds[0]); + CPPUNIT_ASSERT_EQUAL(nId1, aViewIds[1]); + + // Make sure the created view is the active one, then switch to the old + // one. + CPPUNIT_ASSERT_EQUAL(nId1, pDocument->m_pDocumentClass->getView(pDocument)); + pDocument->m_pDocumentClass->setView(pDocument, nId0); + CPPUNIT_ASSERT_EQUAL(nId0, pDocument->m_pDocumentClass->getView(pDocument)); + + pDocument->m_pDocumentClass->destroyView(pDocument, nId1); + CPPUNIT_ASSERT_EQUAL(1, pDocument->m_pDocumentClass->getViewsCount(pDocument)); +} + +void DesktopLOKTest::testGetPartPageRectangles() +{ + // Test that we get as many page rectangles as expected: blank document is + // one page. + LibLODocument_Impl* pDocument = loadDoc("blank_text.odt"); + char* pRectangles = pDocument->pClass->getPartPageRectangles(pDocument); + OUString sRectangles = OUString::fromUtf8(pRectangles); + + std::vector<OUString> aRectangles; + sal_Int32 nIndex = 0; + do + { + OUString aRectangle = sRectangles.getToken(0, ';', nIndex); + if (!aRectangle.isEmpty()) + aRectangles.push_back(aRectangle); + } + while (nIndex >= 0); + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aRectangles.size()); + + free(pRectangles); +} + +void DesktopLOKTest::testGetFilterTypes() +{ + LibLibreOffice_Impl aOffice; + char* pJSON = aOffice.m_pOfficeClass->getFilterTypes(&aOffice); + + std::stringstream aStream(pJSON); + boost::property_tree::ptree aTree; + boost::property_tree::read_json(aStream, aTree); + + CPPUNIT_ASSERT(!aTree.empty()); + CPPUNIT_ASSERT_EQUAL(std::string("application/vnd.oasis.opendocument.text"), aTree.get_child("writer8").get_child("MediaType").get_value<std::string>()); + free(pJSON); +} + +void DesktopLOKTest::testSearchCalc() +{ + LibLibreOffice_Impl aOffice; + LibLODocument_Impl* pDocument = loadDoc("search.ods"); + pDocument->pClass->initializeForRendering(pDocument, nullptr); + pDocument->pClass->registerCallback(pDocument, &DesktopLOKTest::callback, this); + + uno::Sequence<beans::PropertyValue> aPropertyValues(comphelper::InitPropertySequence( + { + {"SearchItem.SearchString", uno::Any(OUString("foo"))}, + {"SearchItem.Backward", uno::Any(false)}, + {"SearchItem.Command", uno::Any(static_cast<sal_uInt16>(SvxSearchCmd::FIND_ALL))}, + })); + dispatchCommand(mxComponent, ".uno:ExecuteSearch", aPropertyValues); + + std::vector<OString> aSelections; + sal_Int32 nIndex = 0; + do + { + OString aToken = m_aTextSelection.getToken(0, ';', nIndex); + aSelections.push_back(aToken); + } while (nIndex >= 0); + // This was 1, find-all only found one match. + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(2), aSelections.size()); + // Make sure that we get exactly as many rectangle lists as matches. + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(2), m_aSearchResultSelection.size()); + // Result is on the first sheet. + CPPUNIT_ASSERT_EQUAL(0, m_aSearchResultPart[0]); +} + +void DesktopLOKTest::testSearchAllNotificationsCalc() +{ + LibLibreOffice_Impl aOffice; + LibLODocument_Impl* pDocument = loadDoc("search.ods"); + pDocument->pClass->initializeForRendering(pDocument, nullptr); + pDocument->pClass->registerCallback(pDocument, &DesktopLOKTest::callback, this); + + uno::Sequence<beans::PropertyValue> aPropertyValues(comphelper::InitPropertySequence( + { + {"SearchItem.SearchString", uno::Any(OUString("foo"))}, + {"SearchItem.Backward", uno::Any(false)}, + {"SearchItem.Command", uno::Any(static_cast<sal_uInt16>(SvxSearchCmd::FIND_ALL))}, + })); + dispatchCommand(mxComponent, ".uno:ExecuteSearch", aPropertyValues); + + // This was 1, make sure that we get no notifications about selection changes during search. + CPPUNIT_ASSERT_EQUAL(0, m_nSelectionBeforeSearchResult); + // But we do get the selection afterwards. + CPPUNIT_ASSERT(m_nSelectionAfterSearchResult > 0); +} + +void DesktopLOKTest::testPaintTile() +{ + LibLODocument_Impl* pDocument = loadDoc("blank_text.odt"); + int nCanvasWidth = 100; + int nCanvasHeight = 300; + sal_Int32 nStride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, nCanvasWidth); + std::vector<unsigned char> aBuffer(nStride * nCanvasHeight); + int nTilePosX = 0; + int nTilePosY = 0; + int nTileWidth = 1000; + int nTileHeight = 3000; + + // This used to crash: paintTile() implementation did not handle + // nCanvasWidth != nCanvasHeight correctly, as usually both are just always + // 256. + pDocument->pClass->paintTile(pDocument, aBuffer.data(), nCanvasWidth, nCanvasHeight, nTilePosX, nTilePosY, nTileWidth, nTileHeight); + + // This crashed in OutputDevice::DrawDeviceAlphaBitmap(). + nCanvasWidth = 200; + nCanvasHeight = 200; + nTileWidth = 4000; + nTileHeight = 4000; + aBuffer.resize(nCanvasWidth * nCanvasHeight * 4); + pDocument->pClass->paintTile(pDocument, aBuffer.data(), nCanvasWidth, nCanvasHeight, nTilePosX, nTilePosY, nTileWidth, nTileHeight); +} + +void DesktopLOKTest::testSaveAs() +{ + LibLODocument_Impl* pDocument = loadDoc("blank_text.odt"); + CPPUNIT_ASSERT(pDocument->pClass->saveAs(pDocument, maTempFile.GetURL().toUtf8().getStr(), "png", nullptr)); +} + +void DesktopLOKTest::testSaveAsJsonOptions() +{ + // Given a document with 3 pages: + LibLODocument_Impl* pDocument = loadDoc("3page.odg"); + + // When exporting that document to PDF, skipping the first page: + OString aOptions("{\"PageRange\":{\"type\":\"string\",\"value\":\"2-\"}}"_ostr); + CPPUNIT_ASSERT(pDocument->pClass->saveAs(pDocument, maTempFile.GetURL().toUtf8().getStr(), "pdf", aOptions.getStr())); + + std::shared_ptr<vcl::pdf::PDFium> pPDFium = vcl::pdf::PDFiumLibrary::get(); + if (!pPDFium) + return; + + // Then make sure the resulting PDF has 2 pages: + std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument + = parsePDFExport(); + // Without the accompanying fix in place, this test would have failed with: + // - Expected: 2 + // - Actual : 3 + // i.e. FilterOptions was ignored. + CPPUNIT_ASSERT_EQUAL(2, pPdfDocument->getPageCount()); +} + +void DesktopLOKTest::testSaveAsCalc() +{ + LibLODocument_Impl* pDocument = loadDoc("search.ods"); + CPPUNIT_ASSERT(pDocument->pClass->saveAs(pDocument, maTempFile.GetURL().toUtf8().getStr(), "png", nullptr)); +} + +void DesktopLOKTest::testPasteWriter() +{ + LibLODocument_Impl* pDocument = loadDoc("blank_text.odt"); + OString aText("hello"_ostr); + + CPPUNIT_ASSERT(pDocument->pClass->paste(pDocument, "text/plain;charset=utf-8", aText.getStr(), aText.getLength())); + + pDocument->pClass->postUnoCommand(pDocument, ".uno:SelectAll", nullptr, false); + Scheduler::ProcessEventsToIdle(); + char* pText = pDocument->pClass->getTextSelection(pDocument, "text/plain;charset=utf-8", nullptr); + CPPUNIT_ASSERT_EQUAL("hello"_ostr, OString(pText)); + free(pText); + + // textt/plain should be rejected. + CPPUNIT_ASSERT(!pDocument->pClass->paste(pDocument, "textt/plain;charset=utf-8", aText.getStr(), aText.getLength())); + // Writer is expected to support text/html. + CPPUNIT_ASSERT(pDocument->pClass->paste(pDocument, "text/html", aText.getStr(), aText.getLength())); + + // Overwrite doc contents with a HTML paste. + pDocument->pClass->postUnoCommand(pDocument, ".uno:SelectAll", nullptr, false); + Scheduler::ProcessEventsToIdle(); + OString aComment("foo <!-- bar --> baz"_ostr); + CPPUNIT_ASSERT(pDocument->pClass->paste(pDocument, "text/html", aComment.getStr(), aComment.getLength())); + + // Check if we have a comment. + uno::Reference<text::XTextDocument> xTextDocument(mxComponent, uno::UNO_QUERY); + uno::Reference<container::XEnumerationAccess> xParagraphEnumerationAccess(xTextDocument->getText(), uno::UNO_QUERY); + uno::Reference<container::XEnumeration> xParagraphEnumeration = xParagraphEnumerationAccess->createEnumeration(); + uno::Reference<container::XEnumerationAccess> xParagraph(xParagraphEnumeration->nextElement(), uno::UNO_QUERY); + uno::Reference<container::XEnumeration> xTextPortionEnumeration = xParagraph->createEnumeration(); + uno::Reference<beans::XPropertySet> xTextPortion(xTextPortionEnumeration->nextElement(), uno::UNO_QUERY); + CPPUNIT_ASSERT_EQUAL(OUString("Text"), xTextPortion->getPropertyValue("TextPortionType").get<OUString>()); + // Without the accompanying fix in place, this test would have failed, as we had a comment + // between "foo" and "baz". + CPPUNIT_ASSERT(!xTextPortionEnumeration->hasMoreElements()); +} + +void DesktopLOKTest::testPasteWriterJPEG() +{ + LibLODocument_Impl* pDocument = loadDoc("blank_text.odt"); + + OUString aFileURL = createFileURL(u"paste.jpg"); + std::ifstream aImageStream(aFileURL.toUtf8().copy(strlen("file://")).getStr()); + std::vector<char> aImageContents((std::istreambuf_iterator<char>(aImageStream)), std::istreambuf_iterator<char>()); + + CPPUNIT_ASSERT(pDocument->pClass->paste(pDocument, "image/jpeg", aImageContents.data(), aImageContents.size())); + + uno::Reference<drawing::XDrawPageSupplier> xDrawPageSupplier(mxComponent, uno::UNO_QUERY); + uno::Reference<drawing::XDrawPage> xDrawPage = xDrawPageSupplier->getDrawPage(); + // This was 0, JPEG was not handled as a format for clipboard paste. + CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(1), xDrawPage->getCount()); + + uno::Reference<beans::XPropertySet> xShape(xDrawPage->getByIndex(0), uno::UNO_QUERY); + // This was text::TextContentAnchorType_AT_PARAGRAPH. + CPPUNIT_ASSERT_EQUAL(text::TextContentAnchorType_AS_CHARACTER, xShape->getPropertyValue("AnchorType").get<text::TextContentAnchorType>()); + + // Delete the pasted picture, and paste again with a custom anchor type. + uno::Reference<lang::XComponent>(xShape, uno::UNO_QUERY_THROW)->dispose(); + uno::Sequence<beans::PropertyValue> aPropertyValues(comphelper::InitPropertySequence( + { + {"AnchorType", uno::Any(static_cast<sal_uInt16>(text::TextContentAnchorType_AT_CHARACTER))}, + })); + dispatchCommand(mxComponent, ".uno:Paste", aPropertyValues); + xShape.set(xDrawPage->getByIndex(0), uno::UNO_QUERY); + // This was text::TextContentAnchorType_AS_CHARACTER, AnchorType argument was ignored. + CPPUNIT_ASSERT_EQUAL(text::TextContentAnchorType_AT_CHARACTER, xShape->getPropertyValue("AnchorType").get<text::TextContentAnchorType>()); +} + +void DesktopLOKTest::testUndoWriter() +{ + // Load a Writer document and press a key. + LibLODocument_Impl* pDocument = loadDoc("blank_text.odt"); + pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 't', 0); + pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYUP, 't', 0); + Scheduler::ProcessEventsToIdle(); + // Get undo info. + boost::property_tree::ptree aTree; + char* pJSON = pDocument->m_pDocumentClass->getCommandValues(pDocument, ".uno:Undo"); + std::stringstream aStream(pJSON); + free(pJSON); + CPPUNIT_ASSERT(!aStream.str().empty()); + boost::property_tree::read_json(aStream, aTree); + // Make sure that pressing a key creates exactly one undo action. + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aTree.get_child("actions").size()); +} + +void DesktopLOKTest::testRowColumnHeaders() +{ + /* + * Payload example: + * + * { + * "rows": [ + * { + * "size": "254.987250637468", + * "text": "1" + * }, + * { + * "size": "509.974501274936", + * "text": "2" + * } + * ], + * "columns": [ + * { + * "size": "1274.93625318734", + * "text": "A" + * }, + * { + * "size": "2549.87250637468", + * "text": "B" + * } + * ] + * } + * + * "size" defines the bottom/right boundary of a row/column in twips (size between 0 and boundary) + * "text" has the header label in UTF-8 + */ + LibLODocument_Impl* pDocument = loadDoc("search.ods"); + + pDocument->pClass->initializeForRendering(pDocument, nullptr); + + tools::Long nWidth = 0; + tools::Long nHeight = 0; + pDocument->m_pDocumentClass->getDocumentSize(pDocument, &nWidth, &nHeight); + tools::Long nX = rtl::math::round(nWidth / 4.0); + tools::Long nY = rtl::math::round(nHeight / 4.0); + nWidth = rtl::math::round(nWidth / 2.0); + nHeight = rtl::math::round(nHeight / 2.0); + + std::stringstream aPayload; + aPayload << ".uno:ViewRowColumnHeaders?x=" << nX << "&y=" << nY << "&width=" << nWidth << "&height=" << nHeight; + + boost::property_tree::ptree aTree; + char* pJSON = pDocument->m_pDocumentClass->getCommandValues(pDocument, aPayload.str().c_str()); + std::stringstream aStream(pJSON); + free(pJSON); + + CPPUNIT_ASSERT(!aStream.str().empty()); + + boost::property_tree::read_json(aStream, aTree); + sal_Int32 nPrevious = 0; + bool bFirstHeader = true; + bool bNotEnoughHeaders = true; + for (const boost::property_tree::ptree::value_type& rValue : aTree.get_child("rows")) + { + sal_Int32 nSize = o3tl::toInt32(rValue.second.get<std::string>("size")); + nSize = o3tl::convert(nSize, o3tl::Length::px, o3tl::Length::twip); + OString aText(rValue.second.get<std::string>("text")); + + if (bFirstHeader) + { + CPPUNIT_ASSERT(nSize <= nY); + CPPUNIT_ASSERT_EQUAL("10"_ostr, aText); + bFirstHeader = false; + } + else + { + CPPUNIT_ASSERT(nSize > 0); + CPPUNIT_ASSERT(nPrevious < nSize); + if (nSize > nY + nHeight) + { + bNotEnoughHeaders = false; + break; + } + } + nPrevious = nSize; + } + CPPUNIT_ASSERT(!bNotEnoughHeaders); + + nPrevious = 0; + bFirstHeader = true; + bNotEnoughHeaders = true; + for (const boost::property_tree::ptree::value_type& rValue : aTree.get_child("columns")) + { + sal_Int32 nSize = o3tl::toInt32(rValue.second.get<std::string>("size")); + nSize = o3tl::convert(nSize, o3tl::Length::px, o3tl::Length::twip); + OString aText(rValue.second.get<std::string>("text")); + if (bFirstHeader) + { + CPPUNIT_ASSERT(nSize <= nX); + CPPUNIT_ASSERT_EQUAL("3"_ostr, aText); + bFirstHeader = false; + } + else + { + CPPUNIT_ASSERT(nSize > 0); + CPPUNIT_ASSERT(nPrevious < nSize); + if (nSize > nX + nWidth) + { + bNotEnoughHeaders = false; + break; + } + } + nPrevious = nSize; + } + CPPUNIT_ASSERT(!bNotEnoughHeaders); +} + +void DesktopLOKTest::testHiddenRowHeaders() +{ + LibLODocument_Impl* pDocument = loadDoc("hidden-row.ods"); + + pDocument->pClass->initializeForRendering(pDocument, nullptr); + + tools::Long const nX = 0; + tools::Long const nY = 0; + tools::Long nWidth = 0; + tools::Long nHeight = 0; + pDocument->m_pDocumentClass->getDocumentSize(pDocument, &nWidth, &nHeight); + + std::stringstream aPayload; + aPayload << ".uno:ViewRowColumnHeaders?x=" << nX << "&y=" << nY << "&width=" << nWidth << "&height=" << nHeight; + + boost::property_tree::ptree aTree; + char* pJSON = pDocument->m_pDocumentClass->getCommandValues(pDocument, aPayload.str().c_str()); + std::stringstream aStream(pJSON); + CPPUNIT_ASSERT(!aStream.str().empty()); + + boost::property_tree::read_json(aStream, aTree); + free(pJSON); + sal_Int32 nPrevious = 0; + sal_Int32 nIndex = 0; + for (const boost::property_tree::ptree::value_type& rValue : aTree.get_child("rows")) + { + sal_Int32 nSize = o3tl::toInt32(rValue.second.get<std::string>("size")); + + if (nIndex++ == 2) + { + // nSize was 510, nPrevious was 255, i.e. hidden row wasn't reported as 0 height. + CPPUNIT_ASSERT_EQUAL(nPrevious, nSize); + break; + } + nPrevious = nSize; + } +} + +void DesktopLOKTest::testCellCursor() +{ + LibLODocument_Impl* pDocument = loadDoc("search.ods"); + + boost::property_tree::ptree aTree; + + char* pJSON = pDocument->m_pDocumentClass->getCommandValues(pDocument, ".uno:CellCursor?tileWidth=1&tileHeight=1&outputWidth=1&outputHeight=1"); + + std::stringstream aStream(pJSON); + free(pJSON); + CPPUNIT_ASSERT(!aStream.str().empty()); + + boost::property_tree::read_json(aStream, aTree); + + OString aRectangle(aTree.get<std::string>("commandValues")); + // cell cursor geometry + col + row + CPPUNIT_ASSERT_EQUAL("0, 0, 1274, 254, 0, 0"_ostr, aRectangle); +} + +void DesktopLOKTest::testCommandResult() +{ + LibLODocument_Impl* pDocument = loadDoc("blank_text.odt"); + + // the postUnoCommand() is supposed to be async, let's test it safely + // [no idea if it is async in reality - most probably we are operating + // under some solar mutex or something anyway ;-) - but...] + TimeValue aTimeValue = { 2 , 0 }; // 2 seconds max + + // nothing is triggered when we have no callback yet, we just time out on + // the condition var. + m_aCommandResultCondition.reset(); + pDocument->pClass->postUnoCommand(pDocument, ".uno:Bold", nullptr, true); + Scheduler::ProcessEventsToIdle(); + m_aCommandResultCondition.wait(aTimeValue); + + CPPUNIT_ASSERT(m_aCommandResult.isEmpty()); + + // but we get some real values when the callback is set up + pDocument->pClass->registerCallback(pDocument, &DesktopLOKTest::callback, this); + + m_aCommandResultCondition.reset(); + pDocument->pClass->postUnoCommand(pDocument, ".uno:Bold", nullptr, true); + Scheduler::ProcessEventsToIdle(); + m_aCommandResultCondition.wait(aTimeValue); + + boost::property_tree::ptree aTree; + std::stringstream aStream((std::string(m_aCommandResult))); + boost::property_tree::read_json(aStream, aTree); + + CPPUNIT_ASSERT_EQUAL(std::string(".uno:Bold"), aTree.get_child("commandName").get_value<std::string>()); + CPPUNIT_ASSERT_EQUAL(true, aTree.get_child("success").get_value<bool>()); +} + +void DesktopLOKTest::testWriterComments() +{ + LibLODocument_Impl* pDocument = loadDoc("blank_text.odt"); + pDocument->pClass->registerCallback(pDocument, &DesktopLOKTest::callback, this); + uno::Reference<awt::XReschedule> xToolkit = com::sun::star::awt::Toolkit::create(comphelper::getProcessComponentContext()); + + // Insert a comment at the beginning of the document and wait till the main + // loop grabs the focus, so characters end up in the annotation window. + TimeValue const aTimeValue = {2 , 0}; // 2 seconds max + m_aCommandResultCondition.reset(); + pDocument->pClass->postUnoCommand(pDocument, ".uno:InsertAnnotation", nullptr, true); + Scheduler::ProcessEventsToIdle(); + m_aCommandResultCondition.wait(aTimeValue); + CPPUNIT_ASSERT(!m_aCommandResult.isEmpty()); + xToolkit->reschedule(); + + // Test that we have a comment. + uno::Reference<text::XTextDocument> xTextDocument(mxComponent, uno::UNO_QUERY); + uno::Reference<container::XEnumerationAccess> xParagraphEnumerationAccess(xTextDocument->getText(), uno::UNO_QUERY); + uno::Reference<container::XEnumeration> xParagraphEnumeration = xParagraphEnumerationAccess->createEnumeration(); + uno::Reference<container::XEnumerationAccess> xParagraph(xParagraphEnumeration->nextElement(), uno::UNO_QUERY); + uno::Reference<container::XEnumeration> xTextPortionEnumeration = xParagraph->createEnumeration(); + uno::Reference<beans::XPropertySet> xTextPortion(xTextPortionEnumeration->nextElement(), uno::UNO_QUERY); + CPPUNIT_ASSERT_EQUAL(OUString("Annotation"), xTextPortion->getPropertyValue("TextPortionType").get<OUString>()); + + // Type "test" and finish editing. + pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 't', 0); + pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 'e', 0); + pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 's', 0); + pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 't', 0); + pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 0, com::sun::star::awt::Key::ESCAPE); + Scheduler::ProcessEventsToIdle(); + + // Test that the typed characters ended up in the right window. + auto xTextField = xTextPortion->getPropertyValue("TextField").get< uno::Reference<beans::XPropertySet> >(); + // This was empty, typed characters ended up in the body text. + CPPUNIT_ASSERT_EQUAL(OUString("test"), xTextField->getPropertyValue("Content").get<OUString>()); +} + +void DesktopLOKTest::testTrackChanges() +{ + // Load a document and create two views. + LibLibreOffice_Impl aOffice; + LibLODocument_Impl* pDocument = loadDoc("blank_text.odt"); + pDocument->pClass->initializeForRendering(pDocument, nullptr); + pDocument->pClass->registerCallback(pDocument, &DesktopLOKTest::callback, this); + pDocument->pClass->createView(pDocument); + pDocument->pClass->initializeForRendering(pDocument, nullptr); + pDocument->pClass->registerCallback(pDocument, &DesktopLOKTest::callback, this); + Scheduler::ProcessEventsToIdle(); + + // Enable track changes and assert that both views get notified. + m_nTrackChanges = 0; + pDocument->pClass->postUnoCommand(pDocument, ".uno:TrackChanges", nullptr, false); + Scheduler::ProcessEventsToIdle(); + // This was 1, only the active view was notified. + CPPUNIT_ASSERT_EQUAL(2, m_nTrackChanges); +} + +void DesktopLOKTest::testSheetOperations() +{ + LibLODocument_Impl* pDocument = loadDoc("sheets.ods"); + + // insert the last sheet + pDocument->pClass->postUnoCommand(pDocument, ".uno:Insert", + "{ \"Name\": { \"type\": \"string\", \"value\": \"LastSheet\" }, \"Index\": { \"type\": \"long\", \"value\": 0 } }", false); + + // insert the first sheet + pDocument->pClass->postUnoCommand(pDocument, ".uno:Insert", + "{ \"Name\": { \"type\": \"string\", \"value\": \"FirstSheet\" }, \"Index\": { \"type\": \"long\", \"value\": 1 } }", false); + + // rename the \"Sheet1\" (2nd now) to \"Renamed\" + pDocument->pClass->postUnoCommand(pDocument, ".uno:Name", + "{ \"Name\": { \"type\": \"string\", \"value\": \"Renamed\" }, \"Index\": { \"type\": \"long\", \"value\": 2 } }", false); + + // delete the \"Sheet2\" (3rd) + pDocument->pClass->postUnoCommand(pDocument, ".uno:Remove", + "{ \"Index\": { \"type\": \"long\", \"value\": 3 } }", false); + + Scheduler::ProcessEventsToIdle(); + CPPUNIT_ASSERT_EQUAL(6, pDocument->pClass->getParts(pDocument)); + + std::vector<OString> aExpected = { "FirstSheet"_ostr, "Renamed"_ostr, "Sheet3"_ostr, "Sheet4"_ostr, "Sheet5"_ostr, "LastSheet"_ostr }; + for (int i = 0; i < 6; ++i) + { + char* pPartName = pDocument->pClass->getPartName(pDocument, i); + CPPUNIT_ASSERT_EQUAL(aExpected[i], OString(pPartName)); + free(pPartName); + } +} + +void DesktopLOKTest::testSheetSelections() +{ + LibLODocument_Impl* pDocument = loadDoc("sheets.ods", LOK_DOCTYPE_SPREADSHEET); + pDocument->pClass->initializeForRendering(pDocument, nullptr); + pDocument->pClass->registerCallback(pDocument, &DesktopLOKTest::callback, this); + + /* + * Check if selection data is correct + */ + // Values in twips + int row5 = 1150; + int col1 = 1100; + int const col2 = 2200; + int const col3 = 3300; + int col4 = 4400; + int col5 = 5500; + + // Select row 5 from column 1 through column 5 + pDocument->pClass->postMouseEvent(pDocument, + LOK_MOUSEEVENT_MOUSEBUTTONDOWN, + col1, row5, + 1, 1, 0); + pDocument->pClass->postMouseEvent(pDocument, + LOK_MOUSEEVENT_MOUSEMOVE, + col2, row5, + 1, 1, 0); + pDocument->pClass->postMouseEvent(pDocument, + LOK_MOUSEEVENT_MOUSEMOVE, + col3, row5, + 1, 1, 0); + pDocument->pClass->postMouseEvent(pDocument, + LOK_MOUSEEVENT_MOUSEMOVE, + col4, row5, + 1, 1, 0); + pDocument->pClass->postMouseEvent(pDocument, + LOK_MOUSEEVENT_MOUSEMOVE, + col5, row5, + 1, 1, 0); + pDocument->pClass->postMouseEvent(pDocument, + LOK_MOUSEEVENT_MOUSEBUTTONUP, + col5, row5, + 1, 1, 0); + Scheduler::ProcessEventsToIdle(); + + // Copy the contents and check if matches expected data + { + char* pUsedMimeType = nullptr; + char* pCopiedContent = pDocument->pClass->getTextSelection(pDocument, nullptr, &pUsedMimeType); + std::vector<long> aExpected = {5, 6, 7, 8, 9}; + std::istringstream iss(pCopiedContent); + for (const long nIndex : aExpected) + { + std::string token; + iss >> token; + CPPUNIT_ASSERT_EQUAL(nIndex, strtol(token.c_str(), nullptr, 10)); + } + + free(pUsedMimeType); + free(pCopiedContent); + } + + /* + * Check if clicking inside the selection deselects the whole selection + */ + + // Click at row5, col4 + pDocument->pClass->postMouseEvent(pDocument, + LOK_MOUSEEVENT_MOUSEBUTTONDOWN, + col4, row5, + 1, 1, 0); + pDocument->pClass->postMouseEvent(pDocument, + LOK_MOUSEEVENT_MOUSEBUTTONUP, + col4, row5, + 1, 1, 0); + Scheduler::ProcessEventsToIdle(); + + // Selected text should get deselected and copying should give us + // content of only one cell, now + { + char* pUsedMimeType = nullptr; + char* pCopiedContent = pDocument->pClass->getTextSelection(pDocument, nullptr, &pUsedMimeType); + std::vector<long> aExpected = { 8 }; + std::istringstream iss(pCopiedContent); + for (const long nIndex : aExpected) + { + std::string token; + iss >> token; + CPPUNIT_ASSERT_EQUAL(nIndex, strtol(token.c_str(), nullptr, 10)); + } + + free(pUsedMimeType); + free(pCopiedContent); + } +} + +void DesktopLOKTest::testSheetDragDrop() +{ + LibLODocument_Impl* pDocument = loadDoc("sheets.ods", LOK_DOCTYPE_SPREADSHEET); + pDocument->pClass->initializeForRendering(pDocument, nullptr); + pDocument->pClass->registerCallback(pDocument, &DesktopLOKTest::callback, this); + + int row01 = 100; + int col01 = 1100; + int col02 = 2200; + int col03 = 3300; + int col05 = 5500; + int col07 = 5700; + + // Select row 01 from column 01 through column 05 + pDocument->pClass->postMouseEvent(pDocument, + LOK_MOUSEEVENT_MOUSEBUTTONDOWN, + col01, row01, + 1, 1, 0); + pDocument->pClass->postMouseEvent(pDocument, + LOK_MOUSEEVENT_MOUSEMOVE, + col02, row01, + 1, 1, 0); + pDocument->pClass->postMouseEvent(pDocument, + LOK_MOUSEEVENT_MOUSEMOVE, + col05, row01, + 1, 1, 0); + pDocument->pClass->postMouseEvent(pDocument, + LOK_MOUSEEVENT_MOUSEBUTTONUP, + col05, row01, + 1, 1, 0); + + Scheduler::ProcessEventsToIdle(); + { + SfxViewShell* pViewShell = SfxViewShell::Current(); + SfxViewFrame& rViewFrame = pViewShell->GetViewFrame(); + + OUString sValue; + css::uno::Any aValue; + css::util::URL aURL; + std::unique_ptr<SfxPoolItem> pState; + + aURL.Protocol = ".uno:"; + aURL.Complete = ".uno:Address"; + aURL.Path = "Address"; + aURL.Main = ".uno:Address"; + + rViewFrame.GetBindings().QueryState(rViewFrame.GetBindings().QuerySlotId(aURL), pState); + pState->QueryValue(aValue); + aValue >>= sValue; + CPPUNIT_ASSERT_EQUAL(OUString("Sheet5.A1:E1"), sValue); + } + + // Check selection content + { + char* pMimeType = nullptr; + char* pContent = pDocument->pClass->getTextSelection(pDocument, nullptr, &pMimeType); + std::vector<long> aExpected = {1, 2, 3, 4, 5}; + std::istringstream aContent(pContent); + std::string token; + for (const long nIndex : aExpected) + { + aContent >> token; + CPPUNIT_ASSERT_EQUAL(nIndex, strtol(token.c_str(), nullptr, 10)); + } + + free(pMimeType); + free(pContent); + } + + // drag and drop + pDocument->pClass->postMouseEvent(pDocument, + LOK_MOUSEEVENT_MOUSEBUTTONDOWN, + col01, row01, + 1, 1, 0); + pDocument->pClass->postMouseEvent(pDocument, + LOK_MOUSEEVENT_MOUSEMOVE, + col02, row01, + 1, 1, 0); + pDocument->pClass->postMouseEvent(pDocument, + LOK_MOUSEEVENT_MOUSEMOVE, + col03, row01, + 1, 1, 0); + pDocument->pClass->postMouseEvent(pDocument, + LOK_MOUSEEVENT_MOUSEBUTTONUP, + col07, row01, + 1, 1, 0); + + Scheduler::ProcessEventsToIdle(); + { + SfxViewShell* pViewShell = SfxViewShell::Current(); + SfxViewFrame& rViewFrame = pViewShell->GetViewFrame(); + + OUString sValue; + css::uno::Any aValue; + css::util::URL aURL; + std::unique_ptr<SfxPoolItem> pState; + + aURL.Protocol = ".uno:"; + aURL.Complete = ".uno:Address"; + aURL.Path = "Address"; + aURL.Main = ".uno:Address"; + + rViewFrame.GetBindings().QueryState(rViewFrame.GetBindings().QuerySlotId(aURL), pState); + pState->QueryValue(aValue); + aValue >>= sValue; + CPPUNIT_ASSERT_EQUAL(OUString("Sheet5.D1:H1"), sValue); + } + + // Check selection content + { + char* pMimeType = nullptr; + char* pContent = pDocument->pClass->getTextSelection(pDocument, nullptr, &pMimeType); + std::vector<long> aExpected = {1, 2, 3, 4, 5}; + std::istringstream aContent(pContent); + std::string token; + for (const long nIndex : aExpected) + { + aContent >> token; + CPPUNIT_ASSERT_EQUAL(nIndex, strtol(token.c_str(), nullptr, 10)); + } + + free(pMimeType); + free(pContent); + } +} + +namespace { + + void verifyContextMenuStructure(boost::property_tree::ptree& aRoot) + { + for (const auto& aItemPair: aRoot) + { + // This is an array, so no key + CPPUNIT_ASSERT_EQUAL(aItemPair.first, std::string("")); + + boost::property_tree::ptree aItemValue = aItemPair.second; + boost::optional<boost::property_tree::ptree&> aText = aItemValue.get_child_optional("text"); + boost::optional<boost::property_tree::ptree&> aType = aItemValue.get_child_optional("type"); + boost::optional<boost::property_tree::ptree&> aCommand = aItemValue.get_child_optional("command"); + boost::optional<boost::property_tree::ptree&> aSubmenu = aItemValue.get_child_optional("menu"); + boost::optional<boost::property_tree::ptree&> aEnabled = aItemValue.get_child_optional("enabled"); + boost::optional<boost::property_tree::ptree&> aChecktype = aItemValue.get_child_optional("checktype"); + boost::optional<boost::property_tree::ptree&> aChecked = aItemValue.get_child_optional("checked"); + + // type is omnipresent + CPPUNIT_ASSERT( aType ); + + // separator doesn't have any other attribs + if ( aType.get().data() == "separator" ) + { + CPPUNIT_ASSERT( !aText ); + CPPUNIT_ASSERT( !aCommand ); + CPPUNIT_ASSERT( !aSubmenu ); + CPPUNIT_ASSERT( !aEnabled ); + CPPUNIT_ASSERT( !aChecktype ); + CPPUNIT_ASSERT( !aChecked ); + } + else if ( aType.get().data() == "command" ) + { + CPPUNIT_ASSERT( aCommand ); + CPPUNIT_ASSERT( aText ); + } + else if ( aType.get().data() == "menu") + { + CPPUNIT_ASSERT( aSubmenu ); + CPPUNIT_ASSERT( aText ); + verifyContextMenuStructure( aSubmenu.get() ); + } + + if ( aChecktype ) + { + CPPUNIT_ASSERT( aChecktype.get().data() == "radio" || + aChecktype.get().data() == "checkmark" || + aChecktype.get().data() == "auto" ); + + CPPUNIT_ASSERT( aChecked ); + CPPUNIT_ASSERT( aChecked.get().data() == "true" || aChecked.get().data() == "false" ); + } + } + + } + + boost::optional<boost::property_tree::ptree> + getContextMenuItem(boost::property_tree::ptree& aMenu, std::string const & unoSelector) + { + boost::optional<boost::property_tree::ptree> aMenuItem; + for (const auto& aItemPair: aMenu) + { + boost::property_tree::ptree aItemValue = aItemPair.second; + + boost::optional<boost::property_tree::ptree&> aCommand = aItemValue.get_child_optional("command"); + if (aCommand && aCommand.get().data() == unoSelector ) + { + aMenuItem = aItemValue; + break; + } + } + + return aMenuItem; + } + +} // end anonymous namespace + +void DesktopLOKTest::testContextMenuCalc() +{ + LibLODocument_Impl* pDocument = loadDoc("sheet_with_image.ods", LOK_DOCTYPE_SPREADSHEET); + pDocument->pClass->initializeForRendering(pDocument, nullptr); + pDocument->pClass->registerCallback(pDocument, &DesktopLOKTest::callback, this); + + // Values in twips + Point aPointOnImage(1150, 1100); + pDocument->pClass->postMouseEvent(pDocument, + LOK_MOUSEEVENT_MOUSEBUTTONDOWN, + aPointOnImage.X(), aPointOnImage.Y(), + 1, 4, 0); + Scheduler::ProcessEventsToIdle(); + + TimeValue const aTimeValue = {2 , 0}; // 2 seconds max + m_aContextMenuCondition.wait(aTimeValue); + + CPPUNIT_ASSERT( !m_aContextMenuResult.empty() ); + boost::optional<boost::property_tree::ptree&> aMenu = m_aContextMenuResult.get_child_optional("menu"); + CPPUNIT_ASSERT( aMenu ); + verifyContextMenuStructure( aMenu.get() ); + + // tests for calc specific context menu + // Cut is enabled + { + boost::optional<boost::property_tree::ptree> aMenuItem = getContextMenuItem(aMenu.get(), ".uno:Cut"); + CPPUNIT_ASSERT(aMenuItem); + + boost::optional<boost::property_tree::ptree&> aEnabled = aMenuItem.get().get_child_optional("enabled"); + CPPUNIT_ASSERT(aEnabled); + CPPUNIT_ASSERT_EQUAL(aEnabled.get().data(), std::string("true")); + } + + // Copy is enabled + { + boost::optional<boost::property_tree::ptree> aMenuItem = getContextMenuItem(aMenu.get(), ".uno:Copy"); + CPPUNIT_ASSERT(aMenuItem); + + boost::optional<boost::property_tree::ptree&> aEnabled = aMenuItem.get().get_child_optional("enabled"); + CPPUNIT_ASSERT(aEnabled); + CPPUNIT_ASSERT_EQUAL(aEnabled.get().data(), std::string("true")); + } + + // Paste is enabled + { + boost::optional<boost::property_tree::ptree> aMenuItem = getContextMenuItem(aMenu.get(), ".uno:Paste"); + CPPUNIT_ASSERT(aMenuItem); + + boost::optional<boost::property_tree::ptree&> aEnabled = aMenuItem.get().get_child_optional("enabled"); + CPPUNIT_ASSERT(aEnabled); + CPPUNIT_ASSERT_EQUAL(aEnabled.get().data(), std::string("true")); + } + + // Remove hyperlink is disabled + { + boost::optional<boost::property_tree::ptree> aMenuItem = getContextMenuItem(aMenu.get(), ".uno:RemoveHyperlink"); + CPPUNIT_ASSERT(aMenuItem); + + boost::optional<boost::property_tree::ptree&> aEnabled = aMenuItem.get().get_child_optional("enabled"); + CPPUNIT_ASSERT(aEnabled); + CPPUNIT_ASSERT_EQUAL(aEnabled.get().data(), std::string("false")); + } + + // open hyperlink is disabled + { + boost::optional<boost::property_tree::ptree> aMenuItem = getContextMenuItem(aMenu.get(), ".uno:OpenHyperlinkOnCursor"); + CPPUNIT_ASSERT(aMenuItem); + + boost::optional<boost::property_tree::ptree&> aEnabled = aMenuItem.get().get_child_optional("enabled"); + CPPUNIT_ASSERT(aEnabled); + CPPUNIT_ASSERT_EQUAL(aEnabled.get().data(), std::string("false")); + } + + // checkbutton tests + { + boost::optional<boost::property_tree::ptree> aMenuItem = getContextMenuItem(aMenu.get(), ".uno:AnchorMenu"); + CPPUNIT_ASSERT(aMenuItem); + + boost::optional<boost::property_tree::ptree&> aSubmenu = aMenuItem.get().get_child_optional("menu"); + CPPUNIT_ASSERT(aSubmenu); + + boost::optional<boost::property_tree::ptree> aMenuItemToPage = getContextMenuItem(aSubmenu.get(), ".uno:SetAnchorToPage"); + CPPUNIT_ASSERT(aMenuItemToPage); + + boost::optional<boost::property_tree::ptree> aMenuItemToCell = getContextMenuItem(aSubmenu.get(), ".uno:SetAnchorToCell"); + CPPUNIT_ASSERT(aMenuItemToCell); + + // these are radio buttons + boost::optional<boost::property_tree::ptree&> aChecktypeToPage = aMenuItemToPage.get().get_child_optional("checktype"); + CPPUNIT_ASSERT(aChecktypeToPage); + CPPUNIT_ASSERT_EQUAL(aChecktypeToPage.get().data(), std::string("radio")); + + boost::optional<boost::property_tree::ptree&> aChecktypeToCell = aMenuItemToCell.get().get_child_optional("checktype"); + CPPUNIT_ASSERT(aChecktypeToCell); + CPPUNIT_ASSERT_EQUAL(aChecktypeToCell.get().data(), std::string("radio")); + + // ToPage is checked + boost::optional<boost::property_tree::ptree&> aCheckedToPage = aMenuItemToPage.get().get_child_optional("checked"); + CPPUNIT_ASSERT(aCheckedToPage); + CPPUNIT_ASSERT_EQUAL(aCheckedToPage.get().data(), std::string("true")); + + // ToCell is unchecked + boost::optional<boost::property_tree::ptree&> aCheckedToCell = aMenuItemToCell.get().get_child_optional("checked"); + CPPUNIT_ASSERT(aCheckedToCell); + CPPUNIT_ASSERT_EQUAL(aCheckedToCell.get().data(), std::string("false")); + } +} + +void DesktopLOKTest::testContextMenuWriter() +{ + LibLODocument_Impl* pDocument = loadDoc("blank_text.odt"); + pDocument->pClass->initializeForRendering(pDocument, nullptr); + pDocument->pClass->registerCallback(pDocument, &DesktopLOKTest::callback, this); + + Point aRandomPoint(1150, 1100); + pDocument->pClass->postMouseEvent(pDocument, + LOK_MOUSEEVENT_MOUSEBUTTONDOWN, + aRandomPoint.X(), aRandomPoint.Y(), + 1, 4, 0); + Scheduler::ProcessEventsToIdle(); + + TimeValue const aTimeValue = {2 , 0}; // 2 seconds max + m_aContextMenuCondition.wait(aTimeValue); + + CPPUNIT_ASSERT( !m_aContextMenuResult.empty() ); + boost::optional<boost::property_tree::ptree&> aMenu = m_aContextMenuResult.get_child_optional("menu"); + CPPUNIT_ASSERT( aMenu ); + verifyContextMenuStructure( aMenu.get() ); + + // tests for writer specific context menu + // Cut is disabled + { + boost::optional<boost::property_tree::ptree> aMenuItem = getContextMenuItem(aMenu.get(), ".uno:Cut"); + CPPUNIT_ASSERT(aMenuItem); + + boost::optional<boost::property_tree::ptree&> aEnabled = aMenuItem.get().get_child_optional("enabled"); + CPPUNIT_ASSERT(aEnabled); + CPPUNIT_ASSERT_EQUAL(aEnabled.get().data(), std::string("false")); + } + + // Copy is disabled + { + boost::optional<boost::property_tree::ptree> aMenuItem = getContextMenuItem(aMenu.get(), ".uno:Copy"); + CPPUNIT_ASSERT(aMenuItem); + + boost::optional<boost::property_tree::ptree&> aEnabled = aMenuItem.get().get_child_optional("enabled"); + CPPUNIT_ASSERT(aEnabled); + CPPUNIT_ASSERT_EQUAL(aEnabled.get().data(), std::string("false")); + } + + // Paste is enabled + { + boost::optional<boost::property_tree::ptree> aMenuItem = getContextMenuItem(aMenu.get(), ".uno:Paste"); + CPPUNIT_ASSERT(aMenuItem); + + boost::optional<boost::property_tree::ptree&> aEnabled = aMenuItem.get().get_child_optional("enabled"); + CPPUNIT_ASSERT(aEnabled); + CPPUNIT_ASSERT_EQUAL(aEnabled.get().data(), std::string("true")); + } +} + +void DesktopLOKTest::testContextMenuImpress() +{ + LibLODocument_Impl* pDocument = loadDoc("blank_presentation.odp", LOK_DOCTYPE_PRESENTATION); + pDocument->pClass->initializeForRendering(pDocument, nullptr); + pDocument->pClass->registerCallback(pDocument, &DesktopLOKTest::callback, this); + + // random point where we don't hit an underlying comment or text box + Point aRandomPoint(10, 1150); + pDocument->pClass->postMouseEvent(pDocument, + LOK_MOUSEEVENT_MOUSEBUTTONDOWN, + aRandomPoint.X(), aRandomPoint.Y(), + 1, 4, 0); + Scheduler::ProcessEventsToIdle(); + + TimeValue const aTimeValue = {2 , 0}; // 2 seconds max + m_aContextMenuCondition.wait(aTimeValue); + + CPPUNIT_ASSERT( !m_aContextMenuResult.empty() ); + boost::optional<boost::property_tree::ptree&> aMenu = m_aContextMenuResult.get_child_optional("menu"); + CPPUNIT_ASSERT( aMenu ); + verifyContextMenuStructure( aMenu.get() ); + + // tests for impress specific context menu + // Cut is disabled + { + boost::optional<boost::property_tree::ptree> aMenuItem = getContextMenuItem(aMenu.get(), ".uno:Cut"); + CPPUNIT_ASSERT(aMenuItem); + + boost::optional<boost::property_tree::ptree&> aEnabled = aMenuItem.get().get_child_optional("enabled"); + CPPUNIT_ASSERT(aEnabled); + CPPUNIT_ASSERT_EQUAL(aEnabled.get().data(), std::string("false")); + } + + // Copy is disabled + { + boost::optional<boost::property_tree::ptree> aMenuItem = getContextMenuItem(aMenu.get(), ".uno:Copy"); + CPPUNIT_ASSERT(aMenuItem); + + boost::optional<boost::property_tree::ptree&> aEnabled = aMenuItem.get().get_child_optional("enabled"); + CPPUNIT_ASSERT(aEnabled); + CPPUNIT_ASSERT_EQUAL(aEnabled.get().data(), std::string("false")); + } + + // Paste is enabled + { + boost::optional<boost::property_tree::ptree> aMenuItem = getContextMenuItem(aMenu.get(), ".uno:Paste"); + CPPUNIT_ASSERT(aMenuItem); + + boost::optional<boost::property_tree::ptree&> aEnabled = aMenuItem.get().get_child_optional("enabled"); + CPPUNIT_ASSERT(aEnabled); + CPPUNIT_ASSERT_EQUAL(aEnabled.get().data(), std::string("true")); + } + + // SaveBackground is disabled + { + boost::optional<boost::property_tree::ptree> aMenuItem = getContextMenuItem(aMenu.get(), ".uno:SaveBackground"); + CPPUNIT_ASSERT(aMenuItem); + + boost::optional<boost::property_tree::ptree&> aEnabled = aMenuItem.get().get_child_optional("enabled"); + CPPUNIT_ASSERT(aEnabled); + CPPUNIT_ASSERT_EQUAL(aEnabled.get().data(), std::string("false")); + } + + // checkbutton tests + { + boost::optional<boost::property_tree::ptree> aMenuItem = getContextMenuItem(aMenu.get(), ".uno:ShowRuler"); + CPPUNIT_ASSERT(aMenuItem); + + boost::optional<boost::property_tree::ptree&> aChecktype = aMenuItem.get().get_child_optional("checktype"); + CPPUNIT_ASSERT(aChecktype); + CPPUNIT_ASSERT_EQUAL(aChecktype.get().data(), std::string("checkmark")); + + boost::optional<boost::property_tree::ptree&> aChecked = aMenuItem.get().get_child_optional("checked"); + CPPUNIT_ASSERT(aChecked); + CPPUNIT_ASSERT_EQUAL(aChecked.get().data(), std::string("false")); + } + + // Checkbutton tests inside SnapLines submenu + { + boost::optional<boost::property_tree::ptree> aMenuItem = getContextMenuItem(aMenu.get(), ".uno:SnapLinesMenu"); + CPPUNIT_ASSERT(aMenuItem); + + boost::optional<boost::property_tree::ptree&> aSubmenu = aMenuItem.get().get_child_optional("menu"); + CPPUNIT_ASSERT(aSubmenu); + + boost::optional<boost::property_tree::ptree> aMenuItemHelpVis = getContextMenuItem(aSubmenu.get(), ".uno:HelplinesVisible"); + CPPUNIT_ASSERT(aMenuItemHelpVis); + + boost::optional<boost::property_tree::ptree> aMenuItemHelpUse = getContextMenuItem(aSubmenu.get(), ".uno:HelplinesUse"); + CPPUNIT_ASSERT(aMenuItemHelpUse); + + boost::optional<boost::property_tree::ptree> aMenuItemHelpFront = getContextMenuItem(aSubmenu.get(), ".uno:HelplinesFront"); + CPPUNIT_ASSERT(aMenuItemHelpFront); + + // these are checkmarks + boost::optional<boost::property_tree::ptree&> aChecktypeHelpVis = aMenuItemHelpVis.get().get_child_optional("checktype"); + CPPUNIT_ASSERT(aChecktypeHelpVis); + CPPUNIT_ASSERT_EQUAL(aChecktypeHelpVis.get().data(), std::string("checkmark")); + + boost::optional<boost::property_tree::ptree&> aChecktypeHelpUse = aMenuItemHelpUse.get().get_child_optional("checktype"); + CPPUNIT_ASSERT(aChecktypeHelpUse); + CPPUNIT_ASSERT_EQUAL(aChecktypeHelpUse.get().data(), std::string("checkmark")); + + boost::optional<boost::property_tree::ptree&> aChecktypeHelpFront = aMenuItemHelpFront.get().get_child_optional("checktype"); + CPPUNIT_ASSERT(aChecktypeHelpFront); + CPPUNIT_ASSERT_EQUAL(aChecktypeHelpFront.get().data(), std::string("checkmark")); + + // HelplineVisible is unchecked + boost::optional<boost::property_tree::ptree&> aCheckedHelpVis = aMenuItemHelpVis.get().get_child_optional("checked"); + CPPUNIT_ASSERT(aCheckedHelpVis); + CPPUNIT_ASSERT_EQUAL(aCheckedHelpVis.get().data(), std::string("false")); + + // HelplineUse is checked + boost::optional<boost::property_tree::ptree&> aCheckedHelpUse = aMenuItemHelpUse.get().get_child_optional("checked"); + CPPUNIT_ASSERT(aCheckedHelpUse); + CPPUNIT_ASSERT_EQUAL(aCheckedHelpUse.get().data(), std::string("true")); + + // HelplineFront is checked + boost::optional<boost::property_tree::ptree&> aCheckedHelpFront = aMenuItemHelpFront.get().get_child_optional("checked"); + CPPUNIT_ASSERT(aCheckedHelpFront); + CPPUNIT_ASSERT_EQUAL(aCheckedHelpFront.get().data(), std::string("true")); + } +} + +static void callbackCompressionTest(const int type, const char* payload, void* data) +{ + std::vector<std::tuple<int, std::string>>* notifs = static_cast<std::vector<std::tuple<int, std::string>>*>(data); + notifs->emplace_back(type, std::string(payload ? payload : "(nil)")); +} + +void DesktopLOKTest::testNotificationCompression() +{ + LibLODocument_Impl* pDocument = loadDoc("blank_text.odt"); + std::vector<std::tuple<int, std::string>> notifs; + std::unique_ptr<CallbackFlushHandler> handler(new CallbackFlushHandler(pDocument, callbackCompressionTest, ¬ifs)); + handler->setViewId(SfxLokHelper::getView()); + + handler->queue(LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR, ""_ostr); // 0 + handler->queue(LOK_CALLBACK_TEXT_SELECTION, "15, 25, 15, 10"_ostr); // Superseded. + handler->queue(LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR, ""_ostr); // Should be dropped. + handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "15, 25, 15, 10"_ostr); // 1 + handler->queue(LOK_CALLBACK_TEXT_SELECTION, "15, 25, 15, 10"_ostr); // Should be dropped. + handler->queue(LOK_CALLBACK_TEXT_SELECTION, ""_ostr); // Superseded. + handler->queue(LOK_CALLBACK_STATE_CHANGED, ""_ostr); // 2 + handler->queue(LOK_CALLBACK_STATE_CHANGED, ".uno:Bold"_ostr); // 3 + handler->queue(LOK_CALLBACK_STATE_CHANGED, ""_ostr); // 4 + handler->queue(LOK_CALLBACK_MOUSE_POINTER, "text"_ostr); // 5 + handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "15, 25, 15, 10"_ostr); // Should be dropped. + handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "15, 25, 15, 10"_ostr); // Should be dropped. + handler->queue(LOK_CALLBACK_MOUSE_POINTER, "text"_ostr); // Should be dropped. + handler->queue(LOK_CALLBACK_TEXT_SELECTION_START, "15, 25, 15, 10"_ostr); // Superseded. + handler->queue(LOK_CALLBACK_TEXT_SELECTION_END, "15, 25, 15, 10"_ostr); // Superseded. + handler->queue(LOK_CALLBACK_TEXT_SELECTION, "15, 25, 15, 10"_ostr); // Superseded. + handler->queue(LOK_CALLBACK_TEXT_SELECTION_START, "15, 25, 15, 10"_ostr); // Should be dropped. + handler->queue(LOK_CALLBACK_TEXT_SELECTION_END, "15, 25, 15, 10"_ostr); // Should be dropped. + handler->queue(LOK_CALLBACK_TEXT_SELECTION, ""_ostr); // 7 + handler->queue(LOK_CALLBACK_TEXT_SELECTION_START, "15, 25, 15, 10"_ostr); // 8 + handler->queue(LOK_CALLBACK_TEXT_SELECTION_END, "15, 25, 15, 10"_ostr); // 9 + handler->queue(LOK_CALLBACK_CELL_CURSOR, "15, 25, 15, 10"_ostr); // 10 + handler->queue(LOK_CALLBACK_CURSOR_VISIBLE, ""_ostr); // 11 + handler->queue(LOK_CALLBACK_CELL_CURSOR, "15, 25, 15, 10"_ostr); // Should be dropped. + handler->queue(LOK_CALLBACK_CELL_FORMULA, "blah"_ostr); // 12 + handler->queue(LOK_CALLBACK_SET_PART, "1"_ostr); // 13 + handler->queue(LOK_CALLBACK_STATE_CHANGED, ".uno:AssignLayout=20"_ostr); // Superseded + handler->queue(LOK_CALLBACK_CURSOR_VISIBLE, ""_ostr); // Should be dropped. + handler->queue(LOK_CALLBACK_CELL_FORMULA, "blah"_ostr); // Should be dropped. + handler->queue(LOK_CALLBACK_SET_PART, "1"_ostr); // Should be dropped. + handler->queue(LOK_CALLBACK_STATE_CHANGED, ".uno:AssignLayout=1"_ostr); // 14 + + Scheduler::ProcessEventsToIdle(); + + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(14), notifs.size()); + + size_t i = 0; + CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR), std::get<0>(notifs[i])); + CPPUNIT_ASSERT_EQUAL(std::string(""), std::get<1>(notifs[i++])); + + CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_INVALIDATE_TILES), std::get<0>(notifs[i])); + CPPUNIT_ASSERT_EQUAL(std::string("15, 25, 15, 10"), std::get<1>(notifs[i++])); + + CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_STATE_CHANGED), std::get<0>(notifs[i])); + CPPUNIT_ASSERT_EQUAL(std::string(""), std::get<1>(notifs[i++])); + + CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_STATE_CHANGED), std::get<0>(notifs[i])); + CPPUNIT_ASSERT_EQUAL(std::string(".uno:Bold"), std::get<1>(notifs[i++])); + + CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_STATE_CHANGED), std::get<0>(notifs[i])); + CPPUNIT_ASSERT_EQUAL(std::string(""), std::get<1>(notifs[i++])); + + CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_MOUSE_POINTER), std::get<0>(notifs[i])); + CPPUNIT_ASSERT_EQUAL(std::string("text"), std::get<1>(notifs[i++])); + + CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_TEXT_SELECTION), std::get<0>(notifs[i])); + CPPUNIT_ASSERT_EQUAL(std::string(""), std::get<1>(notifs[i++])); + + CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_TEXT_SELECTION_START), std::get<0>(notifs[i])); + CPPUNIT_ASSERT_EQUAL(std::string("15, 25, 15, 10"), std::get<1>(notifs[i++])); + + CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_TEXT_SELECTION_END), std::get<0>(notifs[i])); + CPPUNIT_ASSERT_EQUAL(std::string("15, 25, 15, 10"), std::get<1>(notifs[i++])); + + CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_CELL_CURSOR), std::get<0>(notifs[i])); + CPPUNIT_ASSERT_EQUAL(std::string("15, 25, 15, 10"), std::get<1>(notifs[i++])); + + CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_CURSOR_VISIBLE), std::get<0>(notifs[i])); + CPPUNIT_ASSERT_EQUAL(std::string(""), std::get<1>(notifs[i++])); + + CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_CELL_FORMULA), std::get<0>(notifs[i])); + CPPUNIT_ASSERT_EQUAL(std::string("blah"), std::get<1>(notifs[i++])); + + CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_SET_PART), std::get<0>(notifs[i])); + CPPUNIT_ASSERT_EQUAL(std::string("1"), std::get<1>(notifs[i++])); + + CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_STATE_CHANGED), std::get<0>(notifs[i])); + CPPUNIT_ASSERT_EQUAL(std::string(".uno:AssignLayout=1"), std::get<1>(notifs[i++])); +} + +void DesktopLOKTest::testTileInvalidationCompression() +{ + LibLODocument_Impl* pDocument = loadDoc("blank_text.odt"); + + comphelper::LibreOfficeKit::setPartInInvalidation(true); + comphelper::ScopeGuard aGuard([]() + { + comphelper::LibreOfficeKit::setPartInInvalidation(false); + }); + + // Single part merging + { + std::vector<std::tuple<int, std::string>> notifs; + std::unique_ptr<CallbackFlushHandler> handler(new CallbackFlushHandler(pDocument, callbackCompressionTest, ¬ifs)); + handler->setViewId(SfxLokHelper::getView()); + + handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "0, 0, 239, 239, 0, 0"_ostr); + handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "0, 0, 239, 239, 0, 0"_ostr); + handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "-100, -50, 500, 650, 0, 0"_ostr); + handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "0, 0, -32767, -32767, 0, 0"_ostr); + handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "100, 100, 200, 200, 0, 0"_ostr); + + Scheduler::ProcessEventsToIdle(); + + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), notifs.size()); + + size_t i = 0; + CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_INVALIDATE_TILES), std::get<0>(notifs[i])); + CPPUNIT_ASSERT_EQUAL(std::string("0, 0, 400, 600, 0, 0"), std::get<1>(notifs[i++])); + } + + // Part Number + { + std::vector<std::tuple<int, std::string>> notifs; + std::unique_ptr<CallbackFlushHandler> handler(new CallbackFlushHandler(pDocument, callbackCompressionTest, ¬ifs)); + handler->setViewId(SfxLokHelper::getView()); + + handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "0, 0, 239, 239, 0, 0"_ostr); + handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "0, 0, 200, 200, 1, 0"_ostr); // Different part + handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "0, 0, 0, 0, 2, 0"_ostr); // Invalid + handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "-121, -121, 200, 200, 0, 0"_ostr); // Inside first + handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "0, 0, -32767, -32767, 1, 0"_ostr); // Invalid + + Scheduler::ProcessEventsToIdle(); + + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(2), notifs.size()); + + size_t i = 0; + CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_INVALIDATE_TILES), std::get<0>(notifs[i])); + CPPUNIT_ASSERT_EQUAL(std::string("0, 0, 200, 200, 1, 0"), std::get<1>(notifs[i++])); + + CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_INVALIDATE_TILES), std::get<0>(notifs[i])); + CPPUNIT_ASSERT_EQUAL(std::string("0, 0, 239, 239, 0, 0"), std::get<1>(notifs[i++])); + } + + // All Parts + { + std::vector<std::tuple<int, std::string>> notifs; + std::unique_ptr<CallbackFlushHandler> handler(new CallbackFlushHandler(pDocument, callbackCompressionTest, ¬ifs)); + handler->setViewId(SfxLokHelper::getView()); + + handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "0, 0, 239, 239, 0, 0"_ostr); // 0 + handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "0, 0, 200, 200, 1, 0"_ostr); // 1: Different part + handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "0, 0, 0, 0, -1, 0"_ostr); // Invalid + handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "-121, -121, 200, 200, -1, 0"_ostr); // 0: All parts + handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "0, 0, -32767, -32767, -1, 0"_ostr); // Invalid + handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "-100, -100, 1200, 1200, -1, 0"_ostr); // 0: All parts + handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "0, 0, 239, 239, 3, 0"_ostr); // Overlapped + handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "1000, 1000, 1239, 1239, 2, 0"_ostr); // 1: Unique region + + Scheduler::ProcessEventsToIdle(); + + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(2), notifs.size()); + + size_t i = 0; + CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_INVALIDATE_TILES), std::get<0>(notifs[i])); + CPPUNIT_ASSERT_EQUAL(std::string("0, 0, 1100, 1100, -1, 0"), std::get<1>(notifs[i++])); + + CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_INVALIDATE_TILES), std::get<0>(notifs[i])); + CPPUNIT_ASSERT_EQUAL(std::string("1000, 1000, 1239, 1239, 2, 0"), std::get<1>(notifs[i++])); + } + + // All Parts (partial) + { + std::vector<std::tuple<int, std::string>> notifs; + std::unique_ptr<CallbackFlushHandler> handler(new CallbackFlushHandler(pDocument, callbackCompressionTest, ¬ifs)); + handler->setViewId(SfxLokHelper::getView()); + + handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "0, 0, 200, 200, 0, 0"_ostr); // 0 + handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "0, 0, 100, 100, 1, 0"_ostr); // 1: Different part + handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "0, 0, 0, 0, -1, 0"_ostr); // Invalid + handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "150, 150, 50, 50, -1, 0"_ostr); // 2: All-parts + handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "0, 0, -32767, -32767, -1, 0"_ostr); // Invalid + handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "150, 150, 40, 40, 3, 0"_ostr); // Overlapped w/ 2 + handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "0, 0, 200, 200, 4, 0"_ostr); // 3: Unique + handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "1000, 1000, 1239, 1239, 1, 0"_ostr); // 4: Unique + + Scheduler::ProcessEventsToIdle(); + + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(5), notifs.size()); + + size_t i = 0; + CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_INVALIDATE_TILES), std::get<0>(notifs[i])); + CPPUNIT_ASSERT_EQUAL(std::string("0, 0, 200, 200, 0, 0"), std::get<1>(notifs[i++])); + + CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_INVALIDATE_TILES), std::get<0>(notifs[i])); + CPPUNIT_ASSERT_EQUAL(std::string("0, 0, 100, 100, 1, 0"), std::get<1>(notifs[i++])); + + CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_INVALIDATE_TILES), std::get<0>(notifs[i])); + CPPUNIT_ASSERT_EQUAL(std::string("150, 150, 50, 50, -1, 0"), std::get<1>(notifs[i++])); + + CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_INVALIDATE_TILES), std::get<0>(notifs[i])); + CPPUNIT_ASSERT_EQUAL(std::string("0, 0, 200, 200, 4, 0"), std::get<1>(notifs[i++])); + + CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_INVALIDATE_TILES), std::get<0>(notifs[i])); + CPPUNIT_ASSERT_EQUAL(std::string("1000, 1000, 1239, 1239, 1, 0"), std::get<1>(notifs[i++])); + } + + // Merge with "EMPTY" + { + std::vector<std::tuple<int, std::string>> notifs; + std::unique_ptr<CallbackFlushHandler> handler(new CallbackFlushHandler(pDocument, callbackCompressionTest, ¬ifs)); + handler->setViewId(SfxLokHelper::getView()); + + handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "0, 0, 239, 239, 0, 0"_ostr); + handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "EMPTY, 0, 0"_ostr); + handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "0, 0, 239, 240, 0, 0"_ostr); + handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "-121, -121, 300, 300, 0, 0"_ostr); + handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "0, 0, -32767, -32767, 0, 0"_ostr); + + Scheduler::ProcessEventsToIdle(); + + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), notifs.size()); + + size_t i = 0; + CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_INVALIDATE_TILES), std::get<0>(notifs[i])); + CPPUNIT_ASSERT_EQUAL(std::string("EMPTY, 0, 0"), std::get<1>(notifs[i++])); + } +} + +void DesktopLOKTest::testPartInInvalidation() +{ + LibLODocument_Impl* pDocument = loadDoc("blank_text.odt"); + // No part in invalidation: merge. + { + std::vector<std::tuple<int, std::string>> notifs; + std::unique_ptr<CallbackFlushHandler> handler(new CallbackFlushHandler(pDocument, callbackCompressionTest, ¬ifs)); + handler->setViewId(SfxLokHelper::getView()); + + handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "10, 10, 20, 10"_ostr); + handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "20, 10, 20, 10"_ostr); + + Scheduler::ProcessEventsToIdle(); + + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), notifs.size()); + + CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_INVALIDATE_TILES), std::get<0>(notifs[0])); + CPPUNIT_ASSERT_EQUAL(std::string("10, 10, 30, 10"), std::get<1>(notifs[0])); + } + // No part in invalidation: don't merge. + { + std::vector<std::tuple<int, std::string>> notifs; + std::unique_ptr<CallbackFlushHandler> handler(new CallbackFlushHandler(pDocument, callbackCompressionTest, ¬ifs)); + handler->setViewId(SfxLokHelper::getView()); + + handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "10, 10, 20, 10"_ostr); + handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "40, 10, 20, 10"_ostr); + + Scheduler::ProcessEventsToIdle(); + + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(2), notifs.size()); + } + + // Part in invalidation, intersection and parts match -> merge. + { + comphelper::LibreOfficeKit::setPartInInvalidation(true); + comphelper::ScopeGuard aGuard([]() + { + comphelper::LibreOfficeKit::setPartInInvalidation(false); + }); + + std::vector<std::tuple<int, std::string>> notifs; + std::unique_ptr<CallbackFlushHandler> handler(new CallbackFlushHandler(pDocument, callbackCompressionTest, ¬ifs)); + handler->setViewId(SfxLokHelper::getView()); + + handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "10, 10, 20, 10, 0, 0"_ostr); + handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "20, 10, 20, 10, 0, 0"_ostr); + + Scheduler::ProcessEventsToIdle(); + + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), notifs.size()); + } + // Part in invalidation, intersection and parts don't match -> don't merge. + { + comphelper::LibreOfficeKit::setPartInInvalidation(true); + comphelper::ScopeGuard aGuard([]() + { + comphelper::LibreOfficeKit::setPartInInvalidation(false); + }); + + std::vector<std::tuple<int, std::string>> notifs; + std::unique_ptr<CallbackFlushHandler> handler(new CallbackFlushHandler(pDocument, callbackCompressionTest, ¬ifs)); + handler->setViewId(SfxLokHelper::getView()); + + handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "10, 10, 20, 10, 0, 0"_ostr); + handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "20, 10, 20, 10, 1, 0"_ostr); + + Scheduler::ProcessEventsToIdle(); + + // This failed as RectangleAndPart::Create() always assumed no part in + // payload, so this was merged -> it was 1. + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(2), notifs.size()); + } +} + +static void callbackBinaryCallbackTest(const int type, const char* payload, void* data) +{ + std::vector<std::tuple<int, std::string>>* notifs = static_cast<std::vector<std::tuple<int, std::string>>*>(data); + notifs->emplace_back(type, std::string(payload ? payload : "(nil)")); +} + +void DesktopLOKTest::testBinaryCallback() +{ + LibLODocument_Impl* pDocument = loadDoc("blank_text.odt"); + + const tools::Rectangle rect1(Point(10,15),Size(20,25)); + const std::string rect1String(rect1.toString()); + // Verify that using queue() and libreOfficeKitViewInvalidateTilesCallback() has the same result. + { + std::vector<std::tuple<int, std::string>> notifs; + std::unique_ptr<CallbackFlushHandler> handler(new CallbackFlushHandler(pDocument, callbackBinaryCallbackTest, ¬ifs)); + handler->setViewId(SfxLokHelper::getView()); + + handler->queue(LOK_CALLBACK_INVALIDATE_TILES, OString(rect1String)); + + Scheduler::ProcessEventsToIdle(); + + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), notifs.size()); + CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_INVALIDATE_TILES), std::get<0>(notifs[0])); + CPPUNIT_ASSERT_EQUAL(rect1String, std::get<1>(notifs[0])); + } + { + std::vector<std::tuple<int, std::string>> notifs; + std::unique_ptr<CallbackFlushHandler> handler(new CallbackFlushHandler(pDocument, callbackBinaryCallbackTest, ¬ifs)); + handler->setViewId(SfxLokHelper::getView()); + + handler->libreOfficeKitViewInvalidateTilesCallback(&rect1, INT_MIN, 0); + + Scheduler::ProcessEventsToIdle(); + + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), notifs.size()); + CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_INVALIDATE_TILES), std::get<0>(notifs[0])); + CPPUNIT_ASSERT_EQUAL(rect1String, std::get<1>(notifs[0])); + } + // Verify that the "EMPTY" invalidation gets converted properly. + { + std::vector<std::tuple<int, std::string>> notifs; + std::unique_ptr<CallbackFlushHandler> handler(new CallbackFlushHandler(pDocument, callbackBinaryCallbackTest, ¬ifs)); + handler->setViewId(SfxLokHelper::getView()); + + handler->libreOfficeKitViewInvalidateTilesCallback(nullptr, INT_MIN, 0); + + Scheduler::ProcessEventsToIdle(); + + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), notifs.size()); + CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_INVALIDATE_TILES), std::get<0>(notifs[0])); + CPPUNIT_ASSERT_EQUAL(std::string("EMPTY"), std::get<1>(notifs[0])); + } +} + +void DesktopLOKTest::testInput() +{ + // Load a Writer document, enable change recording and press a key. + LibLODocument_Impl* pDocument = loadDoc("blank_text.odt"); + + Scheduler::ProcessEventsToIdle(); // Get focus & other bits setup. + + pDocument->pClass->postWindowExtTextInputEvent(pDocument, 0, LOK_EXT_TEXTINPUT, "far"); + pDocument->pClass->postWindowExtTextInputEvent(pDocument, 0, LOK_EXT_TEXTINPUT_END, "far"); + pDocument->pClass->postWindowExtTextInputEvent(pDocument, 0, LOK_EXT_TEXTINPUT, " "); + pDocument->pClass->postWindowExtTextInputEvent(pDocument, 0, LOK_EXT_TEXTINPUT_END, " "); + pDocument->pClass->postWindowExtTextInputEvent(pDocument, 0, LOK_EXT_TEXTINPUT, "beyond"); + pDocument->pClass->postWindowExtTextInputEvent(pDocument, 0, LOK_EXT_TEXTINPUT_END, "beyond"); + pDocument->pClass->postWindowExtTextInputEvent(pDocument, 0, LOK_EXT_TEXTINPUT, " "); + pDocument->pClass->postWindowExtTextInputEvent(pDocument, 0, LOK_EXT_TEXTINPUT_END, " "); + // Mis-spelled ... + pDocument->pClass->postWindowExtTextInputEvent(pDocument, 0, LOK_EXT_TEXTINPUT, "kovely"); + pDocument->pClass->postWindowExtTextInputEvent(pDocument, 0, LOK_EXT_TEXTINPUT_END, "kovely"); + // Remove it again + pDocument->pClass->removeTextContext(pDocument, 0, 6, 0); + // Replace it with lovely + pDocument->pClass->postWindowExtTextInputEvent(pDocument, 0, LOK_EXT_TEXTINPUT, "lovely"); + pDocument->pClass->postWindowExtTextInputEvent(pDocument, 0, LOK_EXT_TEXTINPUT_END, "lovely"); + pDocument->pClass->postWindowExtTextInputEvent(pDocument, 0, LOK_EXT_TEXTINPUT, " "); + pDocument->pClass->postWindowExtTextInputEvent(pDocument, 0, LOK_EXT_TEXTINPUT_END, " "); + + // get the text ... + pDocument->pClass->postUnoCommand(pDocument, ".uno:SelectAll", nullptr, false); + Scheduler::ProcessEventsToIdle(); + char* pText = pDocument->pClass->getTextSelection(pDocument, "text/plain;charset=utf-8", nullptr); + CPPUNIT_ASSERT(pText != nullptr); + CPPUNIT_ASSERT_EQUAL("far beyond lovely "_ostr, OString(pText)); + free(pText); +} + +void DesktopLOKTest::testRedlineWriter() +{ + // Load a Writer document, enable change recording and press a key. + LibLODocument_Impl* pDocument = loadDoc("blank_text.odt"); + uno::Reference<beans::XPropertySet> xPropertySet(mxComponent, uno::UNO_QUERY); + xPropertySet->setPropertyValue("RecordChanges", uno::Any(true)); + pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 't', 0); + pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYUP, 't', 0); + Scheduler::ProcessEventsToIdle(); + + // Get redline info. + boost::property_tree::ptree aTree; + char* pJSON = pDocument->m_pDocumentClass->getCommandValues(pDocument, ".uno:AcceptTrackedChanges"); + std::stringstream aStream(pJSON); + free(pJSON); + CPPUNIT_ASSERT(!aStream.str().empty()); + boost::property_tree::read_json(aStream, aTree); + // Make sure that pressing a key creates exactly one redline. + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aTree.get_child("redlines").size()); + + for (const boost::property_tree::ptree::value_type& rRedline : aTree.get_child("redlines")) + // This failed with boost::property_tree::ptree_bad_path, as there were no description field. + CPPUNIT_ASSERT_EQUAL(std::string("Insert \xE2\x80\x9Ct\xE2\x80\x9D"), rRedline.second.get<std::string>("description")); + // U+201C LEFT DOUBLE QUOTATION MARK, U+201D RIGHT DOUBLE QUOTATION + // MARK +} + +void DesktopLOKTest::testRedlineCalc() +{ + // Load a Writer document, enable change recording and press a key. + LibLODocument_Impl* pDocument = loadDoc("sheets.ods"); + uno::Reference<beans::XPropertySet> xPropertySet(mxComponent, uno::UNO_QUERY); + xPropertySet->setPropertyValue("RecordChanges", uno::Any(true)); + pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 't', 0); + pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYUP, 't', 0); + pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 0, KEY_RETURN); + pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYUP, 0, KEY_RETURN); + Scheduler::ProcessEventsToIdle(); + + // Get redline info. + boost::property_tree::ptree aTree; + char* pJSON = pDocument->m_pDocumentClass->getCommandValues(pDocument, ".uno:AcceptTrackedChanges"); + std::stringstream aStream(pJSON); + free(pJSON); + CPPUNIT_ASSERT(!aStream.str().empty()); + boost::property_tree::read_json(aStream, aTree); + // Make sure that pressing a key creates exactly one redline. + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aTree.get_child("redlines").size()); + + for (const boost::property_tree::ptree::value_type& rRedline : aTree.get_child("redlines")) + // This failed with boost::property_tree::ptree_bad_path, as there were no description field. + CPPUNIT_ASSERT_EQUAL(std::string("Cell B4 changed from '5' to 't'"), rRedline.second.get<std::string>("description")); +} + +namespace { + +class ViewCallback +{ + LibLODocument_Impl* mpDocument; + int mnView; +public: + OString m_aCellFormula; + int m_nTableSelectionCount; + int m_nColorPaletteCallbackCount = 0; + bool m_bEmptyTableSelection; + bool m_bTilesInvalidated; + bool m_bZeroCursor; + tools::Rectangle m_aOwnCursor; + boost::property_tree::ptree m_aCommentCallbackResult; + boost::property_tree::ptree m_aColorPaletteCallbackResult; + + ViewCallback(LibLODocument_Impl* pDocument) + : mpDocument(pDocument), + m_nTableSelectionCount(0), + m_bEmptyTableSelection(false), + m_bTilesInvalidated(false), + m_bZeroCursor(false) + { + mnView = SfxLokHelper::getView(); + mpDocument->m_pDocumentClass->registerCallback(pDocument, &ViewCallback::callback, this); + } + + ~ViewCallback() + { + mpDocument->m_pDocumentClass->setView(mpDocument, mnView); + mpDocument->m_pDocumentClass->registerCallback(mpDocument, nullptr, nullptr); + } + + static void callback(int nType, const char* pPayload, void* pData) + { + static_cast<ViewCallback*>(pData)->callbackImpl(nType, pPayload); + } + + void callbackImpl(int nType, const char* pPayload) + { + OString aPayload(pPayload); + switch (nType) + { + case LOK_CALLBACK_INVALIDATE_TILES: + { + m_bTilesInvalidated = true; + } + break; + case LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR: + { + uno::Sequence<OUString> aSeq = comphelper::string::convertCommaSeparated(OUString::fromUtf8(aPayload)); + if (std::string_view("EMPTY") == pPayload) + return; + CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(4), aSeq.getLength()); + m_aOwnCursor.SetLeft(aSeq[0].toInt32()); + m_aOwnCursor.SetTop(aSeq[1].toInt32()); + m_aOwnCursor.setWidth(aSeq[2].toInt32()); + m_aOwnCursor.setHeight(aSeq[3].toInt32()); + + if (m_aOwnCursor.Left() == 0 && m_aOwnCursor.Top() == 0) + m_bZeroCursor = true; + } + break; + case LOK_CALLBACK_COMMENT: + { + m_aCommentCallbackResult.clear(); + std::stringstream aStream(pPayload); + boost::property_tree::read_json(aStream, m_aCommentCallbackResult); + m_aCommentCallbackResult = m_aCommentCallbackResult.get_child("comment"); + } + break; + break; + case LOK_CALLBACK_CELL_FORMULA: + { + m_aCellFormula = aPayload; + } + break; + case LOK_CALLBACK_TABLE_SELECTED: + { + m_bEmptyTableSelection = (std::string(pPayload).compare("{ }") == 0); + ++m_nTableSelectionCount; + } + break; + case LOK_CALLBACK_COLOR_PALETTES: + { + m_aColorPaletteCallbackResult.clear(); + std::stringstream aStream(pPayload); + boost::property_tree::read_json(aStream, m_aColorPaletteCallbackResult); + ++m_nColorPaletteCallbackCount; + } + break; + } + } +}; + +} + +void DesktopLOKTest::testPaintPartTile() +{ + // Load an impress doc of 2 slides. +// ViewCallback aView1; +// ViewCallback aView2; + LibLODocument_Impl* pDocument = loadDoc("2slides.odp"); + pDocument->m_pDocumentClass->initializeForRendering(pDocument, "{}"); +// pDocument->m_pDocumentClass->registerCallback(pDocument, &ViewCallback::callback, &aView1); + int nView1 = pDocument->m_pDocumentClass->getView(pDocument); + + // Create a second view. + pDocument->m_pDocumentClass->createView(pDocument); + pDocument->m_pDocumentClass->initializeForRendering(pDocument, "{}"); +// pDocument->m_pDocumentClass->registerCallback(pDocument, &ViewCallback::callback, &aView2); + + // Go to the second slide in the second view. + pDocument->m_pDocumentClass->setPart(pDocument, 1); + + // Switch back to the first view and start typing. + pDocument->m_pDocumentClass->setView(pDocument, nView1); + pDocument->m_pDocumentClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 0, awt::Key::TAB); + pDocument->m_pDocumentClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYUP, 0, awt::Key::TAB); + pDocument->m_pDocumentClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 'x', 0); + pDocument->m_pDocumentClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYUP, 'x', 0); + Scheduler::ProcessEventsToIdle(); + + // Call paintPartTile() to paint the second part (in whichever view it finds suitable for this). + unsigned char pPixels[256 * 256 * 4]; + pDocument->m_pDocumentClass->paintPartTile(pDocument, pPixels, 1, 0, 256, 256, 0, 0, 256, 256); + + // Type again. + Scheduler::ProcessEventsToIdle(); +// aView1.m_bTilesInvalidated = false; + pDocument->m_pDocumentClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 'x', 0); + pDocument->m_pDocumentClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYUP, 'x', 0); + Scheduler::ProcessEventsToIdle(); + // This failed: paintPartTile() (as a side-effect) ended the text edit of + // the first view, so there were no invalidations. + //CPPUNIT_ASSERT(aView1.m_bTilesInvalidated); +} + +void DesktopLOKTest::testPaintPartTileDifferentSchemes() +{ + Color aDarkColor(0x1c, 0x1c, 0x1c); + + // Add a minimal dark scheme + { + svtools::EditableColorConfig aColorConfig; + svtools::ColorConfigValue aValue; + aValue.bIsVisible = true; + aValue.nColor = aDarkColor; + aColorConfig.SetColorValue(svtools::DOCCOLOR, aValue); + aColorConfig.AddScheme(u"Dark"_ustr); + } + + // Add a minimal light scheme + { + svtools::EditableColorConfig aColorConfig; + svtools::ColorConfigValue aValue; + aValue.bIsVisible = true; + aValue.nColor = COL_WHITE; + aColorConfig.SetColorValue(svtools::DOCCOLOR, aValue); + aColorConfig.AddScheme(u"Light"_ustr); + } + + // This view will default to light scheme + LibLODocument_Impl* pDocument = loadDoc("2slides.odp"); + pDocument->m_pDocumentClass->initializeForRendering(pDocument, "{}"); + int nView1 = pDocument->m_pDocumentClass->getView(pDocument); + + // Create a second view + pDocument->m_pDocumentClass->createView(pDocument); + pDocument->m_pDocumentClass->initializeForRendering(pDocument, "{}"); + + // Go to the second slide in the second view + pDocument->m_pDocumentClass->setPart(pDocument, 1); + + // Set to dark scheme + { + uno::Sequence<beans::PropertyValue> aPropertyValues = comphelper::InitPropertySequence( + { + { "NewTheme", uno::Any(OUString("Dark")) }, + } + ); + dispatchCommand(mxComponent, ".uno:ChangeTheme", aPropertyValues); + } + + constexpr int nCanvasWidth = 256; + constexpr int nCanvasHeight = 256; + + // Just a random pixel in the middle of the canvas + constexpr int nPixelX = 128; + constexpr int nPixelY = 128 * nCanvasWidth; + + std::array<sal_uInt8, nCanvasWidth * nCanvasHeight * 4> aPixels; + + // Both parts should be painted with dark scheme + pDocument->m_pDocumentClass->paintPartTile(pDocument, aPixels.data(), 0, 0, nCanvasWidth, nCanvasHeight, 0, 0, nCanvasWidth, nCanvasHeight); + Color aPixel(aPixels[nPixelX + nPixelY + 0], aPixels[nPixelX + nPixelY + 1], aPixels[nPixelX + nPixelY + 2]); + CPPUNIT_ASSERT_EQUAL(aDarkColor, aPixel); + + pDocument->m_pDocumentClass->paintPartTile(pDocument, aPixels.data(), 0, 0, nCanvasWidth, nCanvasHeight, 0, 0, nCanvasWidth, nCanvasHeight); + aPixel = Color(aPixels[nPixelX + nPixelY + 0], aPixels[nPixelX + nPixelY + 1], aPixels[nPixelX + nPixelY + 2]); + CPPUNIT_ASSERT_EQUAL(aDarkColor, aPixel); + + // Switch back to first view + pDocument->m_pDocumentClass->setView(pDocument, nView1); + + // Both parts should be painted with light scheme + pDocument->m_pDocumentClass->paintPartTile(pDocument, aPixels.data(), 0, 0, nCanvasWidth, nCanvasHeight, 0, 0, nCanvasWidth, nCanvasHeight); + aPixel = Color(aPixels[nPixelX + nPixelY + 0], aPixels[nPixelX + nPixelY + 1], aPixels[nPixelX + nPixelY + 2]); + CPPUNIT_ASSERT_EQUAL(COL_WHITE, aPixel); + + pDocument->m_pDocumentClass->paintPartTile(pDocument, aPixels.data(), 0, 0, nCanvasWidth, nCanvasHeight, 0, 0, nCanvasWidth, nCanvasHeight); + aPixel = Color(aPixels[nPixelX + nPixelY + 0], aPixels[nPixelX + nPixelY + 1], aPixels[nPixelX + nPixelY + 2]); + CPPUNIT_ASSERT_EQUAL(COL_WHITE, aPixel); +} + +#if HAVE_MORE_FONTS +void DesktopLOKTest::testGetFontSubset() +{ + LibLODocument_Impl* pDocument = loadDoc("blank_text.odt"); + OUString aFontName = rtl::Uri::encode( + OUString("Liberation Sans"), + rtl_UriCharClassRelSegment, + rtl_UriEncodeKeepEscapes, + RTL_TEXTENCODING_UTF8 + ); + OString aCommand = ".uno:FontSubset&name=" + OUStringToOString(aFontName, RTL_TEXTENCODING_UTF8); + boost::property_tree::ptree aTree; + char* pJSON = pDocument->m_pDocumentClass->getCommandValues(pDocument, aCommand.getStr()); + std::stringstream aStream(pJSON); + boost::property_tree::read_json(aStream, aTree); + CPPUNIT_ASSERT( !aTree.empty() ); + CPPUNIT_ASSERT_EQUAL( std::string(".uno:FontSubset"), aTree.get_child("commandName").get_value<std::string>() ); + boost::property_tree::ptree aValues = aTree.get_child("commandValues"); + CPPUNIT_ASSERT( !aValues.empty() ); + free(pJSON); +} +#endif + +void DesktopLOKTest::testCommentsWriter() +{ + // Disable tiled rendering for comments + comphelper::LibreOfficeKit::setTiledAnnotations(false); + + LibLODocument_Impl* pDocument = loadDoc("comments.odt"); + pDocument->m_pDocumentClass->initializeForRendering(pDocument, nullptr); + tools::Long nWidth, nHeight; + pDocument->m_pDocumentClass->getDocumentSize(pDocument, &nWidth, &nHeight); + + // Document width alongwith without sidebar comes to be < 13000 + CPPUNIT_ASSERT( nWidth < 13000 ); + + // Can we get all the comments using .uno:ViewAnnotations command ? + boost::property_tree::ptree aTree; + char* pJSON = pDocument->m_pDocumentClass->getCommandValues(pDocument, ".uno:ViewAnnotations"); + std::stringstream aStream(pJSON); + free(pJSON); + CPPUNIT_ASSERT(!aStream.str().empty()); + boost::property_tree::read_json(aStream, aTree); + // There are 3 comments in the document already + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(3), aTree.get_child("comments").size()); + + int nComment2Id = 0; + // Check if all comment fields have valid data + for (const auto& rComment : aTree.get_child("comments")) + { + CPPUNIT_ASSERT(rComment.second.get<int>("id") > 0); + CPPUNIT_ASSERT(!rComment.second.get<std::string>("author").empty()); + CPPUNIT_ASSERT(!rComment.second.get<std::string>("text").empty()); + // Has a valid iso 8601 date time string + css::util::DateTime aDateTime; + OUString aDateTimeString = OUString::createFromAscii(rComment.second.get<std::string>("dateTime")); + CPPUNIT_ASSERT(utl::ISO8601parseDateTime(aDateTimeString, aDateTime)); + + // This comment has a marked text range + if (rComment.second.get<std::string>("text") == "Comment 2") + { + CPPUNIT_ASSERT(!rComment.second.get<std::string>("textRange").empty()); + nComment2Id = rComment.second.get<int>("id"); + } + // This is a reply comment + else if (rComment.second.get<std::string>("text") == "Reply to Comment 2") + { + CPPUNIT_ASSERT_EQUAL(nComment2Id, rComment.second.get<int>("parentId")); + } + } + + comphelper::LibreOfficeKit::setTiledAnnotations(true); +} + + +void DesktopLOKTest::testCommentsCalc() +{ + // Disable tiled rendering for comments + comphelper::LibreOfficeKit::setTiledAnnotations(false); + + LibLODocument_Impl* pDocument = loadDoc("sheets.ods"); + pDocument->m_pDocumentClass->initializeForRendering(pDocument, nullptr); + + // Can we get all the comments using .uno:ViewAnnotations command ? + boost::property_tree::ptree aTree; + char* pJSON = pDocument->m_pDocumentClass->getCommandValues(pDocument, ".uno:ViewAnnotations"); + std::stringstream aStream(pJSON); + free(pJSON); + CPPUNIT_ASSERT(!aStream.str().empty()); + boost::property_tree::read_json(aStream, aTree); + // There are 2 comments in the document already + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(2), aTree.get_child("comments").size()); + + // Check if all comment fields have valid data + int nIdx = 0; + for (const auto& rComment : aTree.get_child("comments")) + { + switch(nIdx) + { + case 0: + { + CPPUNIT_ASSERT_EQUAL(std::string("4"), rComment.second.get<std::string>("tab")); + CPPUNIT_ASSERT_EQUAL(std::string("Comment1"), rComment.second.get<std::string>("text")); + CPPUNIT_ASSERT_EQUAL(std::string("6 14 6 14"), rComment.second.get<std::string>("cellRange")); + } + break; + case 1: + { + CPPUNIT_ASSERT_EQUAL(std::string("4"), rComment.second.get<std::string>("tab")); + CPPUNIT_ASSERT_EQUAL(std::string("Comment2"), rComment.second.get<std::string>("text")); + CPPUNIT_ASSERT_EQUAL(std::string("7 17 7 17"), rComment.second.get<std::string>("cellRange")); + } + break; + } + + ++nIdx; + } + + // We checked all the comments + CPPUNIT_ASSERT_EQUAL(2, nIdx); + + comphelper::LibreOfficeKit::setTiledAnnotations(true); +} + + +void DesktopLOKTest::testCommentsImpress() +{ + // Disable tiled rendering for comments + comphelper::LibreOfficeKit::setTiledAnnotations(false); + + LibLODocument_Impl* pDocument = loadDoc("blank_presentation.odp"); + pDocument->m_pDocumentClass->initializeForRendering(pDocument, nullptr); + + // Can we get all the comments using .uno:ViewAnnotations command ? + boost::property_tree::ptree aTree; + char* pJSON = pDocument->m_pDocumentClass->getCommandValues(pDocument, ".uno:ViewAnnotations"); + std::stringstream aStream(pJSON); + free(pJSON); + CPPUNIT_ASSERT(!aStream.str().empty()); + boost::property_tree::read_json(aStream, aTree); + // There are 2 comments in the document already + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(2), aTree.get_child("comments").size()); + + // Check if all comment fields have valid data + int nIdx = 0; + for (const auto& rComment : aTree.get_child("comments")) + { + switch(nIdx) + { + case 0: + { + CPPUNIT_ASSERT(rComment.second.get<int>("id") > 0); + CPPUNIT_ASSERT_EQUAL(std::string("This is comment1"), rComment.second.get<std::string>("text")); + CPPUNIT_ASSERT_EQUAL(std::string("LOK User1"), rComment.second.get<std::string>("author")); + css::util::DateTime aDateTime; + OUString aDateTimeString = OUString::createFromAscii(rComment.second.get<std::string>("dateTime")); + CPPUNIT_ASSERT(utl::ISO8601parseDateTime(aDateTimeString, aDateTime)); + } + break; + case 1: + { + CPPUNIT_ASSERT(rComment.second.get<int>("id") > 0); + CPPUNIT_ASSERT_EQUAL(std::string("This is comment2"), rComment.second.get<std::string>("text")); + CPPUNIT_ASSERT_EQUAL(std::string("LOK User2"), rComment.second.get<std::string>("author")); + css::util::DateTime aDateTime; + OUString aDateTimeString = OUString::createFromAscii(rComment.second.get<std::string>("dateTime")); + CPPUNIT_ASSERT(utl::ISO8601parseDateTime(aDateTimeString, aDateTime)); + } + break; + } + + ++nIdx; + } + + // We checked all the comments + CPPUNIT_ASSERT_EQUAL(2, nIdx); + + comphelper::LibreOfficeKit::setTiledAnnotations(true); +} + +void DesktopLOKTest::testCommentsCallbacksWriter() +{ + // Comments callback are emitted only if tiled annotations are off + comphelper::LibreOfficeKit::setTiledAnnotations(false); + LibLODocument_Impl* pDocument = loadDoc("comments.odt"); + pDocument->m_pDocumentClass->initializeForRendering(pDocument, "{}"); + ViewCallback aView1(pDocument); + pDocument->m_pDocumentClass->createView(pDocument); + pDocument->m_pDocumentClass->initializeForRendering(pDocument, "{}"); + ViewCallback aView2(pDocument); + + // Add a new comment + OString aCommandArgs("{ \"Text\": { \"type\": \"string\", \"value\": \"Additional comment\" }, \"Author\": { \"type\": \"string\", \"value\": \"LOK User1\" } }"_ostr); + pDocument->pClass->postUnoCommand(pDocument, ".uno:InsertAnnotation", aCommandArgs.getStr(), false); + Scheduler::ProcessEventsToIdle(); + + // We received a LOK_CALLBACK_COMMENT callback with comment 'Add' action + CPPUNIT_ASSERT_EQUAL(std::string("Add"), aView1.m_aCommentCallbackResult.get<std::string>("action")); + CPPUNIT_ASSERT_EQUAL(std::string("Add"), aView2.m_aCommentCallbackResult.get<std::string>("action")); + int nCommentId1 = aView1.m_aCommentCallbackResult.get<int>("id"); + + // Reply to a comment just added + aCommandArgs = "{ \"Id\": { \"type\": \"string\", \"value\": \"" + OString::number(nCommentId1) + "\" }, \"Text\": { \"type\": \"string\", \"value\": \"Reply comment\" } }"; + pDocument->pClass->postUnoCommand(pDocument, ".uno:ReplyComment", aCommandArgs.getStr(), false); + Scheduler::ProcessEventsToIdle(); + + // We received a LOK_CALLBACK_COMMENT callback with comment 'Add' action and linked to its parent comment + CPPUNIT_ASSERT_EQUAL(std::string("Add"), aView1.m_aCommentCallbackResult.get<std::string>("action")); + CPPUNIT_ASSERT_EQUAL(std::string("Add"), aView2.m_aCommentCallbackResult.get<std::string>("action")); + CPPUNIT_ASSERT_EQUAL(nCommentId1, aView1.m_aCommentCallbackResult.get<int>("parentId")); + CPPUNIT_ASSERT_EQUAL(nCommentId1, aView2.m_aCommentCallbackResult.get<int>("parentId")); + CPPUNIT_ASSERT_EQUAL(std::string("Reply comment"), aView1.m_aCommentCallbackResult.get<std::string>("text")); + CPPUNIT_ASSERT_EQUAL(std::string("Reply comment"), aView2.m_aCommentCallbackResult.get<std::string>("text")); + int nCommentId2 = aView1.m_aCommentCallbackResult.get<int>("id"); + + // Edit the previously added comment + aCommandArgs = "{ \"Id\": { \"type\": \"string\", \"value\": \"" + OString::number(nCommentId2) + "\" }, \"Text\": { \"type\": \"string\", \"value\": \"Edited comment\" } }"; + pDocument->pClass->postUnoCommand(pDocument, ".uno:EditAnnotation", aCommandArgs.getStr(), false); + Scheduler::ProcessEventsToIdle(); + + // We received a LOK_CALLBACK_COMMENT callback with comment 'Modify' action + CPPUNIT_ASSERT_EQUAL(std::string("Modify"), aView1.m_aCommentCallbackResult.get<std::string>("action")); + CPPUNIT_ASSERT_EQUAL(std::string("Modify"), aView2.m_aCommentCallbackResult.get<std::string>("action")); + // parent is unchanged still + CPPUNIT_ASSERT_EQUAL(nCommentId1, aView1.m_aCommentCallbackResult.get<int>("parentId")); + CPPUNIT_ASSERT_EQUAL(nCommentId1, aView2.m_aCommentCallbackResult.get<int>("parentId")); + CPPUNIT_ASSERT_EQUAL(std::string("Edited comment"), aView1.m_aCommentCallbackResult.get<std::string>("text")); + CPPUNIT_ASSERT_EQUAL(std::string("Edited comment"), aView2.m_aCommentCallbackResult.get<std::string>("text")); + + // Delete the reply comment just added + aCommandArgs = "{ \"Id\": { \"type\": \"string\", \"value\": \"" + OString::number(nCommentId2) + "\" } }"; + pDocument->pClass->postUnoCommand(pDocument, ".uno:DeleteComment", aCommandArgs.getStr(), false); + Scheduler::ProcessEventsToIdle(); + + // We received a LOK_CALLBACK_COMMENT callback with comment 'Remove' action + CPPUNIT_ASSERT_EQUAL(std::string("Remove"), aView1.m_aCommentCallbackResult.get<std::string>("action")); + CPPUNIT_ASSERT_EQUAL(std::string("Remove"), aView2.m_aCommentCallbackResult.get<std::string>("action")); + CPPUNIT_ASSERT_EQUAL(nCommentId2, aView1.m_aCommentCallbackResult.get<int>("id")); + CPPUNIT_ASSERT_EQUAL(nCommentId2, aView2.m_aCommentCallbackResult.get<int>("id")); + + // Reply to nCommentId1 again + aCommandArgs = "{ \"Id\": { \"type\": \"string\", \"value\": \"" + OString::number(nCommentId1) + "\" }, \"Text\": { \"type\": \"string\", \"value\": \"Reply comment again\" } }"; + pDocument->pClass->postUnoCommand(pDocument, ".uno:ReplyComment", aCommandArgs.getStr(), false); + Scheduler::ProcessEventsToIdle(); + + // We received a LOK_CALLBACK_COMMENT callback with comment 'Add' action and linked to its parent comment + CPPUNIT_ASSERT_EQUAL(std::string("Add"), aView1.m_aCommentCallbackResult.get<std::string>("action")); + CPPUNIT_ASSERT_EQUAL(std::string("Add"), aView2.m_aCommentCallbackResult.get<std::string>("action")); + CPPUNIT_ASSERT_EQUAL(nCommentId1, aView1.m_aCommentCallbackResult.get<int>("parentId")); + CPPUNIT_ASSERT_EQUAL(nCommentId1, aView2.m_aCommentCallbackResult.get<int>("parentId")); + CPPUNIT_ASSERT_EQUAL(std::string("Reply comment again"), aView1.m_aCommentCallbackResult.get<std::string>("text")); + CPPUNIT_ASSERT_EQUAL(std::string("Reply comment again"), aView2.m_aCommentCallbackResult.get<std::string>("text")); + + // .uno:ViewAnnotations returns total of 5 comments + boost::property_tree::ptree aTree; + char* pJSON = pDocument->m_pDocumentClass->getCommandValues(pDocument, ".uno:ViewAnnotations"); + std::stringstream aStream(pJSON); + free(pJSON); + CPPUNIT_ASSERT(!aStream.str().empty()); + boost::property_tree::read_json(aStream, aTree); + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(5), aTree.get_child("comments").size()); +} + +namespace +{ + +void addParameter(tools::JsonWriter& rJson, const char* sName, std::string_view type, std::string_view value) +{ + auto testNode = rJson.startNode(sName); + rJson.put("type", type); + rJson.put("value", value); +} + +} + +void DesktopLOKTest::testCommentsAddEditDeleteDraw() +{ + // Comments callback are emitted only if tiled annotations are off + comphelper::LibreOfficeKit::setTiledAnnotations(false); + LibLODocument_Impl* pDocument = loadDoc("BlankDrawDocument.odg"); + pDocument->m_pDocumentClass->initializeForRendering(pDocument, "{}"); + ViewCallback aView1(pDocument); + + // Add a new comment + OString aCommandArgs; + { + tools::JsonWriter aJson; + addParameter(aJson, "Text", "string", "Comment"); + addParameter(aJson, "Author", "string", "LOK User1"); + aCommandArgs = aJson.finishAndGetAsOString(); + } + + pDocument->pClass->postUnoCommand(pDocument, ".uno:InsertAnnotation", aCommandArgs.getStr(), false); + Scheduler::ProcessEventsToIdle(); + + // We received a LOK_CALLBACK_COMMENT callback with comment 'Add' action + CPPUNIT_ASSERT_EQUAL(std::string("Add"), aView1.m_aCommentCallbackResult.get<std::string>("action")); + int nCommentId1 = aView1.m_aCommentCallbackResult.get<int>("id"); + + // Edit the previously added comment + { + tools::JsonWriter aJson; + addParameter(aJson, "Id", "string", OString::number(nCommentId1)); + addParameter(aJson, "Text", "string", "Edited comment"); + aCommandArgs = aJson.finishAndGetAsOString(); + } + + pDocument->pClass->postUnoCommand(pDocument, ".uno:EditAnnotation", aCommandArgs.getStr(), false); + Scheduler::ProcessEventsToIdle(); + + // We received a LOK_CALLBACK_COMMENT callback with comment 'Modify' action + CPPUNIT_ASSERT_EQUAL(std::string("Modify"), aView1.m_aCommentCallbackResult.get<std::string>("action")); + CPPUNIT_ASSERT_EQUAL(nCommentId1, aView1.m_aCommentCallbackResult.get<int>("id")); + + // Delete Comment + { + tools::JsonWriter aJson; + addParameter(aJson, "Id", "string", OString::number(nCommentId1)); + aCommandArgs = aJson.finishAndGetAsOString(); + } + pDocument->pClass->postUnoCommand(pDocument, ".uno:DeleteAnnotation", aCommandArgs.getStr(), false); + Scheduler::ProcessEventsToIdle(); + + // We received a LOK_CALLBACK_COMMENT callback with comment 'Remove' action + CPPUNIT_ASSERT_EQUAL(std::string("Remove"), aView1.m_aCommentCallbackResult.get<std::string>("action")); + CPPUNIT_ASSERT_EQUAL(nCommentId1, aView1.m_aCommentCallbackResult.get<int>("id")); +} + +void DesktopLOKTest::testRunMacro() +{ + LibLibreOffice_Impl aOffice; + bool bGoodMacro, bNonExistentMacro; + + // Tools macros come pre-installed in system share/basic folder, + bGoodMacro = aOffice.m_pOfficeClass->runMacro(&aOffice, "macro:///Tools.Debug.ActivateReadOnlyFlag()"); + CPPUNIT_ASSERT(bGoodMacro); + + bNonExistentMacro = aOffice.m_pOfficeClass->runMacro(&aOffice, "macro:///I.Am.Not(There)"); + CPPUNIT_ASSERT(!bNonExistentMacro); +} + +void DesktopLOKTest::testExtractParameter() +{ + OUString aOptions("Language=de-DE"); + OUString aValue = extractParameter(aOptions, u"Language"); + CPPUNIT_ASSERT_EQUAL(OUString("de-DE"), aValue); + CPPUNIT_ASSERT_EQUAL(OUString(), aOptions); + + aOptions = "Language=en-US,Something"; + aValue = extractParameter(aOptions, u"Language"); + CPPUNIT_ASSERT_EQUAL(OUString("en-US"), aValue); + CPPUNIT_ASSERT_EQUAL(OUString("Something"), aOptions); + + aOptions = "SomethingElse,Language=cs-CZ"; + aValue = extractParameter(aOptions, u"Language"); + CPPUNIT_ASSERT_EQUAL(OUString("cs-CZ"), aValue); + CPPUNIT_ASSERT_EQUAL(OUString("SomethingElse"), aOptions); + + aOptions = "Something1,Language=hu-HU,Something2"; + aValue = extractParameter(aOptions, u"Language"); + CPPUNIT_ASSERT_EQUAL(OUString("hu-HU"), aValue); + CPPUNIT_ASSERT_EQUAL(OUString("Something1,Something2"), aOptions); + + aOptions = "Something1,Something2=blah,Something3"; + aValue = extractParameter(aOptions, u"Language"); + CPPUNIT_ASSERT_EQUAL(OUString(), aValue); + CPPUNIT_ASSERT_EQUAL(OUString("Something1,Something2=blah,Something3"), aOptions); +} + +void DesktopLOKTest::readFileIntoByteVector(std::u16string_view sFilename, std::vector<unsigned char> & rByteVector) +{ + rByteVector.clear(); + OUString aURL = createFileURL(sFilename); + SvFileStream aStream(aURL, StreamMode::READ); + rByteVector.resize(aStream.remainingSize()); + aStream.ReadBytes(rByteVector.data(), aStream.remainingSize()); +} + +void DesktopLOKTest::testGetSignatureState_Signed() +{ + LibLODocument_Impl* pDocument = loadDoc("signed.odt"); + Scheduler::ProcessEventsToIdle(); + pDocument->m_pDocumentClass->initializeForRendering(pDocument, "{}"); + int nState = pDocument->m_pDocumentClass->getSignatureState(pDocument); + if (nState == 1) + { + // Already SignatureState::OK, then can't test the effect of trusting new CAs. + return; + } + + CPPUNIT_ASSERT_EQUAL(int(4), nState); + + std::vector<unsigned char> aCertificate; + { + readFileIntoByteVector(u"rootCA.der", aCertificate); + bool bResult = pDocument->m_pDocumentClass->addCertificate( + pDocument, aCertificate.data(), int(aCertificate.size())); + CPPUNIT_ASSERT(bResult); + } + + { + readFileIntoByteVector(u"intermediateRootCA.der", aCertificate); + bool bResult = pDocument->m_pDocumentClass->addCertificate( + pDocument, aCertificate.data(), int(aCertificate.size())); + CPPUNIT_ASSERT(bResult); + } + + nState = pDocument->m_pDocumentClass->getSignatureState(pDocument); + CPPUNIT_ASSERT_EQUAL(int(1), nState); +} + +void DesktopLOKTest::testGetSignatureState_NonSigned() +{ + LibLODocument_Impl* pDocument = loadDoc("blank_text.odt"); + Scheduler::ProcessEventsToIdle(); + pDocument->m_pDocumentClass->initializeForRendering(pDocument, "{}"); + int nState = pDocument->m_pDocumentClass->getSignatureState(pDocument); + CPPUNIT_ASSERT_EQUAL(int(0), nState); +} + +#if 0 // broken with system nss on RHEL 7 +void DesktopLOKTest::testInsertCertificate_DER_ODT() +{ + // Load the document, save it into a temp file and load that file again + LibLODocument_Impl* pDocument = loadDoc("blank_text.odt"); + CPPUNIT_ASSERT(pDocument->pClass->saveAs(pDocument, maTempFile.GetURL().toUtf8().getStr(), "odt", nullptr)); + closeDoc(); + + pDocument = loadDocUrl(maTempFile.GetURL(), LOK_DOCTYPE_TEXT); + + Scheduler::ProcessEventsToIdle(); + CPPUNIT_ASSERT(mxComponent.is()); + pDocument->m_pDocumentClass->initializeForRendering(pDocument, "{}"); + Scheduler::ProcessEventsToIdle(); + + std::vector<unsigned char> aCertificate; + std::vector<unsigned char> aPrivateKey; + + { + readFileIntoByteVector(u"rootCA.der", aCertificate); + + bool bResult = pDocument->m_pDocumentClass->addCertificate( + pDocument, aCertificate.data(), int(aCertificate.size())); + CPPUNIT_ASSERT(bResult); + } + + { + readFileIntoByteVector(u"intermediateRootCA.der", aCertificate); + + bool bResult = pDocument->m_pDocumentClass->addCertificate( + pDocument, aCertificate.data(), int(aCertificate.size())); + CPPUNIT_ASSERT(bResult); + } + + { + readFileIntoByteVector(u"certificate.der", aCertificate); + readFileIntoByteVector(u"certificatePrivateKey.der", aPrivateKey); + + bool bResult = pDocument->m_pDocumentClass->insertCertificate(pDocument, + aCertificate.data(), int(aCertificate.size()), + aPrivateKey.data(), int(aPrivateKey.size())); + CPPUNIT_ASSERT(bResult); + } + + int nState = pDocument->m_pDocumentClass->getSignatureState(pDocument); + CPPUNIT_ASSERT_EQUAL(int(1), nState); +} + + +void DesktopLOKTest::testInsertCertificate_PEM_ODT() +{ + // Load the document, save it into a temp file and load that file again + LibLODocument_Impl* pDocument = loadDoc("blank_text.odt"); + CPPUNIT_ASSERT(pDocument->pClass->saveAs(pDocument, maTempFile.GetURL().toUtf8().getStr(), "odt", nullptr)); + closeDoc(); + + pDocument = loadDocUrl(maTempFile.GetURL(), LOK_DOCTYPE_TEXT); + + Scheduler::ProcessEventsToIdle(); + CPPUNIT_ASSERT(mxComponent.is()); + pDocument->m_pDocumentClass->initializeForRendering(pDocument, "{}"); + Scheduler::ProcessEventsToIdle(); + + std::vector<unsigned char> aCertificate; + std::vector<unsigned char> aPrivateKey; + + { + readFileIntoByteVector(u"test-cert-chain-1.pem", aCertificate); + + bool bResult = pDocument->m_pDocumentClass->addCertificate( + pDocument, aCertificate.data(), int(aCertificate.size())); + CPPUNIT_ASSERT(bResult); + } + + { + readFileIntoByteVector(u"test-cert-chain-2.pem", aCertificate); + + bool bResult = pDocument->m_pDocumentClass->addCertificate( + pDocument, aCertificate.data(), int(aCertificate.size())); + CPPUNIT_ASSERT(bResult); + } + + { + readFileIntoByteVector(u"test-cert-chain-3.pem", aCertificate); + + bool bResult = pDocument->m_pDocumentClass->addCertificate( + pDocument, aCertificate.data(), int(aCertificate.size())); + CPPUNIT_ASSERT(bResult); + } + + { + readFileIntoByteVector(u"test-cert-signing.pem", aCertificate); + readFileIntoByteVector(u"test-PK-signing.pem", aPrivateKey); + + bool bResult = pDocument->m_pDocumentClass->insertCertificate(pDocument, + aCertificate.data(), int(aCertificate.size()), + aPrivateKey.data(), int(aPrivateKey.size())); + CPPUNIT_ASSERT(bResult); + } + + int nState = pDocument->m_pDocumentClass->getSignatureState(pDocument); + CPPUNIT_ASSERT_EQUAL(int(1), nState); +} + +void DesktopLOKTest::testInsertCertificate_PEM_DOCX() +{ + // Load the document, save it into a temp file and load that file again + LibLODocument_Impl* pDocument = loadDoc("blank_text.docx"); + CPPUNIT_ASSERT(pDocument->pClass->saveAs(pDocument, maTempFile.GetURL().toUtf8().getStr(), "docx", nullptr)); + closeDoc(); + + pDocument = loadDocUrl(maTempFile.GetURL(), LOK_DOCTYPE_TEXT); + + Scheduler::ProcessEventsToIdle(); + CPPUNIT_ASSERT(mxComponent.is()); + pDocument->m_pDocumentClass->initializeForRendering(pDocument, "{}"); + Scheduler::ProcessEventsToIdle(); + + std::vector<unsigned char> aCertificate; + std::vector<unsigned char> aPrivateKey; + + { + readFileIntoByteVector(u"test-cert-chain-1.pem", aCertificate); + + bool bResult = pDocument->m_pDocumentClass->addCertificate( + pDocument, aCertificate.data(), int(aCertificate.size())); + CPPUNIT_ASSERT(bResult); + } + + { + readFileIntoByteVector(u"test-cert-chain-2.pem", aCertificate); + + bool bResult = pDocument->m_pDocumentClass->addCertificate( + pDocument, aCertificate.data(), int(aCertificate.size())); + CPPUNIT_ASSERT(bResult); + } + + { + readFileIntoByteVector(u"test-cert-chain-3.pem", aCertificate); + + bool bResult = pDocument->m_pDocumentClass->addCertificate( + pDocument, aCertificate.data(), int(aCertificate.size())); + CPPUNIT_ASSERT(bResult); + } + + { + readFileIntoByteVector(u"test-cert-signing.pem", aCertificate); + readFileIntoByteVector(u"test-PK-signing.pem", aPrivateKey); + + bool bResult = pDocument->m_pDocumentClass->insertCertificate(pDocument, + aCertificate.data(), int(aCertificate.size()), + aPrivateKey.data(), int(aPrivateKey.size())); + CPPUNIT_ASSERT(bResult); + } + + int nState = pDocument->m_pDocumentClass->getSignatureState(pDocument); + CPPUNIT_ASSERT_EQUAL(int(5), nState); +} +#endif + +void DesktopLOKTest::testSignDocument_PEM_PDF() +{ + // Load the document, save it into a temp file and load that file again + LibLODocument_Impl* pDocument = loadDoc("blank_text.odt"); + + Scheduler::ProcessEventsToIdle(); + CPPUNIT_ASSERT(mxComponent.is()); + pDocument->m_pDocumentClass->initializeForRendering(pDocument, "{}"); + Scheduler::ProcessEventsToIdle(); + + std::vector<unsigned char> aCertificate; + std::vector<unsigned char> aPrivateKey; + + { + readFileIntoByteVector(u"test-cert-chain-1.pem", aCertificate); + + bool bResult = pDocument->m_pDocumentClass->addCertificate( + pDocument, aCertificate.data(), int(aCertificate.size())); + CPPUNIT_ASSERT(bResult); + } + + { + readFileIntoByteVector(u"test-cert-chain-2.pem", aCertificate); + + bool bResult = pDocument->m_pDocumentClass->addCertificate( + pDocument, aCertificate.data(), int(aCertificate.size())); + CPPUNIT_ASSERT(bResult); + } + + { + readFileIntoByteVector(u"test-cert-chain-3.pem", aCertificate); + + bool bResult = pDocument->m_pDocumentClass->addCertificate( + pDocument, aCertificate.data(), int(aCertificate.size())); + CPPUNIT_ASSERT(bResult); + } + + CPPUNIT_ASSERT(pDocument->pClass->saveAs(pDocument, maTempFile.GetURL().toUtf8().getStr(), "pdf", nullptr)); + + closeDoc(); + + Scheduler::ProcessEventsToIdle(); + + readFileIntoByteVector(u"test-cert-signing.pem", aCertificate); + readFileIntoByteVector(u"test-PK-signing.pem", aPrivateKey); + + LibLibreOffice_Impl aOffice; + bool bResult = aOffice.m_pOfficeClass->signDocument(&aOffice, maTempFile.GetURL().toUtf8().getStr(), + aCertificate.data(), int(aCertificate.size()), + aPrivateKey.data(), int(aPrivateKey.size())); + + CPPUNIT_ASSERT(bResult); +} + +void DesktopLOKTest::testTextSelectionHandles() +{ + LibLODocument_Impl* pDocument = loadDoc("blank_text.odt"); + pDocument->pClass->registerCallback(pDocument, &DesktopLOKTest::callback, this); + + OString aText("hello"_ostr); + CPPUNIT_ASSERT(pDocument->pClass->paste(pDocument, "text/plain;charset=utf-8", aText.getStr(), aText.getLength())); + + // select the inserted text + pDocument->pClass->postUnoCommand(pDocument, ".uno:SelectAll", nullptr, false); + Scheduler::ProcessEventsToIdle(); + char* pText = pDocument->pClass->getTextSelection(pDocument, "text/plain;charset=utf-8", nullptr); + CPPUNIT_ASSERT_EQUAL(aText, OString(pText)); + free(pText); + CPPUNIT_ASSERT_EQUAL("1418, 1418, 0, 275"_ostr, m_aTextSelectionStart); + CPPUNIT_ASSERT_EQUAL("1898, 1418, 0, 275"_ostr, m_aTextSelectionEnd); + + // deselect & check + m_aTextSelectionStart = ""_ostr; + m_aTextSelectionEnd = ""_ostr; + pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 0, com::sun::star::awt::Key::ESCAPE); + Scheduler::ProcessEventsToIdle(); + pText = pDocument->pClass->getTextSelection(pDocument, "text/plain;charset=utf-8", nullptr); + CPPUNIT_ASSERT_EQUAL(static_cast<char *>(nullptr), pText); + free(pText); + CPPUNIT_ASSERT_EQUAL(OString(), m_aTextSelectionStart); + CPPUNIT_ASSERT_EQUAL(OString(), m_aTextSelectionEnd); + + // select again; the positions of the selection handles have to be sent + // again + pDocument->pClass->postUnoCommand(pDocument, ".uno:SelectAll", nullptr, false); + Scheduler::ProcessEventsToIdle(); + pText = pDocument->pClass->getTextSelection(pDocument, "text/plain;charset=utf-8", nullptr); + CPPUNIT_ASSERT_EQUAL(aText, OString(pText)); + free(pText); + CPPUNIT_ASSERT_EQUAL("1418, 1418, 0, 275"_ostr, m_aTextSelectionStart); + CPPUNIT_ASSERT_EQUAL("1898, 1418, 0, 275"_ostr, m_aTextSelectionEnd); +} + +void DesktopLOKTest::testDialogPaste() +{ + LibLODocument_Impl* pDocument = loadDoc("blank_text.odt"); + pDocument->pClass->postUnoCommand(pDocument, ".uno:HyperlinkDialog", nullptr, false); + Scheduler::ProcessEventsToIdle(); + + SfxViewShell* pViewShell = SfxViewShell::Current(); + pViewShell->GetViewFrame().GetBindings().Update(); + + VclPtr<vcl::Window> pWindow(Application::GetActiveTopWindow()); + CPPUNIT_ASSERT(pWindow); + + pDocument->pClass->postWindow(pDocument, pWindow->GetLOKWindowId(), LOK_WINDOW_PASTE, + "{ \"MimeType\" : { \"type\" : \"string\", \"value\" : \"text/plain;charset=utf-8\" }, \"Data\" : { \"type\" : \"[]byte\", \"value\" : \"www.softwarelibre.org.bo\" } }"); + Scheduler::ProcessEventsToIdle(); + + Control* pCtrlFocused = GetFocusControl(pWindow.get()); + CPPUNIT_ASSERT(pCtrlFocused); + CPPUNIT_ASSERT_EQUAL(WindowType::COMBOBOX, pCtrlFocused->GetType()); + CPPUNIT_ASSERT_EQUAL(OUString("www.softwarelibre.org.bo"), pCtrlFocused->GetText()); + + static_cast<SystemWindow*>(pWindow.get())->Close(); + Scheduler::ProcessEventsToIdle(); +} + +void DesktopLOKTest::testComplexSelection() +{ + // Start with a blank text file and add contents. + LibLODocument_Impl* pDocument = loadDoc("blank_text.odt"); + static constexpr OString aText("hello world"_ostr); + + // Certainly not complex. + CPPUNIT_ASSERT_EQUAL(static_cast<int>(LOK_SELTYPE_NONE), pDocument->pClass->getSelectionType(pDocument)); + CPPUNIT_ASSERT_EQUAL(static_cast<int>(LOK_SELTYPE_NONE), pDocument->pClass->getSelectionTypeAndText(pDocument, + "", nullptr, nullptr)); + + // Paste text. + CPPUNIT_ASSERT(pDocument->pClass->paste(pDocument, "text/plain;charset=utf-8", aText.getStr(), aText.getLength())); + + // No selection. + CPPUNIT_ASSERT_EQUAL(static_cast<int>(LOK_SELTYPE_NONE), pDocument->pClass->getSelectionType(pDocument)); + CPPUNIT_ASSERT_EQUAL(static_cast<int>(LOK_SELTYPE_NONE), pDocument->pClass->getSelectionTypeAndText(pDocument, + "", nullptr, nullptr)); + + // Paste an image. + OUString aFileURL = createFileURL(u"paste.jpg"); + std::ifstream aImageStream(aFileURL.toUtf8().copy(strlen("file://")).getStr()); + std::vector<char> aImageContents((std::istreambuf_iterator<char>(aImageStream)), std::istreambuf_iterator<char>()); + CPPUNIT_ASSERT(pDocument->pClass->paste(pDocument, "image/jpeg", aImageContents.data(), aImageContents.size())); + + // Now select-all. + pDocument->pClass->postUnoCommand(pDocument, ".uno:SelectAll", nullptr, false); + Scheduler::ProcessEventsToIdle(); + + // Export as plain text, we should get only the text part "hello". + char* pText = pDocument->pClass->getTextSelection(pDocument, "text/plain;charset=utf-8", nullptr); + CPPUNIT_ASSERT(pText != nullptr); + CPPUNIT_ASSERT_EQUAL(aText, OString(pText)); + free(pText); + + // Export as rtf, we should also get the image. + pText = pDocument->pClass->getTextSelection(pDocument, "text/rtf", nullptr); + CPPUNIT_ASSERT(pText != nullptr); + CPPUNIT_ASSERT(std::string(pText).find(aText.getStr()) != std::string::npos); // Must have the text. + CPPUNIT_ASSERT(std::string(pText).find("pict{") != std::string::npos); // Must have the image as well. + free(pText); + + // Export as html, we should also get the image. + pText = pDocument->pClass->getTextSelection(pDocument, "text/html", nullptr); + CPPUNIT_ASSERT(pText != nullptr); + CPPUNIT_ASSERT(std::string(pText).find(aText.getStr()) != std::string::npos); // Must have the text. + CPPUNIT_ASSERT(std::string(pText).find("<img") != std::string::npos); // Must have the image as well. + free(pText); + + // We expect this to be complex. + CPPUNIT_ASSERT_EQUAL(static_cast<int>(LOK_SELTYPE_COMPLEX), pDocument->pClass->getSelectionType(pDocument)); + CPPUNIT_ASSERT_EQUAL(static_cast<int>(LOK_SELTYPE_COMPLEX), pDocument->pClass->getSelectionTypeAndText(pDocument, + "", nullptr, nullptr)); +} + +void DesktopLOKTest::testCalcSaveAs() +{ + LibLODocument_Impl* pDocument = loadDoc("sheets.ods"); + CPPUNIT_ASSERT(pDocument); + + // Enter some text, but don't commit. + pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 'X', 0); + pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYUP, 'X', 0); + Scheduler::ProcessEventsToIdle(); + + // Save as a new file. + pDocument->pClass->saveAs(pDocument, maTempFile.GetURL().toUtf8().getStr(), "ods", nullptr); + closeDoc(); + + // Load the new document and verify that the in-flight changes are saved. + pDocument = loadDocUrl(maTempFile.GetURL(), LOK_DOCTYPE_SPREADSHEET); + CPPUNIT_ASSERT(pDocument); + + ViewCallback aView(pDocument); + pDocument->m_pDocumentClass->initializeForRendering(pDocument, "{}"); + pDocument->m_pDocumentClass->registerCallback(pDocument, &ViewCallback::callback, &aView); + + pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 0, KEY_RIGHT); + pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYUP, 0, KEY_RIGHT); + pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 0, KEY_LEFT); + pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYUP, 0, KEY_LEFT); + Scheduler::ProcessEventsToIdle(); + + CPPUNIT_ASSERT_EQUAL("X"_ostr, aView.m_aCellFormula); +} + +void DesktopLOKTest::testSpellcheckerMultiView() +{ + static constexpr OUString aLangISO(u"en-US"_ustr); + SvtSysLocaleOptions aSysLocaleOptions; + aSysLocaleOptions.SetLocaleConfigString(aLangISO); + aSysLocaleOptions.SetUILocaleConfigString(aLangISO); + comphelper::LibreOfficeKit::setLanguageTag(LanguageTag(aLangISO, true)); + + auto aSavedSettings = Application::GetSettings(); + std::unique_ptr<Resetter> pResetter( + new Resetter([&]() { Application::SetSettings(aSavedSettings); })); + AllSettings aSettings(aSavedSettings); + aSettings.SetLanguageTag(aLangISO, true); + Application::SetSettings(aSettings); + + LibLODocument_Impl* pDocument = loadDoc("sheet_with_image.ods", LOK_DOCTYPE_SPREADSHEET); + pDocument->pClass->setViewLanguage(pDocument, 0, "en-US"); // For spellchecking. + pDocument->pClass->initializeForRendering(pDocument, nullptr); + pDocument->pClass->registerCallback(pDocument, &DesktopLOKTest::callback, this); + + pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 'a', 0); + pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 'a', 0); + pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 'a', 0); + pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 0, com::sun::star::awt::Key::ESCAPE); + + // Start spellchecking. + pDocument->pClass->postUnoCommand(pDocument, ".uno:SpellDialog", nullptr, false); + + // Uncommenting this will result in a deadlock. + // Because the language configuration above is not effective, and no + // language is actually set, the spell-dialog finds no misspelled + // words, and displays a message box, which must be dismissed to + // continue. + // Need to fix the language configuration issue to enable this. + // Scheduler::ProcessEventsToIdle(); + + CPPUNIT_ASSERT_EQUAL(1, pDocument->m_pDocumentClass->getViewsCount(pDocument)); + + // Now create another view. + const int nViewId = pDocument->m_pDocumentClass->createView(pDocument); + CPPUNIT_ASSERT_EQUAL(2, pDocument->m_pDocumentClass->getViewsCount(pDocument)); + + // And destroy it. + pDocument->m_pDocumentClass->destroyView(pDocument, nViewId); + + // We should survive the destroyed view. + CPPUNIT_ASSERT_EQUAL(1, pDocument->m_pDocumentClass->getViewsCount(pDocument)); +} + +void DesktopLOKTest::testMultiDocuments() +{ + for (int i = 0; i < 3; i++) + { + // Load a document. + std::unique_ptr<LibLODocument_Impl> document1 = loadDocImpl("blank_text.odt"); + LibLODocument_Impl* pDocument1 = document1.get(); + CPPUNIT_ASSERT_EQUAL(1, pDocument1->m_pDocumentClass->getViewsCount(pDocument1)); + const int nDocId1 = pDocument1->mnDocumentId; + + const int nDoc1View0 = pDocument1->m_pDocumentClass->getView(pDocument1); + CPPUNIT_ASSERT_EQUAL(nDocId1, SfxLokHelper::getDocumentIdOfView(nDoc1View0)); + const int nDoc1View1 = pDocument1->m_pDocumentClass->createView(pDocument1); + CPPUNIT_ASSERT_EQUAL(nDoc1View1, pDocument1->m_pDocumentClass->getView(pDocument1)); + CPPUNIT_ASSERT_EQUAL(nDocId1, SfxLokHelper::getDocumentIdOfView(nDoc1View1)); + CPPUNIT_ASSERT_EQUAL(2, pDocument1->m_pDocumentClass->getViewsCount(pDocument1)); + + // Validate the views of document 1. + std::vector<int> aViewIdsDoc1(2); + CPPUNIT_ASSERT(pDocument1->m_pDocumentClass->getViewIds(pDocument1, aViewIdsDoc1.data(), aViewIdsDoc1.size())); + CPPUNIT_ASSERT_EQUAL(nDoc1View0, aViewIdsDoc1[0]); + CPPUNIT_ASSERT_EQUAL(nDoc1View1, aViewIdsDoc1[1]); + + CPPUNIT_ASSERT_EQUAL(nDoc1View1, pDocument1->m_pDocumentClass->getView(pDocument1)); + CPPUNIT_ASSERT_EQUAL(nDocId1, SfxLokHelper::getDocumentIdOfView(nDoc1View1)); + pDocument1->m_pDocumentClass->setView(pDocument1, nDoc1View0); + CPPUNIT_ASSERT_EQUAL(nDoc1View0, pDocument1->m_pDocumentClass->getView(pDocument1)); + CPPUNIT_ASSERT_EQUAL(nDocId1, SfxLokHelper::getDocumentIdOfView(nDoc1View0)); + pDocument1->m_pDocumentClass->setView(pDocument1, nDoc1View1); + CPPUNIT_ASSERT_EQUAL(nDoc1View1, pDocument1->m_pDocumentClass->getView(pDocument1)); + CPPUNIT_ASSERT_EQUAL(nDocId1, SfxLokHelper::getDocumentIdOfView(nDoc1View1)); + CPPUNIT_ASSERT_EQUAL(2, pDocument1->m_pDocumentClass->getViewsCount(pDocument1)); + + // Load another document. + std::unique_ptr<LibLODocument_Impl> document2 = loadDocImpl("blank_presentation.odp"); + LibLODocument_Impl* pDocument2 = document2.get(); + CPPUNIT_ASSERT_EQUAL(1, pDocument2->m_pDocumentClass->getViewsCount(pDocument2)); + const int nDocId2 = pDocument2->mnDocumentId; + + const int nDoc2View0 = pDocument2->m_pDocumentClass->getView(pDocument2); + CPPUNIT_ASSERT_EQUAL(nDocId2, SfxLokHelper::getDocumentIdOfView(nDoc2View0)); + const int nDoc2View1 = pDocument2->m_pDocumentClass->createView(pDocument2); + CPPUNIT_ASSERT_EQUAL(nDoc2View1, pDocument2->m_pDocumentClass->getView(pDocument2)); + CPPUNIT_ASSERT_EQUAL(nDocId2, SfxLokHelper::getDocumentIdOfView(nDoc2View1)); + CPPUNIT_ASSERT_EQUAL(2, pDocument2->m_pDocumentClass->getViewsCount(pDocument2)); + + // Validate the views of document 2. + std::vector<int> aViewIdsDoc2(2); + CPPUNIT_ASSERT(pDocument2->m_pDocumentClass->getViewIds(pDocument2, aViewIdsDoc2.data(), aViewIdsDoc2.size())); + CPPUNIT_ASSERT_EQUAL(nDoc2View0, aViewIdsDoc2[0]); + CPPUNIT_ASSERT_EQUAL(nDoc2View1, aViewIdsDoc2[1]); + + CPPUNIT_ASSERT_EQUAL(nDoc2View1, pDocument2->m_pDocumentClass->getView(pDocument2)); + CPPUNIT_ASSERT_EQUAL(nDocId2, SfxLokHelper::getDocumentIdOfView(nDoc2View1)); + pDocument2->m_pDocumentClass->setView(pDocument2, nDoc2View0); + CPPUNIT_ASSERT_EQUAL(nDoc2View0, pDocument2->m_pDocumentClass->getView(pDocument2)); + CPPUNIT_ASSERT_EQUAL(nDocId2, SfxLokHelper::getDocumentIdOfView(nDoc2View0)); + pDocument2->m_pDocumentClass->setView(pDocument2, nDoc2View1); + CPPUNIT_ASSERT_EQUAL(nDoc2View1, pDocument2->m_pDocumentClass->getView(pDocument2)); + CPPUNIT_ASSERT_EQUAL(nDocId2, SfxLokHelper::getDocumentIdOfView(nDoc2View1)); + CPPUNIT_ASSERT_EQUAL(2, pDocument2->m_pDocumentClass->getViewsCount(pDocument2)); + + // The views of document1 should be unchanged. + CPPUNIT_ASSERT(pDocument1->m_pDocumentClass->getViewIds(pDocument1, aViewIdsDoc1.data(), aViewIdsDoc1.size())); + CPPUNIT_ASSERT_EQUAL(nDoc1View0, aViewIdsDoc1[0]); + CPPUNIT_ASSERT_EQUAL(nDoc1View1, aViewIdsDoc1[1]); + // Switch views in the first doc. + CPPUNIT_ASSERT_EQUAL(nDocId1, SfxLokHelper::getDocumentIdOfView(nDoc1View0)); + pDocument1->m_pDocumentClass->setView(pDocument1, nDoc1View0); + CPPUNIT_ASSERT_EQUAL(nDoc1View0, pDocument1->m_pDocumentClass->getView(pDocument1)); + CPPUNIT_ASSERT_EQUAL(nDocId1, SfxLokHelper::getDocumentIdOfView(nDoc1View1)); + pDocument1->m_pDocumentClass->destroyView(pDocument1, nDoc1View1); + CPPUNIT_ASSERT_EQUAL(1, pDocument1->m_pDocumentClass->getViewsCount(pDocument1)); + + // The views of document2 should be unchanged. + CPPUNIT_ASSERT(pDocument2->m_pDocumentClass->getViewIds(pDocument2, aViewIdsDoc2.data(), aViewIdsDoc2.size())); + CPPUNIT_ASSERT_EQUAL(nDoc2View0, aViewIdsDoc2[0]); + CPPUNIT_ASSERT_EQUAL(nDoc2View1, aViewIdsDoc2[1]); + // Switch views in the second doc. + CPPUNIT_ASSERT_EQUAL(nDocId2, SfxLokHelper::getDocumentIdOfView(nDoc2View0)); + pDocument2->m_pDocumentClass->setView(pDocument2, nDoc2View0); + CPPUNIT_ASSERT_EQUAL(nDoc2View0, pDocument2->m_pDocumentClass->getView(pDocument2)); + CPPUNIT_ASSERT_EQUAL(nDocId2, SfxLokHelper::getDocumentIdOfView(nDoc2View1)); + pDocument2->m_pDocumentClass->destroyView(pDocument2, nDoc2View1); + CPPUNIT_ASSERT_EQUAL(1, pDocument2->m_pDocumentClass->getViewsCount(pDocument2)); + + closeDoc(document2); + + closeDoc(document1); + } +} + +namespace +{ + SfxChildWindow* lcl_initializeSidebar() + { + // in init.cxx we do setupSidebar which creates the controller, do it here + + SfxViewShell* pViewShell = SfxViewShell::Current(); + CPPUNIT_ASSERT(pViewShell); + + SfxViewFrame& rViewFrame = pViewShell->GetViewFrame(); + SfxChildWindow* pSideBar = rViewFrame.GetChildWindow(SID_SIDEBAR); + CPPUNIT_ASSERT(pSideBar); + + auto pDockingWin = dynamic_cast<sfx2::sidebar::SidebarDockingWindow *>(pSideBar->GetWindow()); + CPPUNIT_ASSERT(pDockingWin); + + pDockingWin->GetOrCreateSidebarController(); // just to create the controller + + return pSideBar; + } +}; + +void DesktopLOKTest::testControlState() +{ + LibLODocument_Impl* pDocument = loadDoc("search.ods"); + pDocument->pClass->postUnoCommand(pDocument, ".uno:StarShapes", nullptr, false); + lcl_initializeSidebar(); + Scheduler::ProcessEventsToIdle(); + + boost::property_tree::ptree aState; + SfxViewShell* pViewShell = SfxViewShell::Current(); + pViewShell->GetViewFrame().GetBindings().Update(); + pViewShell->GetViewFrame().GetBindings().QueryControlState(SID_ATTR_TRANSFORM_WIDTH, aState); + CPPUNIT_ASSERT(!aState.empty()); +} + +void DesktopLOKTest::testMetricField() +{ + LibLODocument_Impl* pDocument = loadDoc("search.ods"); + pDocument->pClass->postUnoCommand(pDocument, ".uno:StarShapes", nullptr, false); + SfxChildWindow* pSideBar = lcl_initializeSidebar(); + Scheduler::ProcessEventsToIdle(); + + vcl::Window* pWin = pSideBar->GetWindow(); + CPPUNIT_ASSERT(pWin); + + WindowUIObject aWinUI(pWin); + std::unique_ptr<UIObject> pUIWin(aWinUI.get_child("selectwidth")); + CPPUNIT_ASSERT(pUIWin); + + StringMap aMap; + aMap["VALUE"] = "75.06"; + pUIWin->execute("VALUE", aMap); + + StringMap aRet = pUIWin->get_state(); + CPPUNIT_ASSERT_EQUAL(aMap["VALUE"], aRet["Value"]); +} + +void DesktopLOKTest::testJumpCursor() +{ + comphelper::LibreOfficeKit::setTiledAnnotations(false); + + LibLODocument_Impl* pDocument = loadDoc("blank_text.odt"); + pDocument->m_pDocumentClass->initializeForRendering(pDocument, "{}"); + + pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 'B', 0); + pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 'o', 0); + pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 'l', 0); + pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 'i', 0); + pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 'v', 0); + pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 'i', 0); + pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 'a', 0); + pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 0, com::sun::star::awt::Key::ESCAPE); + Scheduler::ProcessEventsToIdle(); + + // There is a cursor jump to (0, 0) due to + // mpOutlinerView->SetOutputArea( PixelToLogic( tools::Rectangle(0,0,1,1) ) ); + // when creating a comment + ViewCallback aView1(pDocument); + + pDocument->pClass->postUnoCommand(pDocument, ".uno:InsertAnnotation", nullptr, true); + Scheduler::ProcessEventsToIdle(); + + CPPUNIT_ASSERT(!aView1.m_bZeroCursor); + + comphelper::LibreOfficeKit::setTiledAnnotations(true); +} + +void DesktopLOKTest::testRenderSearchResult_WriterNode() +{ + constexpr const bool bDumpBitmap = false; + + LibLODocument_Impl* pDocument = loadDoc("SearchIndexResultTest.odt"); + pDocument->m_pDocumentClass->initializeForRendering(pDocument, "{}"); + + Scheduler::ProcessEventsToIdle(); + + unsigned char* pBuffer = nullptr; + OString aPayload = + "<indexing>" + "<paragraph node_type=\"writer\" index=\"19\">ABC</paragraph>" + "</indexing>"_ostr; + + int nWidth = 0; + int nHeight = 0; + size_t nByteSize = 0; + + bool bResult = pDocument->m_pDocumentClass->renderSearchResult(pDocument, aPayload.getStr(), &pBuffer, &nWidth, &nHeight, &nByteSize); + + CPPUNIT_ASSERT(bResult); + CPPUNIT_ASSERT(pBuffer); + + Scheduler::ProcessEventsToIdle(); + + CPPUNIT_ASSERT_EQUAL(642, nWidth); + CPPUNIT_ASSERT_EQUAL(561, nHeight); + CPPUNIT_ASSERT_EQUAL(size_t(1440648), nByteSize); + + const sal_uInt8* pD = reinterpret_cast<const sal_uInt8*>(pBuffer); + BitmapEx aBitmap = vcl::bitmap::CreateFromData(pD, nWidth, nHeight, nWidth * 4, /*nBitsPerPixel*/32, true, true); + + if (bDumpBitmap) + { + SvFileStream aStream("~/SearchResultBitmap.png", StreamMode::WRITE | StreamMode::TRUNC); + vcl::PngImageWriter aPNGWriter(aStream); + aPNGWriter.write(aBitmap); + } + CPPUNIT_ASSERT_EQUAL(tools::Long(642), aBitmap.GetSizePixel().Width()); + CPPUNIT_ASSERT_EQUAL(tools::Long(561), aBitmap.GetSizePixel().Height()); + + std::free(pBuffer); +} + +void DesktopLOKTest::testRenderSearchResult_CommonNode() +{ + constexpr const bool bDumpBitmap = false; + + LibLODocument_Impl* pDocument = loadDoc("SearchIndexResultShapeTest.odt"); + pDocument->m_pDocumentClass->initializeForRendering(pDocument, "{}"); + + Scheduler::ProcessEventsToIdle(); + + unsigned char* pBuffer = nullptr; + OString aPayload = + "<indexing>" + "<paragraph node_type=\"common\" index=\"0\" object_name=\"Shape 1\" />" + "</indexing>"_ostr; + + int nWidth = 0; + int nHeight = 0; + size_t nByteSize = 0; + + bool bResult = pDocument->m_pDocumentClass->renderSearchResult(pDocument, aPayload.getStr(), &pBuffer, &nWidth, &nHeight, &nByteSize); + + CPPUNIT_ASSERT(bResult); + CPPUNIT_ASSERT(pBuffer); + + Scheduler::ProcessEventsToIdle(); + + CPPUNIT_ASSERT_EQUAL(192, nWidth); + CPPUNIT_ASSERT_EQUAL(96, nHeight); + CPPUNIT_ASSERT_EQUAL(size_t(73728), nByteSize); + + const sal_uInt8* pD = reinterpret_cast<const sal_uInt8*>(pBuffer); + BitmapEx aBitmap = vcl::bitmap::CreateFromData(pD, nWidth, nHeight, nWidth * 4, /*nBitsPerPixel*/32, true, true); + + if (bDumpBitmap) + { + SvFileStream aStream("~/SearchResultBitmap.png", StreamMode::WRITE | StreamMode::TRUNC); + vcl::PngImageWriter aPNGWriter(aStream); + aPNGWriter.write(aBitmap); + } + CPPUNIT_ASSERT_EQUAL(tools::Long(192), aBitmap.GetSizePixel().Width()); + CPPUNIT_ASSERT_EQUAL(tools::Long(96), aBitmap.GetSizePixel().Height()); + + std::free(pBuffer); +} + +static void lcl_repeatKeyStroke(LibLODocument_Impl *pDocument, int nCharCode, int nKeyCode, size_t nCount) +{ + for (size_t nCtr = 0; nCtr < nCount; ++nCtr) + { + pDocument->m_pDocumentClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, nCharCode, nKeyCode); + pDocument->m_pDocumentClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYUP, nCharCode, nKeyCode); + } +} + +void DesktopLOKTest::testNoDuplicateTableSelection() +{ + LibLODocument_Impl* pDocument = loadDoc("table-selection.odt"); + + // Create view 1. + pDocument->m_pDocumentClass->initializeForRendering(pDocument, "{}"); + ViewCallback aView1(pDocument); + + lcl_repeatKeyStroke(pDocument, 0, KEY_DOWN, 1); + Scheduler::ProcessEventsToIdle(); + CPPUNIT_ASSERT_EQUAL(1, aView1.m_nTableSelectionCount); + CPPUNIT_ASSERT(aView1.m_bEmptyTableSelection); + + aView1.m_nTableSelectionCount = 0; + // Go to Table1. + lcl_repeatKeyStroke(pDocument, 0, KEY_DOWN, 1); + Scheduler::ProcessEventsToIdle(); + CPPUNIT_ASSERT_EQUAL(1, aView1.m_nTableSelectionCount); + CPPUNIT_ASSERT(!aView1.m_bEmptyTableSelection); + + aView1.m_nTableSelectionCount = 0; + // Move to the last row in Table1. + lcl_repeatKeyStroke(pDocument, 0, KEY_DOWN, 2); + Scheduler::ProcessEventsToIdle(); + CPPUNIT_ASSERT_EQUAL(0, aView1.m_nTableSelectionCount); + + // Go outside Table1. + lcl_repeatKeyStroke(pDocument, 0, KEY_DOWN, 1); + Scheduler::ProcessEventsToIdle(); + CPPUNIT_ASSERT_EQUAL(1, aView1.m_nTableSelectionCount); + CPPUNIT_ASSERT(aView1.m_bEmptyTableSelection); +} + +void DesktopLOKTest::testMultiViewTableSelection() +{ + LibLODocument_Impl* pDocument = loadDoc("table-selection.odt"); + + // Create view 1. + pDocument->m_pDocumentClass->initializeForRendering(pDocument, "{}"); + ViewCallback aView1(pDocument); + int nView1 = pDocument->m_pDocumentClass->getView(pDocument); + + // Create view 2. + pDocument->m_pDocumentClass->createView(pDocument); + pDocument->m_pDocumentClass->initializeForRendering(pDocument, "{}"); + ViewCallback aView2(pDocument); + int nView2 = pDocument->m_pDocumentClass->getView(pDocument); + + // switch to view 1. + pDocument->m_pDocumentClass->setView(pDocument, nView1); + lcl_repeatKeyStroke(pDocument, 0, KEY_DOWN, 1); + Scheduler::ProcessEventsToIdle(); + CPPUNIT_ASSERT_EQUAL(1, aView1.m_nTableSelectionCount); + CPPUNIT_ASSERT_EQUAL(1, aView2.m_nTableSelectionCount); + CPPUNIT_ASSERT(aView1.m_bEmptyTableSelection); + CPPUNIT_ASSERT(aView2.m_bEmptyTableSelection); + + aView1.m_nTableSelectionCount = 0; + aView2.m_nTableSelectionCount = 0; + + pDocument->m_pDocumentClass->setView(pDocument, nView1); + // Go to Table1. + lcl_repeatKeyStroke(pDocument, 0, KEY_DOWN, 1); + Scheduler::ProcessEventsToIdle(); + CPPUNIT_ASSERT_EQUAL(1, aView1.m_nTableSelectionCount); + CPPUNIT_ASSERT_EQUAL(0, aView2.m_nTableSelectionCount); + + aView1.m_nTableSelectionCount = 0; + // Switch to view 2 + pDocument->m_pDocumentClass->setView(pDocument, nView2); + // Go to Table2 in view 2. + lcl_repeatKeyStroke(pDocument, 0, KEY_DOWN, 7); + Scheduler::ProcessEventsToIdle(); + // View1 should not get any table selection messages. + CPPUNIT_ASSERT_EQUAL(0, aView1.m_nTableSelectionCount); + // View2 will first get table selection of Table1, then empty selection, and finally on 7th down arrow keypress, + // it will get table-selection of Table2. So in total it should get 3 table selections. + CPPUNIT_ASSERT_EQUAL(3, aView2.m_nTableSelectionCount); + CPPUNIT_ASSERT(!aView2.m_bEmptyTableSelection); + + aView1.m_nTableSelectionCount = 0; + aView2.m_nTableSelectionCount = 0; + + // Switch to view 1 + pDocument->m_pDocumentClass->setView(pDocument, nView1); + // Go out of Table1 and re-enter.. + lcl_repeatKeyStroke(pDocument, 0, KEY_UP, 1); + lcl_repeatKeyStroke(pDocument, 0, KEY_DOWN, 1); + Scheduler::ProcessEventsToIdle(); + // View1 should get one empty table selection, then get Table1 selection. + CPPUNIT_ASSERT_EQUAL(2, aView1.m_nTableSelectionCount); + // View2 should not get any table selection. + CPPUNIT_ASSERT_EQUAL(0, aView2.m_nTableSelectionCount); + CPPUNIT_ASSERT(!aView1.m_bEmptyTableSelection); +} + +void DesktopLOKTest::testColorPaletteCallback() +{ + LibLODocument_Impl* pDocument = loadDoc("ThemeDocument.docx"); + + // Create view 1. + pDocument->m_pDocumentClass->initializeForRendering(pDocument, "{}"); + ViewCallback aView1(pDocument); + Scheduler::ProcessEventsToIdle(); + { + CPPUNIT_ASSERT_EQUAL(1, aView1.m_nColorPaletteCallbackCount); + boost::property_tree::ptree aValues = aView1.m_aColorPaletteCallbackResult.get_child("ThemeColors"); + CPPUNIT_ASSERT(!aValues.empty()); + CPPUNIT_ASSERT_EQUAL(size_t(6), aValues.size()); + } + + // Create view 2. + pDocument->m_pDocumentClass->createView(pDocument); + pDocument->m_pDocumentClass->initializeForRendering(pDocument, "{}"); + ViewCallback aView2(pDocument); + Scheduler::ProcessEventsToIdle(); + { + CPPUNIT_ASSERT_EQUAL(1, aView2.m_nColorPaletteCallbackCount); + boost::property_tree::ptree aValues = aView1.m_aColorPaletteCallbackResult.get_child("ThemeColors"); + CPPUNIT_ASSERT(!aValues.empty()); + CPPUNIT_ASSERT_EQUAL(size_t(6), aValues.size()); + } +} + +namespace { + +constexpr size_t classOffset(int i) +{ + return sizeof(static_cast<struct _LibreOfficeKitClass*>(nullptr)->nSize) + i * sizeof(void*); +} + +constexpr size_t documentClassOffset(int i) +{ + return sizeof(static_cast<struct _LibreOfficeKitDocumentClass*>(nullptr)->nSize) + i * sizeof(void*); +} + +} + +void DesktopLOKTest::testABI() +{ + // STABLE ABI, NEVER CHANGE (unless there's a very good reason, agreed by ESC, etc.) + CPPUNIT_ASSERT_EQUAL(classOffset(0), offsetof(struct _LibreOfficeKitClass, destroy)); + CPPUNIT_ASSERT_EQUAL(classOffset(1), offsetof(struct _LibreOfficeKitClass, documentLoad)); + CPPUNIT_ASSERT_EQUAL(classOffset(2), offsetof(struct _LibreOfficeKitClass, getError)); + CPPUNIT_ASSERT_EQUAL(classOffset(3), offsetof(struct _LibreOfficeKitClass, documentLoadWithOptions)); + CPPUNIT_ASSERT_EQUAL(classOffset(4), offsetof(struct _LibreOfficeKitClass, freeError)); + CPPUNIT_ASSERT_EQUAL(classOffset(5), offsetof(struct _LibreOfficeKitClass, registerCallback)); + CPPUNIT_ASSERT_EQUAL(classOffset(6), offsetof(struct _LibreOfficeKitClass, getFilterTypes)); + CPPUNIT_ASSERT_EQUAL(classOffset(7), offsetof(struct _LibreOfficeKitClass, setOptionalFeatures)); + CPPUNIT_ASSERT_EQUAL(classOffset(8), offsetof(struct _LibreOfficeKitClass, setDocumentPassword)); + CPPUNIT_ASSERT_EQUAL(classOffset(9), offsetof(struct _LibreOfficeKitClass, getVersionInfo)); + CPPUNIT_ASSERT_EQUAL(classOffset(10), offsetof(struct _LibreOfficeKitClass, runMacro)); + CPPUNIT_ASSERT_EQUAL(classOffset(11), offsetof(struct _LibreOfficeKitClass, signDocument)); + CPPUNIT_ASSERT_EQUAL(classOffset(12), offsetof(struct _LibreOfficeKitClass, runLoop)); + CPPUNIT_ASSERT_EQUAL(classOffset(13), offsetof(struct _LibreOfficeKitClass, sendDialogEvent)); + CPPUNIT_ASSERT_EQUAL(classOffset(14), offsetof(struct _LibreOfficeKitClass, setOption)); + CPPUNIT_ASSERT_EQUAL(classOffset(15), offsetof(struct _LibreOfficeKitClass, dumpState)); + CPPUNIT_ASSERT_EQUAL(classOffset(16), offsetof(struct _LibreOfficeKitClass, extractRequest)); + CPPUNIT_ASSERT_EQUAL(classOffset(17), offsetof(struct _LibreOfficeKitClass, trimMemory)); + CPPUNIT_ASSERT_EQUAL(classOffset(18), offsetof(struct _LibreOfficeKitClass, startURP)); + CPPUNIT_ASSERT_EQUAL(classOffset(19), offsetof(struct _LibreOfficeKitClass, stopURP)); + + // When extending LibreOfficeKit with a new function pointer, add new assert for the offsetof the + // new function pointer and bump this assert for the size of the class. + CPPUNIT_ASSERT_EQUAL(classOffset(20), sizeof(struct _LibreOfficeKitClass)); + + CPPUNIT_ASSERT_EQUAL(documentClassOffset(0), offsetof(struct _LibreOfficeKitDocumentClass, destroy)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(1), offsetof(struct _LibreOfficeKitDocumentClass, saveAs)); + + // Unstable ABI, but still think twice before changing this + // Eg. can't you add your new member at the end of the struct instead of + // in the middle? The thing you are changing - is it already part of some + // release? + CPPUNIT_ASSERT_EQUAL(documentClassOffset(2), offsetof(struct _LibreOfficeKitDocumentClass, getDocumentType)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(3), offsetof(struct _LibreOfficeKitDocumentClass, getParts)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(4), offsetof(struct _LibreOfficeKitDocumentClass, getPartPageRectangles)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(5), offsetof(struct _LibreOfficeKitDocumentClass, getPart)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(6), offsetof(struct _LibreOfficeKitDocumentClass, setPart)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(7), offsetof(struct _LibreOfficeKitDocumentClass, getPartName)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(8), offsetof(struct _LibreOfficeKitDocumentClass, setPartMode)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(9), offsetof(struct _LibreOfficeKitDocumentClass, paintTile)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(10), offsetof(struct _LibreOfficeKitDocumentClass, getTileMode)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(11), offsetof(struct _LibreOfficeKitDocumentClass, getDocumentSize)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(12), offsetof(struct _LibreOfficeKitDocumentClass, initializeForRendering)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(13), offsetof(struct _LibreOfficeKitDocumentClass, registerCallback)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(14), offsetof(struct _LibreOfficeKitDocumentClass, postKeyEvent)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(15), offsetof(struct _LibreOfficeKitDocumentClass, postMouseEvent)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(16), offsetof(struct _LibreOfficeKitDocumentClass, postUnoCommand)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(17), offsetof(struct _LibreOfficeKitDocumentClass, setTextSelection)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(18), offsetof(struct _LibreOfficeKitDocumentClass, getTextSelection)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(19), offsetof(struct _LibreOfficeKitDocumentClass, paste)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(20), offsetof(struct _LibreOfficeKitDocumentClass, setGraphicSelection)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(21), offsetof(struct _LibreOfficeKitDocumentClass, resetSelection)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(22), offsetof(struct _LibreOfficeKitDocumentClass, getCommandValues)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(23), offsetof(struct _LibreOfficeKitDocumentClass, setClientZoom)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(24), offsetof(struct _LibreOfficeKitDocumentClass, setClientVisibleArea)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(25), offsetof(struct _LibreOfficeKitDocumentClass, createView)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(26), offsetof(struct _LibreOfficeKitDocumentClass, destroyView)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(27), offsetof(struct _LibreOfficeKitDocumentClass, setView)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(28), offsetof(struct _LibreOfficeKitDocumentClass, getView)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(29), offsetof(struct _LibreOfficeKitDocumentClass, getViewsCount)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(30), offsetof(struct _LibreOfficeKitDocumentClass, renderFont)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(31), offsetof(struct _LibreOfficeKitDocumentClass, getPartHash)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(32), offsetof(struct _LibreOfficeKitDocumentClass, paintPartTile)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(33), offsetof(struct _LibreOfficeKitDocumentClass, getViewIds)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(34), offsetof(struct _LibreOfficeKitDocumentClass, setOutlineState)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(35), offsetof(struct _LibreOfficeKitDocumentClass, paintWindow)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(36), offsetof(struct _LibreOfficeKitDocumentClass, postWindow)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(37), offsetof(struct _LibreOfficeKitDocumentClass, postWindowKeyEvent)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(38), offsetof(struct _LibreOfficeKitDocumentClass, postWindowMouseEvent)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(39), offsetof(struct _LibreOfficeKitDocumentClass, setViewLanguage)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(40), offsetof(struct _LibreOfficeKitDocumentClass, postWindowExtTextInputEvent)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(41), offsetof(struct _LibreOfficeKitDocumentClass, getPartInfo)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(42), offsetof(struct _LibreOfficeKitDocumentClass, paintWindowDPI)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(43), offsetof(struct _LibreOfficeKitDocumentClass, insertCertificate)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(44), offsetof(struct _LibreOfficeKitDocumentClass, addCertificate)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(45), offsetof(struct _LibreOfficeKitDocumentClass, getSignatureState)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(46), offsetof(struct _LibreOfficeKitDocumentClass, renderShapeSelection)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(47), offsetof(struct _LibreOfficeKitDocumentClass, postWindowGestureEvent)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(48), offsetof(struct _LibreOfficeKitDocumentClass, createViewWithOptions)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(49), offsetof(struct _LibreOfficeKitDocumentClass, selectPart)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(50), offsetof(struct _LibreOfficeKitDocumentClass, moveSelectedParts)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(51), offsetof(struct _LibreOfficeKitDocumentClass, resizeWindow)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(52), offsetof(struct _LibreOfficeKitDocumentClass, getClipboard)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(53), offsetof(struct _LibreOfficeKitDocumentClass, setClipboard)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(54), offsetof(struct _LibreOfficeKitDocumentClass, getSelectionType)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(55), offsetof(struct _LibreOfficeKitDocumentClass, removeTextContext)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(56), offsetof(struct _LibreOfficeKitDocumentClass, sendDialogEvent)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(57), offsetof(struct _LibreOfficeKitDocumentClass, renderFontOrientation)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(58), offsetof(struct _LibreOfficeKitDocumentClass, paintWindowForView)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(59), offsetof(struct _LibreOfficeKitDocumentClass, completeFunction)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(60), offsetof(struct _LibreOfficeKitDocumentClass, setWindowTextSelection)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(61), offsetof(struct _LibreOfficeKitDocumentClass, sendFormFieldEvent)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(62), offsetof(struct _LibreOfficeKitDocumentClass, setBlockedCommandList)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(63), offsetof(struct _LibreOfficeKitDocumentClass, renderSearchResult)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(64), + offsetof(struct _LibreOfficeKitDocumentClass, sendContentControlEvent)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(65), offsetof(struct _LibreOfficeKitDocumentClass, getSelectionTypeAndText)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(66), offsetof(struct _LibreOfficeKitDocumentClass, getDataArea)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(67), offsetof(struct _LibreOfficeKitDocumentClass, getEditMode)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(68), + offsetof(struct _LibreOfficeKitDocumentClass, setViewTimezone)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(69), + offsetof(struct _LibreOfficeKitDocumentClass, setAccessibilityState)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(70), + offsetof(struct _LibreOfficeKitDocumentClass, getA11yFocusedParagraph)); + CPPUNIT_ASSERT_EQUAL(documentClassOffset(71), + offsetof(struct _LibreOfficeKitDocumentClass, getA11yCaretPosition)); + + // As above + CPPUNIT_ASSERT_EQUAL(documentClassOffset(72), sizeof(struct _LibreOfficeKitDocumentClass)); +} + +CPPUNIT_TEST_SUITE_REGISTRATION(DesktopLOKTest); + +CPPUNIT_PLUGIN_IMPLEMENT(); + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/qa/unit/data/desktop-dialogs-test.txt b/desktop/qa/unit/data/desktop-dialogs-test.txt new file mode 100644 index 0000000000..a266956871 --- /dev/null +++ b/desktop/qa/unit/data/desktop-dialogs-test.txt @@ -0,0 +1,44 @@ +# -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# + +# This file contains all dialogs that the unit tests in the module +# will work on if it is in script mode. It will read one-by-one, +# try to open it and create a screenshot that will be saved in +# workdir/screenshots using the pattern of the ui-file name. +# +# Syntax: +# - empty lines are allowed +# - lines starting with '#' are treated as comment +# - all other lines should contain a *.ui filename in the same +# notation as in the dialog constructors (see code) + +# +# The 'known' dialogs which have a hard-coded representation +# in registerKnownDialogsByID/createDialogByID +# + +# No known dialogs in desktop for now + +# +# Dialogs without a hard-coded representation. These will +# be visualized using a fallback based on weld::Builder +# + +# currently deactivated, leads to problems and the test to not work +# This is typically a hint that these should be hard-coded in the +# test case since they need some document and model data to work +# desktop/ui/extensionmanager.ui + +desktop/ui/dependenciesdialog.ui +desktop/ui/updaterequireddialog.ui +desktop/ui/showlicensedialog.ui +desktop/ui/updatedialog.ui +desktop/ui/updateinstalldialog.ui +desktop/ui/licensedialog.ui +desktop/ui/installforalldialog.ui diff --git a/desktop/qa/unit/desktop-dialogs-test.cxx b/desktop/qa/unit/desktop-dialogs-test.cxx new file mode 100644 index 0000000000..56c39eb710 --- /dev/null +++ b/desktop/qa/unit/desktop-dialogs-test.cxx @@ -0,0 +1,61 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include <sal/config.h> +#include <test/screenshot_test.hxx> +#include <vcl/abstdlg.hxx> + +using namespace ::com::sun::star; + +/// Test opening a dialog in desktop +class DesktopDialogsTest : public ScreenshotTest +{ +private: + /// helper method to populate KnownDialogs, called in setUp(). Needs to be + /// written and has to add entries to KnownDialogs + virtual void registerKnownDialogsByID(mapType& rKnownDialogs) override; + + /// dialog creation for known dialogs by ID. Has to be implemented for + /// each registered known dialog + virtual VclPtr<VclAbstractDialog> createDialogByID(sal_uInt32 nID) override; + +public: + DesktopDialogsTest(); + + // try to open a dialog + void openAnyDialog(); + + CPPUNIT_TEST_SUITE(DesktopDialogsTest); + CPPUNIT_TEST(openAnyDialog); + CPPUNIT_TEST_SUITE_END(); +}; + +DesktopDialogsTest::DesktopDialogsTest() {} + +void DesktopDialogsTest::registerKnownDialogsByID(mapType& /*rKnownDialogs*/) +{ + // fill map of known dialogs +} + +VclPtr<VclAbstractDialog> DesktopDialogsTest::createDialogByID(sal_uInt32 /*nID*/) +{ + return nullptr; +} + +void DesktopDialogsTest::openAnyDialog() +{ + /// process input file containing the UXMLDescriptions of the dialogs to dump + processDialogBatchFile(u"desktop/qa/unit/data/desktop-dialogs-test.txt"); +} + +CPPUNIT_TEST_SUITE_REGISTRATION(DesktopDialogsTest); + +CPPUNIT_PLUGIN_IMPLEMENT(); + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/qa/unit/desktop-lok-init.cxx b/desktop/qa/unit/desktop-lok-init.cxx new file mode 100644 index 0000000000..49971afc2e --- /dev/null +++ b/desktop/qa/unit/desktop-lok-init.cxx @@ -0,0 +1,160 @@ +/* -*- 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 <cppunit/TestFixture.h> +#include <cppunit/plugin/TestPlugIn.h> +#include <cppunit/extensions/HelperMacros.h> +#include <comphelper/anytostring.hxx> +#include <comphelper/propertyvalue.hxx> +#include <comphelper/sequence.hxx> + +#include <tools/color.hxx> + +#include <lib/init.hxx> + +#include <com/sun/star/table/BorderLine2.hpp> +#include <com/sun/star/table/BorderLineStyle.hpp> + +using namespace css; + +/// Unit tests for desktop/source/lib/init.cxx internals. +class LOKInitTest : public ::CppUnit::TestFixture +{ +public: + LOKInitTest() {} + + void testJsonToPropertyValues(); + void testJsonToPropertyValuesBorder(); + + CPPUNIT_TEST_SUITE(LOKInitTest); + CPPUNIT_TEST(testJsonToPropertyValues); + CPPUNIT_TEST(testJsonToPropertyValuesBorder); + CPPUNIT_TEST_SUITE_END(); +}; + +namespace +{ +void assertSequencesEqual(const uno::Sequence<beans::PropertyValue>& expected, + const uno::Sequence<beans::PropertyValue>& actual) +{ + CPPUNIT_ASSERT_EQUAL_MESSAGE("The sequences should have the same length", expected.getLength(), + actual.getLength()); + for (int i = 0; i < expected.getLength(); ++i) + { + CPPUNIT_ASSERT_EQUAL(expected[i].Name, actual[i].Name); + CPPUNIT_ASSERT_EQUAL(comphelper::anyToString(expected[i].Value), + comphelper::anyToString(actual[i].Value)); + } +} +} // namespace + +void LOKInitTest::testJsonToPropertyValues() +{ + const char arguments[] = "{" + "\"FileName\":{" + "\"type\":\"string\"," + "\"value\":\"something.odt\"" + "}}"; + + uno::Sequence aArgs{ comphelper::makePropertyValue("FileName", OUString("something.odt")) }; + + assertSequencesEqual( + aArgs, comphelper::containerToSequence(desktop::jsonToPropertyValuesVector(arguments))); +} + +void LOKInitTest::testJsonToPropertyValuesBorder() +{ + const char arguments[] + = "{" + "\"OuterBorder\": {" + "\"type\" : \"[]any\"," + "\"value\" : [" + "{ \"type\" : \"com.sun.star.table.BorderLine2\", \"value\" : { \"Color\" : { \"type\" : " + "\"com.sun.star.util.Color\", \"value\" : 0 }, \"InnerLineWidth\" : { \"type\" : " + "\"short\", \"value\" : 0 }, \"OuterLineWidth\" : { \"type\" : \"short\", \"value\" : 1 " + "}, \"LineDistance\" : { \"type\" : \"short\", \"value\" : 0 }, \"LineStyle\" : { " + "\"type\" : \"short\", \"value\" : 0 }, \"LineWidth\" : { \"type\" : \"unsigned long\", " + "\"value\" : 1 } } }," + "{ \"type\" : \"com.sun.star.table.BorderLine2\", \"value\" : { \"Color\" : { \"type\" : " + "\"com.sun.star.util.Color\", \"value\" : 0 }, \"InnerLineWidth\" : { \"type\" : " + "\"short\", \"value\" : 0 }, \"OuterLineWidth\" : { \"type\" : \"short\", \"value\" : 1 " + "}, \"LineDistance\" : { \"type\" : \"short\", \"value\" : 0 }, \"LineStyle\" : { " + "\"type\" : \"short\", \"value\" : 0 }, \"LineWidth\" : { \"type\" : \"unsigned long\", " + "\"value\" : 1 } } }," + "{ \"type\" : \"com.sun.star.table.BorderLine2\", \"value\" : { \"Color\" : { \"type\" : " + "\"com.sun.star.util.Color\", \"value\" : 0 }, \"InnerLineWidth\" : { \"type\" : " + "\"short\", \"value\" : 0 }, \"OuterLineWidth\" : { \"type\" : \"short\", \"value\" : 1 " + "}, \"LineDistance\" : { \"type\" : \"short\", \"value\" : 0 }, \"LineStyle\" : { " + "\"type\" : \"short\", \"value\" : 0 }, \"LineWidth\" : { \"type\" : \"unsigned long\", " + "\"value\" : 1 } } }," + "{ \"type\" : \"com.sun.star.table.BorderLine2\", \"value\" : { \"Color\" : { \"type\" : " + "\"com.sun.star.util.Color\", \"value\" : 0 }, \"InnerLineWidth\" : { \"type\" : " + "\"short\", \"value\" : 0 }, \"OuterLineWidth\" : { \"type\" : \"short\", \"value\" : 1 " + "}, \"LineDistance\" : { \"type\" : \"short\", \"value\" : 0 }, \"LineStyle\" : { " + "\"type\" : \"short\", \"value\" : 0 }, \"LineWidth\" : { \"type\" : \"unsigned long\", " + "\"value\" : 1 } } }," + "{ \"type\" : \"long\", \"value\" : 0 }," + "{ \"type\" : \"long\", \"value\" : 0 }," + "{ \"type\" : \"long\", \"value\" : 0 }," + "{ \"type\" : \"long\", \"value\" : 0 }," + "{ \"type\" : \"long\", \"value\" : 0 }" + "]" + "}," + "\"InnerBorder\":{" + "\"type\" : \"[]any\"," + "\"value\" : [" + "{ \"type\" : \"com.sun.star.table.BorderLine2\", \"value\" : { \"Color\" : { \"type\" : " + "\"com.sun.star.util.Color\", \"value\" : 0 }, \"InnerLineWidth\" : { \"type\" : " + "\"short\", \"value\" : 0 }, \"OuterLineWidth\" : { \"type\" : \"short\", \"value\" : 1 " + "}, \"LineDistance\" : { \"type\" : \"short\", \"value\" : 0 }, \"LineStyle\" : { " + "\"type\" : \"short\", \"value\" : 0 }, \"LineWidth\" : { \"type\" : \"unsigned long\", " + "\"value\" : 1 } } }," + "{ \"type\" : \"com.sun.star.table.BorderLine2\", \"value\" : { \"Color\" : { \"type\" : " + "\"com.sun.star.util.Color\", \"value\" : 0 }, \"InnerLineWidth\" : { \"type\" : " + "\"short\", \"value\" : 0 }, \"OuterLineWidth\" : { \"type\" : \"short\", \"value\" : 1 " + "}, \"LineDistance\" : { \"type\" : \"short\", \"value\" : 0 }, \"LineStyle\" : { " + "\"type\" : \"short\", \"value\" : 0 }, \"LineWidth\" : { \"type\" : \"unsigned long\", " + "\"value\" : 1 } } }," + "{ \"type\" : \"short\", \"value\" : 0 }," + "{ \"type\" : \"short\", \"value\" : 127 }," + "{ \"type\" : \"long\", \"value\" : 0 }" + "]" + "}}"; + + // see SvxBoxItem::QueryValue for details + table::BorderLine2 aLine(sal_Int32(COL_BLACK), 0, 1, 0, table::BorderLineStyle::SOLID, 1); + uno::Sequence<uno::Any> aOuterSeq{ uno::Any(aLine), // left + uno::Any(aLine), // right + uno::Any(aLine), // bottom + uno::Any(aLine), // top + uno::Any(static_cast<sal_Int32>(0)), + uno::Any(static_cast<sal_Int32>(0)), + uno::Any(static_cast<sal_Int32>(0)), + uno::Any(static_cast<sal_Int32>(0)), + uno::Any(static_cast<sal_Int32>(0)) }; + + // see SvxBoxInfoItem::QueryValue() for details + uno::Sequence<uno::Any> aInnerSeq{ uno::Any(aLine), // horizontal + uno::Any(aLine), // vertical + uno::Any(static_cast<sal_Int16>(0)), + uno::Any(static_cast<sal_Int16>(0x7F)), + uno::Any(static_cast<sal_Int32>(0)) }; + + uno::Sequence aArgs{ comphelper::makePropertyValue("OuterBorder", aOuterSeq), + comphelper::makePropertyValue("InnerBorder", aInnerSeq) }; + + assertSequencesEqual( + aArgs, comphelper::containerToSequence(desktop::jsonToPropertyValuesVector(arguments))); +} + +CPPUNIT_TEST_SUITE_REGISTRATION(LOKInitTest); + +CPPUNIT_PLUGIN_IMPLEMENT(); + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/scripts/gdbtrace b/desktop/scripts/gdbtrace new file mode 100644 index 0000000000..d4eae973c5 --- /dev/null +++ b/desktop/scripts/gdbtrace @@ -0,0 +1,13 @@ +set pagination off +echo log will be saved as gdbtrace.log, this will take some time, patience...\n +handle SIGPIPE SIGXCPU SIG33 SIG35 SIGPWR nostop noprint +set logging redirect on +set logging file gdbtrace.log +set logging enabled on +set logging overwrite on +run +bt +thread apply all bt +quit +set logging enabled off +echo log is saved as gdbtrace.log\n diff --git a/desktop/scripts/sbase.sh b/desktop/scripts/sbase.sh new file mode 100755 index 0000000000..82e5e4ba2b --- /dev/null +++ b/desktop/scripts/sbase.sh @@ -0,0 +1,4 @@ +#!/bin/sh + +cmd=$(dirname "$0")/soffice +exec "$cmd" --base "$@" diff --git a/desktop/scripts/scalc.sh b/desktop/scripts/scalc.sh new file mode 100755 index 0000000000..ff3d597951 --- /dev/null +++ b/desktop/scripts/scalc.sh @@ -0,0 +1,4 @@ +#!/bin/sh + +cmd=$(dirname "$0")/soffice +exec "$cmd" --calc "$@" diff --git a/desktop/scripts/sdraw.sh b/desktop/scripts/sdraw.sh new file mode 100755 index 0000000000..9f7c1e4eda --- /dev/null +++ b/desktop/scripts/sdraw.sh @@ -0,0 +1,4 @@ +#!/bin/sh + +cmd=$(dirname "$0")/soffice +exec "$cmd" --draw "$@" diff --git a/desktop/scripts/simpress.sh b/desktop/scripts/simpress.sh new file mode 100755 index 0000000000..a1808c3cb1 --- /dev/null +++ b/desktop/scripts/simpress.sh @@ -0,0 +1,4 @@ +#!/bin/sh + +cmd=$(dirname "$0")/soffice +exec "$cmd" --impress "$@" diff --git a/desktop/scripts/smath.sh b/desktop/scripts/smath.sh new file mode 100755 index 0000000000..9c05223b45 --- /dev/null +++ b/desktop/scripts/smath.sh @@ -0,0 +1,4 @@ +#!/bin/sh + +cmd=$(dirname "$0")/soffice +exec "$cmd" --math "$@" diff --git a/desktop/scripts/soffice.sh b/desktop/scripts/soffice.sh new file mode 100755 index 0000000000..8866a2cc8c --- /dev/null +++ b/desktop/scripts/soffice.sh @@ -0,0 +1,187 @@ +#!/bin/sh +# +# 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 . +# + +# use POSIX locale for well-defined tool output +LO_SAVE_LC_ALL="$LC_ALL" +LC_ALL=C +export LC_ALL + +# +# STAR_PROFILE_LOCKING_DISABLED=1 +# export STAR_PROFILE_LOCKING_DISABLED +# + +# file locking now enabled by default +SAL_ENABLE_FILE_LOCKING=1 +export SAL_ENABLE_FILE_LOCKING + +# uncomment line below to disable anti aliasing of fonts +# SAL_ANTIALIAS_DISABLE=true; export SAL_ANTIALIAS_DISABLE + +# uncomment line below if you encounter problems starting soffice on your system +# SAL_NO_XINITTHREADS=true; export SAL_NO_XINITTHREADS + +#@JITC_PROCESSOR_TYPE_EXPORT@ + +# resolve installation directory +sd_cwd=$(pwd) +sd_res="$0" +while [ -h "$sd_res" ] ; do + sd_dirname=$(dirname "$sd_res") + cd "$sd_dirname" || exit $? + sd_basename=$(basename "$sd_res") + sd_res=$(ls -l "$sd_basename" | sed "s/.*$sd_basename -> //g") +done +sd_dirname=$(dirname "$sd_res") +cd "$sd_dirname" || exit $? +sd_prog=$(pwd) +cd "$sd_cwd" || exit $? + +# linked build needs additional settings +if [ -e "${sd_prog}/ooenv" ] ; then + # shellcheck source=../../instsetoo_native/ooenv + . "${sd_prog}/ooenv" +fi + +# try to get some debug output? +GDBTRACECHECK= +STRACECHECK= +VALGRINDCHECK= +RRCHECK= + +# count number of selected checks; only one is allowed +checks= +EXTRAOPT= +# force the --valgrind option if the VALGRIND variable is set +test -n "$VALGRIND" && EXTRAOPT="--valgrind" + +# force the --record option if the RR variable is set +test -n "$RR" && EXTRAOPT="--record" + +for arg in "$@" $EXTRAOPT ; do + case "$arg" in + --record) + if which rr >/dev/null 2>&1 ; then + # smoketest may already be recorded => ignore nested + RRCHECK="rr record --nested=ignore" + checks="c$checks" + else + echo "Error: Can't find the tool \"rr\", --record option will be ignored." + exit 1 + fi + ;; + --backtrace) + if which gdb >/dev/null 2>&1 ; then + GDBTRACECHECK="gdb -nx --command=$sd_prog/gdbtrace --args" + checks="c$checks" + else + echo "Error: Can't find the tool \"gdb\", --backtrace option will be ignored." + exit 1 + fi + ;; + --strace) + if which strace >/dev/null 2>&1 ; then + STRACECHECK="strace -o strace.log -f -tt -s 256" + checks="c$checks" + else + echo "Error: Can't find the tool \"strace\", --strace option will be ignored." + exit 1; + fi + ;; + --valgrind) + test -n "$VALGRINDCHECK" && continue; + if which valgrind >/dev/null 2>&1 ; then + # another valgrind tool might be forced via the environment variable + test -z "$VALGRIND" && VALGRIND="memcheck" + # --trace-children-skip is pretty useful but supported only with valgrind >= 3.6.0 + valgrind_ver=$(valgrind --version | sed -e "s/valgrind-//") + valgrind_ver_maj=$(echo "$valgrind_ver" | awk -F. '{ print $1 }') + valgrind_ver_min=$(echo "$valgrind_ver" | awk -F. '{ print $2 }') + valgrind_skip= + if [ "$valgrind_ver_maj" -gt 3 ] || ( [ "$valgrind_ver_maj" -eq 3 ] && [ "$valgrind_ver_min" -ge 6 ] ) ; then + valgrind_skip='--trace-children-skip=*/java,*/gij' + fi + # finally set the valgrind check + VALGRINDCHECK="valgrind --tool=$VALGRIND --trace-children=yes $valgrind_skip --num-callers=50 --error-limit=no" + echo "use kill -SIGUSR2 pid to dump traces of active allocations" + checks="c$checks" + case "$VALGRIND" in + helgrind|memcheck|massif|exp-dhat) + export G_SLICE=always-malloc + export GLIBCXX_FORCE_NEW=1 + ;; + callgrind) + unset MALLOC_CHECK_ MALLOC_PERTURB_ G_SLICE + export SAL_DISABLE_FLOATGRAB=1 + export OOO_DISABLE_RECOVERY=1 + export SAL_DISABLE_WATCHDOG=1 + export LD_BIND_NOW=1 + ;; + esac + else + echo "Error: Can't find the tool \"valgrind\", --valgrind option will be ignored" + exit 1 + fi + ;; + esac +done + +if echo "$checks" | grep -q "cc" ; then + echo "Error: The debug options --record, --backtrace, --strace, and --valgrind cannot be used together." + echo " Please, use them one by one." + exit 1; +fi + +case "$(uname -s)" in +OpenBSD) +# this is a temporary hack until we can live with the default search paths + LD_LIBRARY_PATH="$sd_prog${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}" + JAVA_HOME=$(javaPathHelper -h libreoffice-java 2> /dev/null) + export LD_LIBRARY_PATH + if [ -n "${JAVA_HOME}" ]; then + export JAVA_HOME + fi + ;; +NetBSD|DragonFly) +# this is a temporary hack until we can live with the default search paths + LD_LIBRARY_PATH="$sd_prog${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}" + export LD_LIBRARY_PATH + ;; +esac + +# restore locale setting, avoiding to export empty LC_ALL, s. tdf#130080 +if [ -n "$LO_SAVE_LC_ALL" ]; then + LC_ALL="$LO_SAVE_LC_ALL" +else + unset LC_ALL +fi + +# run soffice.bin directly when you want to get the backtrace +if [ -n "$GDBTRACECHECK" ] ; then + exec $GDBTRACECHECK "$sd_prog/soffice.bin" "$@" +fi + +# valgrind --log-file=valgrind.log does not work well with --trace-children=yes +if [ -n "$VALGRINDCHECK" ] && [ -z "$VALGRIND" ] ; then + echo "redirecting the standard and the error output to valgrind.log" + exec > valgrind.log 2>&1 +fi + +# oosplash does the rest: forcing pages in, javaldx etc. are +exec $RRCHECK $VALGRINDCHECK $STRACECHECK "$sd_prog/oosplash" "$@" diff --git a/desktop/scripts/swriter.sh b/desktop/scripts/swriter.sh new file mode 100755 index 0000000000..19a7c9ed41 --- /dev/null +++ b/desktop/scripts/swriter.sh @@ -0,0 +1,4 @@ +#!/bin/sh + +cmd=$(dirname "$0")/soffice +exec "$cmd" --writer "$@" diff --git a/desktop/scripts/unoinfo-mac.sh b/desktop/scripts/unoinfo-mac.sh new file mode 100755 index 0000000000..9b37d1b7d8 --- /dev/null +++ b/desktop/scripts/unoinfo-mac.sh @@ -0,0 +1,46 @@ +#!/bin/sh +# +# 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 . +# + +set -e + +# resolve installation directory +sd_res="$0" +while [ -h "$sd_res" ] ; do + sd_dirname=$(dirname "$sd_res") + cd "$sd_dirname" + sd_basename=$(basename "$sd_res") + sd_res=$(ls -l "$sd_basename" | sed "s/.*$sd_basename -> //g") +done +sd_dirname=$(dirname "$sd_res") +cd "$sd_dirname" +sd_prog=$(pwd) + +case "$1" in +c++) + printf '%s' "$sd_prog/../Frameworks" + ;; +java) + printf '0%s\0%s' \ + "$sd_prog/../Resources/java/libreoffice.jar" \ + "$sd_prog/../MacOS" + ;; +*) + exit 1 + ;; +esac diff --git a/desktop/scripts/unoinfo.sh b/desktop/scripts/unoinfo.sh new file mode 100755 index 0000000000..14cba80644 --- /dev/null +++ b/desktop/scripts/unoinfo.sh @@ -0,0 +1,46 @@ +#!/bin/sh +# +# 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 . +# + +set -e + +# resolve installation directory +sd_res="$0" +while [ -h "$sd_res" ] ; do + sd_dirname=$(dirname "$sd_res") + cd "$sd_dirname" + sd_basename=$(basename "$sd_res") + sd_res=$(ls -l "$sd_basename" | sed "s/.*$sd_basename -> //g") +done +sd_dirname=$(dirname "$sd_res") +cd "$sd_dirname" +sd_prog=$(pwd) + +case "$1" in +c++) + printf '%s' "$sd_prog" + ;; +java) + printf '0%s\0%s' \ + "$sd_prog/classes/libreoffice.jar" \ + "$sd_prog" + ;; +*) + exit 1 + ;; +esac diff --git a/desktop/scripts/unopkg.sh b/desktop/scripts/unopkg.sh new file mode 100755 index 0000000000..de3823857f --- /dev/null +++ b/desktop/scripts/unopkg.sh @@ -0,0 +1,86 @@ +#!/bin/sh +# +# 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 . +# + +# enable file locking +SAL_ENABLE_FILE_LOCKING=1 +export SAL_ENABLE_FILE_LOCKING + +# resolve installation directory +sd_cwd=$(pwd) +sd_res="$0" +while [ -h "$sd_res" ] ; do + sd_dirname=$(dirname "$sd_res") + cd "$sd_dirname" || exit $? + sd_basename=$(basename "$sd_res") + sd_res=$(ls -l "$sd_basename" | sed "s/.*$sd_basename -> //g") +done +sd_dirname=$(dirname "$sd_res") +cd "$sd_dirname" || exit $? +sd_prog=$(pwd) +cd "$sd_cwd" || exit $? + +# this is a temporary hack until we can live with the default search paths +case "$(uname -s)" in +OpenBSD) + LD_LIBRARY_PATH="$sd_prog${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}}" + JAVA_HOME=$(javaPathHelper -h libreoffice-java 2> /dev/null) + export LD_LIBRARY_PATH + if [ -n "${JAVA_HOME}" ]; then + export JAVA_HOME + fi + ;; +NetBSD|FreeBSD|DragonFly) + LD_LIBRARY_PATH="$sd_prog${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}}" + export LD_LIBRARY_PATH + ;; +esac + +for arg in "$@" +do + case "$arg" in + #collect all bootstrap variables specified on the command line + #so that they can be passed as arguments to javaldx later on + -env:*) BOOTSTRAPVARS=$BOOTSTRAPVARS" ""$arg";; + + # make sure shared extensions will be readable by all users + --shared) umask 0022;; + esac +done + +# extend the ld_library_path for java: javaldx checks the sofficerc for us +if [ -x "${sd_prog}/javaldx" ] ; then + my_path=$("${sd_prog}/javaldx" "$BOOTSTRAPVARS" \ + "-env:INIFILENAME=vnd.sun.star.pathname:$sd_prog/redirectrc") + if [ -n "$my_path" ] ; then + LD_LIBRARY_PATH="$my_path${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}" + export LD_LIBRARY_PATH + fi +fi + +unset XENVIRONMENT + +# uncomment line below to disable anti aliasing of fonts +# SAL_ANTIALIAS_DISABLE=true; export SAL_ANTIALIAS_DISABLE + +# uncomment line below if you encounter problems starting soffice on your system +# SAL_NO_XINITTHREADS=true; export SAL_NO_XINITTHREADS + +# execute binary +exec "$sd_prog/unopkg.bin" "$@" \ + "-env:INIFILENAME=vnd.sun.star.pathname:$sd_prog/redirectrc" 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: */ diff --git a/desktop/source/deployment/deployment.component b/desktop/source/deployment/deployment.component new file mode 100644 index 0000000000..f7a481bf43 --- /dev/null +++ b/desktop/source/deployment/deployment.component @@ -0,0 +1,65 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + * 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 . + --> + +<component loader="com.sun.star.loader.SharedLibrary" environment="@CPPU_ENV@" + xmlns="http://openoffice.org/2010/uno-components"> + <implementation name="com.sun.star.comp.deployment.ExtensionManager" + constructor="com_sun_star_comp_deployment_ExtensionManager_get_implementation"> + <service name="com.sun.star.comp.deployment.ExtensionManager"/> + <singleton name="com.sun.star.deployment.ExtensionManager"/> + </implementation> + <implementation name="com.sun.star.comp.deployment.PackageInformationProvider" + constructor="com_sun_star_comp_deployment_PackageInformationProvider_get_implementation"> + <service name="com.sun.star.comp.deployment.PackageInformationProvider"/> + <singleton name="com.sun.star.deployment.PackageInformationProvider"/> + </implementation> + <implementation name="com.sun.star.comp.deployment.PackageManagerFactory" + constructor="com_sun_star_comp_deployment_PackageManagerFactory_get_implementation"> + <service name="com.sun.star.comp.deployment.PackageManagerFactory"/> + <singleton name="com.sun.star.deployment.thePackageManagerFactory"/> + </implementation> + <implementation name="com.sun.star.comp.deployment.ProgressLog" + constructor="com_sun_star_comp_deployment_ProgressLog_get_implementation"> + <service name="com.sun.star.comp.deployment.ProgressLog"/> + </implementation> + <implementation name="com.sun.star.comp.deployment.component.PackageRegistryBackend" + constructor="com_sun_star_comp_deployment_component_PackageRegistryBackend_get_implementation"> + <service name="com.sun.star.deployment.PackageRegistryBackend"/> + </implementation> + <implementation name="com.sun.star.comp.deployment.configuration.PackageRegistryBackend" + constructor="com_sun_star_comp_deployment_configuration_PackageRegistryBackend_get_implementation"> + <service name="com.sun.star.deployment.PackageRegistryBackend"/> + </implementation> + <implementation name="com.sun.star.comp.deployment.executable.PackageRegistryBackend" + constructor="com_sun_star_comp_deployment_executable_PackageRegistryBackend_get_implementation"> + <service name="com.sun.star.deployment.PackageRegistryBackend"/> + </implementation> + <implementation name="com.sun.star.comp.deployment.help.PackageRegistryBackend" + constructor="com_sun_star_comp_deployment_help_PackageRegistryBackend_get_implementation"> + <service name="com.sun.star.deployment.PackageRegistryBackend"/> + </implementation> + <implementation name="com.sun.star.comp.deployment.script.PackageRegistryBackend" + constructor="com_sun_star_comp_deployment_script_PackageRegistryBackend_get_implementation"> + <service name="com.sun.star.deployment.PackageRegistryBackend"/> + </implementation> + <implementation name="com.sun.star.comp.deployment.sfwk.PackageRegistryBackend" + constructor="com_sun_star_comp_deployment_sfwk_PackageRegistryBackend_get_implementation"> + <service name="com.sun.star.deployment.PackageRegistryBackend"/> + </implementation> +</component> diff --git a/desktop/source/deployment/dp_log.cxx b/desktop/source/deployment/dp_log.cxx new file mode 100644 index 0000000000..5d75422cf0 --- /dev/null +++ b/desktop/source/deployment/dp_log.cxx @@ -0,0 +1,143 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + + +#include <cppuhelper/basemutex.hxx> +#include <cppuhelper/compbase.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <comphelper/anytostring.hxx> +#include <comphelper/logging.hxx> +#include <rtl/ustrbuf.hxx> +#include <com/sun/star/logging/LogLevel.hpp> +#include <com/sun/star/ucb/XProgressHandler.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::logging; + +namespace dp_log { + +typedef ::cppu::WeakComponentImplHelper<ucb::XProgressHandler, lang::XServiceInfo> t_log_helper; + +namespace { + +class ProgressLogImpl : public cppu::BaseMutex, public t_log_helper +{ + comphelper::EventLogger m_logger; + +protected: + virtual void SAL_CALL disposing() override; + virtual ~ProgressLogImpl() override; + +public: + ProgressLogImpl( Sequence<Any> const & args, + Reference<XComponentContext> const & xContext ); + + // 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; + + // XProgressHandler + virtual void SAL_CALL push( Any const & Status ) override; + virtual void SAL_CALL update( Any const & Status ) override; + virtual void SAL_CALL pop() override; +}; + +} + +ProgressLogImpl::~ProgressLogImpl() +{ +} + + +void ProgressLogImpl::disposing() +{ +} + + +ProgressLogImpl::ProgressLogImpl( + Sequence<Any> const & /* args */, + Reference<XComponentContext> const & xContext ) + : t_log_helper( m_aMutex ) + // Use the logger created by unopkg app + , m_logger(xContext, "unopkg") +{ +} + +// XServiceInfo +OUString ProgressLogImpl::getImplementationName() +{ + return "com.sun.star.comp.deployment.ProgressLog"; +} + +sal_Bool ProgressLogImpl::supportsService( const OUString& ServiceName ) +{ + return cppu::supportsService(this, ServiceName); +} + +css::uno::Sequence< OUString > ProgressLogImpl::getSupportedServiceNames() +{ + // a private one + return { "com.sun.star.comp.deployment.ProgressLog" }; +} + +// XProgressHandler + +void ProgressLogImpl::push( Any const & Status ) +{ + update( Status ); +} + +void ProgressLogImpl::update( Any const & Status ) +{ + if (! Status.hasValue()) + return; + + OUStringBuffer buf; + + OUString msg; + sal_Int32 logLevel = LogLevel::INFO; + if (Status >>= msg) { + buf.append( msg ); + } + else { + logLevel = LogLevel::SEVERE; + buf.append( ::comphelper::anyToString(Status) ); + } + m_logger.log(logLevel, buf.makeStringAndClear()); +} + + +void ProgressLogImpl::pop() +{ +} + +} // namespace dp_log + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +com_sun_star_comp_deployment_ProgressLog_get_implementation( + css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const& args) +{ + return cppu::acquire(new dp_log::ProgressLogImpl(args, context)); +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/dp_persmap.cxx b/desktop/source/deployment/dp_persmap.cxx new file mode 100644 index 0000000000..8b032fffb9 --- /dev/null +++ b/desktop/source/deployment/dp_persmap.cxx @@ -0,0 +1,312 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <cstddef> + +#include <dp_misc.h> +#include <dp_persmap.h> +#include <o3tl/safeint.hxx> +#include <rtl/byteseq.hxx> +#include <rtl/strbuf.hxx> +#include <sal/log.hxx> + +using namespace ::rtl; + +// the persistent map is used to manage a handful of key-value string pairs +// this implementation replaces a rather heavy-weight berkeleydb integration + +// the file backing up a persistent map consists of line pairs with +// - a key string (encoded with chars 0x00..0x0F being escaped) +// - a value string (encoded with chars 0x00..0x0F being escaped) + +namespace dp_misc +{ + +const char PmapMagic[4] = {'P','m','p','1'}; + +PersistentMap::PersistentMap( OUString const & url_ ) +: m_MapFile( expandUnoRcUrl(url_) ) +, m_bIsOpen( false ) +, m_bToBeCreated( true ) +, m_bIsDirty( false ) +{ + open(); +} + +PersistentMap::PersistentMap() +: m_MapFile( OUString() ) +, m_bIsOpen( false ) +, m_bToBeCreated( false ) +, m_bIsDirty( false ) +{} + +PersistentMap::~PersistentMap() +{ + if( m_bIsDirty ) + flush(); + if( m_bIsOpen ) + m_MapFile.close(); +} + + +// replace 0x00..0x0F with "%0".."%F" +// replace "%" with "%%" +static OString encodeString( const OString& rStr) +{ + const char* pChar = rStr.getStr(); + const sal_Int32 nLen = rStr.getLength(); + sal_Int32 i = nLen; + // short circuit for the simple non-encoded case + while( --i >= 0) + { + const unsigned char c = static_cast<unsigned char>(*(pChar++)); + if( c <= 0x0F ) + break; + if( c == '%') + break; + } + if( i < 0) + return rStr; + + // escape chars 0x00..0x0F with "%0".."%F" + OStringBuffer aEncStr( nLen + 32); + aEncStr.append( pChar - (nLen-i), nLen - i); + while( --i >= 0) + { + unsigned char c = static_cast<unsigned char>(*(pChar++)); + if( c <= 0x0F ) + { + aEncStr.append( '%'); + c += (c <= 0x09) ? '0' : 'A'-10; + } else if( c == '%') + aEncStr.append( '%'); + aEncStr.append( char(c) ); + } + + return aEncStr.makeStringAndClear(); +} + +// replace "%0".."%F" with 0x00..0x0F +// replace "%%" with "%" +static OString decodeString( const char* pEncChars, int nLen) +{ + const char* pChar = pEncChars; + sal_Int32 i = nLen; + // short circuit for the simple non-encoded case + while( --i >= 0) + if( *(pChar++) == '%') + break; + if( i < 0) + return OString( pEncChars, nLen); + + // replace escaped chars with their decoded counterparts + OStringBuffer aDecStr( nLen); + pChar = pEncChars; + for( i = nLen; --i >= 0;) + { + char c = *(pChar++); + // handle escaped character + if( c == '%') + { + --i; + OSL_ASSERT( i >= 0); + c = *(pChar++); + if( ('0' <= c) && (c <= '9')) + c -= '0'; + else + { + OSL_ASSERT( ('A' <= c) && (c <= 'F')); + c -= ('A'-10); + } + } + aDecStr.append( c); + } + + return aDecStr.makeStringAndClear(); +} + +void PersistentMap::open() +{ + // open the existing file + sal_uInt32 const nOpenFlags = osl_File_OpenFlag_Read | osl_File_OpenFlag_Write; + + const osl::File::RC rcOpen = m_MapFile.open( nOpenFlags); + m_bIsOpen = (rcOpen == osl::File::E_None); + + // or create later if needed + m_bToBeCreated &= (rcOpen == osl::File::E_NOENT) && !m_bIsOpen; + + if( !m_bIsOpen) + return; + + readAll(); +} + + +void PersistentMap::readAll() +{ + // prepare for re-reading the map-file + m_entries.clear(); + const osl::FileBase::RC nRes = m_MapFile.setPos( osl_Pos_Absolut, 0); + if (nRes != osl::FileBase::E_None) + { + SAL_WARN("desktop.deployment", "setPos failed with " << +nRes); + return; + } + + // read header and check magic + char aHeaderBytes[ sizeof(PmapMagic)]; + sal_uInt64 nBytesRead = 0; + m_MapFile.read( aHeaderBytes, sizeof(aHeaderBytes), nBytesRead); + OSL_ASSERT( nBytesRead == sizeof(aHeaderBytes)); + if( nBytesRead != sizeof(aHeaderBytes)) + return; + // check header magic + for( std::size_t i = 0; i < sizeof(PmapMagic); ++i) + if( aHeaderBytes[i] != PmapMagic[i]) + return; + + // read key value pairs and add them to the map + ByteSequence aKeyLine; + ByteSequence aValLine; + for(;;) + { + // read key-value line pair + // an empty key name indicates the end of the line pairs + if( m_MapFile.readLine( aKeyLine) != osl::File::E_None) + return; + if( !aKeyLine.getLength()) + break; + if( m_MapFile.readLine( aValLine) != osl::File::E_None) + return; + // decode key and value strings + const OString aKeyName = decodeString( reinterpret_cast<char const *>(aKeyLine.getConstArray()), aKeyLine.getLength()); + const OString aValName = decodeString( reinterpret_cast<char const *>(aValLine.getConstArray()), aValLine.getLength()); + // insert key-value pair into map + add( aKeyName, aValName ); + // check end-of-file status + sal_Bool bIsEOF = true; + if( m_MapFile.isEndOfFile( &bIsEOF) != osl::File::E_None ) + return; + if( bIsEOF ) + break; + } + + m_bIsDirty = false; +} + +void PersistentMap::flush() +{ + if( !m_bIsDirty) + return; + if( m_bToBeCreated && !m_entries.empty()) + { + const sal_uInt32 nOpenFlags = osl_File_OpenFlag_Read | osl_File_OpenFlag_Write | osl_File_OpenFlag_Create; + const osl::File::RC rcOpen = m_MapFile.open( nOpenFlags); + m_bIsOpen = (rcOpen == osl::File::E_None); + m_bToBeCreated = !m_bIsOpen; + } + if( !m_bIsOpen) + return; + + // write header magic + const osl::FileBase::RC nRes = m_MapFile.setPos( osl_Pos_Absolut, 0); + if (nRes != osl::FileBase::E_None) + { + SAL_WARN("desktop.deployment", "setPos failed with " << +nRes); + return; + } + sal_uInt64 nBytesWritten = 0; + m_MapFile.write( PmapMagic, sizeof(PmapMagic), nBytesWritten); + + // write key value pairs + for (auto const& entry : m_entries) + { + // write line for key + const OString aKeyString = encodeString( entry.first); + const sal_Int32 nKeyLen = aKeyString.getLength(); + m_MapFile.write( aKeyString.getStr(), nKeyLen, nBytesWritten); + OSL_ASSERT( o3tl::make_unsigned(nKeyLen) == nBytesWritten); + m_MapFile.write( "\n", 1, nBytesWritten); + // write line for value + const OString& rValString = encodeString( entry.second); + const sal_Int32 nValLen = rValString.getLength(); + m_MapFile.write( rValString.getStr(), nValLen, nBytesWritten); + OSL_ASSERT( o3tl::make_unsigned(nValLen) == nBytesWritten); + m_MapFile.write( "\n", 1, nBytesWritten); + } + + // write a file delimiter (an empty key-string) + m_MapFile.write( "\n", 1, nBytesWritten); + // truncate file here + sal_uInt64 nNewFileSize; + if( m_MapFile.getPos( nNewFileSize) == osl::File::E_None) + m_MapFile.setSize( nNewFileSize); + // flush to disk + m_MapFile.sync(); + // the in-memory map now matches to the file on disk + m_bIsDirty = false; +} + +bool PersistentMap::has( OString const & key ) const +{ + return get( nullptr, key ); +} + +bool PersistentMap::get( OString * value, OString const & key ) const +{ + t_string2string_map::const_iterator it = m_entries.find( key); + if( it == m_entries.end()) + return false; + if( value) + *value = it->second; + return true; +} + +void PersistentMap::add( OString const & key, OString const & value ) +{ + auto r = m_entries.emplace(key,value); + m_bIsDirty = r.second; +} + + +void PersistentMap::put( OString const & key, OString const & value ) +{ + add( key, value); + // HACK: flush now as the extension manager does not seem + // to properly destruct this object in some situations + if(m_bIsDirty) + flush(); +} + +bool PersistentMap::erase( OString const & key ) +{ + size_t nCount = m_entries.erase( key); + if( !nCount) + return false; + m_bIsDirty = true; + flush(); + return true; +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/dp_xml.cxx b/desktop/source/deployment/dp_xml.cxx new file mode 100644 index 0000000000..d61267e2fa --- /dev/null +++ b/desktop/source/deployment/dp_xml.cxx @@ -0,0 +1,51 @@ +/* -*- 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 <dp_xml.h> +#include <ucbhelper/content.hxx> +#include <com/sun/star/xml/sax/Parser.hpp> + + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; + +namespace dp_misc +{ + + +void xml_parse( + Reference<xml::sax::XDocumentHandler> const & xDocHandler, + ::ucbhelper::Content & ucb_content, + Reference<XComponentContext> const & xContext ) +{ + // raise sax parser: + Reference<xml::sax::XParser> xParser = xml::sax::Parser::create(xContext); + + // error handler, entity resolver omitted + xParser->setDocumentHandler( xDocHandler ); + xml::sax::InputSource source; + source.aInputStream = ucb_content.openStream(); + source.sSystemId = ucb_content.getURL(); + xParser->parseStream( source ); +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/gui/deploymentgui.component b/desktop/source/deployment/gui/deploymentgui.component new file mode 100644 index 0000000000..3b56863c13 --- /dev/null +++ b/desktop/source/deployment/gui/deploymentgui.component @@ -0,0 +1,34 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + * 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 . + --> + +<component loader="com.sun.star.loader.SharedLibrary" environment="@CPPU_ENV@" + xmlns="http://openoffice.org/2010/uno-components"> + <implementation name="com.sun.star.comp.deployment.ui.LicenseDialog" + constructor="desktop_LicenseDialog_get_implementation"> + <service name="com.sun.star.deployment.ui.LicenseDialog"/> + </implementation> + <implementation name="com.sun.star.comp.deployment.ui.PackageManagerDialog" + constructor="desktop_ServiceImpl_get_implementation"> + <service name="com.sun.star.deployment.ui.PackageManagerDialog"/> + </implementation> + <implementation name="com.sun.star.comp.deployment.ui.UpdateRequiredDialog" + constructor="desktop_UpdateRequiredDialogService_get_implementation"> + <service name="com.sun.star.deployment.ui.UpdateRequiredDialog"/> + </implementation> +</component> diff --git a/desktop/source/deployment/gui/dp_gui.h b/desktop/source/deployment/gui/dp_gui.h new file mode 100644 index 0000000000..cb2932ca4f --- /dev/null +++ b/desktop/source/deployment/gui/dp_gui.h @@ -0,0 +1,28 @@ +/* -*- 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 + +namespace dp_gui { + +enum PackageState { REGISTERED, NOT_REGISTERED, AMBIGUOUS, NOT_AVAILABLE }; + +} // namespace dp_gui + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/gui/dp_gui_dependencydialog.cxx b/desktop/source/deployment/gui/dp_gui_dependencydialog.cxx new file mode 100644 index 0000000000..f94b298917 --- /dev/null +++ b/desktop/source/deployment/gui/dp_gui_dependencydialog.cxx @@ -0,0 +1,42 @@ +/* -*- 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 <rtl/ustring.hxx> +#include <vcl/weld.hxx> + +#include "dp_gui_dependencydialog.hxx" + +using dp_gui::DependencyDialog; + +DependencyDialog::DependencyDialog(weld::Window* parent, std::vector<OUString> const& dependencies) + : GenericDialogController(parent, "desktop/ui/dependenciesdialog.ui", "Dependencies") + , m_xList(m_xBuilder->weld_tree_view("depListTreeview")) +{ + m_xList->set_size_request(-1, m_xList->get_height_rows(10)); + for (auto const& dependency : dependencies) + { + m_xList->append_text(dependency); + } +} + +DependencyDialog::~DependencyDialog() {} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/gui/dp_gui_dependencydialog.hxx b/desktop/source/deployment/gui/dp_gui_dependencydialog.hxx new file mode 100644 index 0000000000..bdbab41d19 --- /dev/null +++ b/desktop/source/deployment/gui/dp_gui_dependencydialog.hxx @@ -0,0 +1,49 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#pragma once + +#include <sal/config.h> + +#include <vcl/weld.hxx> + +#include <vector> + +namespace vcl +{ +class Window; +} + +namespace dp_gui +{ +class DependencyDialog : public weld::GenericDialogController +{ +public: + DependencyDialog(weld::Window* parent, std::vector<OUString> const& dependencies); + virtual ~DependencyDialog() override; + +private: + DependencyDialog(DependencyDialog const&) = delete; + DependencyDialog& operator=(DependencyDialog const&) = delete; + + std::unique_ptr<weld::TreeView> m_xList; +}; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/gui/dp_gui_dialog2.cxx b/desktop/source/deployment/gui/dp_gui_dialog2.cxx new file mode 100644 index 0000000000..03885b1619 --- /dev/null +++ b/desktop/source/deployment/gui/dp_gui_dialog2.cxx @@ -0,0 +1,1403 @@ +/* -*- 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_extensions.h> + +#include <strings.hrc> +#include <helpids.h> + +#include "dp_gui.h" +#include "dp_gui_dialog2.hxx" +#include "dp_gui_extlistbox.hxx" +#include <dp_shared.hxx> +#include "dp_gui_theextmgr.hxx" +#include "dp_gui_extensioncmdqueue.hxx" +#include <dp_misc.h> +#include <dp_update.hxx> +#include <dp_identifier.hxx> + +#include <fpicker/strings.hrc> + +#include <utility> +#include <vcl/commandevent.hxx> +#include <vcl/svapp.hxx> + +#include <osl/mutex.hxx> +#include <sal/log.hxx> +#include <rtl/ustrbuf.hxx> + +#include <svtools/restartdialog.hxx> + +#include <sfx2/filedlghelper.hxx> +#include <sfx2/sfxdlg.hxx> + +#include <comphelper/anytostring.hxx> +#include <cppuhelper/exc_hlp.hxx> +#include <cppuhelper/supportsservice.hxx> + +#include <comphelper/processfactory.hxx> +#include <comphelper/diagnose_ex.hxx> +#include <unotools/configmgr.hxx> + +#include <com/sun/star/deployment/DeploymentException.hpp> +#include <com/sun/star/lang/IllegalArgumentException.hpp> +#include <com/sun/star/system/SystemShellExecuteFlags.hpp> +#include <com/sun/star/system/SystemShellExecute.hpp> + +#include <com/sun/star/ui/dialogs/ExecutableDialogResults.hpp> +#include <com/sun/star/ui/dialogs/TemplateDescription.hpp> +#include <com/sun/star/ui/dialogs/XFilePicker3.hpp> + +#include <officecfg/Office/ExtensionManager.hxx> + +#include <map> +#include <memory> +#include <vector> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::system; + + +namespace dp_gui { + +constexpr OUStringLiteral USER_PACKAGE_MANAGER = u"user"; +constexpr OUString SHARED_PACKAGE_MANAGER = u"shared"_ustr; +constexpr OUStringLiteral BUNDLED_PACKAGE_MANAGER = u"bundled"; + +// ExtBoxWithBtns_Impl +class ExtBoxWithBtns_Impl : public ExtensionBox_Impl +{ + bool m_bInterfaceLocked; + + ExtMgrDialog* m_pParent; + + void SetButtonStatus( const TEntry_Impl& rEntry ); + OUString ShowPopupMenu( const Point &rPos, const tools::Long nPos ); + +public: + explicit ExtBoxWithBtns_Impl(std::unique_ptr<weld::ScrolledWindow> xScroll); + + void InitFromDialog(ExtMgrDialog *pParentDialog); + + virtual bool MouseButtonDown( const MouseEvent& rMEvt ) override; + virtual bool Command( const CommandEvent& rCEvt ) override; + + virtual void RecalcAll() override; + virtual void selectEntry( const tools::Long nPos ) override; + + void enableButtons( bool bEnable ); +}; + +ExtBoxWithBtns_Impl::ExtBoxWithBtns_Impl(std::unique_ptr<weld::ScrolledWindow> xScroll) + : ExtensionBox_Impl(std::move(xScroll)) + , m_bInterfaceLocked(false) + , m_pParent(nullptr) +{ +} + +void ExtBoxWithBtns_Impl::InitFromDialog(ExtMgrDialog *pParentDialog) +{ + setExtensionManager(pParentDialog->getExtensionManager()); + + m_pParent = pParentDialog; +} + +void ExtBoxWithBtns_Impl::RecalcAll() +{ + const sal_Int32 nActive = getSelIndex(); + + if ( nActive != ExtensionBox_Impl::ENTRY_NOTFOUND ) + { + SetButtonStatus( GetEntryData( nActive) ); + } + else + { + m_pParent->enableOptionsButton( false ); + m_pParent->enableRemoveButton( false ); + m_pParent->enableEnableButton( false ); + } + + ExtensionBox_Impl::RecalcAll(); +} + + +//This function may be called with nPos < 0 +void ExtBoxWithBtns_Impl::selectEntry( const tools::Long nPos ) +{ + if ( HasActive() && ( nPos == getSelIndex() ) ) + return; + + ExtensionBox_Impl::selectEntry( nPos ); +} + +void ExtBoxWithBtns_Impl::SetButtonStatus(const TEntry_Impl& rEntry) +{ + bool bShowOptionBtn = true; + + rEntry->m_bHasButtons = false; + if ( ( rEntry->m_eState == REGISTERED ) || ( rEntry->m_eState == NOT_AVAILABLE ) ) + { + m_pParent->enableButtontoEnable( false ); + } + else + { + m_pParent->enableButtontoEnable( true ); + bShowOptionBtn = false; + } + + if ( ( !rEntry->m_bUser || ( rEntry->m_eState == NOT_AVAILABLE ) || rEntry->m_bMissingDeps ) + && !rEntry->m_bMissingLic ) + { + m_pParent->enableEnableButton( false ); + } + else + { + m_pParent->enableEnableButton( !rEntry->m_bLocked ); + rEntry->m_bHasButtons = true; + } + + if ( rEntry->m_bHasOptions && bShowOptionBtn ) + { + m_pParent->enableOptionsButton( true ); + rEntry->m_bHasButtons = true; + } + else + { + m_pParent->enableOptionsButton( false ); + } + + if ( rEntry->m_bUser || rEntry->m_bShared ) + { + m_pParent->enableRemoveButton( !rEntry->m_bLocked ); + rEntry->m_bHasButtons = true; + } + else + { + m_pParent->enableRemoveButton( false ); + } +} + +bool ExtBoxWithBtns_Impl::Command(const CommandEvent& rCEvt) +{ + if (rCEvt.GetCommand() != CommandEventId::ContextMenu) + return ExtensionBox_Impl::Command(rCEvt); + + const Point aMousePos(rCEvt.GetMousePosPixel()); + const auto nPos = PointToPos(aMousePos); + OUString sCommand = ShowPopupMenu(aMousePos, nPos); + + if (sCommand == "CMD_ENABLE") + m_pParent->enablePackage( GetEntryData( nPos )->m_xPackage, true ); + else if (sCommand == "CMD_DISABLE") + m_pParent->enablePackage( GetEntryData( nPos )->m_xPackage, false ); + else if (sCommand == "CMD_UPDATE") + m_pParent->updatePackage( GetEntryData( nPos )->m_xPackage ); + else if (sCommand == "CMD_REMOVE") + m_pParent->removePackage( GetEntryData( nPos )->m_xPackage ); + else if (sCommand == "CMD_SHOW_LICENSE") + { + m_pParent->incBusy(); + ShowLicenseDialog aLicenseDlg(m_pParent->getDialog(), GetEntryData(nPos)->m_xPackage); + aLicenseDlg.run(); + m_pParent->decBusy(); + } + + return true; +} + +OUString ExtBoxWithBtns_Impl::ShowPopupMenu( const Point & rPos, const tools::Long nPos ) +{ + if ( nPos >= static_cast<tools::Long>(getItemCount()) ) + return "CMD_NONE"; + + std::unique_ptr<weld::Builder> xBuilder(Application::CreateBuilder(nullptr, "desktop/ui/extensionmenu.ui")); + std::unique_ptr<weld::Menu> xPopup(xBuilder->weld_menu("menu")); + +#if ENABLE_EXTENSION_UPDATE + xPopup->append("CMD_UPDATE", DpResId( RID_CTX_ITEM_CHECK_UPDATE ) ); +#endif + + if ( ! GetEntryData( nPos )->m_bLocked ) + { + if ( GetEntryData( nPos )->m_bUser ) + { + if ( GetEntryData( nPos )->m_eState == REGISTERED ) + xPopup->append("CMD_DISABLE", DpResId(RID_CTX_ITEM_DISABLE)); + else if ( GetEntryData( nPos )->m_eState != NOT_AVAILABLE ) + xPopup->append("CMD_ENABLE", DpResId(RID_CTX_ITEM_ENABLE)); + } + if (!officecfg::Office::ExtensionManager::ExtensionSecurity::DisableExtensionRemoval::get()) + { + xPopup->append("CMD_REMOVE", DpResId(RID_CTX_ITEM_REMOVE)); + } + } + + if ( !GetEntryData( nPos )->m_sLicenseText.isEmpty() ) + xPopup->append("CMD_SHOW_LICENSE", DpResId(RID_STR_SHOW_LICENSE_CMD)); + + return xPopup->popup_at_rect(GetDrawingArea(), tools::Rectangle(rPos, Size(1, 1))); +} + +bool ExtBoxWithBtns_Impl::MouseButtonDown( const MouseEvent& rMEvt ) +{ + if (m_bInterfaceLocked) + return false; + return ExtensionBox_Impl::MouseButtonDown(rMEvt); +} + +void ExtBoxWithBtns_Impl::enableButtons( bool bEnable ) +{ + m_bInterfaceLocked = ! bEnable; + + if ( bEnable ) + { + sal_Int32 nIndex = getSelIndex(); + if ( nIndex != ExtensionBox_Impl::ENTRY_NOTFOUND ) + SetButtonStatus( GetEntryData( nIndex ) ); + } + else + { + m_pParent->enableEnableButton( false ); + m_pParent->enableOptionsButton( false ); + m_pParent->enableRemoveButton( false ); + } +} + +// DialogHelper + +DialogHelper::DialogHelper(const uno::Reference< uno::XComponentContext > &xContext, + weld::Window* pWindow) + : m_pWindow(pWindow) + , m_nEventID(nullptr) +{ + m_xContext = xContext; +} + +DialogHelper::~DialogHelper() +{ + if ( m_nEventID ) + Application::RemoveUserEvent( m_nEventID ); +} + + +bool DialogHelper::IsSharedPkgMgr( const uno::Reference< deployment::XPackage > &xPackage ) +{ + return xPackage->getRepositoryName() == SHARED_PACKAGE_MANAGER; +} + +bool DialogHelper::continueOnSharedExtension( const uno::Reference< deployment::XPackage > &xPackage, + weld::Widget* pParent, + TranslateId pResID, + bool &bHadWarning ) +{ + if ( !bHadWarning && IsSharedPkgMgr( xPackage ) ) + { + const SolarMutexGuard guard; + incBusy(); + std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(pParent, + VclMessageType::Warning, VclButtonsType::OkCancel, DpResId(pResID))); + bHadWarning = true; + + bool bRet = RET_OK == xBox->run(); + xBox.reset(); + decBusy(); + return bRet; + } + else + return true; +} + +void DialogHelper::openWebBrowser(const OUString& sURL, const OUString& sTitle) +{ + if ( sURL.isEmpty() ) // Nothing to do, when the URL is empty + return; + + try + { + uno::Reference< XSystemShellExecute > xSystemShellExecute( + SystemShellExecute::create(m_xContext)); + //throws css::lang::IllegalArgumentException, css::system::SystemShellExecuteException + xSystemShellExecute->execute( sURL, OUString(), SystemShellExecuteFlags::URIS_ONLY ); + } + catch ( const uno::Exception& ) + { + uno::Any exc( ::cppu::getCaughtException() ); + OUString msg( ::comphelper::anyToString( exc ) ); + const SolarMutexGuard guard; + incBusy(); + std::unique_ptr<weld::MessageDialog> xErrorBox(Application::CreateMessageDialog(getFrameWeld(), + VclMessageType::Warning, VclButtonsType::Ok, msg)); + xErrorBox->set_title(sTitle); + xErrorBox->run(); + xErrorBox.reset(); + decBusy(); + } +} + +bool DialogHelper::installExtensionWarn(std::u16string_view rExtensionName) +{ + const SolarMutexGuard guard; + + // Check if extension installation is disabled in the expert configurations + if (officecfg::Office::ExtensionManager::ExtensionSecurity::DisableExtensionInstallation::get()) + { + incBusy(); + std::unique_ptr<weld::MessageDialog> xWarnBox(Application::CreateMessageDialog(getFrameWeld(), + VclMessageType::Warning, VclButtonsType::Ok, + DpResId(RID_STR_WARNING_INSTALL_EXTENSION_DISABLED))); + xWarnBox->run(); + xWarnBox.reset(); + decBusy(); + + return false; + } + + incBusy(); + std::unique_ptr<weld::MessageDialog> xInfoBox(Application::CreateMessageDialog(getFrameWeld(), + VclMessageType::Warning, VclButtonsType::OkCancel, + DpResId(RID_STR_WARNING_INSTALL_EXTENSION))); + OUString sText(xInfoBox->get_primary_text()); + sText = sText.replaceAll("%NAME", rExtensionName); + xInfoBox->set_primary_text(sText); + + bool bRet = RET_OK == xInfoBox->run(); + xInfoBox.reset(); + decBusy(); + return bRet; +} + +bool DialogHelper::installForAllUsers(bool &bInstallForAll) +{ + const SolarMutexGuard guard; + incBusy(); + std::unique_ptr<weld::Builder> xBuilder(Application::CreateBuilder(getFrameWeld(), "desktop/ui/installforalldialog.ui")); + std::unique_ptr<weld::MessageDialog> xQuery(xBuilder->weld_message_dialog("InstallForAllDialog")); + short nRet = xQuery->run(); + xQuery.reset(); + decBusy(); + if (nRet == RET_CANCEL) + return false; + + bInstallForAll = ( nRet == RET_NO ); + return true; +} + +void DialogHelper::PostUserEvent( const Link<void*,void>& rLink, void* pCaller ) +{ + if ( m_nEventID ) + Application::RemoveUserEvent( m_nEventID ); + + m_nEventID = Application::PostUserEvent(rLink, pCaller); +} + +// ExtMgrDialog +ExtMgrDialog::ExtMgrDialog(weld::Window *pParent, TheExtensionManager *pManager) + : GenericDialogController(pParent, "desktop/ui/extensionmanager.ui", "ExtensionManagerDialog") + , DialogHelper(pManager->getContext(), m_xDialog.get()) + , m_sAddPackages(DpResId(RID_STR_ADD_PACKAGES)) + , m_bHasProgress(false) + , m_bProgressChanged(false) + , m_bStartProgress(false) + , m_bStopProgress(false) + , m_bEnableWarning(false) + , m_bDisableWarning(false) + , m_bDeleteWarning(false) + , m_bClosed(false) + , m_nProgress(0) + , m_aIdle( "ExtMgrDialog m_aIdle TimeOutHdl" ) + , m_pManager(pManager) + , m_xExtensionBox(new ExtBoxWithBtns_Impl(m_xBuilder->weld_scrolled_window("scroll", true))) + , m_xExtensionBoxWnd(new weld::CustomWeld(*m_xBuilder, "extensions", *m_xExtensionBox)) + , m_xOptionsBtn(m_xBuilder->weld_button("optionsbtn")) + , m_xAddBtn(m_xBuilder->weld_button("addbtn")) + , m_xRemoveBtn(m_xBuilder->weld_button("removebtn")) + , m_xEnableBtn(m_xBuilder->weld_button("enablebtn")) + , m_xUpdateBtn(m_xBuilder->weld_button("updatebtn")) + , m_xCloseBtn(m_xBuilder->weld_button("close")) + , m_xBundledCbx(m_xBuilder->weld_check_button("bundled")) + , m_xSharedCbx(m_xBuilder->weld_check_button("shared")) + , m_xUserCbx(m_xBuilder->weld_check_button("user")) + , m_xGetExtensions(m_xBuilder->weld_link_button("getextensions")) + , m_xProgressText(m_xBuilder->weld_label("progressft")) + , m_xProgressBar(m_xBuilder->weld_progress_bar("progressbar")) + , m_xCancelBtn(m_xBuilder->weld_button("cancel")) + , m_xSearchEntry(m_xBuilder->weld_entry("search")) +{ + m_xExtensionBox->InitFromDialog(this); + + m_xEnableBtn->set_help_id(HID_EXTENSION_MANAGER_LISTBOX_ENABLE); + + m_xOptionsBtn->connect_clicked( LINK( this, ExtMgrDialog, HandleOptionsBtn ) ); + m_xAddBtn->connect_clicked( LINK( this, ExtMgrDialog, HandleAddBtn ) ); + m_xRemoveBtn->connect_clicked( LINK( this, ExtMgrDialog, HandleRemoveBtn ) ); + m_xEnableBtn->connect_clicked( LINK( this, ExtMgrDialog, HandleEnableBtn ) ); + m_xCloseBtn->connect_clicked( LINK( this, ExtMgrDialog, HandleCloseBtn ) ); + + m_xCancelBtn->connect_clicked( LINK( this, ExtMgrDialog, HandleCancelBtn ) ); + + m_xBundledCbx->connect_toggled( LINK( this, ExtMgrDialog, HandleExtTypeCbx ) ); + m_xSharedCbx->connect_toggled( LINK( this, ExtMgrDialog, HandleExtTypeCbx ) ); + m_xUserCbx->connect_toggled( LINK( this, ExtMgrDialog, HandleExtTypeCbx ) ); + + m_xSearchEntry->connect_changed( LINK( this, ExtMgrDialog, HandleSearch ) ); + + m_xBundledCbx->set_active(true); + m_xSharedCbx->set_active(true); + m_xUserCbx->set_active(true); + + m_xProgressBar->hide(); + +#if ENABLE_EXTENSION_UPDATE + m_xUpdateBtn->connect_clicked( LINK( this, ExtMgrDialog, HandleUpdateBtn ) ); + m_xUpdateBtn->set_sensitive(false); +#else + m_xUpdateBtn->hide(); +#endif + + if (officecfg::Office::ExtensionManager::ExtensionSecurity::DisableExtensionInstallation::get()) + { + m_xAddBtn->set_sensitive(false); + m_xAddBtn->set_tooltip_text(DpResId(RID_STR_WARNING_INSTALL_EXTENSION_DISABLED)); + } + if (officecfg::Office::ExtensionManager::ExtensionSecurity::DisableExtensionRemoval::get()) + { + m_xRemoveBtn->set_sensitive(false); + m_xRemoveBtn->set_tooltip_text(DpResId(RID_STR_WARNING_REMOVE_EXTENSION_DISABLED)); + } + + m_aIdle.SetPriority(TaskPriority::LOWEST); + m_aIdle.SetInvokeHandler( LINK( this, ExtMgrDialog, TimeOutHdl ) ); +} + +ExtMgrDialog::~ExtMgrDialog() +{ + m_aIdle.Stop(); +} + +void ExtMgrDialog::setGetExtensionsURL( const OUString &rURL ) +{ + m_xGetExtensions->set_uri( rURL ); +} + +void ExtMgrDialog::addPackageToList( const uno::Reference< deployment::XPackage > &xPackage, + bool bLicenseMissing ) +{ + const SolarMutexGuard aGuard; + m_xUpdateBtn->set_sensitive(true); + + bool bSearchMatch = m_xSearchEntry->get_text().isEmpty(); + if (!m_xSearchEntry->get_text().isEmpty() + && xPackage->getDisplayName().toAsciiLowerCase().indexOf( + m_xSearchEntry->get_text().toAsciiLowerCase()) + >= 0) + { + bSearchMatch = true; + } + + if (!bSearchMatch) + return; + + if (m_xBundledCbx->get_active() && (xPackage->getRepositoryName() == BUNDLED_PACKAGE_MANAGER) ) + { + m_xExtensionBox->addEntry( xPackage, bLicenseMissing ); + } + else if (m_xSharedCbx->get_active() && (xPackage->getRepositoryName() == SHARED_PACKAGE_MANAGER) ) + { + m_xExtensionBox->addEntry( xPackage, bLicenseMissing ); + } + else if (m_xUserCbx->get_active() && (xPackage->getRepositoryName() == USER_PACKAGE_MANAGER )) + { + m_xExtensionBox->addEntry( xPackage, bLicenseMissing ); + } +} + +void ExtMgrDialog::updateList() +{ + // re-creates the list of packages with addEntry selecting the packages + prepareChecking(); + m_pManager->createPackageList(); + checkEntries(); +} + +void ExtMgrDialog::prepareChecking() +{ + m_xExtensionBox->prepareChecking(); +} + +void ExtMgrDialog::checkEntries() +{ + const SolarMutexGuard guard; + m_xExtensionBox->checkEntries(); +} + +bool ExtMgrDialog::removeExtensionWarn(std::u16string_view rExtensionName) +{ + const SolarMutexGuard guard; + incBusy(); + std::unique_ptr<weld::MessageDialog> xInfoBox(Application::CreateMessageDialog(m_xDialog.get(), + VclMessageType::Warning, VclButtonsType::OkCancel, + DpResId(RID_STR_WARNING_REMOVE_EXTENSION))); + + OUString sText(xInfoBox->get_primary_text()); + sText = sText.replaceAll("%NAME", rExtensionName); + xInfoBox->set_primary_text(sText); + + bool bRet = RET_OK == xInfoBox->run(); + xInfoBox.reset(); + decBusy(); + + return bRet; +} + +void ExtMgrDialog::enablePackage( const uno::Reference< deployment::XPackage > &xPackage, + bool bEnable ) +{ + if ( !xPackage.is() ) + return; + + if ( bEnable ) + { + if (!continueOnSharedExtension(xPackage, m_xDialog.get(), RID_STR_WARNING_ENABLE_SHARED_EXTENSION, m_bEnableWarning)) + return; + } + else + { + if (!continueOnSharedExtension(xPackage, m_xDialog.get(), RID_STR_WARNING_DISABLE_SHARED_EXTENSION, m_bDisableWarning)) + return; + } + + m_pManager->getCmdQueue()->enableExtension( xPackage, bEnable ); +} + + +void ExtMgrDialog::removePackage( const uno::Reference< deployment::XPackage > &xPackage ) +{ + if ( !xPackage.is() ) + return; + + if ( !IsSharedPkgMgr( xPackage ) || m_bDeleteWarning ) + { + if ( ! removeExtensionWarn( xPackage->getDisplayName() ) ) + return; + } + + if (!continueOnSharedExtension(xPackage, m_xDialog.get(), RID_STR_WARNING_REMOVE_SHARED_EXTENSION, m_bDeleteWarning)) + return; + + m_pManager->getCmdQueue()->removeExtension( xPackage ); +} + + +void ExtMgrDialog::updatePackage( const uno::Reference< deployment::XPackage > &xPackage ) +{ + if ( !xPackage.is() ) + return; + + // get the extension with highest version + uno::Sequence<uno::Reference<deployment::XPackage> > seqExtensions = + m_pManager->getExtensionManager()->getExtensionsWithSameIdentifier( + dp_misc::getIdentifier(xPackage), xPackage->getName(), uno::Reference<ucb::XCommandEnvironment>()); + uno::Reference<deployment::XPackage> extension = + dp_misc::getExtensionWithHighestVersion(seqExtensions); + OSL_ASSERT(extension.is()); + std::vector< css::uno::Reference< css::deployment::XPackage > > vEntries { extension }; + + m_pManager->getCmdQueue()->checkForUpdates( std::move(vEntries) ); +} + + +bool ExtMgrDialog::acceptLicense( const uno::Reference< deployment::XPackage > &xPackage ) +{ + if ( !xPackage.is() ) + return false; + + m_pManager->getCmdQueue()->acceptLicense( xPackage ); + + return true; +} + + +uno::Sequence< OUString > ExtMgrDialog::raiseAddPicker() +{ + sfx2::FileDialogHelper aDlgHelper(ui::dialogs::TemplateDescription::FILEOPEN_SIMPLE, FileDialogFlags::NONE, m_xDialog.get()); + aDlgHelper.SetContext(sfx2::FileDialogHelper::ExtensionManager); + const uno::Reference<ui::dialogs::XFilePicker3>& xFilePicker = aDlgHelper.GetFilePicker(); + xFilePicker->setTitle( m_sAddPackages ); + + // collect and set filter list: + typedef std::map< OUString, OUString > t_string2string; + t_string2string title2filter; + OUStringBuffer supportedFilters; + + const uno::Sequence< uno::Reference< deployment::XPackageTypeInfo > > packageTypes( + m_pManager->getExtensionManager()->getSupportedPackageTypes() ); + + for ( uno::Reference< deployment::XPackageTypeInfo > const & xPackageType : packageTypes ) + { + const OUString filter( xPackageType->getFileFilter() ); + if (!filter.isEmpty()) + { + const OUString title( xPackageType->getShortDescription() ); + const std::pair< t_string2string::iterator, bool > insertion( + title2filter.emplace( title, filter ) ); + if (!supportedFilters.isEmpty()) + supportedFilters.append(';'); + supportedFilters.append(filter); + if ( ! insertion.second ) + { // already existing, append extensions: + insertion.first->second = insertion.first->second + + ";" + filter; + } + } + } + + static const OUString StrAllFiles = []() + { + const SolarMutexGuard guard; + std::locale loc = Translate::Create("fps"); + return Translate::get(STR_FILTERNAME_ALL, loc); + }(); + + // All files at top: + xFilePicker->appendFilter( StrAllFiles, "*.*" ); + xFilePicker->appendFilter( DpResId(RID_STR_ALL_SUPPORTED), supportedFilters.makeStringAndClear() ); + // then supported ones: + for (auto const& elem : title2filter) + { + try + { + xFilePicker->appendFilter( elem.first, elem.second ); + } + catch (const lang::IllegalArgumentException &) + { + TOOLS_WARN_EXCEPTION( "desktop", "" ); + } + } + xFilePicker->setCurrentFilter( DpResId(RID_STR_ALL_SUPPORTED) ); + + if ( xFilePicker->execute() != ui::dialogs::ExecutableDialogResults::OK ) + return uno::Sequence<OUString>(); // cancelled + + uno::Sequence< OUString > files( xFilePicker->getSelectedFiles() ); + OSL_ASSERT( files.hasElements() ); + return files; +} + +void ExtMgrDialog::enableOptionsButton( bool bEnable ) +{ + m_xOptionsBtn->set_sensitive( bEnable ); +} + +void ExtMgrDialog::enableRemoveButton( bool bEnable ) +{ + m_xRemoveBtn->set_sensitive( bEnable && !officecfg::Office::ExtensionManager::ExtensionSecurity::DisableExtensionRemoval::get()); + + if (officecfg::Office::ExtensionManager::ExtensionSecurity::DisableExtensionRemoval::get()) + { + m_xRemoveBtn->set_tooltip_text(DpResId(RID_STR_WARNING_REMOVE_EXTENSION_DISABLED)); + } + else + { + m_xRemoveBtn->set_tooltip_text(""); + } +} + +void ExtMgrDialog::enableEnableButton( bool bEnable ) +{ + m_xEnableBtn->set_sensitive( bEnable ); +} + +void ExtMgrDialog::enableButtontoEnable( bool bEnable ) +{ + if (bEnable) + { + m_xEnableBtn->set_label( DpResId( RID_CTX_ITEM_ENABLE ) ); + m_xEnableBtn->set_help_id( HID_EXTENSION_MANAGER_LISTBOX_ENABLE ); + } + else + { + m_xEnableBtn->set_label( DpResId( RID_CTX_ITEM_DISABLE ) ); + m_xEnableBtn->set_help_id( HID_EXTENSION_MANAGER_LISTBOX_DISABLE ); + } +} + +IMPL_LINK_NOARG(ExtMgrDialog, HandleCancelBtn, weld::Button&, void) +{ + if ( m_xAbortChannel.is() ) + { + try + { + m_xAbortChannel->sendAbort(); + } + catch ( const uno::RuntimeException & ) + { + TOOLS_WARN_EXCEPTION( "dbaccess", "" ); + } + } +} + +IMPL_LINK_NOARG(ExtMgrDialog, HandleCloseBtn, weld::Button&, void) +{ + bool bCallClose = true; + + //only suggest restart if modified and this is the first close attempt + if (!m_bClosed && m_pManager->isModified()) + { + m_pManager->clearModified(); + + //only suggest restart if we're actually running, e.g. not from standalone unopkg gui + if (dp_misc::office_is_running()) + { + SolarMutexGuard aGuard; + bCallClose = !::svtools::executeRestartDialog(comphelper::getProcessComponentContext(), + m_xDialog.get(), + svtools::RESTART_REASON_EXTENSION_INSTALL); + } + } + + if (bCallClose) + m_xDialog->response(RET_CANCEL); +} + +IMPL_LINK( ExtMgrDialog, startProgress, void*, _bLockInterface, void ) +{ + std::unique_lock aGuard( m_aMutex ); + bool bLockInterface = static_cast<bool>(_bLockInterface); + + if ( m_bStartProgress && !m_bHasProgress ) + m_aIdle.Start(); + + if ( m_bStopProgress ) + { + if ( m_xProgressBar->get_visible() ) + m_xProgressBar->set_percentage( 100 ); + m_xAbortChannel.clear(); + + SAL_INFO( "desktop.deployment", " startProgress handler: stop" ); + } + else + { + SAL_INFO( "desktop.deployment", " startProgress handler: start" ); + } + + m_xCancelBtn->set_sensitive( bLockInterface ); + m_xAddBtn->set_sensitive( !bLockInterface && !officecfg::Office::ExtensionManager::ExtensionSecurity::DisableExtensionInstallation::get()); + if (officecfg::Office::ExtensionManager::ExtensionSecurity::DisableExtensionInstallation::get()) + { + m_xAddBtn->set_tooltip_text(DpResId(RID_STR_WARNING_INSTALL_EXTENSION_DISABLED)); + } + else + { + m_xAddBtn->set_tooltip_text(""); + } + + m_xUpdateBtn->set_sensitive( !bLockInterface && m_xExtensionBox->getItemCount() ); + m_xExtensionBox->enableButtons( !bLockInterface ); + + clearEventID(); +} + + +void ExtMgrDialog::showProgress( bool _bStart ) +{ + std::unique_lock aGuard( m_aMutex ); + + bool bStart = _bStart; + + if ( bStart ) + { + m_nProgress = 0; + m_bStartProgress = true; + SAL_INFO( "desktop.deployment", "showProgress start" ); + } + else + { + m_nProgress = 100; + m_bStopProgress = true; + SAL_INFO( "desktop.deployment", "showProgress stop!" ); + } + + DialogHelper::PostUserEvent( LINK( this, ExtMgrDialog, startProgress ), reinterpret_cast<void*>(bStart) ); + m_aIdle.Start(); +} + + +void ExtMgrDialog::updateProgress( const tools::Long nProgress ) +{ + if ( m_nProgress != nProgress ) + { + std::unique_lock aGuard( m_aMutex ); + m_nProgress = nProgress; + m_aIdle.Start(); + } +} + + +void ExtMgrDialog::updateProgress( const OUString &rText, + const uno::Reference< task::XAbortChannel > &xAbortChannel) +{ + std::unique_lock aGuard( m_aMutex ); + + m_xAbortChannel = xAbortChannel; + m_sProgressText = rText; + m_bProgressChanged = true; + m_aIdle.Start(); +} + + +void ExtMgrDialog::updatePackageInfo( const uno::Reference< deployment::XPackage > &xPackage ) +{ + const SolarMutexGuard aGuard; + m_xExtensionBox->updateEntry( xPackage ); +} + +IMPL_LINK_NOARG(ExtMgrDialog, HandleOptionsBtn, weld::Button&, void) +{ + const sal_Int32 nActive = m_xExtensionBox->getSelIndex(); + + if ( nActive != ExtensionBox_Impl::ENTRY_NOTFOUND ) + { + SfxAbstractDialogFactory* pFact = SfxAbstractDialogFactory::Create(); + + OUString sExtensionId = m_xExtensionBox->GetEntryData( nActive )->m_xPackage->getIdentifier().Value; + ScopedVclPtr<VclAbstractDialog> pDlg(pFact->CreateOptionsDialog(m_xDialog.get(), sExtensionId)); + + pDlg->Execute(); + } +} + +IMPL_LINK_NOARG(ExtMgrDialog, HandleAddBtn, weld::Button&, void) +{ + incBusy(); + + uno::Sequence< OUString > aFileList = raiseAddPicker(); + + if ( aFileList.hasElements() ) + { + m_pManager->installPackage( aFileList[0] ); + } + + decBusy(); +} + +IMPL_LINK_NOARG(ExtMgrDialog, HandleRemoveBtn, weld::Button&, void) +{ + const sal_Int32 nActive = m_xExtensionBox->getSelIndex(); + + if ( nActive != ExtensionBox_Impl::ENTRY_NOTFOUND ) + { + TEntry_Impl pEntry = m_xExtensionBox->GetEntryData( nActive ); + removePackage( pEntry->m_xPackage ); + } +} + +IMPL_LINK_NOARG(ExtMgrDialog, HandleEnableBtn, weld::Button&, void) +{ + const sal_Int32 nActive = m_xExtensionBox->getSelIndex(); + + if ( nActive != ExtensionBox_Impl::ENTRY_NOTFOUND ) + { + TEntry_Impl pEntry = m_xExtensionBox->GetEntryData( nActive ); + + if ( pEntry->m_bMissingLic ) + acceptLicense( pEntry->m_xPackage ); + else + { + const bool bEnable( pEntry->m_eState != REGISTERED ); + enablePackage( pEntry->m_xPackage, bEnable ); + } + } +} + +IMPL_LINK_NOARG(ExtMgrDialog, HandleExtTypeCbx, weld::Toggleable&, void) +{ + updateList(); +} + +IMPL_LINK_NOARG(ExtMgrDialog, HandleSearch, weld::Entry&, void) +{ + updateList(); +} + +IMPL_LINK_NOARG(ExtMgrDialog, HandleUpdateBtn, weld::Button&, void) +{ +#if ENABLE_EXTENSION_UPDATE + m_pManager->checkUpdates(); +#else + (void) this; +#endif +} + +IMPL_LINK_NOARG(ExtMgrDialog, TimeOutHdl, Timer *, void) +{ + if ( m_bStopProgress ) + { + m_bHasProgress = false; + m_bStopProgress = false; + m_xProgressText->hide(); + m_xProgressBar->hide(); + m_xCancelBtn->hide(); + } + else + { + if ( m_bProgressChanged ) + { + m_bProgressChanged = false; + m_xProgressText->set_label(m_sProgressText); + } + + if ( m_bStartProgress ) + { + m_bStartProgress = false; + m_bHasProgress = true; + m_xProgressBar->show(); + m_xProgressText->show(); + m_xCancelBtn->set_sensitive(true); + m_xCancelBtn->show(); + } + + if ( m_xProgressBar->get_visible() ) + m_xProgressBar->set_percentage( static_cast<sal_uInt16>(m_nProgress) ); + } +} + +void ExtMgrDialog::Close() +{ + m_pManager->terminateDialog(); + m_bClosed = true; +} + +//UpdateRequiredDialog +UpdateRequiredDialog::UpdateRequiredDialog(weld::Window *pParent, TheExtensionManager *pManager) + : GenericDialogController(pParent, "desktop/ui/updaterequireddialog.ui", "UpdateRequiredDialog") + , DialogHelper(pManager->getContext(), m_xDialog.get()) + , m_sCloseText(DpResId(RID_STR_CLOSE_BTN)) + , m_bHasProgress(false) + , m_bProgressChanged(false) + , m_bStartProgress(false) + , m_bStopProgress(false) + , m_bHasLockedEntries(false) + , m_nProgress(0) + , m_aIdle( "UpdateRequiredDialog m_aIdle TimeOutHdl" ) + , m_pManager(pManager) + , m_xExtensionBox(new ExtensionBox_Impl(m_xBuilder->weld_scrolled_window("scroll", true))) + , m_xExtensionBoxWnd(new weld::CustomWeld(*m_xBuilder, "extensions", *m_xExtensionBox)) + , m_xUpdateNeeded(m_xBuilder->weld_label("updatelabel")) + , m_xUpdateBtn(m_xBuilder->weld_button("ok")) + , m_xCloseBtn(m_xBuilder->weld_button("disable")) + , m_xCancelBtn(m_xBuilder->weld_button("cancel")) + , m_xProgressText(m_xBuilder->weld_label("progresslabel")) + , m_xProgressBar(m_xBuilder->weld_progress_bar("progress")) +{ + m_xExtensionBox->setExtensionManager(pManager); + + m_xUpdateBtn->connect_clicked( LINK( this, UpdateRequiredDialog, HandleUpdateBtn ) ); + m_xCloseBtn->connect_clicked( LINK( this, UpdateRequiredDialog, HandleCloseBtn ) ); + m_xCancelBtn->connect_clicked( LINK( this, UpdateRequiredDialog, HandleCancelBtn ) ); + + OUString aText = m_xUpdateNeeded->get_label(); + aText = aText.replaceAll( + "%PRODUCTNAME", utl::ConfigManager::getProductName()); + m_xUpdateNeeded->set_label(aText); + + m_xProgressBar->hide(); + m_xUpdateBtn->set_sensitive( false ); + m_xCloseBtn->grab_focus(); + + m_aIdle.SetPriority( TaskPriority::LOWEST ); + m_aIdle.SetInvokeHandler( LINK( this, UpdateRequiredDialog, TimeOutHdl ) ); +} + +UpdateRequiredDialog::~UpdateRequiredDialog() +{ + m_aIdle.Stop(); +} + +void UpdateRequiredDialog::addPackageToList( const uno::Reference< deployment::XPackage > &xPackage, + bool bLicenseMissing ) +{ + // We will only add entries to the list with unsatisfied dependencies + if ( !bLicenseMissing && !checkDependencies( xPackage ) ) + { + m_bHasLockedEntries |= m_pManager->isReadOnly( xPackage ); + const SolarMutexGuard aGuard; + m_xUpdateBtn->set_sensitive(true); + m_xExtensionBox->addEntry( xPackage ); + } +} + + +void UpdateRequiredDialog::prepareChecking() +{ + m_xExtensionBox->prepareChecking(); +} + + +void UpdateRequiredDialog::checkEntries() +{ + const SolarMutexGuard guard; + m_xExtensionBox->checkEntries(); + + if ( ! hasActiveEntries() ) + { + m_xCloseBtn->set_label( m_sCloseText ); + m_xCloseBtn->grab_focus(); + } +} + + +IMPL_LINK_NOARG(UpdateRequiredDialog, HandleCancelBtn, weld::Button&, void) +{ + if ( m_xAbortChannel.is() ) + { + try + { + m_xAbortChannel->sendAbort(); + } + catch ( const uno::RuntimeException & ) + { + TOOLS_WARN_EXCEPTION( "desktop", "" ); + } + } +} + + +IMPL_LINK( UpdateRequiredDialog, startProgress, void*, _bLockInterface, void ) +{ + std::unique_lock aGuard( m_aMutex ); + bool bLockInterface = static_cast<bool>(_bLockInterface); + + if ( m_bStartProgress && !m_bHasProgress ) + m_aIdle.Start(); + + if ( m_bStopProgress ) + { + if ( m_xProgressBar->get_visible() ) + m_xProgressBar->set_percentage( 100 ); + m_xAbortChannel.clear(); + SAL_INFO( "desktop.deployment", " startProgress handler: stop" ); + } + else + { + SAL_INFO( "desktop.deployment", " startProgress handler: start" ); + } + + m_xCancelBtn->set_sensitive( bLockInterface ); + m_xUpdateBtn->set_sensitive( false ); + clearEventID(); +} + + +void UpdateRequiredDialog::showProgress( bool _bStart ) +{ + std::unique_lock aGuard( m_aMutex ); + + bool bStart = _bStart; + + if ( bStart ) + { + m_nProgress = 0; + m_bStartProgress = true; + SAL_INFO( "desktop.deployment", "showProgress start" ); + } + else + { + m_nProgress = 100; + m_bStopProgress = true; + SAL_INFO( "desktop.deployment", "showProgress stop!" ); + } + + DialogHelper::PostUserEvent( LINK( this, UpdateRequiredDialog, startProgress ), reinterpret_cast<void*>(bStart) ); + m_aIdle.Start(); +} + + +void UpdateRequiredDialog::updateProgress( const tools::Long nProgress ) +{ + if ( m_nProgress != nProgress ) + { + std::unique_lock aGuard( m_aMutex ); + m_nProgress = nProgress; + m_aIdle.Start(); + } +} + + +void UpdateRequiredDialog::updateProgress( const OUString &rText, + const uno::Reference< task::XAbortChannel > &xAbortChannel) +{ + std::unique_lock aGuard( m_aMutex ); + + m_xAbortChannel = xAbortChannel; + m_sProgressText = rText; + m_bProgressChanged = true; + m_aIdle.Start(); +} + + +void UpdateRequiredDialog::updatePackageInfo( const uno::Reference< deployment::XPackage > &xPackage ) +{ + // We will remove all updated packages with satisfied dependencies, but + // we will show all disabled entries so the user sees the result + // of the 'disable all' button + const SolarMutexGuard aGuard; + if ( isEnabled( xPackage ) && checkDependencies( xPackage ) ) + m_xExtensionBox->removeEntry( xPackage ); + else + m_xExtensionBox->updateEntry( xPackage ); + + if ( ! hasActiveEntries() ) + { + m_xCloseBtn->set_label( m_sCloseText ); + m_xCloseBtn->grab_focus(); + } +} + + +IMPL_LINK_NOARG(UpdateRequiredDialog, HandleUpdateBtn, weld::Button&, void) +{ + std::unique_lock aGuard( m_aMutex ); + + std::vector< uno::Reference< deployment::XPackage > > vUpdateEntries; + sal_Int32 nCount = m_xExtensionBox->GetEntryCount(); + + for ( sal_Int32 i = 0; i < nCount; ++i ) + { + TEntry_Impl pEntry = m_xExtensionBox->GetEntryData( i ); + vUpdateEntries.push_back( pEntry->m_xPackage ); + } + + aGuard.unlock(); + + m_pManager->getCmdQueue()->checkForUpdates( std::move(vUpdateEntries) ); +} + + +IMPL_LINK_NOARG(UpdateRequiredDialog, HandleCloseBtn, weld::Button&, void) +{ + std::unique_lock aGuard( m_aMutex ); + + if ( !isBusy() ) + { + if ( m_bHasLockedEntries ) + m_xDialog->response(-1); + else if ( hasActiveEntries() ) + disableAllEntries(); + else + m_xDialog->response(RET_CANCEL); + } +} + + +IMPL_LINK_NOARG(UpdateRequiredDialog, TimeOutHdl, Timer *, void) +{ + if ( m_bStopProgress ) + { + m_bHasProgress = false; + m_bStopProgress = false; + m_xProgressText->hide(); + m_xProgressBar->hide(); + m_xCancelBtn->hide(); + } + else + { + if ( m_bProgressChanged ) + { + m_bProgressChanged = false; + m_xProgressText->set_label( m_sProgressText ); + } + + if ( m_bStartProgress ) + { + m_bStartProgress = false; + m_bHasProgress = true; + m_xProgressBar->show(); + m_xProgressText->show(); + m_xCancelBtn->set_sensitive(true); + m_xCancelBtn->show(); + } + + if (m_xProgressBar->get_visible()) + m_xProgressBar->set_percentage(m_nProgress); + } +} + +// VCL::Dialog +short UpdateRequiredDialog::run() +{ + //ToDo + //I believe m_bHasLockedEntries was used to prevent showing extensions which cannot + //be disabled because they are in a read only repository. However, disabling extensions + //is now always possible because the registration data of all repositories + //are in the user installation. + //Therefore all extensions could be displayed and all the handling around m_bHasLockedEntries + //could be removed. + if ( m_bHasLockedEntries ) + { + // Set other text, disable update btn, remove not shared entries from list; + m_xUpdateNeeded->set_label( DpResId( RID_STR_NO_ADMIN_PRIVILEGE ) ); + m_xCloseBtn->set_label( DpResId( RID_STR_EXIT_BTN ) ); + m_xUpdateBtn->set_sensitive( false ); + m_xExtensionBox->RemoveUnlocked(); + } + + return GenericDialogController::run(); +} + +// Check dependencies of all packages + +bool UpdateRequiredDialog::isEnabled( const uno::Reference< deployment::XPackage > &xPackage ) +{ + 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", "" ); + bRegistered = false; + } + + return bRegistered; +} + +// Checks the dependencies no matter if the extension is enabled or disabled! +bool UpdateRequiredDialog::checkDependencies( const uno::Reference< deployment::XPackage > &xPackage ) +{ + bool bDependenciesValid = false; + try { + bDependenciesValid = xPackage->checkDependencies( uno::Reference< ucb::XCommandEnvironment >() ); + } + catch ( const deployment::DeploymentException & ) {} + return bDependenciesValid; +} + + +bool UpdateRequiredDialog::hasActiveEntries() +{ + std::unique_lock aGuard( m_aMutex ); + + bool bRet = false; + tools::Long nCount = m_xExtensionBox->GetEntryCount(); + for ( tools::Long nIndex = 0; nIndex < nCount; nIndex++ ) + { + TEntry_Impl pEntry = m_xExtensionBox->GetEntryData( nIndex ); + + if ( isEnabled(pEntry->m_xPackage) && !checkDependencies( pEntry->m_xPackage ) ) + { + bRet = true; + break; + } + } + + return bRet; +} + + +void UpdateRequiredDialog::disableAllEntries() +{ + std::unique_lock aGuard( m_aMutex ); + + incBusy(); + + tools::Long nCount = m_xExtensionBox->GetEntryCount(); + for ( tools::Long nIndex = 0; nIndex < nCount; nIndex++ ) + { + TEntry_Impl pEntry = m_xExtensionBox->GetEntryData( nIndex ); + m_pManager->getCmdQueue()->enableExtension( pEntry->m_xPackage, false ); + } + + decBusy(); + + if ( ! hasActiveEntries() ) + m_xCloseBtn->set_label( m_sCloseText ); +} + +// ShowLicenseDialog +ShowLicenseDialog::ShowLicenseDialog(weld::Window* pParent, + const uno::Reference< deployment::XPackage> &xPackage) + : GenericDialogController(pParent, "desktop/ui/showlicensedialog.ui", "ShowLicenseDialog") + , m_xLicenseText(m_xBuilder->weld_text_view("textview")) +{ + m_xLicenseText->set_size_request(m_xLicenseText->get_approximate_digit_width() * 72, + m_xLicenseText->get_height_rows(21)); + m_xLicenseText->set_text(xPackage->getLicenseText()); +} + +ShowLicenseDialog::~ShowLicenseDialog() +{ +} + +// UpdateRequiredDialogService + +UpdateRequiredDialogService::UpdateRequiredDialogService( SAL_UNUSED_PARAMETER uno::Sequence< uno::Any > const&, + uno::Reference< uno::XComponentContext > xComponentContext ) + : m_xComponentContext(std::move( xComponentContext )) +{ +} + +// XServiceInfo +OUString UpdateRequiredDialogService::getImplementationName() +{ + return "com.sun.star.comp.deployment.ui.UpdateRequiredDialog"; +} + +sal_Bool UpdateRequiredDialogService::supportsService( const OUString& ServiceName ) +{ + return cppu::supportsService(this, ServiceName); +} + +css::uno::Sequence< OUString > UpdateRequiredDialogService::getSupportedServiceNames() +{ + return { "com.sun.star.deployment.ui.UpdateRequiredDialog" }; +} + + +// XExecutableDialog + +void UpdateRequiredDialogService::setTitle( OUString const & ) +{ +} + + +sal_Int16 UpdateRequiredDialogService::execute() +{ + ::rtl::Reference< ::dp_gui::TheExtensionManager > xManager( TheExtensionManager::get( + m_xComponentContext) ); + xManager->createDialog( true ); + sal_Int16 nRet = xManager->execute(); + + return nRet; +} + + +} //namespace dp_gui + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/gui/dp_gui_dialog2.hxx b/desktop/source/deployment/gui/dp_gui_dialog2.hxx new file mode 100644 index 0000000000..b235aed185 --- /dev/null +++ b/desktop/source/deployment/gui/dp_gui_dialog2.hxx @@ -0,0 +1,267 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#pragma once + +#include <vcl/timer.hxx> +#include <vcl/idle.hxx> +#include <vcl/locktoplevels.hxx> +#include <vcl/customweld.hxx> +#include <vcl/weld.hxx> + +#include <mutex> + +#include <rtl/ustring.hxx> + +#include <cppuhelper/implbase.hxx> +#include <unotools/resmgr.hxx> + +#include <com/sun/star/deployment/XPackage.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <com/sun/star/ui/dialogs/XExecutableDialog.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> + +struct ImplSVEvent; + +namespace dp_gui { + +class ExtBoxWithBtns_Impl; +class ExtensionBox_Impl; +class TheExtensionManager; + +class DialogHelper +{ + css::uno::Reference< css::uno::XComponentContext > m_xContext; + weld::Window* m_pWindow; + ImplSVEvent * m_nEventID; + TopLevelWindowLocker m_aBusy; + +public: + DialogHelper(const css::uno::Reference< css::uno::XComponentContext > &, + weld::Window* pWindow); + virtual ~DialogHelper(); + + void openWebBrowser(const OUString& rURL, const OUString& rTitle); + weld::Window* getFrameWeld() const { return m_pWindow; } + void PostUserEvent( const Link<void*,void>& rLink, void* pCaller ); + void clearEventID() { m_nEventID = nullptr; } + + virtual void showProgress( bool bStart ) = 0; + virtual void updateProgress( const OUString &rText, + const css::uno::Reference< css::task::XAbortChannel > &xAbortChannel) = 0; + virtual void updateProgress( const tools::Long nProgress ) = 0; + + virtual void updatePackageInfo( const css::uno::Reference< css::deployment::XPackage > &xPackage ) = 0; + virtual void addPackageToList( const css::uno::Reference< css::deployment::XPackage > &xPackage, + bool bLicenseMissing = false ) = 0; + + virtual void prepareChecking() = 0; + virtual void checkEntries() = 0; + + static bool IsSharedPkgMgr( const css::uno::Reference< css::deployment::XPackage > &); + bool continueOnSharedExtension( const css::uno::Reference< css::deployment::XPackage > &, + weld::Widget* pParent, + TranslateId pResID, + bool &bHadWarning ); + + void incBusy() { m_aBusy.incBusy(m_pWindow); } + void decBusy() { m_aBusy.decBusy(); } + bool isBusy() const { return m_aBusy.isBusy(); } + bool installExtensionWarn(std::u16string_view rExtensionURL); + bool installForAllUsers(bool &bInstallForAll); +}; + +class ExtMgrDialog : public weld::GenericDialogController + , public DialogHelper +{ + const OUString m_sAddPackages; + OUString m_sProgressText; + std::mutex m_aMutex; + bool m_bHasProgress; + bool m_bProgressChanged; + bool m_bStartProgress; + bool m_bStopProgress; + bool m_bEnableWarning; + bool m_bDisableWarning; + bool m_bDeleteWarning; + bool m_bClosed; + tools::Long m_nProgress; + Idle m_aIdle; + TheExtensionManager *m_pManager; + + css::uno::Reference< css::task::XAbortChannel > m_xAbortChannel; + + std::unique_ptr<ExtBoxWithBtns_Impl> m_xExtensionBox; + std::unique_ptr<weld::CustomWeld> m_xExtensionBoxWnd; + std::unique_ptr<weld::Button> m_xOptionsBtn; + std::unique_ptr<weld::Button> m_xAddBtn; + std::unique_ptr<weld::Button> m_xRemoveBtn; + std::unique_ptr<weld::Button> m_xEnableBtn; + std::unique_ptr<weld::Button> m_xUpdateBtn; + std::unique_ptr<weld::Button> m_xCloseBtn; + std::unique_ptr<weld::CheckButton> m_xBundledCbx; + std::unique_ptr<weld::CheckButton> m_xSharedCbx; + std::unique_ptr<weld::CheckButton> m_xUserCbx; + std::unique_ptr<weld::LinkButton> m_xGetExtensions; + std::unique_ptr<weld::Label> m_xProgressText; + std::unique_ptr<weld::ProgressBar> m_xProgressBar; + std::unique_ptr<weld::Button> m_xCancelBtn; + std::unique_ptr<weld::Entry> m_xSearchEntry; + + bool removeExtensionWarn(std::u16string_view rExtensionTitle); + + DECL_LINK( HandleOptionsBtn, weld::Button&, void ); + DECL_LINK( HandleAddBtn, weld::Button&, void ); + DECL_LINK( HandleRemoveBtn, weld::Button&, void ); + DECL_LINK( HandleEnableBtn, weld::Button&, void ); + DECL_LINK( HandleUpdateBtn, weld::Button&, void ); + DECL_LINK( HandleCancelBtn, weld::Button&, void ); + DECL_LINK( HandleCloseBtn, weld::Button&, void ); + DECL_LINK( HandleExtTypeCbx, weld::Toggleable&, void ); + DECL_LINK( HandleSearch, weld::Entry&, void ); + DECL_LINK( TimeOutHdl, Timer *, void ); + DECL_LINK( startProgress, void *, void ); + +public: + ExtMgrDialog(weld::Window * pParent, TheExtensionManager *pManager); + virtual ~ExtMgrDialog() override; + + virtual void showProgress( bool bStart ) override; + virtual void updateProgress( const OUString &rText, + const css::uno::Reference< css::task::XAbortChannel > &xAbortChannel) override; + virtual void updateProgress( const tools::Long nProgress ) override; + + virtual void updatePackageInfo( const css::uno::Reference< css::deployment::XPackage > &xPackage ) override; + + void setGetExtensionsURL( const OUString &rURL ); + virtual void addPackageToList( const css::uno::Reference< css::deployment::XPackage > &, + bool bLicenseMissing = false ) override; + void enablePackage(const css::uno::Reference< css::deployment::XPackage > &xPackage, + bool bEnable ); + void removePackage(const css::uno::Reference< css::deployment::XPackage > &xPackage ); + void updatePackage(const css::uno::Reference< css::deployment::XPackage > &xPackage ); + bool acceptLicense(const css::uno::Reference< css::deployment::XPackage > &xPackage ); + + void Close(); + + TheExtensionManager* getExtensionManager() const { return m_pManager; } + + void updateList(); + virtual void prepareChecking() override; + virtual void checkEntries() override; + + css::uno::Sequence< OUString > raiseAddPicker(); + + void enableOptionsButton( bool bEnable ); + void enableRemoveButton( bool bEnable ); + void enableEnableButton( bool bEnable ); + /* + * Transform the button to "Enable", or to "Disable" + * based on the value of bEnable. + */ + void enableButtontoEnable( bool bEnable ); +}; + + +class UpdateRequiredDialog : public weld::GenericDialogController + , public DialogHelper +{ + const OUString m_sCloseText; + OUString m_sProgressText; + std::mutex m_aMutex; + bool m_bHasProgress; + bool m_bProgressChanged; + bool m_bStartProgress; + bool m_bStopProgress; + bool m_bHasLockedEntries; + tools::Long m_nProgress; + Idle m_aIdle; + TheExtensionManager *m_pManager; + + css::uno::Reference< css::task::XAbortChannel > m_xAbortChannel; + + std::unique_ptr<ExtensionBox_Impl> m_xExtensionBox; + std::unique_ptr<weld::CustomWeld> m_xExtensionBoxWnd; + std::unique_ptr<weld::Label> m_xUpdateNeeded; + std::unique_ptr<weld::Button> m_xUpdateBtn; + std::unique_ptr<weld::Button> m_xCloseBtn; + std::unique_ptr<weld::Button> m_xCancelBtn; + std::unique_ptr<weld::Label> m_xProgressText; + std::unique_ptr<weld::ProgressBar> m_xProgressBar; + + DECL_LINK( HandleUpdateBtn, weld::Button&, void ); + DECL_LINK( HandleCloseBtn, weld::Button&, void ); + DECL_LINK( HandleCancelBtn, weld::Button&, void ); + DECL_LINK( TimeOutHdl, Timer *, void ); + DECL_LINK( startProgress, void *, void ); + + static bool isEnabled( const css::uno::Reference< css::deployment::XPackage > &xPackage ); + static bool checkDependencies( const css::uno::Reference< css::deployment::XPackage > &xPackage ); + bool hasActiveEntries(); + void disableAllEntries(); + +public: + UpdateRequiredDialog(weld::Window * pParent, TheExtensionManager *pManager); + virtual ~UpdateRequiredDialog() override; + + virtual short run() override; + + virtual void showProgress( bool bStart ) override; + virtual void updateProgress( const OUString &rText, + const css::uno::Reference< css::task::XAbortChannel > &xAbortChannel) override; + virtual void updateProgress( const tools::Long nProgress ) override; + + virtual void updatePackageInfo( const css::uno::Reference< css::deployment::XPackage > &xPackage ) override; + + virtual void addPackageToList( const css::uno::Reference< css::deployment::XPackage > &, + bool bLicenseMissing = false ) override; + + virtual void prepareChecking() override; + virtual void checkEntries() override; +}; + + +class ShowLicenseDialog : public weld::GenericDialogController +{ + std::unique_ptr<weld::TextView> m_xLicenseText; +public: + ShowLicenseDialog(weld::Window * pParent, const css::uno::Reference< css::deployment::XPackage > &xPackage); + virtual ~ShowLicenseDialog() override; +}; + +class UpdateRequiredDialogService : public ::cppu::WeakImplHelper< css::ui::dialogs::XExecutableDialog, css::lang::XServiceInfo > +{ + css::uno::Reference< css::uno::XComponentContext > const m_xComponentContext; +public: + UpdateRequiredDialogService( css::uno::Sequence< css::uno::Any > const & args, + css::uno::Reference< css::uno::XComponentContext> xComponentContext ); + + // 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; + + // XExecutableDialog + virtual void SAL_CALL setTitle( OUString const & title ) override; + virtual sal_Int16 SAL_CALL execute() override; +}; + +} // namespace dp_gui + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/gui/dp_gui_extensioncmdqueue.cxx b/desktop/source/deployment/gui/dp_gui_extensioncmdqueue.cxx new file mode 100644 index 0000000000..fd70b79822 --- /dev/null +++ b/desktop/source/deployment/gui/dp_gui_extensioncmdqueue.cxx @@ -0,0 +1,1115 @@ +/* -*- 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/beans/NamedValue.hpp> + +#include <com/sun/star/deployment/DependencyException.hpp> +#include <com/sun/star/deployment/LicenseException.hpp> +#include <com/sun/star/deployment/VersionException.hpp> +#include <com/sun/star/deployment/InstallException.hpp> +#include <com/sun/star/deployment/PlatformException.hpp> + +#include <com/sun/star/deployment/ui/LicenseDialog.hpp> +#include <com/sun/star/deployment/DeploymentException.hpp> +#include <com/sun/star/deployment/XPackage.hpp> + +#include <com/sun/star/task/InteractionHandler.hpp> +#include <com/sun/star/task/XAbortChannel.hpp> +#include <com/sun/star/task/XInteractionAbort.hpp> +#include <com/sun/star/task/XInteractionApprove.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/ui/dialogs/ExecutableDialogResults.hpp> + +#include <com/sun/star/uno/Reference.hxx> +#include <com/sun/star/uno/Sequence.hxx> +#include <com/sun/star/uno/TypeClass.hpp> +#include <o3tl/any.hxx> +#include <osl/diagnose.h> +#include <rtl/ref.hxx> +#include <rtl/ustring.hxx> +#include <sal/types.h> +#include <salhelper/thread.hxx> +#include <ucbhelper/content.hxx> +#include <cppuhelper/exc_hlp.hxx> +#include <cppuhelper/implbase.hxx> +#include <comphelper/anytostring.hxx> +#include <utility> +#include <vcl/svapp.hxx> +#include <vcl/weld.hxx> + +#include "dp_gui_extensioncmdqueue.hxx" +#include "dp_gui_dependencydialog.hxx" +#include "dp_gui_dialog2.hxx" +#include <dp_shared.hxx> +#include <strings.hrc> +#include "dp_gui_theextmgr.hxx" +#include "dp_gui_updatedialog.hxx" +#include "dp_gui_updateinstalldialog.hxx" +#include <dp_dependencies.hxx> +#include <dp_misc.h> +#include <dp_identifier.hxx> +#include <dp_version.hxx> + +#include <condition_variable> +#include <queue> +#include <memory> +#include <mutex> + +#ifdef _WIN32 +#include <o3tl/safeCoInitUninit.hxx> +#endif + + +using namespace ::com::sun::star; + +namespace { + +OUString getVersion( OUString const & sVersion ) +{ + return ( sVersion.isEmpty() ) ? OUString( "0" ) : sVersion; +} + +OUString getVersion( const uno::Reference< deployment::XPackage > &rPackage ) +{ + return getVersion( rPackage->getVersion()); +} +} + + +namespace dp_gui { + +namespace { + +class ProgressCmdEnv + : public ::cppu::WeakImplHelper< ucb::XCommandEnvironment, + task::XInteractionHandler, + ucb::XProgressHandler > +{ + uno::Reference< task::XInteractionHandler2> m_xHandler; + uno::Reference< uno::XComponentContext > m_xContext; + + DialogHelper* m_pDialogHelper; + OUString m_sTitle; + bool m_bWarnUser; + sal_Int32 m_nCurrentProgress; + + void updateProgress(); + + /// @throws uno::RuntimeException + void update_( uno::Any const & Status ); + +public: + /** When param bAskWhenInstalling = true, then the user is asked if he + agrees to install this extension. In case this extension is already installed + then the user is also notified and asked if he wants to replace that existing + extension. In first case an interaction request with an InstallException + will be handled and in the second case a VersionException will be handled. + */ + + ProgressCmdEnv( uno::Reference< uno::XComponentContext > xContext, + DialogHelper* pDialogHelper, + OUString aTitle ) + : m_xContext(std::move( xContext )) + , m_pDialogHelper( pDialogHelper ) + , m_sTitle(std::move( aTitle )) + , m_bWarnUser( false ) + , m_nCurrentProgress(0) + {} + + weld::Window* activeDialog() { return m_pDialogHelper ? m_pDialogHelper->getFrameWeld() : nullptr; } + + void startProgress(); + void stopProgress(); + void progressSection( const OUString &rText, + const uno::Reference< task::XAbortChannel > &xAbortChannel ); + void setWarnUser( bool bNewVal ) { m_bWarnUser = bNewVal; } + + // 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; +}; + + +struct ExtensionCmd +{ + enum E_CMD_TYPE { ADD, ENABLE, DISABLE, REMOVE, CHECK_FOR_UPDATES, ACCEPT_LICENSE }; + + E_CMD_TYPE m_eCmdType; + bool m_bWarnUser; + OUString m_sExtensionURL; + OUString m_sRepository; + uno::Reference< deployment::XPackage > m_xPackage; + std::vector< uno::Reference< deployment::XPackage > > m_vExtensionList; + + ExtensionCmd( const E_CMD_TYPE eCommand, + OUString aExtensionURL, + OUString aRepository, + const bool bWarnUser ) + : m_eCmdType( eCommand ), + m_bWarnUser( bWarnUser ), + m_sExtensionURL(std::move( aExtensionURL )), + m_sRepository(std::move( aRepository )) {}; + ExtensionCmd( const E_CMD_TYPE eCommand, + uno::Reference< deployment::XPackage > xPackage ) + : m_eCmdType( eCommand ), + m_bWarnUser( false ), + m_xPackage(std::move( xPackage )) {}; + ExtensionCmd( const E_CMD_TYPE eCommand, + std::vector<uno::Reference<deployment::XPackage > >&&vExtensionList ) + : m_eCmdType( eCommand ), + m_bWarnUser( false ), + m_vExtensionList( std::move(vExtensionList) ) {}; +}; + +} + +typedef std::shared_ptr< ExtensionCmd > TExtensionCmd; + + +class ExtensionCmdQueue::Thread: public salhelper::Thread +{ +public: + Thread( DialogHelper *pDialogHelper, + TheExtensionManager *pManager, + uno::Reference< uno::XComponentContext > xContext ); + + void addExtension( const OUString &rExtensionURL, + const OUString &rRepository, + const bool bWarnUser ); + void removeExtension( const uno::Reference< deployment::XPackage > &rPackage ); + void enableExtension( const uno::Reference< deployment::XPackage > &rPackage, + const bool bEnable ); + void checkForUpdates( std::vector<uno::Reference<deployment::XPackage > > && vExtensionList ); + void acceptLicense( const uno::Reference< deployment::XPackage > &rPackage ); + void stop(); + bool isBusy(); + +private: + virtual ~Thread() override; + + virtual void execute() override; + + void _insert(const TExtensionCmd& rExtCmd); + + void _addExtension( ::rtl::Reference< ProgressCmdEnv > const &rCmdEnv, + const OUString &rPackageURL, + const OUString &rRepository, + const bool bWarnUser ); + void _removeExtension( ::rtl::Reference< ProgressCmdEnv > const &rCmdEnv, + const uno::Reference< deployment::XPackage > &xPackage ); + void _enableExtension( ::rtl::Reference< ProgressCmdEnv > const &rCmdEnv, + const uno::Reference< deployment::XPackage > &xPackage ); + void _disableExtension( ::rtl::Reference< ProgressCmdEnv > const &rCmdEnv, + const uno::Reference< deployment::XPackage > &xPackage ); + void _checkForUpdates( std::vector<uno::Reference<deployment::XPackage > > &&vExtensionList ); + void _acceptLicense( ::rtl::Reference< ProgressCmdEnv > const &rCmdEnv, + const uno::Reference< deployment::XPackage > &xPackage ); + + enum Input { NONE, START, STOP }; + + uno::Reference< uno::XComponentContext > m_xContext; + std::queue< TExtensionCmd > m_queue; + + DialogHelper *m_pDialogHelper; + TheExtensionManager *m_pManager; + + const OUString m_sEnablingPackages; + const OUString m_sDisablingPackages; + const OUString m_sAddingPackages; + const OUString m_sRemovingPackages; + const OUString m_sDefaultCmd; + const OUString m_sAcceptLicense; + std::condition_variable m_wakeup; + std::mutex m_mutex; + Input m_eInput; + bool m_bStopped; + bool m_bWorking; +}; + + +void ProgressCmdEnv::startProgress() +{ + m_nCurrentProgress = 0; + + if ( m_pDialogHelper ) + m_pDialogHelper->showProgress( true ); +} + + +void ProgressCmdEnv::stopProgress() +{ + if ( m_pDialogHelper ) + m_pDialogHelper->showProgress( false ); +} + + +void ProgressCmdEnv::progressSection( const OUString &rText, + const uno::Reference< task::XAbortChannel > &xAbortChannel ) +{ + m_nCurrentProgress = 0; + if ( m_pDialogHelper ) + { + m_pDialogHelper->updateProgress( rText, xAbortChannel ); + m_pDialogHelper->updateProgress( 5 ); + } +} + + +void ProgressCmdEnv::updateProgress() +{ + tools::Long nProgress = ((m_nCurrentProgress*5) % 100) + 5; + if ( m_pDialogHelper ) + m_pDialogHelper->updateProgress( nProgress ); +} + +// XCommandEnvironment + +uno::Reference< task::XInteractionHandler > ProgressCmdEnv::getInteractionHandler() +{ + return this; +} + + +uno::Reference< ucb::XProgressHandler > ProgressCmdEnv::getProgressHandler() +{ + return this; +} + + +// XInteractionHandler + +void ProgressCmdEnv::handle( uno::Reference< task::XInteractionRequest > const & xRequest ) +{ + uno::Any request( xRequest->getRequest() ); + OSL_ASSERT( request.getValueTypeClass() == uno::TypeClass_EXCEPTION ); + dp_misc::TRACE( "[dp_gui_cmdenv.cxx] incoming request:\n" + + ::comphelper::anyToString(request) + "\n"); + + lang::WrappedTargetException wtExc; + deployment::DependencyException depExc; + deployment::LicenseException licExc; + deployment::VersionException verExc; + deployment::InstallException instExc; + deployment::PlatformException platExc; + + // selections: + bool approve = false; + bool abort = false; + + if (request >>= wtExc) { + // handable deployment error signalled, e.g. + // bundle item registration failed, notify cause only: + uno::Any cause; + deployment::DeploymentException dpExc; + if (wtExc.TargetException >>= dpExc) + cause = dpExc.Cause; + else { + ucb::CommandFailedException cfExc; + if (wtExc.TargetException >>= cfExc) + cause = cfExc.Reason; + else + cause = wtExc.TargetException; + } + update_( cause ); + + // ignore intermediate errors of legacy packages, i.e. + // former pkgchk behaviour: + const uno::Reference< deployment::XPackage > xPackage( wtExc.Context, uno::UNO_QUERY ); + OSL_ASSERT( xPackage.is() ); + if ( xPackage.is() ) + { + const uno::Reference< deployment::XPackageTypeInfo > xPackageType( xPackage->getPackageType() ); + OSL_ASSERT( xPackageType.is() ); + if (xPackageType.is()) + { + approve = ( xPackage->isBundle() && + xPackageType->getMediaType().match( + "application/vnd.sun.star.legacy-package-bundle" )); + } + } + abort = !approve; + } + else if (request >>= depExc) + { + std::vector< OUString > deps; + deps.reserve(depExc.UnsatisfiedDependencies.getLength()); + for (auto const & i : std::as_const(depExc.UnsatisfiedDependencies)) + { + deps.push_back( dp_misc::Dependencies::getErrorText(i) ); + } + { + SolarMutexGuard guard; + if (m_pDialogHelper) + m_pDialogHelper->incBusy(); + DependencyDialog aDlg(activeDialog(), deps); + short n = aDlg.run(); + if (m_pDialogHelper) + m_pDialogHelper->decBusy(); + // Distinguish between closing the dialog and programmatically + // canceling the dialog (headless VCL): + approve = n == RET_OK + || (n == RET_CANCEL && !Application::IsDialogCancelEnabled()); + } + } + else if (request >>= licExc) + { + SolarMutexGuard guard; + + weld::Window *pTopLevel = activeDialog(); + if (m_pDialogHelper) + m_pDialogHelper->incBusy(); + uno::Reference< ui::dialogs::XExecutableDialog > xDialog( + deployment::ui::LicenseDialog::create( + m_xContext, pTopLevel ? pTopLevel->GetXWindow() : nullptr, + licExc.ExtensionName, licExc.Text ) ); + sal_Int16 res = xDialog->execute(); + if (m_pDialogHelper) + m_pDialogHelper->decBusy(); + if ( res == ui::dialogs::ExecutableDialogResults::CANCEL ) + abort = true; + else if ( res == ui::dialogs::ExecutableDialogResults::OK ) + approve = true; + else + { + OSL_ASSERT(false); + } + } + else if (request >>= verExc) + { + TranslateId id; + switch (dp_misc::compareVersions( + verExc.NewVersion, verExc.Deployed->getVersion() )) + { + case dp_misc::LESS: + id = RID_STR_WARNING_VERSION_LESS; + break; + case dp_misc::EQUAL: + id = RID_STR_WARNING_VERSION_EQUAL; + break; + default: // dp_misc::GREATER + id = RID_STR_WARNING_VERSION_GREATER; + break; + } + OSL_ASSERT( verExc.Deployed.is() ); + bool bEqualNames = verExc.NewDisplayName == + verExc.Deployed->getDisplayName(); + { + SolarMutexGuard guard; + + if (m_pDialogHelper) + m_pDialogHelper->incBusy(); + + std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(activeDialog(), + VclMessageType::Warning, VclButtonsType::OkCancel, DpResId(id))); + OUString s; + if (bEqualNames) + { + s = xBox->get_primary_text(); + } + else if (id != RID_STR_WARNING_VERSION_EQUAL) + { + //hypothetical: requires two instances of an extension with the same + //version to have different display names. Probably the developer forgot + //to change the version. + s = DpResId(RID_STR_WARNINGBOX_VERSION_EQUAL_DIFFERENT_NAMES); + } + else if (id != RID_STR_WARNING_VERSION_LESS) + { + s = DpResId(RID_STR_WARNINGBOX_VERSION_LESS_DIFFERENT_NAMES); + } + else if (id != RID_STR_WARNING_VERSION_GREATER) + { + s = DpResId(RID_STR_WARNINGBOX_VERSION_GREATER_DIFFERENT_NAMES); + } + s = s.replaceAll("$NAME", verExc.NewDisplayName); + s = s.replaceAll("$OLDNAME", verExc.Deployed->getDisplayName()); + s = s.replaceAll("$NEW", getVersion(verExc.NewVersion)); + s = s.replaceAll("$DEPLOYED", getVersion(verExc.Deployed)); + xBox->set_primary_text(s); + approve = xBox->run() == RET_OK; + if (m_pDialogHelper) + m_pDialogHelper->decBusy(); + abort = !approve; + } + } + else if (request >>= instExc) + { + if ( ! m_bWarnUser ) + { + approve = true; + } + else + { + if ( m_pDialogHelper ) + { + SolarMutexGuard guard; + + approve = m_pDialogHelper->installExtensionWarn( instExc.displayName ); + } + else + approve = false; + abort = !approve; + } + } + else if (request >>= platExc) + { + SolarMutexGuard guard; + OUString sMsg(DpResId(RID_STR_UNSUPPORTED_PLATFORM)); + sMsg = sMsg.replaceAll("%Name", platExc.package->getDisplayName()); + if (m_pDialogHelper) + m_pDialogHelper->incBusy(); + std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(activeDialog(), + VclMessageType::Warning, VclButtonsType::Ok, sMsg)); + xBox->run(); + if (m_pDialogHelper) + m_pDialogHelper->decBusy(); + approve = true; + } + + if (!approve && !abort) + { + // forward to UUI handler: + if (! m_xHandler.is()) { + // late init: + m_xHandler = task::InteractionHandler::createWithParentAndContext(m_xContext, nullptr, m_sTitle); + } + m_xHandler->handle( xRequest ); + } + else + { + // select: + uno::Sequence< uno::Reference< task::XInteractionContinuation > > conts( + xRequest->getContinuations() ); + uno::Reference< task::XInteractionContinuation > const * pConts = conts.getConstArray(); + sal_Int32 len = conts.getLength(); + for ( sal_Int32 pos = 0; pos < len; ++pos ) + { + if (approve) { + uno::Reference< task::XInteractionApprove > xInteractionApprove( pConts[ pos ], uno::UNO_QUERY ); + if (xInteractionApprove.is()) { + xInteractionApprove->select(); + // don't query again for ongoing continuations: + approve = false; + } + } + else if (abort) { + uno::Reference< task::XInteractionAbort > xInteractionAbort( pConts[ pos ], uno::UNO_QUERY ); + if (xInteractionAbort.is()) { + xInteractionAbort->select(); + // don't query again for ongoing continuations: + abort = false; + } + } + } + } +} + + +// XProgressHandler + +void ProgressCmdEnv::push( uno::Any const & rStatus ) +{ + update_( rStatus ); +} + + +void ProgressCmdEnv::update_( uno::Any const & rStatus ) +{ + OUString text; + if ( rStatus.hasValue() && !( rStatus >>= text) ) + { + if ( auto e = o3tl::tryAccess<uno::Exception>(rStatus) ) + text = e->Message; + if ( text.isEmpty() ) + text = ::comphelper::anyToString( rStatus ); // fallback + + const SolarMutexGuard aGuard; + if (m_pDialogHelper) + m_pDialogHelper->incBusy(); + std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(activeDialog(), + VclMessageType::Warning, VclButtonsType::Ok, text)); + xBox->run(); + if (m_pDialogHelper) + m_pDialogHelper->decBusy(); + } + ++m_nCurrentProgress; + updateProgress(); +} + + +void ProgressCmdEnv::update( uno::Any const & rStatus ) +{ + update_( rStatus ); +} + + +void ProgressCmdEnv::pop() +{ + update_( uno::Any() ); // no message +} + + +ExtensionCmdQueue::Thread::Thread( DialogHelper *pDialogHelper, + TheExtensionManager *pManager, + uno::Reference< uno::XComponentContext > xContext ) : + salhelper::Thread( "dp_gui_extensioncmdqueue" ), + m_xContext(std::move( xContext )), + m_pDialogHelper( pDialogHelper ), + m_pManager( pManager ), + m_sEnablingPackages( DpResId( RID_STR_ENABLING_PACKAGES ) ), + m_sDisablingPackages( DpResId( RID_STR_DISABLING_PACKAGES ) ), + m_sAddingPackages( DpResId( RID_STR_ADDING_PACKAGES ) ), + m_sRemovingPackages( DpResId( RID_STR_REMOVING_PACKAGES ) ), + m_sDefaultCmd( DpResId( RID_STR_ADD_PACKAGES ) ), + m_sAcceptLicense( DpResId( RID_STR_ACCEPT_LICENSE ) ), + m_eInput( NONE ), + m_bStopped( false ), + m_bWorking( false ) +{ + OSL_ASSERT( pDialogHelper ); +} + + +void ExtensionCmdQueue::Thread::addExtension( const OUString &rExtensionURL, + const OUString &rRepository, + const bool bWarnUser ) +{ + if ( !rExtensionURL.isEmpty() ) + { + TExtensionCmd pEntry = std::make_shared<ExtensionCmd>( ExtensionCmd::ADD, rExtensionURL, rRepository, bWarnUser ); + _insert( pEntry ); + } +} + + +void ExtensionCmdQueue::Thread::removeExtension( const uno::Reference< deployment::XPackage > &rPackage ) +{ + if ( rPackage.is() ) + { + TExtensionCmd pEntry = std::make_shared<ExtensionCmd>( ExtensionCmd::REMOVE, rPackage ); + _insert( pEntry ); + } +} + + +void ExtensionCmdQueue::Thread::acceptLicense( const uno::Reference< deployment::XPackage > &rPackage ) +{ + if ( rPackage.is() ) + { + TExtensionCmd pEntry = std::make_shared<ExtensionCmd>( ExtensionCmd::ACCEPT_LICENSE, rPackage ); + _insert( pEntry ); + } +} + + +void ExtensionCmdQueue::Thread::enableExtension( const uno::Reference< deployment::XPackage > &rPackage, + const bool bEnable ) +{ + if ( rPackage.is() ) + { + TExtensionCmd pEntry = std::make_shared<ExtensionCmd>( bEnable ? ExtensionCmd::ENABLE : + ExtensionCmd::DISABLE, + rPackage ); + _insert( pEntry ); + } +} + + +void ExtensionCmdQueue::Thread::checkForUpdates( + std::vector<uno::Reference<deployment::XPackage > > && vExtensionList ) +{ + TExtensionCmd pEntry = std::make_shared<ExtensionCmd>( ExtensionCmd::CHECK_FOR_UPDATES, std::move(vExtensionList) ); + _insert( pEntry ); +} + + +//Stopping this thread will not abort the installation of extensions. +void ExtensionCmdQueue::Thread::stop() +{ + std::scoped_lock aGuard( m_mutex ); + m_bStopped = true; + m_eInput = STOP; + m_wakeup.notify_all(); +} + + +bool ExtensionCmdQueue::Thread::isBusy() +{ + std::scoped_lock aGuard( m_mutex ); + return m_bWorking; +} + + +ExtensionCmdQueue::Thread::~Thread() {} + + +void ExtensionCmdQueue::Thread::execute() +{ +#ifdef _WIN32 + //Needed for use of the service "com.sun.star.system.SystemShellExecute" in + //DialogHelper::openWebBrowser + int nNbCallCoInitializeExForReinit = 0; + o3tl::safeCoInitializeEx(COINIT_APARTMENTTHREADED, nNbCallCoInitializeExForReinit); +#endif + for (;;) + { + int nSize; + Input eInput; + { + std::unique_lock aGuard( m_mutex ); + while (m_eInput == NONE) { + m_wakeup.wait(aGuard); + } + eInput = m_eInput; + m_eInput = NONE; + nSize = m_queue.size(); + // coverity[missing_lock: FALSE] - maybe due to (by-design) unique_lock vs. scoped_lock? + m_bWorking = false; + } + + if ( eInput == STOP ) + break; + + // We only install the extension which are currently in the queue. + // The progressbar will be set to show the progress of the current number + // of extensions. If we allowed to add extensions now then the progressbar may + // have reached the end while we still install newly added extensions. + if ( nSize == 0 ) + continue; + + ::rtl::Reference< ProgressCmdEnv > currentCmdEnv( new ProgressCmdEnv( m_xContext, m_pDialogHelper, m_sDefaultCmd ) ); + + // Do not lock the following part with addExtension. addExtension may be called in the main thread. + // If the message box "Do you want to install the extension (or similar)" is shown and then + // addExtension is called, which then blocks the main thread, then we deadlock. + bool bStartProgress = true; + + while ( --nSize >= 0 ) + { + { + std::scoped_lock aGuard( m_mutex ); + m_bWorking = true; + } + + try + { + TExtensionCmd pEntry; + { + std::scoped_lock queueGuard( m_mutex ); + pEntry = m_queue.front(); + m_queue.pop(); + } + + if ( bStartProgress && ( pEntry->m_eCmdType != ExtensionCmd::CHECK_FOR_UPDATES ) ) + { + currentCmdEnv->startProgress(); + bStartProgress = false; + } + + switch ( pEntry->m_eCmdType ) { + case ExtensionCmd::ADD : + _addExtension( currentCmdEnv, pEntry->m_sExtensionURL, pEntry->m_sRepository, pEntry->m_bWarnUser ); + break; + case ExtensionCmd::REMOVE : + _removeExtension( currentCmdEnv, pEntry->m_xPackage ); + break; + case ExtensionCmd::ENABLE : + _enableExtension( currentCmdEnv, pEntry->m_xPackage ); + break; + case ExtensionCmd::DISABLE : + _disableExtension( currentCmdEnv, pEntry->m_xPackage ); + break; + case ExtensionCmd::CHECK_FOR_UPDATES : + _checkForUpdates( std::vector(pEntry->m_vExtensionList) ); + break; + case ExtensionCmd::ACCEPT_LICENSE : + _acceptLicense( currentCmdEnv, pEntry->m_xPackage ); + break; + } + } + catch ( const ucb::CommandAbortedException & ) + { + //This exception is thrown when the user clicks cancel on the progressbar. + //Then we cancel the installation of all extensions and remove them from + //the queue. + { + std::scoped_lock queueGuard2(m_mutex); + while ( --nSize >= 0 ) + m_queue.pop(); + } + break; + } + catch ( const ucb::CommandFailedException & ) + { + //This exception is thrown when a user clicked cancel in the messagebox which was + //started by the interaction handler. For example the user will be asked if he/she + //really wants to install the extension. + //These interactions run for exactly one extension at a time. Therefore we continue + //with installing the remaining extensions. + continue; + } + catch ( const uno::Exception & ) + { + //Todo display the user an error + //see also DialogImpl::SyncPushButton::Click() + uno::Any exc( ::cppu::getCaughtException() ); + OUString msg; + deployment::DeploymentException dpExc; + if (exc >>= dpExc) + { + if (auto e = o3tl::tryAccess<uno::Exception>(dpExc.Cause)) + { + // notify error cause only: + msg = e->Message; + } + } + if (msg.isEmpty()) // fallback for debugging purposes + msg = ::comphelper::anyToString(exc); + + const SolarMutexGuard guard; + if (m_pDialogHelper) + m_pDialogHelper->incBusy(); + + std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(currentCmdEnv->activeDialog(), + VclMessageType::Warning, VclButtonsType::Ok, msg)); + if (m_pDialogHelper) + xBox->set_title(m_pDialogHelper->getFrameWeld()->get_title()); + xBox->run(); + if (m_pDialogHelper) + m_pDialogHelper->decBusy(); + //Continue with installation of the remaining extensions + } + { + std::scoped_lock aGuard( m_mutex ); + m_bWorking = false; + } + } + + { + // when leaving the while loop with break, we should set working to false, too + std::scoped_lock aGuard( m_mutex ); + m_bWorking = false; + } + + if ( !bStartProgress ) + currentCmdEnv->stopProgress(); + } + //end for +#ifdef _WIN32 + o3tl::safeCoUninitializeReinit(COINIT_MULTITHREADED, nNbCallCoInitializeExForReinit); +#endif +} + + +void ExtensionCmdQueue::Thread::_addExtension( ::rtl::Reference< ProgressCmdEnv > const &rCmdEnv, + const OUString &rPackageURL, + const OUString &rRepository, + const bool bWarnUser ) +{ + //check if we have a string in anyTitle. For example "unopkg gui \" caused anyTitle to be void + //and anyTitle.get<OUString> throws as RuntimeException. + uno::Any anyTitle; + try + { + anyTitle = ::ucbhelper::Content( rPackageURL, rCmdEnv, m_xContext ).getPropertyValue( "Title" ); + } + catch ( const uno::Exception & ) + { + return; + } + + OUString sName; + if ( ! (anyTitle >>= sName) ) + { + OSL_FAIL("Could not get file name for extension."); + return; + } + + rCmdEnv->setWarnUser( bWarnUser ); + uno::Reference< deployment::XExtensionManager > xExtMgr = m_pManager->getExtensionManager(); + uno::Reference< task::XAbortChannel > xAbortChannel( xExtMgr->createAbortChannel() ); + OUString sTitle( + m_sAddingPackages.replaceAll("%EXTENSION_NAME", sName)); + rCmdEnv->progressSection( sTitle, xAbortChannel ); + + try + { + xExtMgr->addExtension(rPackageURL, uno::Sequence<beans::NamedValue>(), + rRepository, xAbortChannel, rCmdEnv ); + } + catch ( const ucb::CommandFailedException & ) + { + // When the extension is already installed we'll get a dialog asking if we want to overwrite. If we then press + // cancel this exception is thrown. + } + catch ( const ucb::CommandAbortedException & ) + { + // User clicked the cancel button + // TODO: handle cancel + } + rCmdEnv->setWarnUser( false ); +} + + +void ExtensionCmdQueue::Thread::_removeExtension( ::rtl::Reference< ProgressCmdEnv > const &rCmdEnv, + const uno::Reference< deployment::XPackage > &xPackage ) +{ + uno::Reference< deployment::XExtensionManager > xExtMgr = m_pManager->getExtensionManager(); + uno::Reference< task::XAbortChannel > xAbortChannel( xExtMgr->createAbortChannel() ); + OUString sTitle( + m_sRemovingPackages.replaceAll("%EXTENSION_NAME", + xPackage->getDisplayName())); + rCmdEnv->progressSection( sTitle, xAbortChannel ); + + OUString id( dp_misc::getIdentifier( xPackage ) ); + try + { + xExtMgr->removeExtension( id, xPackage->getName(), xPackage->getRepositoryName(), xAbortChannel, rCmdEnv ); + } + catch ( const deployment::DeploymentException & ) + {} + catch ( const ucb::CommandFailedException & ) + {} + catch ( const ucb::CommandAbortedException & ) + {} + + // Check, if there are still updates to be notified via menu bar icon + uno::Sequence< uno::Sequence< OUString > > aItemList; + UpdateDialog::createNotifyJob( false, aItemList ); +} + + +void ExtensionCmdQueue::Thread::_checkForUpdates( + std::vector<uno::Reference<deployment::XPackage > > && vExtensionList ) +{ + const SolarMutexGuard guard; + + if (m_pDialogHelper) + m_pDialogHelper->incBusy(); + + std::vector< UpdateData > vData; + UpdateDialog aUpdateDialog(m_xContext, m_pDialogHelper ? m_pDialogHelper->getFrameWeld() : nullptr, std::move(vExtensionList), &vData); + + aUpdateDialog.notifyMenubar( true, false ); // prepare the checking, if there updates to be notified via menu bar icon + + bool bOk = aUpdateDialog.run() == RET_OK; + if (m_pDialogHelper) + m_pDialogHelper->decBusy(); + + if (bOk && !vData.empty()) + { + // If there is at least one directly downloadable extension then we + // open the install dialog. + std::vector< UpdateData > dataDownload; + + for (auto const& data : vData) + { + if ( data.sWebsiteURL.isEmpty() ) + dataDownload.push_back(data); + } + + short nDialogResult = RET_OK; + if ( !dataDownload.empty() ) + { + if (m_pDialogHelper) + m_pDialogHelper->incBusy(); + UpdateInstallDialog aDlg(m_pDialogHelper ? m_pDialogHelper->getFrameWeld() : nullptr, dataDownload, m_xContext); + nDialogResult = aDlg.run(); + if (m_pDialogHelper) + m_pDialogHelper->decBusy(); + aUpdateDialog.notifyMenubar( false, true ); // Check, if there are still pending updates to be notified via menu bar icon + } + else + aUpdateDialog.notifyMenubar( false, false ); // Check, if there are pending updates to be notified via menu bar icon + + //Now start the webbrowser and navigate to the websites where we get the updates + if ( RET_OK == nDialogResult ) + { + for (auto const& data : vData) + { + if ( m_pDialogHelper && ( !data.sWebsiteURL.isEmpty() ) ) + m_pDialogHelper->openWebBrowser( data.sWebsiteURL, m_pDialogHelper->getFrameWeld()->get_title() ); + } + } + } + else + aUpdateDialog.notifyMenubar( false, false ); // check if there updates to be notified via menu bar icon +} + + +void ExtensionCmdQueue::Thread::_enableExtension( ::rtl::Reference< ProgressCmdEnv > const &rCmdEnv, + const uno::Reference< deployment::XPackage > &xPackage ) +{ + if ( !xPackage.is() ) + return; + + uno::Reference< deployment::XExtensionManager > xExtMgr = m_pManager->getExtensionManager(); + uno::Reference< task::XAbortChannel > xAbortChannel( xExtMgr->createAbortChannel() ); + OUString sTitle( + m_sEnablingPackages.replaceAll("%EXTENSION_NAME", + xPackage->getDisplayName())); + rCmdEnv->progressSection( sTitle, xAbortChannel ); + + try + { + xExtMgr->enableExtension( xPackage, xAbortChannel, rCmdEnv ); + if ( m_pDialogHelper ) + m_pDialogHelper->updatePackageInfo( xPackage ); + } + catch ( const ::ucb::CommandAbortedException & ) + {} +} + + +void ExtensionCmdQueue::Thread::_disableExtension( ::rtl::Reference< ProgressCmdEnv > const &rCmdEnv, + const uno::Reference< deployment::XPackage > &xPackage ) +{ + if ( !xPackage.is() ) + return; + + uno::Reference< deployment::XExtensionManager > xExtMgr = m_pManager->getExtensionManager(); + uno::Reference< task::XAbortChannel > xAbortChannel( xExtMgr->createAbortChannel() ); + OUString sTitle( + m_sDisablingPackages.replaceAll("%EXTENSION_NAME", + xPackage->getDisplayName())); + rCmdEnv->progressSection( sTitle, xAbortChannel ); + + try + { + xExtMgr->disableExtension( xPackage, xAbortChannel, rCmdEnv ); + if ( m_pDialogHelper ) + m_pDialogHelper->updatePackageInfo( xPackage ); + } + catch ( const ::ucb::CommandAbortedException & ) + {} +} + + +void ExtensionCmdQueue::Thread::_acceptLicense( ::rtl::Reference< ProgressCmdEnv > const &rCmdEnv, + const uno::Reference< deployment::XPackage > &xPackage ) +{ + if ( !xPackage.is() ) + return; + + uno::Reference< deployment::XExtensionManager > xExtMgr = m_pManager->getExtensionManager(); + uno::Reference< task::XAbortChannel > xAbortChannel( xExtMgr->createAbortChannel() ); + OUString sTitle( + m_sAcceptLicense.replaceAll("%EXTENSION_NAME", + xPackage->getDisplayName())); + rCmdEnv->progressSection( sTitle, xAbortChannel ); + + try + { + xExtMgr->checkPrerequisitesAndEnable( xPackage, xAbortChannel, rCmdEnv ); + if ( m_pDialogHelper ) + m_pDialogHelper->updatePackageInfo( xPackage ); + } + catch ( const ::ucb::CommandAbortedException & ) + {} +} + +void ExtensionCmdQueue::Thread::_insert(const TExtensionCmd& rExtCmd) +{ + std::scoped_lock aGuard( m_mutex ); + + // If someone called stop then we do not process the command -> game over! + if ( m_bStopped ) + return; + + m_queue.push( rExtCmd ); + m_eInput = START; + m_wakeup.notify_all(); +} + + +ExtensionCmdQueue::ExtensionCmdQueue( DialogHelper * pDialogHelper, + TheExtensionManager *pManager, + const uno::Reference< uno::XComponentContext > &rContext ) + : m_thread( new Thread( pDialogHelper, pManager, rContext ) ) +{ + m_thread->launch(); +} + +ExtensionCmdQueue::~ExtensionCmdQueue() { + m_thread->stop(); + m_thread->join(); +} + +void ExtensionCmdQueue::addExtension( const OUString & extensionURL, + const OUString & repository, + const bool bWarnUser ) +{ + m_thread->addExtension( extensionURL, repository, bWarnUser ); +} + +void ExtensionCmdQueue::removeExtension( const uno::Reference< deployment::XPackage > &rPackage ) +{ + m_thread->removeExtension( rPackage ); +} + +void ExtensionCmdQueue::enableExtension( const uno::Reference< deployment::XPackage > &rPackage, + const bool bEnable ) +{ + m_thread->enableExtension( rPackage, bEnable ); +} + +void ExtensionCmdQueue::checkForUpdates( std::vector<uno::Reference<deployment::XPackage > > && vExtensionList ) +{ + m_thread->checkForUpdates( std::move(vExtensionList) ); +} + +void ExtensionCmdQueue::acceptLicense( const uno::Reference< deployment::XPackage > &rPackage ) +{ + m_thread->acceptLicense( rPackage ); +} + +void ExtensionCmdQueue::syncRepositories( const uno::Reference< uno::XComponentContext > &xContext ) +{ + dp_misc::syncRepositories( false, new ProgressCmdEnv( xContext, nullptr, "Extension Manager" ) ); +} + +bool ExtensionCmdQueue::isBusy() +{ + return m_thread->isBusy(); +} + +void handleInteractionRequest( const uno::Reference< uno::XComponentContext > & xContext, + const uno::Reference< task::XInteractionRequest > & xRequest ) +{ + ::rtl::Reference< ProgressCmdEnv > xCmdEnv( new ProgressCmdEnv( xContext, nullptr, "Extension Manager" ) ); + xCmdEnv->handle( xRequest ); +} + +} //namespace dp_gui + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/gui/dp_gui_extensioncmdqueue.hxx b/desktop/source/deployment/gui/dp_gui_extensioncmdqueue.hxx new file mode 100644 index 0000000000..3703d1e8c5 --- /dev/null +++ b/desktop/source/deployment/gui/dp_gui_extensioncmdqueue.hxx @@ -0,0 +1,94 @@ +/* -*- 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 <com/sun/star/uno/Reference.hxx> +#include <rtl/ref.hxx> + +#include <vector> + +#include "dp_gui_updatedata.hxx" + +/// @HTML + +namespace com::sun::star { + namespace task { class XInteractionRequest; } + namespace uno { class XComponentContext; } +} + +namespace dp_gui { + +class DialogHelper; +class TheExtensionManager; + +/** + Manages installing of extensions in the GUI mode. Requests for installing + Extensions can be asynchronous. For example, the Extension Manager is running + in an office process and someone uses the system integration to install an Extension. + That is, the user double clicks an extension symbol in a file browser, which then + causes an invocation of "unopkg gui ext". When at that time the Extension Manager + already performs a task, triggered by the user (for example, add, update, disable, + enable) then adding of the extension will be postponed until the user has finished + the task. + + This class also ensures that the extensions are not installed in the main thread. + Doing so would cause a deadlock because of the progress bar which needs to be constantly + updated. +*/ +class ExtensionCmdQueue { + +public: + /** + Create an instance. + */ + ExtensionCmdQueue( DialogHelper * pDialogHelper, + TheExtensionManager *pManager, + const css::uno::Reference< css::uno::XComponentContext > & rContext); + + ~ExtensionCmdQueue(); + + void addExtension( const OUString &rExtensionURL, + const OUString &rRepository, + const bool bWarnUser ); + void removeExtension( const css::uno::Reference< css::deployment::XPackage > &rPackage ); + void enableExtension( const css::uno::Reference< css::deployment::XPackage > &rPackage, + const bool bEnable ); + void checkForUpdates( std::vector< css::uno::Reference< css::deployment::XPackage > > && vList ); + void acceptLicense( const css::uno::Reference< css::deployment::XPackage > &rPackage ); + static void syncRepositories( const css::uno::Reference< css::uno::XComponentContext > & xContext ); + + bool isBusy(); +private: + ExtensionCmdQueue(ExtensionCmdQueue const &) = delete; + ExtensionCmdQueue& operator =(ExtensionCmdQueue const &) = delete; + + class Thread; + + rtl::Reference< Thread > m_thread; +}; + +void handleInteractionRequest( const css::uno::Reference< css::uno::XComponentContext > & xContext, + const css::uno::Reference< css::task::XInteractionRequest > & xRequest ); + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/gui/dp_gui_extlistbox.cxx b/desktop/source/deployment/gui/dp_gui_extlistbox.cxx new file mode 100644 index 0000000000..e7f91f44ab --- /dev/null +++ b/desktop/source/deployment/gui/dp_gui_extlistbox.cxx @@ -0,0 +1,1143 @@ +/* -*- 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 <dp_shared.hxx> +#include <strings.hrc> +#include "dp_gui.h" +#include "dp_gui_extlistbox.hxx" +#include "dp_gui_theextmgr.hxx" +#include <dp_dependencies.hxx> +#include <bitmaps.hlst> + +#include <comphelper/processfactory.hxx> +#include <com/sun/star/i18n/CollatorOptions.hpp> +#include <com/sun/star/deployment/DependencyException.hpp> +#include <com/sun/star/deployment/DeploymentException.hpp> +#include <com/sun/star/deployment/ExtensionRemovedException.hpp> +#include <com/sun/star/system/XSystemShellExecute.hpp> +#include <com/sun/star/system/SystemShellExecuteFlags.hpp> +#include <com/sun/star/system/SystemShellExecute.hpp> +#include <cppuhelper/weakref.hxx> +#include <i18nlangtag/languagetag.hxx> +#include <o3tl/safeint.hxx> +#include <osl/diagnose.h> +#include <rtl/ustrbuf.hxx> +#include <utility> +#include <vcl/event.hxx> +#include <vcl/ptrstyle.hxx> +#include <vcl/svapp.hxx> +#include <vcl/settings.hxx> +#include <vcl/weldutils.hxx> +#include <algorithm> + +constexpr OUStringLiteral USER_PACKAGE_MANAGER = u"user"; +constexpr OUStringLiteral SHARED_PACKAGE_MANAGER = u"shared"; + +using namespace ::com::sun::star; + +namespace dp_gui { + +namespace { + +struct FindWeakRef +{ + const uno::Reference<deployment::XPackage> m_extension; + + explicit FindWeakRef( uno::Reference<deployment::XPackage> ext): m_extension(std::move(ext)) {} + bool operator () (uno::WeakReference< deployment::XPackage > const & ref); +}; + +bool FindWeakRef::operator () (uno::WeakReference< deployment::XPackage > const & ref) +{ + const uno::Reference<deployment::XPackage> ext(ref); + return ext == m_extension; +} + +} // end namespace + +// struct Entry_Impl + +Entry_Impl::Entry_Impl( const uno::Reference< deployment::XPackage > &xPackage, + const PackageState eState, const bool bReadOnly ) : + m_bActive( false ), + m_bLocked( bReadOnly ), + m_bHasOptions( false ), + m_bUser( false ), + m_bShared( false ), + m_bNew( false ), + m_bChecked( false ), + m_bMissingDeps( false ), + m_bHasButtons( false ), + m_bMissingLic( false ), + m_eState( eState ), + m_xPackage( xPackage ) +{ + try + { + m_sTitle = xPackage->getDisplayName(); + m_sVersion = xPackage->getVersion(); + m_sDescription = xPackage->getDescription(); + m_sLicenseText = xPackage->getLicenseText(); + + beans::StringPair aInfo( m_xPackage->getPublisherInfo() ); + m_sPublisher = aInfo.First; + m_sPublisherURL = aInfo.Second; + + // get the icons for the package if there are any + uno::Reference< graphic::XGraphic > xGraphic = xPackage->getIcon( false ); + if ( xGraphic.is() ) + m_aIcon = Image( xGraphic ); + + if ( eState == AMBIGUOUS ) + m_sErrorText = DpResId( RID_STR_ERROR_UNKNOWN_STATUS ); + else if ( eState == NOT_REGISTERED ) + checkDependencies(); + } + catch (const deployment::ExtensionRemovedException &) {} + catch (const uno::RuntimeException &) {} +} + + +Entry_Impl::~Entry_Impl() +{} + + +sal_Int32 Entry_Impl::CompareTo( const CollatorWrapper *pCollator, const TEntry_Impl& rEntry ) const +{ + sal_Int32 eCompare = pCollator->compareString( m_sTitle, rEntry->m_sTitle ); + if ( eCompare == 0 ) + { + eCompare = m_sVersion.compareTo( rEntry->m_sVersion ); + if ( eCompare == 0 ) + { + sal_Int32 nCompare = m_xPackage->getRepositoryName().compareTo( rEntry->m_xPackage->getRepositoryName() ); + if ( nCompare < 0 ) + eCompare = -1; + else if ( nCompare > 0 ) + eCompare = 1; + } + } + return eCompare; +} + + +void Entry_Impl::checkDependencies() +{ + try { + m_xPackage->checkDependencies( uno::Reference< ucb::XCommandEnvironment >() ); + } + catch ( const deployment::DeploymentException &e ) + { + deployment::DependencyException depExc; + if ( e.Cause >>= depExc ) + { + OUStringBuffer aMissingDep( DpResId( RID_STR_ERROR_MISSING_DEPENDENCIES ) ); + for ( const auto& i : std::as_const(depExc.UnsatisfiedDependencies) ) + { + aMissingDep.append("\n" + + dp_misc::Dependencies::getErrorText(i)); + } + aMissingDep.append("\n"); + m_sErrorText = aMissingDep.makeStringAndClear(); + m_bMissingDeps = true; + } + } +} + +// ExtensionRemovedListener + +void ExtensionRemovedListener::disposing( lang::EventObject const & rEvt ) +{ + uno::Reference< deployment::XPackage > xPackage( rEvt.Source, uno::UNO_QUERY ); + + if ( xPackage.is() ) + { + m_pParent->removeEntry( xPackage ); + } +} + + +ExtensionRemovedListener::~ExtensionRemovedListener() +{ +} + + +// ExtensionBox_Impl +ExtensionBox_Impl::ExtensionBox_Impl(std::unique_ptr<weld::ScrolledWindow> xScroll) + : m_bHasScrollBar( false ) + , m_bHasActive( false ) + , m_bNeedsRecalc( true ) + , m_bInCheckMode( false ) + , m_bAdjustActive( false ) + , m_bInDelete( false ) + , m_nActive( 0 ) + , m_nTopIndex( 0 ) + , m_nStdHeight( 0 ) + , m_nActiveHeight( 0 ) + , m_aSharedImage(StockImage::Yes, RID_BMP_SHARED) + , m_aLockedImage(StockImage::Yes, RID_BMP_LOCKED) + , m_aWarningImage(StockImage::Yes, RID_BMP_WARNING) + , m_aDefaultImage(StockImage::Yes, RID_BMP_EXTENSION) + , m_pManager( nullptr ) + , m_xScrollBar(std::move(xScroll)) +{ +} + +void ExtensionBox_Impl::Init() +{ + m_xScrollBar->connect_vadjustment_changed( LINK( this, ExtensionBox_Impl, ScrollHdl ) ); + + auto nIconHeight = 2*TOP_OFFSET + SMALL_ICON_SIZE; + auto nTitleHeight = 2*TOP_OFFSET + GetTextHeight(); + if ( nIconHeight < nTitleHeight ) + m_nStdHeight = nTitleHeight; + else + m_nStdHeight = nIconHeight; + m_nStdHeight += GetTextHeight() + TOP_OFFSET; + + nIconHeight = ICON_HEIGHT + 2*TOP_OFFSET + 1; + if ( m_nStdHeight < nIconHeight ) + m_nStdHeight = nIconHeight; + + m_nActiveHeight = m_nStdHeight; + + m_xRemoveListener = new ExtensionRemovedListener( this ); + + m_pLocale.reset( new lang::Locale( Application::GetSettings().GetLanguageTag().getLocale() ) ); + m_oCollator.emplace( ::comphelper::getProcessComponentContext() ); + m_oCollator->loadDefaultCollator( *m_pLocale, i18n::CollatorOptions::CollatorOptions_IGNORE_CASE ); +} + +ExtensionBox_Impl::~ExtensionBox_Impl() +{ + if ( ! m_bInDelete ) + DeleteRemoved(); + + m_bInDelete = true; + + for (auto const& entry : m_vEntries) + { + entry->m_xPackage->removeEventListener( m_xRemoveListener ); + } + + m_vEntries.clear(); + + m_xRemoveListener.clear(); + + m_pLocale.reset(); + m_oCollator.reset(); +} + +sal_Int32 ExtensionBox_Impl::getItemCount() const +{ + return static_cast< sal_Int32 >( m_vEntries.size() ); +} + + +sal_Int32 ExtensionBox_Impl::getSelIndex() const +{ + if ( m_bHasActive ) + { + OSL_ASSERT( m_nActive >= -1); + return static_cast< sal_Int32 >( m_nActive ); + } + else + return ENTRY_NOTFOUND; +} + + +// Title + description +void ExtensionBox_Impl::CalcActiveHeight( const tools::Long nPos ) +{ + const ::osl::MutexGuard aGuard( m_entriesMutex ); + + // get title height + tools::Long aTextHeight; + tools::Long nIconHeight = 2*TOP_OFFSET + SMALL_ICON_SIZE; + tools::Long nTitleHeight = 2*TOP_OFFSET + GetTextHeight(); + if ( nIconHeight < nTitleHeight ) + aTextHeight = nTitleHeight; + else + aTextHeight = nIconHeight; + + // calc description height + Size aSize = GetOutputSizePixel(); + + aSize.AdjustWidth( -(ICON_OFFSET) ); + aSize.setHeight( 10000 ); + + OUString aText( m_vEntries[ nPos ]->m_sErrorText ); + if ( !aText.isEmpty() ) + aText += "\n"; + aText += m_vEntries[ nPos ]->m_sDescription; + + tools::Rectangle aRect = GetDrawingArea()->get_ref_device().GetTextRect(tools::Rectangle( Point(), aSize ), aText, + DrawTextFlags::MultiLine | DrawTextFlags::WordBreak); + aTextHeight += aRect.GetHeight(); + + if ( aTextHeight < m_nStdHeight ) + aTextHeight = m_nStdHeight; + + m_nActiveHeight = aTextHeight; + + if ( m_vEntries[ nPos ]->m_bHasButtons ) + m_nActiveHeight += 2; +} + +tools::Rectangle ExtensionBox_Impl::GetEntryRect( const tools::Long nPos ) const +{ + const ::osl::MutexGuard aGuard( m_entriesMutex ); + + Size aSize( GetOutputSizePixel() ); + + if ( m_vEntries[ nPos ]->m_bActive ) + aSize.setHeight( m_nActiveHeight ); + else + aSize.setHeight( m_nStdHeight ); + + Point aPos( 0, -m_nTopIndex + nPos * m_nStdHeight ); + if ( m_bHasActive && ( nPos < m_nActive ) ) + aPos.AdjustY(m_nActiveHeight - m_nStdHeight ); + + return tools::Rectangle( aPos, aSize ); +} + + +void ExtensionBox_Impl::DeleteRemoved() +{ + const ::osl::MutexGuard aGuard( m_entriesMutex ); + + m_bInDelete = true; + + m_vRemovedEntries.clear(); + + m_bInDelete = false; +} + + +//This function may be called with nPos < 0 +void ExtensionBox_Impl::selectEntry( const tools::Long nPos ) +{ + bool invalidate = false; + { + //ToDo we should not use the guard at such a big scope here. + //Currently it is used to guard m_vEntries and m_nActive. m_nActive will be + //modified in this function. + //It would be probably best to always use a copy of m_vEntries + //and some other state variables from ExtensionBox_Impl for + //the whole painting operation. See issue i86993 + ::osl::MutexGuard guard(m_entriesMutex); + + if ( m_bInCheckMode ) + return; + + if ( m_bHasActive ) + { + if ( nPos == m_nActive ) + return; + + m_bHasActive = false; + m_vEntries[ m_nActive ]->m_bActive = false; + } + + if ( ( nPos >= 0 ) && ( o3tl::make_unsigned(nPos) < m_vEntries.size() ) ) + { + m_bHasActive = true; + m_nActive = nPos; + m_vEntries[ nPos ]->m_bActive = true; + + if ( IsReallyVisible() ) + { + m_bAdjustActive = true; + } + } + + if ( IsReallyVisible() ) + { + m_bNeedsRecalc = true; + invalidate = true; + } + } + + if (invalidate) + { + SolarMutexGuard g; + Invalidate(); + } +} + + +void ExtensionBox_Impl::DrawRow(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect, const TEntry_Impl& rEntry) +{ + const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings(); + + if (rEntry->m_bActive) + rRenderContext.SetTextColor(rStyleSettings.GetHighlightTextColor()); + else if ((rEntry->m_eState != REGISTERED) && (rEntry->m_eState != NOT_AVAILABLE)) + rRenderContext.SetTextColor(rStyleSettings.GetDisableColor()); + else + rRenderContext.SetTextColor(rStyleSettings.GetFieldTextColor()); + + if (rEntry->m_bActive) + { + rRenderContext.SetLineColor(); + rRenderContext.SetFillColor(rStyleSettings.GetHighlightColor()); + rRenderContext.DrawRect(rRect); + } + else + { + rRenderContext.SetBackground(rStyleSettings.GetFieldColor()); + rRenderContext.SetTextFillColor(); + rRenderContext.Erase(rRect); + } + + // Draw extension icon + Point aPos( rRect.TopLeft() ); + aPos += Point(TOP_OFFSET, TOP_OFFSET); + Image aImage; + if (!rEntry->m_aIcon) + aImage = m_aDefaultImage; + else + aImage = rEntry->m_aIcon; + Size aImageSize = aImage.GetSizePixel(); + if ((aImageSize.Width() <= ICON_WIDTH ) && ( aImageSize.Height() <= ICON_HEIGHT ) ) + rRenderContext.DrawImage(Point(aPos.X() + ((ICON_WIDTH - aImageSize.Width()) / 2), + aPos.Y() + ((ICON_HEIGHT - aImageSize.Height()) / 2)), + aImage); + else + rRenderContext.DrawImage(aPos, Size(ICON_WIDTH, ICON_HEIGHT), aImage); + + // Setup fonts + // expand the point size of the desired font to the equivalent pixel size + weld::SetPointFont(rRenderContext, GetDrawingArea()->get_font()); + vcl::Font aStdFont(rRenderContext.GetFont()); + vcl::Font aBoldFont(aStdFont); + aBoldFont.SetWeight(WEIGHT_BOLD); + rRenderContext.SetFont(aBoldFont); + auto aTextHeight = rRenderContext.GetTextHeight(); + + // Get max title width + auto nMaxTitleWidth = rRect.GetWidth() - ICON_OFFSET; + nMaxTitleWidth -= (2 * SMALL_ICON_SIZE) + (4 * SPACE_BETWEEN); + rRenderContext.SetFont(aStdFont); + tools::Long nLinkWidth = 0; + if (!rEntry->m_sPublisher.isEmpty()) + { + nLinkWidth = rRenderContext.GetTextWidth(rEntry->m_sPublisher); + nMaxTitleWidth -= nLinkWidth + (2 * SPACE_BETWEEN); + } + tools::Long aVersionWidth = rRenderContext.GetTextWidth(rEntry->m_sVersion); + + aPos = rRect.TopLeft() + Point(ICON_OFFSET, TOP_OFFSET); + + rRenderContext.SetFont(aBoldFont); + tools::Long aTitleWidth = rRenderContext.GetTextWidth(rEntry->m_sTitle) + (aTextHeight / 3); + if (aTitleWidth > nMaxTitleWidth - aVersionWidth) + { + aTitleWidth = nMaxTitleWidth - aVersionWidth - (aTextHeight / 3); + OUString aShortTitle = rRenderContext.GetEllipsisString(rEntry->m_sTitle, aTitleWidth); + rRenderContext.DrawText(aPos, aShortTitle); + aTitleWidth += (aTextHeight / 3); + } + else + rRenderContext.DrawText(aPos, rEntry->m_sTitle); + + rRenderContext.SetFont(aStdFont); + rRenderContext.DrawText(Point(aPos.X() + aTitleWidth, aPos.Y()), rEntry->m_sVersion); + + tools::Long nIconHeight = TOP_OFFSET + SMALL_ICON_SIZE; + tools::Long nTitleHeight = TOP_OFFSET + GetTextHeight(); + if ( nIconHeight < nTitleHeight ) + aTextHeight = nTitleHeight; + else + aTextHeight = nIconHeight; + + // draw description + OUString sDescription; + if (!rEntry->m_sErrorText.isEmpty()) + { + if (rEntry->m_bActive) + sDescription = rEntry->m_sErrorText + "\n" + rEntry->m_sDescription; + else + sDescription = rEntry->m_sErrorText; + } + else + sDescription = rEntry->m_sDescription; + + aPos.AdjustY(aTextHeight ); + if (rEntry->m_bActive) + { + tools::Long nExtraHeight = 0; + + if (rEntry->m_bHasButtons) + nExtraHeight = 2; + + rRenderContext.DrawText(tools::Rectangle(aPos.X(), aPos.Y(), rRect.Right(), rRect.Bottom() - nExtraHeight), + sDescription, DrawTextFlags::MultiLine | DrawTextFlags::WordBreak ); + } + else + { + //replace LF to space, so words do not stick together in one line view + sDescription = sDescription.replace(0x000A, ' '); + const tools::Long nWidth = rRenderContext.GetTextWidth( sDescription ); + if (nWidth > rRect.GetWidth() - aPos.X()) + sDescription = rRenderContext.GetEllipsisString(sDescription, rRect.GetWidth() - aPos.X()); + rRenderContext.DrawText(aPos, sDescription); + } + + // Draw publisher link + if (!rEntry->m_sPublisher.isEmpty()) + { + aPos = rRect.TopLeft() + Point( ICON_OFFSET + nMaxTitleWidth + (2*SPACE_BETWEEN), TOP_OFFSET ); + + rRenderContext.Push(vcl::PushFlags::FONT | vcl::PushFlags::TEXTCOLOR | vcl::PushFlags::TEXTFILLCOLOR); + rRenderContext.SetTextColor(rStyleSettings.GetLinkColor()); + rRenderContext.SetTextFillColor(rStyleSettings.GetFieldColor()); + vcl::Font aFont = rRenderContext.GetFont(); + // to underline + aFont.SetUnderline(LINESTYLE_SINGLE); + rRenderContext.SetFont(aFont); + rRenderContext.DrawText(aPos, rEntry->m_sPublisher); + rEntry->m_aLinkRect = tools::Rectangle(aPos, Size(nLinkWidth, aTextHeight)); + rRenderContext.Pop(); + } + + // Draw status icons + if (!rEntry->m_bUser) + { + aPos = rRect.TopRight() + Point( -(RIGHT_ICON_OFFSET + SMALL_ICON_SIZE), TOP_OFFSET ); + if (rEntry->m_bLocked) + rRenderContext.DrawImage(aPos, Size(SMALL_ICON_SIZE, SMALL_ICON_SIZE), m_aLockedImage); + else + rRenderContext.DrawImage(aPos, Size(SMALL_ICON_SIZE, SMALL_ICON_SIZE), m_aSharedImage); + } + if ((rEntry->m_eState == AMBIGUOUS ) || rEntry->m_bMissingDeps || rEntry->m_bMissingLic) + { + aPos = rRect.TopRight() + Point(-(RIGHT_ICON_OFFSET + SPACE_BETWEEN + 2 * SMALL_ICON_SIZE), TOP_OFFSET); + rRenderContext.DrawImage(aPos, Size(SMALL_ICON_SIZE, SMALL_ICON_SIZE), m_aWarningImage); + } + + rRenderContext.SetLineColor(COL_LIGHTGRAY); + rRenderContext.DrawLine(rRect.BottomLeft(), rRect.BottomRight()); +} + + +void ExtensionBox_Impl::RecalcAll() +{ + if ( m_bHasActive ) + CalcActiveHeight( m_nActive ); + + SetupScrollBar(); + + if ( m_bHasActive ) + { + tools::Rectangle aEntryRect = GetEntryRect( m_nActive ); + + if ( m_bAdjustActive ) + { + m_bAdjustActive = false; + + // If the top of the selected entry isn't visible, make it visible + if ( aEntryRect.Top() < 0 ) + { + m_nTopIndex += aEntryRect.Top(); + aEntryRect.Move( 0, -aEntryRect.Top() ); + } + + // If the bottom of the selected entry isn't visible, make it visible even if now the top + // isn't visible any longer ( the buttons are more important ) + Size aOutputSize = GetOutputSizePixel(); + if ( aEntryRect.Bottom() > aOutputSize.Height() ) + { + m_nTopIndex += ( aEntryRect.Bottom() - aOutputSize.Height() ); + aEntryRect.Move( 0, -( aEntryRect.Bottom() - aOutputSize.Height() ) ); + } + + // If there is unused space below the last entry but all entries don't fit into the box, + // move the content down to use the whole space + const tools::Long nTotalHeight = GetTotalHeight(); + if ( m_bHasScrollBar && ( aOutputSize.Height() + m_nTopIndex > nTotalHeight ) ) + { + tools::Long nOffset = m_nTopIndex; + m_nTopIndex = nTotalHeight - aOutputSize.Height(); + nOffset -= m_nTopIndex; + aEntryRect.Move( 0, nOffset ); + } + + if ( m_bHasScrollBar ) + m_xScrollBar->vadjustment_set_value( m_nTopIndex ); + } + } + + m_bNeedsRecalc = false; +} + + +bool ExtensionBox_Impl::HandleCursorKey( sal_uInt16 nKeyCode ) +{ + if ( m_vEntries.empty() ) + return true; + + tools::Long nSelect = 0; + + if ( m_bHasActive ) + { + tools::Long nPageSize = GetOutputSizePixel().Height() / m_nStdHeight; + if ( nPageSize < 2 ) + nPageSize = 2; + + if ( ( nKeyCode == KEY_DOWN ) || ( nKeyCode == KEY_RIGHT ) ) + nSelect = m_nActive + 1; + else if ( ( nKeyCode == KEY_UP ) || ( nKeyCode == KEY_LEFT ) ) + nSelect = m_nActive - 1; + else if ( nKeyCode == KEY_HOME ) + nSelect = 0; + else if ( nKeyCode == KEY_END ) + nSelect = m_vEntries.size() - 1; + else if ( nKeyCode == KEY_PAGEUP ) + nSelect = m_nActive - nPageSize + 1; + else if ( nKeyCode == KEY_PAGEDOWN ) + nSelect = m_nActive + nPageSize - 1; + } + else // when there is no selected entry, we will select the first or the last. + { + if ( ( nKeyCode == KEY_DOWN ) || ( nKeyCode == KEY_PAGEDOWN ) || ( nKeyCode == KEY_HOME ) ) + nSelect = 0; + else if ( ( nKeyCode == KEY_UP ) || ( nKeyCode == KEY_PAGEUP ) || ( nKeyCode == KEY_END ) ) + nSelect = m_vEntries.size() - 1; + } + + if ( nSelect < 0 ) + nSelect = 0; + if ( o3tl::make_unsigned(nSelect) >= m_vEntries.size() ) + nSelect = m_vEntries.size() - 1; + + selectEntry( nSelect ); + + return true; +} + + +void ExtensionBox_Impl::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& /*rPaintRect*/) +{ + if ( !m_bInDelete ) + DeleteRemoved(); + + if ( m_bNeedsRecalc ) + RecalcAll(); + + Point aStart( 0, -m_nTopIndex ); + Size aSize(GetOutputSizePixel()); + + const ::osl::MutexGuard aGuard( m_entriesMutex ); + + for (auto const& entry : m_vEntries) + { + aSize.setHeight( entry->m_bActive ? m_nActiveHeight : m_nStdHeight ); + tools::Rectangle aEntryRect( aStart, aSize ); + DrawRow(rRenderContext, aEntryRect, entry); + aStart.AdjustY(aSize.Height() ); + } +} + + +tools::Long ExtensionBox_Impl::GetTotalHeight() const +{ + tools::Long nHeight = m_vEntries.size() * m_nStdHeight; + + if ( m_bHasActive ) + { + nHeight += m_nActiveHeight - m_nStdHeight; + } + + return nHeight; +} + + +void ExtensionBox_Impl::SetupScrollBar() +{ + const Size aSize = GetOutputSizePixel(); + const auto nTotalHeight = GetTotalHeight(); + const bool bNeedsScrollBar = ( nTotalHeight > aSize.Height() ); + + if ( bNeedsScrollBar ) + { + if ( m_nTopIndex + aSize.Height() > nTotalHeight ) + m_nTopIndex = nTotalHeight - aSize.Height(); + + m_xScrollBar->vadjustment_configure(m_nTopIndex, 0, nTotalHeight, + m_nStdHeight, ( aSize.Height() * 4 ) / 5, + aSize.Height()); + + if (!m_bHasScrollBar) + m_xScrollBar->set_vpolicy(VclPolicyType::ALWAYS); + } + else if ( m_bHasScrollBar ) + { + m_xScrollBar->set_vpolicy(VclPolicyType::NEVER); + m_nTopIndex = 0; + } + + m_bHasScrollBar = bNeedsScrollBar; +} + + +void ExtensionBox_Impl::Resize() +{ + RecalcAll(); + Invalidate(); +} + +void ExtensionBox_Impl::SetDrawingArea(weld::DrawingArea* pDrawingArea) +{ + Size aSize = pDrawingArea->get_ref_device().LogicToPixel(Size(250, 150), MapMode(MapUnit::MapAppFont)); + pDrawingArea->set_size_request(aSize.Width(), aSize.Height()); + CustomWidgetController::SetDrawingArea(pDrawingArea); + SetOutputSizePixel(aSize); + + Init(); +} + +tools::Long ExtensionBox_Impl::PointToPos( const Point& rPos ) +{ + tools::Long nPos = ( rPos.Y() + m_nTopIndex ) / m_nStdHeight; + + if ( m_bHasActive && ( nPos > m_nActive ) ) + { + if ( rPos.Y() + m_nTopIndex <= m_nActive*m_nStdHeight + m_nActiveHeight ) + nPos = m_nActive; + else + nPos = ( rPos.Y() + m_nTopIndex - (m_nActiveHeight - m_nStdHeight) ) / m_nStdHeight; + } + + return nPos; +} + +bool ExtensionBox_Impl::MouseMove( const MouseEvent& rMEvt ) +{ + bool bOverHyperlink = false; + + auto nPos = PointToPos( rMEvt.GetPosPixel() ); + if ( ( nPos >= 0 ) && ( o3tl::make_unsigned(nPos) < m_vEntries.size() ) ) + { + const auto& rEntry = m_vEntries[nPos]; + bOverHyperlink = !rEntry->m_sPublisher.isEmpty() && rEntry->m_aLinkRect.Contains(rMEvt.GetPosPixel()); + } + + if (bOverHyperlink) + SetPointer(PointerStyle::RefHand); + else + SetPointer(PointerStyle::Arrow); + + return false; +} + +OUString ExtensionBox_Impl::RequestHelp(tools::Rectangle& rRect) +{ + auto nPos = PointToPos( rRect.TopLeft() ); + if ( ( nPos >= 0 ) && ( o3tl::make_unsigned(nPos) < m_vEntries.size() ) ) + { + const auto& rEntry = m_vEntries[nPos]; + bool bOverHyperlink = !rEntry->m_sPublisher.isEmpty() && rEntry->m_aLinkRect.Contains(rRect); + if (bOverHyperlink) + { + rRect = rEntry->m_aLinkRect; + return rEntry->m_sPublisherURL; + } + } + + return OUString(); +} + +bool ExtensionBox_Impl::MouseButtonDown( const MouseEvent& rMEvt ) +{ + if ( !rMEvt.IsLeft() ) + return false; + + if (rMEvt.IsMod1() && m_bHasActive) + selectEntry(ExtensionBox_Impl::ENTRY_NOTFOUND); // Selecting a not existing entry will deselect the current one + else + { + auto nPos = PointToPos( rMEvt.GetPosPixel() ); + + if ( ( nPos >= 0 ) && ( o3tl::make_unsigned(nPos) < m_vEntries.size() ) ) + { + const auto& rEntry = m_vEntries[nPos]; + if (!rEntry->m_sPublisher.isEmpty() && rEntry->m_aLinkRect.Contains(rMEvt.GetPosPixel())) + { + try + { + css::uno::Reference<css::system::XSystemShellExecute> xSystemShellExecute( + css::system::SystemShellExecute::create(comphelper::getProcessComponentContext())); + //throws css::lang::IllegalArgumentException, css::system::SystemShellExecuteException + xSystemShellExecute->execute(rEntry->m_sPublisherURL, OUString(), css::system::SystemShellExecuteFlags::URIS_ONLY); + } + catch (...) + { + } + return true; + } + } + + selectEntry( nPos ); + } + return true; +} + +bool ExtensionBox_Impl::KeyInput(const KeyEvent& rKEvt) +{ + if ( !m_bInDelete ) + DeleteRemoved(); + + vcl::KeyCode aKeyCode = rKEvt.GetKeyCode(); + sal_uInt16 nKeyCode = aKeyCode.GetCode(); + + bool bHandled = false; + if (nKeyCode != KEY_TAB && aKeyCode.GetGroup() == KEYGROUP_CURSOR) + bHandled = HandleCursorKey(nKeyCode); + + return bHandled; +} + +bool ExtensionBox_Impl::FindEntryPos( const TEntry_Impl& rEntry, const tools::Long nStart, + const tools::Long nEnd, tools::Long &nPos ) +{ + nPos = nStart; + if ( nStart > nEnd ) + return false; + + sal_Int32 eCompare; + + if ( nStart == nEnd ) + { + eCompare = rEntry->CompareTo( &*m_oCollator, m_vEntries[ nStart ] ); + if ( eCompare < 0 ) + return false; + else if ( eCompare == 0 ) + { + //Workaround. See i86963. + if (rEntry->m_xPackage != m_vEntries[nStart]->m_xPackage) + return false; + + if ( m_bInCheckMode ) + m_vEntries[ nStart ]->m_bChecked = true; + return true; + } + else + { + nPos = nStart + 1; + return false; + } + } + + const tools::Long nMid = nStart + ( ( nEnd - nStart ) / 2 ); + eCompare = rEntry->CompareTo( &*m_oCollator, m_vEntries[ nMid ] ); + + if ( eCompare < 0 ) + return FindEntryPos( rEntry, nStart, nMid-1, nPos ); + else if ( eCompare > 0 ) + return FindEntryPos( rEntry, nMid+1, nEnd, nPos ); + else + { + //Workaround.See i86963. + if (rEntry->m_xPackage != m_vEntries[nMid]->m_xPackage) + return false; + + if ( m_bInCheckMode ) + m_vEntries[ nMid ]->m_bChecked = true; + nPos = nMid; + return true; + } +} + +void ExtensionBox_Impl::cleanVecListenerAdded() +{ + std::erase_if(m_vListenerAdded, + [](const uno::WeakReference<deployment::XPackage>& rxListener) { + const uno::Reference<deployment::XPackage> hardRef(rxListener); + return !hardRef.is(); + }); +} + +void ExtensionBox_Impl::addEventListenerOnce( + uno::Reference<deployment::XPackage > const & extension) +{ + //make sure to only add the listener once + cleanVecListenerAdded(); + if ( std::none_of(m_vListenerAdded.begin(), m_vListenerAdded.end(), + FindWeakRef(extension)) ) + { + extension->addEventListener( m_xRemoveListener ); + m_vListenerAdded.emplace_back(extension); + } +} + + +void ExtensionBox_Impl::addEntry( const uno::Reference< deployment::XPackage > &xPackage, + bool bLicenseMissing ) +{ + PackageState eState = TheExtensionManager::getPackageState( xPackage ); + bool bLocked = m_pManager->isReadOnly( xPackage ); + + TEntry_Impl pEntry = std::make_shared<Entry_Impl>( xPackage, eState, bLocked ); + + // Don't add empty entries + if ( pEntry->m_sTitle.isEmpty() ) + return; + + { + osl::MutexGuard guard(m_entriesMutex); + tools::Long nPos = 0; + if (m_vEntries.empty()) + { + addEventListenerOnce(xPackage); + m_vEntries.push_back(pEntry); + } + else + { + if (!FindEntryPos(pEntry, 0, m_vEntries.size() - 1, nPos)) + { + addEventListenerOnce(xPackage); + m_vEntries.insert(m_vEntries.begin() + nPos, pEntry); + } + else if (!m_bInCheckMode) + { + OSL_FAIL("ExtensionBox_Impl::addEntry(): Will not add duplicate entries"); + } + } + + pEntry->m_bHasOptions = m_pManager->supportsOptions(xPackage); + pEntry->m_bUser = (xPackage->getRepositoryName() == USER_PACKAGE_MANAGER); + pEntry->m_bShared = (xPackage->getRepositoryName() == SHARED_PACKAGE_MANAGER); + pEntry->m_bNew = m_bInCheckMode; + pEntry->m_bMissingLic = bLicenseMissing; + + if (bLicenseMissing) + pEntry->m_sErrorText = DpResId(RID_STR_ERROR_MISSING_LICENSE); + + //access to m_nActive must be guarded + if (!m_bInCheckMode && m_bHasActive && (m_nActive >= nPos)) + m_nActive += 1; + } + + if ( IsReallyVisible() ) + Invalidate(); + + m_bNeedsRecalc = true; +} + +void ExtensionBox_Impl::updateEntry( const uno::Reference< deployment::XPackage > &xPackage ) +{ + for (auto const& entry : m_vEntries) + { + if ( entry->m_xPackage == xPackage ) + { + PackageState eState = TheExtensionManager::getPackageState( xPackage ); + entry->m_bHasOptions = m_pManager->supportsOptions( xPackage ); + entry->m_eState = eState; + entry->m_sTitle = xPackage->getDisplayName(); + entry->m_sVersion = xPackage->getVersion(); + entry->m_sDescription = xPackage->getDescription(); + + if ( eState == REGISTERED ) + entry->m_bMissingLic = false; + + if ( eState == AMBIGUOUS ) + entry->m_sErrorText = DpResId( RID_STR_ERROR_UNKNOWN_STATUS ); + else if ( ! entry->m_bMissingLic ) + entry->m_sErrorText.clear(); + + if ( IsReallyVisible() ) + Invalidate(); + break; + } + } +} + +//This function is also called as a result of removing an extension. +//see PackageManagerImpl::removePackage +//The gui is a registered as listener on the package. Removing it will cause the +//listeners to be notified and then this function is called. At this moment xPackage +//is in the disposing state and all calls on it may result in a DisposedException. +void ExtensionBox_Impl::removeEntry( const uno::Reference< deployment::XPackage > &xPackage ) +{ + if ( m_bInDelete ) + return; + + bool invalidate = false; + { + ::osl::ClearableMutexGuard aGuard( m_entriesMutex ); + + auto iIndex = std::find_if(m_vEntries.begin(), m_vEntries.end(), + [&xPackage](const TEntry_Impl& rxEntry) { return rxEntry->m_xPackage == xPackage; }); + if (iIndex != m_vEntries.end()) + { + tools::Long nPos = iIndex - m_vEntries.begin(); + + // Entries mustn't be removed here, because they contain a hyperlink control + // which can only be deleted when the thread has the solar mutex. Therefore + // the entry will be moved into the m_vRemovedEntries list which will be + // cleared on the next paint event + m_vRemovedEntries.push_back( *iIndex ); + (*iIndex)->m_xPackage->removeEventListener(m_xRemoveListener); + m_vEntries.erase( iIndex ); + + m_bNeedsRecalc = true; + + if ( IsReallyVisible() ) + invalidate = true; + + if ( m_bHasActive ) + { + if ( nPos < m_nActive ) + m_nActive -= 1; + else if ( ( nPos == m_nActive ) && + ( nPos == static_cast<tools::Long>(m_vEntries.size()) ) ) + m_nActive -= 1; + + m_bHasActive = false; + //clear before calling out of this method + aGuard.clear(); + selectEntry( m_nActive ); + } + } + } + + if (invalidate) + { + SolarMutexGuard g; + Invalidate(); + } +} + + +void ExtensionBox_Impl::RemoveUnlocked() +{ + bool bAllRemoved = false; + + while ( ! bAllRemoved ) + { + bAllRemoved = true; + + ::osl::ClearableMutexGuard aGuard( m_entriesMutex ); + + for (auto const& entry : m_vEntries) + { + if ( !entry->m_bLocked ) + { + bAllRemoved = false; + uno::Reference< deployment::XPackage> xPackage = entry->m_xPackage; + aGuard.clear(); + removeEntry( xPackage ); + break; + } + } + } +} + + +void ExtensionBox_Impl::prepareChecking() +{ + m_bInCheckMode = true; + for (auto const& entry : m_vEntries) + { + entry->m_bChecked = false; + entry->m_bNew = false; + } +} + + +void ExtensionBox_Impl::checkEntries() +{ + tools::Long nNewPos = -1; + tools::Long nChangedActivePos = -1; + tools::Long nPos = 0; + bool bNeedsUpdate = false; + + { + osl::MutexGuard guard(m_entriesMutex); + auto iIndex = m_vEntries.begin(); + while (iIndex != m_vEntries.end()) + { + if (!(*iIndex)->m_bChecked) + { + (*iIndex)->m_bChecked = true; + bNeedsUpdate = true; + nPos = iIndex - m_vEntries.begin(); + if ((*iIndex)->m_bNew) + { // add entry to list and correct active pos + if (nNewPos == -1) + nNewPos = nPos; + if (nPos <= m_nActive) + m_nActive += 1; + ++iIndex; + } + else + { // remove entry from list + if (nPos < nNewPos) + { + --nNewPos; + } + if (nPos < nChangedActivePos) + { + --nChangedActivePos; + } + if (nPos < m_nActive) + m_nActive -= 1; + else if (nPos == m_nActive) + { + nChangedActivePos = nPos; + m_nActive = -1; + m_bHasActive = false; + } + m_vRemovedEntries.push_back(*iIndex); + (*iIndex)->m_xPackage->removeEventListener(m_xRemoveListener); + iIndex = m_vEntries.erase(iIndex); + } + } + else + ++iIndex; + } + } + + m_bInCheckMode = false; + + if ( nNewPos != - 1) + selectEntry( nNewPos ); + else if (nChangedActivePos != -1) { + selectEntry(nChangedActivePos); + } + + if ( bNeedsUpdate ) + { + m_bNeedsRecalc = true; + if ( IsReallyVisible() ) + Invalidate(); + } +} + +IMPL_LINK(ExtensionBox_Impl, ScrollHdl, weld::ScrolledWindow&, rScrBar, void) +{ + m_nTopIndex = rScrBar.vadjustment_get_value(); + Invalidate(); +} + +} //namespace dp_gui + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/gui/dp_gui_extlistbox.hxx b/desktop/source/deployment/gui/dp_gui_extlistbox.hxx new file mode 100644 index 0000000000..cfc04f115d --- /dev/null +++ b/desktop/source/deployment/gui/dp_gui_extlistbox.hxx @@ -0,0 +1,215 @@ +/* -*- 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> +#include <vcl/customweld.hxx> +#include <vcl/image.hxx> +#include <vcl/weld.hxx> + +#include <cppuhelper/implbase.hxx> +#include <cppuhelper/weakref.hxx> +#include <unotools/collatorwrapper.hxx> + +#include <com/sun/star/lang/Locale.hpp> +#include <com/sun/star/lang/XEventListener.hpp> +#include <com/sun/star/deployment/XPackage.hpp> + +#include <memory> +#include <optional> + +#include "dp_gui.h" + +namespace dp_gui { + +#define SMALL_ICON_SIZE 16 +#define TOP_OFFSET 5 +#define ICON_HEIGHT 42 +#define ICON_WIDTH 47 +#define ICON_OFFSET 72 +#define RIGHT_ICON_OFFSET 5 +#define SPACE_BETWEEN 3 + +class TheExtensionManager; + + +struct Entry_Impl; + +typedef std::shared_ptr< Entry_Impl > TEntry_Impl; + +struct Entry_Impl +{ + bool m_bActive :1; + bool m_bLocked :1; + bool m_bHasOptions :1; + bool m_bUser :1; + bool m_bShared :1; + bool m_bNew :1; + bool m_bChecked :1; + bool m_bMissingDeps :1; + bool m_bHasButtons :1; + bool m_bMissingLic :1; + PackageState m_eState; + OUString m_sTitle; + OUString m_sVersion; + OUString m_sDescription; + OUString m_sPublisher; + OUString m_sPublisherURL; + OUString m_sErrorText; + OUString m_sLicenseText; + Image m_aIcon; + tools::Rectangle m_aLinkRect; + + css::uno::Reference<css::deployment::XPackage> m_xPackage; + + Entry_Impl(const css::uno::Reference<css::deployment::XPackage> &xPackage, + const PackageState eState, const bool bReadOnly); + ~Entry_Impl(); + + sal_Int32 CompareTo(const CollatorWrapper *pCollator, const TEntry_Impl& rEntry) const; + void checkDependencies(); +}; + +class ExtensionBox_Impl; + + +class ExtensionRemovedListener : public ::cppu::WeakImplHelper<css::lang::XEventListener> +{ + ExtensionBox_Impl* m_pParent; + +public: + + explicit ExtensionRemovedListener( ExtensionBox_Impl *pParent ) { m_pParent = pParent; } + virtual ~ExtensionRemovedListener() override; + + + // XEventListener + virtual void SAL_CALL disposing(css::lang::EventObject const& evt) override; +}; + +class ExtensionBox_Impl : public weld::CustomWidgetController +{ + bool m_bHasScrollBar : 1; + bool m_bHasActive : 1; + bool m_bNeedsRecalc : 1; + bool m_bInCheckMode : 1; + bool m_bAdjustActive : 1; + bool m_bInDelete : 1; + //Must be guarded together with m_vEntries to ensure a valid index at all times. + //Use m_entriesMutex as guard. + tools::Long m_nActive; + tools::Long m_nTopIndex; + tools::Long m_nStdHeight; + tools::Long m_nActiveHeight; + Image m_aSharedImage; + Image m_aLockedImage; + Image m_aWarningImage; + Image m_aDefaultImage; + + rtl::Reference<ExtensionRemovedListener> m_xRemoveListener; + + TheExtensionManager *m_pManager; + //This mutex is used for synchronizing access to m_vEntries. + //Currently it is used to synchronize adding, removing entries and + //functions like getItemName, getItemDescription, etc. to prevent + //that m_vEntries is accessed at an invalid index. + //ToDo: There are many more places where m_vEntries is read and which may + //fail. For example the Paint method is probable called from the main thread + //while new entries are added / removed in a separate thread. + mutable ::osl::Mutex m_entriesMutex; + std::vector< TEntry_Impl > m_vEntries; + std::vector< TEntry_Impl > m_vRemovedEntries; + + std::unique_ptr<css::lang::Locale> m_pLocale; + std::optional<CollatorWrapper> m_oCollator; + + //Holds weak references to extensions to which is we have added an XEventListener + std::vector< css::uno::WeakReference< + css::deployment::XPackage> > m_vListenerAdded; + + std::unique_ptr<weld::ScrolledWindow> m_xScrollBar; + + //Removes the dead weak references from m_vListenerAdded + void cleanVecListenerAdded(); + void addEventListenerOnce(css::uno::Reference<css::deployment::XPackage> const & extension); + + void CalcActiveHeight( const tools::Long nPos ); + tools::Long GetTotalHeight() const; + void SetupScrollBar(); + void DrawRow(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect, const TEntry_Impl& rEntry); + bool HandleCursorKey( sal_uInt16 nKeyCode ); + bool FindEntryPos( const TEntry_Impl& rEntry, tools::Long nStart, tools::Long nEnd, tools::Long &nFound ); + void DeleteRemoved(); + + DECL_LINK( ScrollHdl, weld::ScrolledWindow&, void ); + + void Init(); +public: + explicit ExtensionBox_Impl(std::unique_ptr<weld::ScrolledWindow> xScroll); + virtual ~ExtensionBox_Impl() override; + + virtual bool MouseButtonDown( const MouseEvent& rMEvt ) override; + virtual bool MouseMove( const MouseEvent& rMEvt ) override; + virtual bool KeyInput(const KeyEvent& rKEvt) override; + virtual void Paint( vcl::RenderContext& rRenderContext, const tools::Rectangle &rPaintRect ) override; + virtual void Resize() override; + virtual OUString RequestHelp(tools::Rectangle& rRect) override; + + virtual void SetDrawingArea(weld::DrawingArea* pDrawingArea) override; + + TEntry_Impl const & GetEntryData( tools::Long nPos ) { return m_vEntries[ nPos ]; } + tools::Long GetEntryCount() const { return static_cast<tools::Long>(m_vEntries.size()); } + tools::Rectangle GetEntryRect( const tools::Long nPos ) const; + bool HasActive() const { return m_bHasActive; } + tools::Long PointToPos( const Point& rPos ); + virtual void RecalcAll(); + void RemoveUnlocked(); + + + virtual void selectEntry( const tools::Long nPos ); + void addEntry(const css::uno::Reference<css::deployment::XPackage> &xPackage, + bool bLicenseMissing = false ); + void updateEntry(const css::uno::Reference<css::deployment::XPackage> &xPackage ); + void removeEntry(const css::uno::Reference<css::deployment::XPackage> &xPackage ); + + void prepareChecking(); + void checkEntries(); + + void setExtensionManager(TheExtensionManager* pManager) { m_pManager = pManager; } + + //These functions are used for automatic testing +public: + enum { ENTRY_NOTFOUND = -1 }; + + /** @return The count of the entries in the list box. */ + sal_Int32 getItemCount() const; + + /** @return The index of the first selected entry in the list box. + When nothing is selected, which is the case when getItemCount returns '0', + then this function returns ENTRY_NOTFOUND */ + /** @return The index of the first selected entry in the list box. + When nothing is selected, which is the case when getItemCount returns '0', + then this function returns ENTRY_NOTFOUND */ + sal_Int32 getSelIndex() const; +}; + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/gui/dp_gui_service.cxx b/desktop/source/deployment/gui/dp_gui_service.cxx new file mode 100644 index 0000000000..c359cb7501 --- /dev/null +++ b/desktop/source/deployment/gui/dp_gui_service.cxx @@ -0,0 +1,316 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + + +#include <memory> +#include "dp_gui_theextmgr.hxx" +#include <osl/diagnose.h> +#include <cppuhelper/implbase.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <unotools/configmgr.hxx> +#include <comphelper/processfactory.hxx> +#include <comphelper/unwrapargs.hxx> +#include <unotools/resmgr.hxx> +#include <vcl/weld.hxx> +#include <vcl/window.hxx> +#include <vcl/svapp.hxx> +#include <com/sun/star/task/XJobExecutor.hpp> +#include <com/sun/star/ui/dialogs/XAsynchronousExecutableDialog.hpp> + +#include <optional> +#include "license_dialog.hxx" +#include "dp_gui_dialog2.hxx" +#include "dp_gui_extensioncmdqueue.hxx" +#include <dp_misc.h> + +using namespace ::dp_misc; +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; + +namespace dp_gui { + +namespace { + +class MyApp : public Application +{ +public: + MyApp(); + + MyApp(const MyApp&) = delete; + const MyApp& operator=(const MyApp&) = delete; + + // Application + virtual int Main() override; + virtual void DeInit() override; +}; + +} + +MyApp::MyApp() +{ +} + + +int MyApp::Main() +{ + return EXIT_SUCCESS; +} + +void MyApp::DeInit() +{ + css::uno::Reference< css::uno::XComponentContext > context( + comphelper::getProcessComponentContext()); + dp_misc::disposeBridges(context); + css::uno::Reference< css::lang::XComponent >( + context, css::uno::UNO_QUERY_THROW)->dispose(); + comphelper::setProcessServiceFactory(nullptr); +} + +static OUString ReplaceProductNameHookProc( const OUString& rStr ) +{ + if (rStr.indexOf( "%PRODUCT" ) == -1) + return rStr; + + static const OUString sProductName = utl::ConfigManager::getProductName(); + static const OUString sVersion = utl::ConfigManager::getProductVersion(); + static const OUString sAboutBoxVersion = utl::ConfigManager::getAboutBoxProductVersion(); + static const OUString sAboutBoxVersionSuffix = utl::ConfigManager::getAboutBoxProductVersionSuffix(); + static const OUString sExtension = utl::ConfigManager::getProductExtension(); + static const OUString sOOOVendor = utl::ConfigManager::getVendor(); + + OUString sRet = rStr.replaceAll( "%PRODUCTNAME", sProductName ); + sRet = sRet.replaceAll( "%PRODUCTVERSION", sVersion ); + sRet = sRet.replaceAll( "%ABOUTBOXPRODUCTVERSIONSUFFIX", sAboutBoxVersionSuffix ); + sRet = sRet.replaceAll( "%ABOUTBOXPRODUCTVERSION", sAboutBoxVersion ); + sRet = sRet.replaceAll( "%OOOVENDOR", sOOOVendor ); + sRet = sRet.replaceAll( "%PRODUCTEXTENSION", sExtension ); + return sRet; +} + +namespace { + +class ServiceImpl + : public ::cppu::WeakImplHelper<ui::dialogs::XAsynchronousExecutableDialog, + task::XJobExecutor, css::lang::XServiceInfo> +{ + Reference<XComponentContext> const m_xComponentContext; + std::optional< Reference<awt::XWindow> > /* const */ m_parent; + std::optional<OUString> m_extensionURL; + OUString m_initialTitle; + bool m_bShowUpdateOnly; + +public: + ServiceImpl( Sequence<Any> const & args, + Reference<XComponentContext> const & xComponentContext ); + + // 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; + + // XAsynchronousExecutableDialog + virtual void SAL_CALL setDialogTitle( OUString const & aTitle ) override; + virtual void SAL_CALL startExecuteModal( + Reference< ui::dialogs::XDialogClosedListener > const & xListener ) override; + + // XJobExecutor + virtual void SAL_CALL trigger( OUString const & event ) override; +}; + +} + +ServiceImpl::ServiceImpl( Sequence<Any> const& args, + Reference<XComponentContext> const& xComponentContext) + : m_xComponentContext(xComponentContext), + m_bShowUpdateOnly( false ) +{ + /* if true then this service is running in a unopkg process and not in an office process */ + std::optional<OUString> view; + try { + std::optional<sal_Bool> unopkg; + comphelper::unwrapArgs( args, m_parent, view, unopkg ); + return; + } catch ( const css::lang::IllegalArgumentException & ) { + } + try { + comphelper::unwrapArgs( args, m_extensionURL); + } catch ( const css::lang::IllegalArgumentException & ) { + } + + ResHookProc pProc = Translate::GetReadStringHook(); + if ( !pProc ) + Translate::SetReadStringHook(ReplaceProductNameHookProc); +} + +// XServiceInfo +OUString ServiceImpl::getImplementationName() +{ + return "com.sun.star.comp.deployment.ui.PackageManagerDialog"; +} + +sal_Bool ServiceImpl::supportsService( const OUString& ServiceName ) +{ + return cppu::supportsService(this, ServiceName); +} + +css::uno::Sequence< OUString > ServiceImpl::getSupportedServiceNames() +{ + return { "com.sun.star.deployment.ui.PackageManagerDialog" }; +} + +// XAsynchronousExecutableDialog + +void ServiceImpl::setDialogTitle( OUString const & title ) +{ + if ( dp_gui::TheExtensionManager::s_ExtMgr.is() ) + { + const SolarMutexGuard guard; + ::rtl::Reference< ::dp_gui::TheExtensionManager > dialog( + ::dp_gui::TheExtensionManager::get( m_xComponentContext, + m_parent ? *m_parent : Reference<awt::XWindow>(), + m_extensionURL ? *m_extensionURL : OUString() ) ); + dialog->SetText( title ); + } + else + m_initialTitle = title; +} + + +void ServiceImpl::startExecuteModal( + Reference< ui::dialogs::XDialogClosedListener > const & xListener ) +{ + bool bCloseDialog = true; // only used if m_bShowUpdateOnly is true + std::unique_ptr<Application> app; + //ToDo: synchronize access to s_dialog !!! + if (! dp_gui::TheExtensionManager::s_ExtMgr.is()) + { + const bool bAppUp = (GetpApp() != nullptr); + bool bOfficePipePresent; + try { + bOfficePipePresent = dp_misc::office_is_running(); + } + catch (const Exception & exc) { + if (bAppUp) { + const SolarMutexGuard guard; + vcl::Window* pWin = Application::GetActiveTopWindow(); + std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(pWin ? pWin->GetFrameWeld() : nullptr, + VclMessageType::Warning, VclButtonsType::Ok, exc.Message)); + xBox->run(); + } + throw; + } + + if (! bOfficePipePresent) { + OSL_ASSERT( ! bAppUp ); + app.reset( new MyApp ); + if (! InitVCL() ) + throw RuntimeException( "Cannot initialize VCL!", + static_cast<OWeakObject *>(this) ); + Application::SetDisplayName( + utl::ConfigManager::getProductName() + + " " + + utl::ConfigManager::getProductVersion()); + ExtensionCmdQueue::syncRepositories( m_xComponentContext ); + } + } + else + { + // When m_bShowUpdateOnly is set, we are inside the office and the user clicked + // the update notification icon in the menu bar. We must not close the extensions + // dialog after displaying the update dialog when it has been visible before + if ( m_bShowUpdateOnly ) + bCloseDialog = ! dp_gui::TheExtensionManager::s_ExtMgr->isVisible(); + } + + { + const SolarMutexGuard guard; + ::rtl::Reference< ::dp_gui::TheExtensionManager > myExtMgr( + ::dp_gui::TheExtensionManager::get( + m_xComponentContext, + m_parent ? *m_parent : Reference<awt::XWindow>(), + m_extensionURL ? *m_extensionURL : OUString() ) ); + myExtMgr->createDialog( false ); + if (!m_initialTitle.isEmpty()) { + myExtMgr->SetText( m_initialTitle ); + m_initialTitle.clear(); + } + if ( m_bShowUpdateOnly ) + { + myExtMgr->checkUpdates(); + if ( bCloseDialog ) + myExtMgr->Close(); + else + myExtMgr->ToTop(); + } + else + { + myExtMgr->Show(); + myExtMgr->ToTop(); + } + } + + if (app != nullptr) + { + Application::Execute(); + DeInitVCL(); + } + + if (xListener.is()) + xListener->dialogClosed( + ui::dialogs::DialogClosedEvent( + static_cast< ::cppu::OWeakObject * >(this), + sal_Int16(0)) ); +} + +// XJobExecutor + +void ServiceImpl::trigger( OUString const &rEvent ) +{ + if ( rEvent == "SHOW_UPDATE_DIALOG" ) + m_bShowUpdateOnly = true; + else + m_bShowUpdateOnly = false; + + startExecuteModal( Reference< ui::dialogs::XDialogClosedListener >() ); +} + +} // namespace dp_gui + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +desktop_LicenseDialog_get_implementation( + css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const& args) +{ + return cppu::acquire(new dp_gui::LicenseDialog(args, context)); +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +desktop_ServiceImpl_get_implementation( + css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const& args) +{ + return cppu::acquire(new dp_gui::ServiceImpl(args, context)); +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +desktop_UpdateRequiredDialogService_get_implementation( + css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const& args) +{ + return cppu::acquire(new dp_gui::UpdateRequiredDialogService(args, context)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/gui/dp_gui_theextmgr.cxx b/desktop/source/deployment/gui/dp_gui_theextmgr.cxx new file mode 100644 index 0000000000..48ed3b329b --- /dev/null +++ b/desktop/source/deployment/gui/dp_gui_theextmgr.cxx @@ -0,0 +1,539 @@ +/* -*- 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 <utility> +#include <vcl/svapp.hxx> + +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/configuration/theDefaultProvider.hpp> +#include <com/sun/star/deployment/DeploymentException.hpp> +#include <com/sun/star/deployment/ExtensionManager.hpp> +#include <com/sun/star/lang/WrappedTargetRuntimeException.hpp> +#include <com/sun/star/frame/Desktop.hpp> +#include <com/sun/star/frame/TerminationVetoException.hpp> +#include <com/sun/star/ucb/CommandAbortedException.hpp> +#include <com/sun/star/ucb/CommandFailedException.hpp> +#include <comphelper/propertysequence.hxx> +#include <cppuhelper/exc_hlp.hxx> +#include <osl/diagnose.h> +#include <comphelper/diagnose_ex.hxx> + +#include "dp_gui_dialog2.hxx" +#include "dp_gui_extensioncmdqueue.hxx" +#include "dp_gui_theextmgr.hxx" +#include <dp_misc.h> +#include <dp_update.hxx> + +constexpr OUStringLiteral USER_PACKAGE_MANAGER = u"user"; +constexpr OUString SHARED_PACKAGE_MANAGER = u"shared"_ustr; + +using namespace ::com::sun::star; + +namespace dp_gui { + + +::rtl::Reference< TheExtensionManager > TheExtensionManager::s_ExtMgr; + + +// TheExtensionManager + + +TheExtensionManager::TheExtensionManager( uno::Reference< awt::XWindow > xParent, + const uno::Reference< uno::XComponentContext > &xContext ) : + m_xContext( xContext ), + m_xParent(std::move( xParent )), + m_bModified(false), + m_bExtMgrDialogExecuting(false) +{ + m_xExtensionManager = deployment::ExtensionManager::get( xContext ); + m_xExtensionManager->addModifyListener( this ); + + uno::Reference< lang::XMultiServiceFactory > xConfig( + configuration::theDefaultProvider::get(xContext)); + uno::Sequence<uno::Any> args(comphelper::InitAnyPropertySequence( + { + {"nodepath", uno::Any(OUString("/org.openoffice.Office.OptionsDialog/Nodes"))} + })); + m_xNameAccessNodes.set( + xConfig->createInstanceWithArguments( "com.sun.star.configuration.ConfigurationAccess", args), + uno::UNO_QUERY_THROW); + + // get the 'get more extensions here' url + uno::Sequence<uno::Any> args2(comphelper::InitAnyPropertySequence( + { + {"nodepath", uno::Any(OUString("/org.openoffice.Office.ExtensionManager/ExtensionRepositories"))} + })); + uno::Reference< container::XNameAccess > xNameAccessRepositories; + xNameAccessRepositories.set( + xConfig->createInstanceWithArguments( "com.sun.star.configuration.ConfigurationAccess", args2), + uno::UNO_QUERY_THROW); + try + { //throws css::container::NoSuchElementException, css::lang::WrappedTargetException + uno::Any value = xNameAccessRepositories->getByName("WebsiteLink"); + m_sGetExtensionsURL = value.get< OUString > (); + } + catch ( const uno::Exception& ) + {} + + if ( dp_misc::office_is_running() ) + { + // the registration should be done after the construction has been ended + // otherwise an exception prevents object creation, but it is registered as a listener + m_xDesktop.set( frame::Desktop::create(xContext), uno::UNO_SET_THROW ); + m_xDesktop->addTerminateListener( this ); + } +} + +TheExtensionManager::~TheExtensionManager() +{ + if (m_xUpdReqDialog) + m_xUpdReqDialog->response(RET_CANCEL); + assert(!m_xUpdReqDialog); + if (m_xExtMgrDialog) + { + if (m_bExtMgrDialogExecuting) + m_xExtMgrDialog->response(RET_CANCEL); + else + { + m_xExtMgrDialog->Close(); + m_xExtMgrDialog.reset(); + } + } + assert(!m_xExtMgrDialog); +} + +void TheExtensionManager::createDialog( const bool bCreateUpdDlg ) +{ + const SolarMutexGuard guard; + + if ( bCreateUpdDlg ) + { + if ( !m_xUpdReqDialog ) + { + m_xUpdReqDialog.reset(new UpdateRequiredDialog(Application::GetFrameWeld(m_xParent), this)); + m_xExecuteCmdQueue.reset( new ExtensionCmdQueue( m_xUpdReqDialog.get(), this, m_xContext ) ); + createPackageList(); + } + } + else if ( !m_xExtMgrDialog ) + { + m_xExtMgrDialog = std::make_shared<ExtMgrDialog>(Application::GetFrameWeld(m_xParent), this); + m_xExecuteCmdQueue.reset( new ExtensionCmdQueue( m_xExtMgrDialog.get(), this, m_xContext ) ); + m_xExtMgrDialog->setGetExtensionsURL( m_sGetExtensionsURL ); + createPackageList(); + } +} + +void TheExtensionManager::Show() +{ + const SolarMutexGuard guard; + + m_bExtMgrDialogExecuting = true; + + weld::DialogController::runAsync(m_xExtMgrDialog, [this](sal_Int32 /*nResult*/) { + m_bExtMgrDialogExecuting = false; + auto xExtMgrDialog = m_xExtMgrDialog; + m_xExtMgrDialog.reset(); + xExtMgrDialog->Close(); + }); +} + +void TheExtensionManager::SetText( const OUString &rTitle ) +{ + const SolarMutexGuard guard; + + if (weld::Window* pDialog = getDialog()) + pDialog->set_title( rTitle ); +} + + +void TheExtensionManager::ToTop() +{ + const SolarMutexGuard guard; + + if (weld::Window* pDialog = getDialog()) + pDialog->present(); +} + +void TheExtensionManager::Close() +{ + if (m_xExtMgrDialog) + { + if (m_bExtMgrDialogExecuting) + m_xExtMgrDialog->response(RET_CANCEL); + else + m_xExtMgrDialog->Close(); + } + else if (m_xUpdReqDialog) + m_xUpdReqDialog->response(RET_CANCEL); +} + +sal_Int16 TheExtensionManager::execute() +{ + sal_Int16 nRet = 0; + + if ( m_xUpdReqDialog ) + { + nRet = m_xUpdReqDialog->run(); + m_xUpdReqDialog.reset(); + } + + return nRet; +} + +bool TheExtensionManager::isVisible() +{ + weld::Window* pDialog = getDialog(); + return pDialog && pDialog->get_visible(); +} + +void TheExtensionManager::checkUpdates() +{ + std::vector< uno::Reference< deployment::XPackage > > vEntries; + uno::Sequence< uno::Sequence< uno::Reference< deployment::XPackage > > > xAllPackages; + + try { + xAllPackages = m_xExtensionManager->getAllExtensions( uno::Reference< task::XAbortChannel >(), + uno::Reference< ucb::XCommandEnvironment >() ); + } catch ( const deployment::DeploymentException & ) { + return; + } catch ( const ucb::CommandFailedException & ) { + return; + } catch ( const ucb::CommandAbortedException & ) { + return; + } catch ( const lang::IllegalArgumentException & e ) { + css::uno::Any anyEx = cppu::getCaughtException(); + throw css::lang::WrappedTargetRuntimeException( e.Message, + e.Context, anyEx ); + } + + for ( auto const & i : std::as_const(xAllPackages) ) + { + uno::Reference< deployment::XPackage > xPackage = dp_misc::getExtensionWithHighestVersion(i); + OSL_ASSERT(xPackage.is()); + if ( xPackage.is() ) + { + vEntries.push_back( xPackage ); + } + } + + m_xExecuteCmdQueue->checkForUpdates( std::move(vEntries) ); +} + + +bool TheExtensionManager::installPackage( const OUString &rPackageURL, bool bWarnUser ) +{ + if ( rPackageURL.isEmpty() ) + return false; + + createDialog( false ); + + bool bInstall = true; + bool bInstallForAll = false; + + // DV! missing function is read only repository from extension manager + if ( !bWarnUser && ! m_xExtensionManager->isReadOnlyRepository( SHARED_PACKAGE_MANAGER ) ) + bInstall = getDialogHelper()->installForAllUsers( bInstallForAll ); + + if ( !bInstall ) + return false; + + if ( bInstallForAll ) + m_xExecuteCmdQueue->addExtension( rPackageURL, SHARED_PACKAGE_MANAGER, false ); + else + m_xExecuteCmdQueue->addExtension( rPackageURL, USER_PACKAGE_MANAGER, bWarnUser ); + + return true; +} + + +void TheExtensionManager::terminateDialog() +{ + if ( dp_misc::office_is_running() ) + return; + + const SolarMutexGuard guard; + if (m_xExtMgrDialog) + { + if (m_bExtMgrDialogExecuting) + m_xExtMgrDialog->response(RET_CANCEL); + else + { + m_xExtMgrDialog->Close(); + m_xExtMgrDialog.reset(); + } + } + assert(!m_xExtMgrDialog); + if (m_xUpdReqDialog) + m_xUpdReqDialog->response(RET_CANCEL); + assert(!m_xUpdReqDialog); + Application::Quit(); +} + + +void TheExtensionManager::createPackageList() +{ + uno::Sequence< uno::Sequence< uno::Reference< deployment::XPackage > > > xAllPackages; + + try { + xAllPackages = m_xExtensionManager->getAllExtensions( uno::Reference< task::XAbortChannel >(), + uno::Reference< ucb::XCommandEnvironment >() ); + } catch ( const deployment::DeploymentException & ) { + return; + } catch ( const ucb::CommandFailedException & ) { + return; + } catch ( const ucb::CommandAbortedException & ) { + return; + } catch ( const lang::IllegalArgumentException & e ) { + css::uno::Any anyEx = cppu::getCaughtException(); + throw css::lang::WrappedTargetRuntimeException( e.Message, + e.Context, anyEx ); + } + + for ( uno::Sequence< uno::Reference< deployment::XPackage > > const & xPackageList : std::as_const(xAllPackages) ) + { + for ( uno::Reference< deployment::XPackage > const & xPackage : xPackageList ) + { + if ( xPackage.is() ) + { + PackageState eState = getPackageState( xPackage ); + getDialogHelper()->addPackageToList( xPackage ); + // When the package is enabled, we can stop here, otherwise we have to look for + // another version of this package + if ( ( eState == REGISTERED ) || ( eState == NOT_AVAILABLE ) ) + break; + } + } + } + + const uno::Sequence< uno::Reference< deployment::XPackage > > xNoLicPackages = m_xExtensionManager->getExtensionsWithUnacceptedLicenses( SHARED_PACKAGE_MANAGER, + uno::Reference< ucb::XCommandEnvironment >() ); + for ( uno::Reference< deployment::XPackage > const & xPackage : xNoLicPackages ) + { + if ( xPackage.is() ) + { + getDialogHelper()->addPackageToList( xPackage, true ); + } + } +} + + +PackageState TheExtensionManager::getPackageState( const uno::Reference< deployment::XPackage > &xPackage ) +{ + 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 ) + return AMBIGUOUS; + else + return reg.Value ? REGISTERED : NOT_REGISTERED; + } + else + return NOT_AVAILABLE; + } + catch ( const uno::RuntimeException & ) { + throw; + } + catch (const uno::Exception &) { + TOOLS_WARN_EXCEPTION( "desktop", "" ); + return NOT_AVAILABLE; + } +} + + +bool TheExtensionManager::isReadOnly( const uno::Reference< deployment::XPackage > &xPackage ) const +{ + if ( m_xExtensionManager.is() && xPackage.is() ) + { + return m_xExtensionManager->isReadOnlyRepository( xPackage->getRepositoryName() ); + } + else + return true; +} + + +// The function investigates if the extension supports options. +bool TheExtensionManager::supportsOptions( const uno::Reference< deployment::XPackage > &xPackage ) const +{ + bool bOptions = false; + + if ( ! xPackage->isBundle() ) + return false; + + beans::Optional< OUString > aId = xPackage->getIdentifier(); + + //a bundle must always have an id + OSL_ASSERT( aId.IsPresent ); + + //iterate over all available nodes + const uno::Sequence< OUString > seqNames = m_xNameAccessNodes->getElementNames(); + + for ( OUString const & nodeName : seqNames ) + { + uno::Any anyNode = m_xNameAccessNodes->getByName( nodeName ); + //If we have a node then it must contain the set of leaves. This is part of OptionsDialog.xcs + uno::Reference< XInterface> xIntNode = anyNode.get< uno::Reference< XInterface > >(); + uno::Reference< container::XNameAccess > xNode( xIntNode, uno::UNO_QUERY_THROW ); + + uno::Any anyLeaves = xNode->getByName("Leaves"); + uno::Reference< XInterface > xIntLeaves = anyLeaves.get< uno::Reference< XInterface > >(); + uno::Reference< container::XNameAccess > xLeaves( xIntLeaves, uno::UNO_QUERY_THROW ); + + //iterate over all available leaves + const uno::Sequence< OUString > seqLeafNames = xLeaves->getElementNames(); + for ( OUString const & leafName : seqLeafNames ) + { + uno::Any anyLeaf = xLeaves->getByName( leafName ); + uno::Reference< XInterface > xIntLeaf = anyLeaf.get< uno::Reference< XInterface > >(); + uno::Reference< beans::XPropertySet > xLeaf( xIntLeaf, uno::UNO_QUERY_THROW ); + //investigate the Id property if it matches the extension identifier which + //has been passed in. + uno::Any anyValue = xLeaf->getPropertyValue("Id"); + + OUString sId = anyValue.get< OUString >(); + if ( sId == aId.Value ) + { + bOptions = true; + break; + } + } + if ( bOptions ) + break; + } + return bOptions; +} + + +// XEventListener +void TheExtensionManager::disposing( lang::EventObject const & rEvt ) +{ + bool shutDown = (rEvt.Source == m_xDesktop); + + if ( shutDown && m_xDesktop.is() ) + { + m_xDesktop->removeTerminateListener( this ); + m_xDesktop.clear(); + } + + if ( !shutDown ) + return; + + if ( dp_misc::office_is_running() ) + { + const SolarMutexGuard guard; + if (m_xExtMgrDialog) + { + if (m_bExtMgrDialogExecuting) + m_xExtMgrDialog->response(RET_CANCEL); + else + { + m_xExtMgrDialog->Close(); + m_xExtMgrDialog.reset(); + } + } + assert(!m_xExtMgrDialog); + if (m_xUpdReqDialog) + m_xUpdReqDialog->response(RET_CANCEL); + assert(!m_xUpdReqDialog); + } + s_ExtMgr.clear(); +} + +// XTerminateListener +void TheExtensionManager::queryTermination( ::lang::EventObject const & ) +{ + DialogHelper *pDialogHelper = getDialogHelper(); + + if ( m_xExecuteCmdQueue->isBusy() || ( pDialogHelper && pDialogHelper->isBusy() ) ) + { + ToTop(); + throw frame::TerminationVetoException( + "The office cannot be closed while the Extension Manager is running", + static_cast<frame::XTerminateListener*>(this)); + } + else + { + clearModified(); + if (m_xExtMgrDialog) + { + if (m_bExtMgrDialogExecuting) + m_xExtMgrDialog->response(RET_CANCEL); + else + { + m_xExtMgrDialog->Close(); + m_xExtMgrDialog.reset(); + } + } + if (m_xUpdReqDialog) + m_xUpdReqDialog->response(RET_CANCEL); + } +} + +void TheExtensionManager::notifyTermination( ::lang::EventObject const & rEvt ) +{ + disposing( rEvt ); +} + +// XModifyListener +void TheExtensionManager::modified( ::lang::EventObject const & /*rEvt*/ ) +{ + m_bModified = true; + DialogHelper *pDialogHelper = getDialogHelper(); + if (!pDialogHelper) + return; + pDialogHelper->prepareChecking(); + createPackageList(); + pDialogHelper->checkEntries(); +} + + +::rtl::Reference< TheExtensionManager > TheExtensionManager::get( const uno::Reference< uno::XComponentContext > &xContext, + const uno::Reference< awt::XWindow > &xParent, + const OUString & extensionURL ) +{ + if ( s_ExtMgr.is() ) + { + OSL_DOUBLE_CHECKED_LOCKING_MEMORY_BARRIER(); + if ( !extensionURL.isEmpty() ) + s_ExtMgr->installPackage( extensionURL, true ); + return s_ExtMgr; + } + + ::rtl::Reference<TheExtensionManager> that( new TheExtensionManager( xParent, xContext ) ); + + const SolarMutexGuard guard; + if ( ! s_ExtMgr.is() ) + { + OSL_DOUBLE_CHECKED_LOCKING_MEMORY_BARRIER(); + s_ExtMgr = that; + } + + if ( !extensionURL.isEmpty() ) + s_ExtMgr->installPackage( extensionURL, true ); + + return s_ExtMgr; +} + +} //namespace dp_gui + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/gui/dp_gui_theextmgr.hxx b/desktop/source/deployment/gui/dp_gui_theextmgr.hxx new file mode 100644 index 0000000000..13c329d6d1 --- /dev/null +++ b/desktop/source/deployment/gui/dp_gui_theextmgr.hxx @@ -0,0 +1,127 @@ +/* -*- 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/container/XNameAccess.hpp> +#include <com/sun/star/deployment/XExtensionManager.hpp> +#include <com/sun/star/frame/XDesktop2.hpp> +#include <com/sun/star/frame/XTerminateListener.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <com/sun/star/util/XModifyListener.hpp> + +#include "dp_gui.h" +#include "dp_gui_dialog2.hxx" + + +namespace dp_gui { + + +class ExtensionCmdQueue; + + +class TheExtensionManager : + public ::cppu::WeakImplHelper< css::frame::XTerminateListener, + css::util::XModifyListener > +{ +private: + css::uno::Reference< css::uno::XComponentContext > m_xContext; + css::uno::Reference< css::frame::XDesktop2 > m_xDesktop; + css::uno::Reference< css::deployment::XExtensionManager > m_xExtensionManager; + css::uno::Reference< css::container::XNameAccess > m_xNameAccessNodes; + css::uno::Reference< css::awt::XWindow > m_xParent; + std::shared_ptr<ExtMgrDialog> m_xExtMgrDialog; + std::unique_ptr<UpdateRequiredDialog> m_xUpdReqDialog; + std::unique_ptr<ExtensionCmdQueue> m_xExecuteCmdQueue; + + OUString m_sGetExtensionsURL; + bool m_bModified; + bool m_bExtMgrDialogExecuting; + +public: + static ::rtl::Reference<TheExtensionManager> s_ExtMgr; + + TheExtensionManager( css::uno::Reference< css::awt::XWindow > xParent, + const css::uno::Reference< css::uno::XComponentContext > &xContext ); + virtual ~TheExtensionManager() override; + + void createDialog( const bool bCreateUpdDlg ); + sal_Int16 execute(); + + bool isModified() const { return m_bModified; } + void clearModified() { m_bModified = false; } + + weld::Window* getDialog() + { + if (m_xExtMgrDialog) + return m_xExtMgrDialog->getDialog(); + if (m_xUpdReqDialog) + return m_xUpdReqDialog->getDialog(); + return nullptr; + } + DialogHelper* getDialogHelper() + { + if (m_xExtMgrDialog) + return m_xExtMgrDialog.get(); + return m_xUpdReqDialog.get(); + } + ExtensionCmdQueue* getCmdQueue() const { return m_xExecuteCmdQueue.get(); } + + void SetText( const OUString &rTitle ); + void Show(); + void ToTop(); + void Close(); + bool isVisible(); + + + void checkUpdates(); + bool installPackage( const OUString &rPackageURL, bool bWarnUser = false ); + void createPackageList(); + + void terminateDialog(); + + // Tools + bool supportsOptions( const css::uno::Reference< css::deployment::XPackage > &xPackage ) const; + static PackageState getPackageState( const css::uno::Reference< css::deployment::XPackage > &xPackage ); + const css::uno::Reference< css::uno::XComponentContext >& getContext() const { return m_xContext; } + const css::uno::Reference< css::deployment::XExtensionManager >& getExtensionManager() const { return m_xExtensionManager; } + bool isReadOnly( const css::uno::Reference< css::deployment::XPackage > &xPackage ) const; + + + static ::rtl::Reference<TheExtensionManager> get( + css::uno::Reference< css::uno::XComponentContext> const & xContext, + css::uno::Reference< css::awt::XWindow> const & xParent = nullptr, + OUString const & view = OUString() ); + + // XEventListener + virtual void SAL_CALL disposing( css::lang::EventObject const & evt ) override; + + // XTerminateListener + virtual void SAL_CALL queryTermination( css::lang::EventObject const & evt ) override; + virtual void SAL_CALL notifyTermination( css::lang::EventObject const & evt ) override; + + // XModifyListener + virtual void SAL_CALL modified( css::lang::EventObject const & evt ) override; +}; + +} // namespace dp_gui + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/gui/dp_gui_updatedata.hxx b/desktop/source/deployment/gui/dp_gui_updatedata.hxx new file mode 100644 index 0000000000..efac4c587b --- /dev/null +++ b/desktop/source/deployment/gui/dp_gui_updatedata.hxx @@ -0,0 +1,74 @@ +/* -*- 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 <rtl/ustring.hxx> +#include <com/sun/star/uno/Reference.hxx> +#include <utility> + +namespace com::sun::star::deployment { + class XPackage; +} +namespace com::sun::star::xml::dom { + class XNode; +} + + +namespace dp_gui { + +struct UpdateData +{ + explicit UpdateData( css::uno::Reference< css::deployment::XPackage > xExt): + bIsShared(false), aInstalledPackage(std::move(xExt)) {}; + + //When entries added to the listbox then there can be one for the user update and one + //for the shared update. However, both list entries will contain the same UpdateData. + //isShared is used to indicate which one is used for the shared entry. + bool bIsShared; + + //The currently installed extension which is going to be updated. If the extension exist in + //multiple repositories then it is the one with the highest version. + css::uno::Reference< css::deployment::XPackage > aInstalledPackage; + + //The version of the update + OUString updateVersion; + + //For online update + + // The content of the update information. + //Only if aUpdateInfo is set then there is an online update available with a better version + //than any of the currently installed extensions with the same identifier. + css::uno::Reference< css::xml::dom::XNode > aUpdateInfo; + //The URL of the locally downloaded extension. It will only be set if there were no errors + //during the download + OUString sLocalURL; + //The URL of the website where the download can be obtained. + OUString sWebsiteURL; + + //For local update + + //The locale extension which is used as update for the user or shared repository. + //If set then the data for the online update (aUpdateInfo, sLocalURL, sWebsiteURL) + //are to be ignored. + css::uno::Reference< css::deployment::XPackage > aUpdateSource; +}; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/gui/dp_gui_updatedialog.cxx b/desktop/source/deployment/gui/dp_gui_updatedialog.cxx new file mode 100644 index 0000000000..e8da99f3c1 --- /dev/null +++ b/desktop/source/deployment/gui/dp_gui_updatedialog.cxx @@ -0,0 +1,988 @@ +/* -*- 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 <utility> +#include <vector> + + +#include <optional> +#include <com/sun/star/beans/NamedValue.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/configuration/theDefaultProvider.hpp> +#include <com/sun/star/container/XNameAccess.hpp> +#include <com/sun/star/deployment/UpdateInformationProvider.hpp> +#include <com/sun/star/deployment/ExtensionManager.hpp> +#include <com/sun/star/deployment/XUpdateInformationProvider.hpp> +#include <com/sun/star/frame/Desktop.hpp> +#include <com/sun/star/frame/XDispatch.hpp> +#include <com/sun/star/frame/XDispatchProvider.hpp> +#include <com/sun/star/lang/IllegalArgumentException.hpp> +#include <com/sun/star/task/InteractionHandler.hpp> +#include <com/sun/star/ucb/CommandFailedException.hpp> +#include <com/sun/star/ucb/XCommandEnvironment.hpp> +#include <com/sun/star/uno/Any.hxx> +#include <com/sun/star/uno/Exception.hpp> +#include <com/sun/star/uno/Reference.hxx> +#include <com/sun/star/uno/Sequence.hxx> +#include <com/sun/star/util/URL.hpp> +#include <com/sun/star/util/URLTransformer.hpp> +#include <com/sun/star/util/XURLTransformer.hpp> +#include <com/sun/star/xml/dom/XElement.hpp> +#include <osl/diagnose.h> +#include <rtl/ref.hxx> +#include <rtl/ustrbuf.hxx> +#include <rtl/ustring.hxx> +#include <sal/types.h> +#include <salhelper/thread.hxx> +#include <tools/gen.hxx> +#include <tools/link.hxx> +#include <unotools/configmgr.hxx> +#include <vcl/svapp.hxx> + +#include <comphelper/processfactory.hxx> +#include <comphelper/propertyvalue.hxx> + +#include <dp_dependencies.hxx> +#include <dp_descriptioninfoset.hxx> +#include <dp_identifier.hxx> +#include <dp_misc.h> +#include <dp_update.hxx> + +#include <strings.hrc> +#include "dp_gui_updatedata.hxx" +#include "dp_gui_updatedialog.hxx" +#include <dp_shared.hxx> + +class KeyEvent; +class MouseEvent; +namespace com::sun::star::uno { + class XComponentContext; +} + +using namespace ::com::sun::star; +using dp_gui::UpdateDialog; + +namespace { + +sal_Unicode const LF = 0x000A; +sal_Unicode const CR = 0x000D; + +constexpr OUStringLiteral IGNORED_UPDATES = u"/org.openoffice.Office.ExtensionManager/ExtensionUpdateData/IgnoredUpdates"; +constexpr OUStringLiteral PROPERTY_VERSION = u"Version"; + +enum Kind { ENABLED_UPDATE, DISABLED_UPDATE, SPECIFIC_ERROR }; + +OUString confineToParagraph(OUString const & text) { + // Confine arbitrary text to a single paragraph in a VclMultiLineEdit + // This assumes that U+000A and U+000D are the only paragraph separators in + // a VclMultiLineEdit, and that replacing them with a single space + // each is acceptable: + return text.replace(LF, ' ').replace(CR, ' '); +} +} + +struct UpdateDialog::DisabledUpdate { + OUString name; + uno::Sequence< OUString > unsatisfiedDependencies; + // We also want to show release notes and publisher for disabled updates + css::uno::Reference< css::xml::dom::XNode > aUpdateInfo; +}; + +struct UpdateDialog::SpecificError { + OUString name; + OUString message; +}; + + +struct UpdateDialog::IgnoredUpdate { + OUString sExtensionID; + OUString sVersion; + + IgnoredUpdate( OUString aExtensionID, OUString aVersion ); +}; + + +UpdateDialog::IgnoredUpdate::IgnoredUpdate( OUString aExtensionID, OUString aVersion ): + sExtensionID(std::move( aExtensionID )), + sVersion(std::move( aVersion )) +{} + + +struct UpdateDialog::Index +{ + Kind m_eKind; + bool m_bIgnored; + sal_uInt16 m_nIndex; + OUString m_aName; + + Index( Kind theKind, sal_uInt16 nIndex, OUString aName ) : + m_eKind( theKind ), + m_bIgnored( false ), + m_nIndex( nIndex ), + m_aName(std::move( aName )) {} +}; + + +class UpdateDialog::Thread: public salhelper::Thread { +public: + Thread( + uno::Reference< uno::XComponentContext > const & context, + UpdateDialog & dialog, + std::vector< uno::Reference< deployment::XPackage > > && vExtensionList); + + void stop(); + +private: + virtual ~Thread() override; + + virtual void execute() override; + + void handleSpecificError( + uno::Reference< deployment::XPackage > const & package, + uno::Any const & exception) const; + + OUString getUpdateDisplayString( + dp_gui::UpdateData const & data, std::u16string_view version = std::u16string_view()) const; + + void prepareUpdateData( + css::uno::Reference< css::xml::dom::XNode > const & updateInfo, + UpdateDialog::DisabledUpdate & out_du, + dp_gui::UpdateData & out_data) const; + + bool update( + UpdateDialog::DisabledUpdate const & du, + dp_gui::UpdateData const & data) const; + + uno::Reference< uno::XComponentContext > m_context; + UpdateDialog & m_dialog; + std::vector< uno::Reference< deployment::XPackage > > m_vExtensionList; + uno::Reference< deployment::XUpdateInformationProvider > m_updateInformation; + uno::Reference< task::XInteractionHandler > m_xInteractionHdl; + + // guarded by Application::GetSolarMutex(): + bool m_stop; +}; + +UpdateDialog::Thread::Thread( + uno::Reference< uno::XComponentContext > const & context, + UpdateDialog & dialog, + std::vector< uno::Reference< deployment::XPackage > >&& vExtensionList): + salhelper::Thread("dp_gui_updatedialog"), + m_context(context), + m_dialog(dialog), + m_vExtensionList(std::move(vExtensionList)), + m_updateInformation( + deployment::UpdateInformationProvider::create(context)), + m_stop(false) +{ + if( m_context.is() ) + { + m_xInteractionHdl = + task::InteractionHandler::createWithParent(m_context, dialog.getDialog()->GetXWindow()); + m_updateInformation->setInteractionHandler( m_xInteractionHdl ); + } +} + +void UpdateDialog::Thread::stop() { + { + SolarMutexGuard g; + m_stop = true; + } + m_updateInformation->cancel(); +} + +UpdateDialog::Thread::~Thread() +{ + if ( m_xInteractionHdl.is() ) + m_updateInformation->setInteractionHandler( uno::Reference< task::XInteractionHandler > () ); +} + +void UpdateDialog::Thread::execute() +{ + { + SolarMutexGuard g; + if ( m_stop ) { + return; + } + } + uno::Reference<deployment::XExtensionManager> extMgr = + deployment::ExtensionManager::get(m_context); + + std::vector<std::pair<uno::Reference<deployment::XPackage>, uno::Any > > errors; + + dp_misc::UpdateInfoMap updateInfoMap = dp_misc::getOnlineUpdateInfos( + m_context, extMgr, m_updateInformation, &m_vExtensionList, errors); + + for (auto const& elem : errors) + handleSpecificError(elem.first, elem.second); + + for (auto const& updateInfo : updateInfoMap) + { + dp_misc::UpdateInfo const & info = updateInfo.second; + UpdateData updateData(info.extension); + DisabledUpdate disableUpdate; + //determine if online updates meet the requirements + prepareUpdateData(info.info, disableUpdate, updateData); + + //determine if the update is installed in the user or shared repository + OUString sOnlineVersion; + if (info.info.is()) + sOnlineVersion = info.version; + OUString sVersionUser; + OUString sVersionShared; + OUString sVersionBundled; + uno::Sequence< uno::Reference< deployment::XPackage> > extensions; + try { + extensions = extMgr->getExtensionsWithSameIdentifier( + dp_misc::getIdentifier(info.extension), info.extension->getName(), + uno::Reference<ucb::XCommandEnvironment>()); + } catch ( const lang::IllegalArgumentException& ) { + OSL_ASSERT(false); + continue; + } catch ( const css::ucb::CommandFailedException& ) { + OSL_ASSERT(false); + continue; + } + OSL_ASSERT(extensions.getLength() == 3); + if (extensions[0].is() ) + sVersionUser = extensions[0]->getVersion(); + if (extensions[1].is() ) + sVersionShared = extensions[1]->getVersion(); + if (extensions[2].is() ) + sVersionBundled = extensions[2]->getVersion(); + + bool bSharedReadOnly = extMgr->isReadOnlyRepository("shared"); + + dp_misc::UPDATE_SOURCE sourceUser = dp_misc::isUpdateUserExtension( + bSharedReadOnly, sVersionUser, sVersionShared, sVersionBundled, sOnlineVersion); + dp_misc::UPDATE_SOURCE sourceShared = dp_misc::isUpdateSharedExtension( + bSharedReadOnly, sVersionShared, sVersionBundled, sOnlineVersion); + + if (sourceUser != dp_misc::UPDATE_SOURCE_NONE) + { + if (sourceUser == dp_misc::UPDATE_SOURCE_SHARED) + { + updateData.aUpdateSource = extensions[1]; + updateData.updateVersion = extensions[1]->getVersion(); + } + else if (sourceUser == dp_misc::UPDATE_SOURCE_BUNDLED) + { + updateData.aUpdateSource = extensions[2]; + updateData.updateVersion = extensions[2]->getVersion(); + } + if (!update(disableUpdate, updateData)) + return; + } + + if (sourceShared != dp_misc::UPDATE_SOURCE_NONE) + { + if (sourceShared == dp_misc::UPDATE_SOURCE_BUNDLED) + { + updateData.aUpdateSource = extensions[2]; + updateData.updateVersion = extensions[2]->getVersion(); + } + updateData.bIsShared = true; + if (!update(disableUpdate, updateData)) + return; + } + } + + + SolarMutexGuard g; + if (!m_stop) { + m_dialog.checkingDone(); + } +} + +//Parameter package can be null +void UpdateDialog::Thread::handleSpecificError( + uno::Reference< deployment::XPackage > const & package, + uno::Any const & exception) const +{ + UpdateDialog::SpecificError data; + if (package.is()) + data.name = package->getDisplayName(); + uno::Exception e; + if (exception >>= e) { + data.message = e.Message; + } + SolarMutexGuard g; + if (!m_stop) { + m_dialog.addSpecificError(data); + } +} + +OUString UpdateDialog::Thread::getUpdateDisplayString( + dp_gui::UpdateData const & data, std::u16string_view version) const +{ + OSL_ASSERT(data.aInstalledPackage.is()); + OUStringBuffer b(data.aInstalledPackage->getDisplayName()); + b.append(' '); + { + SolarMutexGuard g; + if(!m_stop) + b.append(m_dialog.m_version); + } + b.append(' '); + if (!version.empty()) + b.append(version); + else + b.append(data.updateVersion); + + if (!data.sWebsiteURL.isEmpty()) + { + b.append(' '); + { + SolarMutexGuard g; + if(!m_stop) + b.append(m_dialog.m_browserbased); + } + } + return b.makeStringAndClear(); +} + +/** out_data will only be filled if all dependencies are ok. + */ +void UpdateDialog::Thread::prepareUpdateData( + uno::Reference< xml::dom::XNode > const & updateInfo, + UpdateDialog::DisabledUpdate & out_du, + dp_gui::UpdateData & out_data) const +{ + if (!updateInfo.is()) + return; + dp_misc::DescriptionInfoset infoset(m_context, updateInfo); + OSL_ASSERT(!infoset.getVersion().isEmpty()); + uno::Sequence< uno::Reference< xml::dom::XElement > > ds( + dp_misc::Dependencies::check(infoset)); + + out_du.aUpdateInfo = updateInfo; + out_du.unsatisfiedDependencies.realloc(ds.getLength()); + auto p_unsatisfiedDependencies = out_du.unsatisfiedDependencies.getArray(); + for (sal_Int32 i = 0; i < ds.getLength(); ++i) { + p_unsatisfiedDependencies[i] = dp_misc::Dependencies::getErrorText(ds[i]); + } + + const ::std::optional< OUString> updateWebsiteURL(infoset.getLocalizedUpdateWebsiteURL()); + + out_du.name = getUpdateDisplayString(out_data, infoset.getVersion()); + + if (!out_du.unsatisfiedDependencies.hasElements()) + { + out_data.aUpdateInfo = updateInfo; + out_data.updateVersion = infoset.getVersion(); + if (updateWebsiteURL) + out_data.sWebsiteURL = *updateWebsiteURL; + } +} + +bool UpdateDialog::Thread::update( + UpdateDialog::DisabledUpdate const & du, + dp_gui::UpdateData const & data) const +{ + bool ret = false; + if (!du.unsatisfiedDependencies.hasElements()) + { + SolarMutexGuard g; + if (!m_stop) { + m_dialog.addEnabledUpdate(getUpdateDisplayString(data), data); + } + ret = !m_stop; + } else { + SolarMutexGuard g; + if (!m_stop) { + m_dialog.addDisabledUpdate(du); + } + ret = !m_stop; + } + return ret; +} + +// UpdateDialog ---------------------------------------------------------- +UpdateDialog::UpdateDialog( + uno::Reference< uno::XComponentContext > const & context, + weld::Window * parent, std::vector<uno::Reference< deployment::XPackage > > && vExtensionList, + std::vector< dp_gui::UpdateData > * updateData) + : GenericDialogController(parent, "desktop/ui/updatedialog.ui", "UpdateDialog") + , m_context(context) + , m_none(DpResId(RID_DLG_UPDATE_NONE)) + , m_noInstallable(DpResId(RID_DLG_UPDATE_NOINSTALLABLE)) + , m_failure(DpResId(RID_DLG_UPDATE_FAILURE)) + , m_unknownError(DpResId(RID_DLG_UPDATE_UNKNOWNERROR)) + , m_noDescription(DpResId(RID_DLG_UPDATE_NODESCRIPTION)) + , m_noInstall(DpResId(RID_DLG_UPDATE_NOINSTALL)) + , m_noDependency(DpResId(RID_DLG_UPDATE_NODEPENDENCY)) + , m_noDependencyCurVer(DpResId(RID_DLG_UPDATE_NODEPENDENCY_CUR_VER)) + , m_browserbased(DpResId(RID_DLG_UPDATE_BROWSERBASED)) + , m_version(DpResId(RID_DLG_UPDATE_VERSION)) + , m_ignoredUpdate(DpResId(RID_DLG_UPDATE_IGNORED_UPDATE)) + , m_updateData(*updateData) + , m_thread(new UpdateDialog::Thread(context, *this, std::move(vExtensionList))) + , m_xChecking(m_xBuilder->weld_label("UPDATE_CHECKING")) + , m_xThrobber(m_xBuilder->weld_spinner("THROBBER")) + , m_xUpdate(m_xBuilder->weld_label("UPDATE_LABEL")) + , m_xUpdates(m_xBuilder->weld_tree_view("checklist")) + , m_xAll(m_xBuilder->weld_check_button("UPDATE_ALL")) + , m_xDescription(m_xBuilder->weld_label("DESCRIPTION_LABEL")) + , m_xPublisherLabel(m_xBuilder->weld_label("PUBLISHER_LABEL")) + , m_xPublisherLink(m_xBuilder->weld_link_button("PUBLISHER_LINK")) + , m_xReleaseNotesLabel(m_xBuilder->weld_label("RELEASE_NOTES_LABEL")) + , m_xReleaseNotesLink(m_xBuilder->weld_link_button("RELEASE_NOTES_LINK")) + , m_xDescriptions(m_xBuilder->weld_text_view("DESCRIPTIONS")) + , m_xOk(m_xBuilder->weld_button("ok")) + , m_xClose(m_xBuilder->weld_button("close")) + , m_xHelp(m_xBuilder->weld_button("help")) +{ + auto nWidth = m_xDescriptions->get_approximate_digit_width() * 62; + auto nHeight = m_xDescriptions->get_height_rows(8); + m_xDescriptions->set_size_request(nWidth, nHeight); + m_xUpdates->set_size_request(nWidth, nHeight); + + m_xUpdates->enable_toggle_buttons(weld::ColumnToggleType::Check); + + OSL_ASSERT(updateData != nullptr); + + m_xExtensionManager = deployment::ExtensionManager::get( context ); + + m_xUpdates->connect_changed(LINK(this, UpdateDialog, selectionHandler)); + m_xUpdates->connect_toggled(LINK(this, UpdateDialog, entryToggled)); + m_xAll->connect_toggled(LINK(this, UpdateDialog, allHandler)); + m_xOk->connect_clicked(LINK(this, UpdateDialog, okHandler)); + m_xClose->connect_clicked(LINK(this, UpdateDialog, closeHandler)); + if (!dp_misc::office_is_running()) + m_xHelp->set_sensitive(false); + + initDescription(); + getIgnoredUpdates(); +} + +UpdateDialog::~UpdateDialog() +{ +} + +short UpdateDialog::run() { + m_xThrobber->start(); + m_thread->launch(); + short nRet = GenericDialogController::run(); + m_thread->stop(); + return nRet; +} + +IMPL_LINK(UpdateDialog, entryToggled, const weld::TreeView::iter_col&, rRowCol, void) +{ + // error's can't be enabled + const UpdateDialog::Index* p = weld::fromId<UpdateDialog::Index const *>(m_xUpdates->get_id(rRowCol.first)); + if (p->m_eKind == SPECIFIC_ERROR) + m_xUpdates->set_toggle(rRowCol.first, TRISTATE_FALSE); + + enableOk(); +} + +void UpdateDialog::insertItem(const UpdateDialog::Index *pEntry, bool bEnabledCheckBox) +{ + int nEntry = m_xUpdates->n_children(); + m_xUpdates->append(); + m_xUpdates->set_toggle(nEntry, bEnabledCheckBox ? TRISTATE_TRUE : TRISTATE_FALSE); + m_xUpdates->set_text(nEntry, pEntry->m_aName, 0); + m_xUpdates->set_id(nEntry, weld::toId(pEntry)); +} + +void UpdateDialog::addAdditional(const UpdateDialog::Index * index, bool bEnabledCheckBox) +{ + m_xAll->set_sensitive(true); + if (m_xAll->get_active()) + { + insertItem(index, bEnabledCheckBox); + m_xUpdate->set_sensitive(true); + m_xUpdates->set_sensitive(true); + m_xDescription->set_sensitive(true); + m_xDescriptions->set_sensitive(true); + } +} + +void UpdateDialog::addEnabledUpdate( OUString const & name, + dp_gui::UpdateData const & data ) +{ + sal_uInt16 nIndex = sal::static_int_cast< sal_uInt16 >( m_enabledUpdates.size() ); + UpdateDialog::Index *pEntry = new UpdateDialog::Index( ENABLED_UPDATE, nIndex, name ); + + m_enabledUpdates.push_back( data ); + m_ListboxEntries.emplace_back( pEntry ); + + if (!isIgnoredUpdate(pEntry)) + { + insertItem(pEntry, true); + } + else + addAdditional(pEntry, false); + + m_xUpdate->set_sensitive(true); + m_xUpdates->set_sensitive(true); + m_xDescription->set_sensitive(true); + m_xDescriptions->set_sensitive(true); +} + +void UpdateDialog::addDisabledUpdate( UpdateDialog::DisabledUpdate const & data ) +{ + sal_uInt16 nIndex = sal::static_int_cast< sal_uInt16 >( m_disabledUpdates.size() ); + UpdateDialog::Index *pEntry = new UpdateDialog::Index( DISABLED_UPDATE, nIndex, data.name ); + + m_disabledUpdates.push_back( data ); + m_ListboxEntries.emplace_back( pEntry ); + + isIgnoredUpdate( pEntry ); + addAdditional(pEntry, false); +} + +void UpdateDialog::addSpecificError( UpdateDialog::SpecificError const & data ) +{ + sal_uInt16 nIndex = sal::static_int_cast< sal_uInt16 >( m_specificErrors.size() ); + UpdateDialog::Index *pEntry = new UpdateDialog::Index( SPECIFIC_ERROR, nIndex, data.name ); + + m_specificErrors.push_back( data ); + m_ListboxEntries.emplace_back( pEntry ); + + addAdditional(pEntry, false); +} + +void UpdateDialog::checkingDone() { + m_xChecking->hide(); + m_xThrobber->stop(); + m_xThrobber->hide(); + if (m_xUpdates->n_children() == 0) + { + clearDescription(); + m_xDescription->set_sensitive(true); + m_xDescriptions->set_sensitive(true); + + if ( m_disabledUpdates.empty() && m_specificErrors.empty() && m_ignoredUpdates.empty() ) + showDescription( m_none ); + else + showDescription( m_noInstallable ); + } + + enableOk(); +} + +void UpdateDialog::enableOk() { + if (!m_xChecking->get_visible()) { + int nChecked = 0; + for (int i = 0, nCount = m_xUpdates->n_children(); i < nCount; ++i) { + if (m_xUpdates->get_toggle(i) == TRISTATE_TRUE) + ++nChecked; + } + m_xOk->set_sensitive(nChecked != 0); + } +} + +// ********************************************************************************* +void UpdateDialog::createNotifyJob( bool bPrepareOnly, + uno::Sequence< uno::Sequence< OUString > > const &rItemList ) +{ + if ( !dp_misc::office_is_running() ) + return; + + // notify update check job + try + { + uno::Reference< lang::XMultiServiceFactory > xConfigProvider( + configuration::theDefaultProvider::get( + comphelper::getProcessComponentContext())); + + uno::Sequence< uno::Any > aArgumentList{ uno::Any(comphelper::makePropertyValue( + "nodepath", + OUString("org.openoffice.Office.Addons/AddonUI/OfficeHelp/UpdateCheckJob"))) }; + + uno::Reference< container::XNameAccess > xNameAccess( + xConfigProvider->createInstanceWithArguments( + "com.sun.star.configuration.ConfigurationAccess", aArgumentList ), + uno::UNO_QUERY_THROW ); + + util::URL aURL; + xNameAccess->getByName("URL") >>= aURL.Complete; + + uno::Reference< uno::XComponentContext > xContext( ::comphelper::getProcessComponentContext() ); + uno::Reference < util::XURLTransformer > xTransformer = util::URLTransformer::create(xContext); + + xTransformer->parseStrict(aURL); + + uno::Reference < frame::XDesktop2 > xDesktop = frame::Desktop::create( xContext ); + uno::Reference< frame::XDispatchProvider > xDispatchProvider( xDesktop->getCurrentFrame(), + uno::UNO_QUERY_THROW ); + uno::Reference< frame::XDispatch > xDispatch = xDispatchProvider->queryDispatch(aURL, OUString(), 0); + + if( xDispatch.is() ) + { + uno::Sequence aPropList{ comphelper::makePropertyValue("updateList", rItemList), + comphelper::makePropertyValue("prepareOnly", bPrepareOnly) }; + + xDispatch->dispatch(aURL, aPropList ); + } + } + catch( const uno::Exception& e ) + { + dp_misc::TRACE( "Caught exception: " + + e.Message + "\n thread terminated.\n\n"); + } +} + +// ********************************************************************************* +void UpdateDialog::notifyMenubar( bool bPrepareOnly, bool bRecheckOnly ) +{ + if ( !dp_misc::office_is_running() ) + return; + + uno::Sequence< uno::Sequence< OUString > > aItemList; + + if ( ! bRecheckOnly ) + { + sal_Int32 nCount = 0; + for (sal_uInt16 i = 0, nItemCount = m_xUpdates->n_children(); i < nItemCount; ++i) + { + + UpdateDialog::Index const * p = weld::fromId<UpdateDialog::Index const*>(m_xUpdates->get_id(i)); + + if ( p->m_eKind == ENABLED_UPDATE ) + { + dp_gui::UpdateData aUpdData = m_enabledUpdates[ p->m_nIndex ]; + + dp_misc::DescriptionInfoset aInfoset( m_context, aUpdData.aUpdateInfo ); + uno::Sequence< OUString > aItem + { + dp_misc::getIdentifier( aUpdData.aInstalledPackage ), + aInfoset.getVersion() + }; + aItemList.realloc( nCount + 1 ); + aItemList.getArray()[ nCount ] = aItem; + nCount += 1; + } + else + continue; + } + } + + createNotifyJob( bPrepareOnly, aItemList ); +} + +// ********************************************************************************* + +void UpdateDialog::initDescription() +{ + m_xPublisherLabel->hide(); + m_xPublisherLink->hide(); + m_xReleaseNotesLabel->hide(); + m_xReleaseNotesLink->hide(); +} + +void UpdateDialog::clearDescription() +{ + m_xPublisherLabel->hide(); + m_xPublisherLink->hide(); + m_xPublisherLink->set_label(""); + m_xPublisherLink->set_uri(""); + m_xReleaseNotesLabel->hide(); + m_xReleaseNotesLink->hide(); + m_xReleaseNotesLink->set_uri( "" ); + m_xDescriptions->set_text(""); +} + +bool UpdateDialog::showDescription(uno::Reference< xml::dom::XNode > const & aUpdateInfo) +{ + dp_misc::DescriptionInfoset infoset(m_context, aUpdateInfo); + return showDescription(infoset.getLocalizedPublisherNameAndURL(), + infoset.getLocalizedReleaseNotesURL()); +} + +bool UpdateDialog::showDescription(uno::Reference< deployment::XPackage > const & aExtension) +{ + OSL_ASSERT(aExtension.is()); + beans::StringPair pubInfo = aExtension->getPublisherInfo(); + return showDescription(std::make_pair(pubInfo.First, pubInfo.Second), + ""); +} + +bool UpdateDialog::showDescription(std::pair< OUString, OUString > const & pairPublisher, + OUString const & sReleaseNotes) +{ + OUString sPub = pairPublisher.first; + OUString sURL = pairPublisher.second; + + if ( sPub.isEmpty() && sURL.isEmpty() && sReleaseNotes.isEmpty() ) + // nothing to show + return false; + + if ( !sPub.isEmpty() ) + { + m_xPublisherLabel->show(); + m_xPublisherLink->show(); + m_xPublisherLink->set_label(sPub); + m_xPublisherLink->set_uri(sURL); + } + + if ( !sReleaseNotes.isEmpty() ) + { + m_xReleaseNotesLabel->show(); + m_xReleaseNotesLink->show(); + m_xReleaseNotesLink->set_uri( sReleaseNotes ); + } + return true; +} + +bool UpdateDialog::showDescription( const OUString& rDescription) +{ + if ( rDescription.isEmpty() ) + // nothing to show + return false; + + m_xDescriptions->set_text(rDescription); + return true; +} + +void UpdateDialog::getIgnoredUpdates() +{ + uno::Reference< lang::XMultiServiceFactory > xConfig( + configuration::theDefaultProvider::get(m_context)); + beans::NamedValue aValue( "nodepath", uno::Any( OUString(IGNORED_UPDATES) ) ); + uno::Sequence< uno::Any > args{ uno::Any(aValue) }; + + uno::Reference< container::XNameAccess > xNameAccess( xConfig->createInstanceWithArguments( "com.sun.star.configuration.ConfigurationAccess", args), uno::UNO_QUERY_THROW ); + const uno::Sequence< OUString > aElementNames = xNameAccess->getElementNames(); + + for ( OUString const & aIdentifier : aElementNames ) + { + OUString aVersion; + + uno::Any aPropValue( uno::Reference< beans::XPropertySet >( xNameAccess->getByName( aIdentifier ), uno::UNO_QUERY_THROW )->getPropertyValue( PROPERTY_VERSION ) ); + aPropValue >>= aVersion; + IgnoredUpdate *pData = new IgnoredUpdate( aIdentifier, aVersion ); + m_ignoredUpdates.emplace_back( pData ); + } +} + + +bool UpdateDialog::isIgnoredUpdate( UpdateDialog::Index * index ) +{ + bool bIsIgnored = false; + + if (! m_ignoredUpdates.empty() ) + { + OUString aExtensionID; + OUString aVersion; + + if ( index->m_eKind == ENABLED_UPDATE ) + { + dp_gui::UpdateData aUpdData = m_enabledUpdates[ index->m_nIndex ]; + aExtensionID = dp_misc::getIdentifier( aUpdData.aInstalledPackage ); + aVersion = aUpdData.updateVersion; + } + else if ( index->m_eKind == DISABLED_UPDATE ) + { + DisabledUpdate &rData = m_disabledUpdates[ index->m_nIndex ]; + dp_misc::DescriptionInfoset aInfoset( m_context, rData.aUpdateInfo ); + ::std::optional< OUString > aID( aInfoset.getIdentifier() ); + if ( aID ) + aExtensionID = *aID; + aVersion = aInfoset.getVersion(); + } + + for (auto const& ignoredUpdate : m_ignoredUpdates) + { + if ( ignoredUpdate->sExtensionID == aExtensionID ) + { + if ( ( !ignoredUpdate->sVersion.isEmpty() ) || ( ignoredUpdate->sVersion == aVersion ) ) + { + bIsIgnored = true; + index->m_bIgnored = true; + } + break; + } + } + } + + return bIsIgnored; +} + + +IMPL_LINK_NOARG(UpdateDialog, selectionHandler, weld::TreeView&, void) +{ + OUStringBuffer b; + int nSelectedPos = m_xUpdates->get_selected_index(); + clearDescription(); + + const UpdateDialog::Index* p = nullptr; + if (nSelectedPos != -1) + p = weld::fromId<UpdateDialog::Index const*>(m_xUpdates->get_id(nSelectedPos)); + if (p != nullptr) + { + sal_uInt16 pos = p->m_nIndex; + + switch (p->m_eKind) + { + case ENABLED_UPDATE: + { + if ( m_enabledUpdates[ pos ].aUpdateSource.is() ) + showDescription( m_enabledUpdates[ pos ].aUpdateSource ); + else + showDescription( m_enabledUpdates[ pos ].aUpdateInfo ); + + if ( p->m_bIgnored ) + b.append( m_ignoredUpdate ); + + break; + } + case DISABLED_UPDATE: + { + if ( !m_disabledUpdates.empty() ) + showDescription( m_disabledUpdates[pos].aUpdateInfo ); + + if ( p->m_bIgnored ) + b.append( m_ignoredUpdate ); + + if ( m_disabledUpdates.empty() ) + break; + + UpdateDialog::DisabledUpdate & data = m_disabledUpdates[ pos ]; + if (data.unsatisfiedDependencies.hasElements()) + { + // create error string for version mismatch + OUString sVersion( "%VERSION" ); + OUString sProductName( "%PRODUCTNAME" ); + sal_Int32 nPos = m_noDependencyCurVer.indexOf( sVersion ); + if ( nPos >= 0 ) + { + m_noDependencyCurVer = m_noDependencyCurVer.replaceAt( nPos, sVersion.getLength(), utl::ConfigManager::getAboutBoxProductVersion() ); + } + nPos = m_noDependencyCurVer.indexOf( sProductName ); + if ( nPos >= 0 ) + { + m_noDependencyCurVer = m_noDependencyCurVer.replaceAt( nPos, sProductName.getLength(), utl::ConfigManager::getProductName() ); + } + nPos = m_noDependency.indexOf( sProductName ); + if ( nPos >= 0 ) + { + m_noDependency = m_noDependency.replaceAt( nPos, sProductName.getLength(), utl::ConfigManager::getProductName() ); + } + + b.append(m_noInstall + OUStringChar(LF) + m_noDependency); + for (sal_Int32 i = 0; + i < data.unsatisfiedDependencies.getLength(); ++i) + { + b.append(OUStringChar(LF) + " "); + // U+2003 EM SPACE would be better than two spaces, + // but some fonts do not contain it + b.append( + confineToParagraph( + data.unsatisfiedDependencies[i])); + } + b.append(OUStringChar(LF) + " " + m_noDependencyCurVer); + } + break; + } + case SPECIFIC_ERROR: + { + UpdateDialog::SpecificError & data = m_specificErrors[ pos ]; + b.append(m_failure + OUStringChar(LF)); + b.append( data.message.isEmpty() ? m_unknownError : data.message ); + break; + } + default: + OSL_ASSERT(false); + break; + } + } + + if ( b.isEmpty() ) + b.append( m_noDescription ); + + showDescription( b.makeStringAndClear() ); +} + +IMPL_LINK_NOARG(UpdateDialog, allHandler, weld::Toggleable&, void) +{ + if (m_xAll->get_active()) + { + m_xUpdate->set_sensitive(true); + m_xUpdates->set_sensitive(true); + m_xDescription->set_sensitive(true); + m_xDescriptions->set_sensitive(true); + + for (auto const& listboxEntry : m_ListboxEntries) + { + if ( listboxEntry->m_bIgnored || ( listboxEntry->m_eKind != ENABLED_UPDATE ) ) + insertItem(listboxEntry.get(), false); + } + } + else + { + for (sal_uInt16 i = m_xUpdates->n_children(); i != 0 ;) + { + i -= 1; + UpdateDialog::Index const * p = weld::fromId<UpdateDialog::Index const*>(m_xUpdates->get_id(i)); + if ( p->m_bIgnored || ( p->m_eKind != ENABLED_UPDATE ) ) + { + m_xUpdates->remove(i); + } + } + + if (m_xUpdates->n_children() == 0) + { + clearDescription(); + m_xUpdate->set_sensitive(false); + m_xUpdates->set_sensitive(false); + if (m_xChecking->get_visible()) + m_xDescription->set_sensitive(false); + else + showDescription(m_noInstallable); + } + } +} + +IMPL_LINK_NOARG(UpdateDialog, okHandler, weld::Button&, void) +{ + //If users are going to update a shared extension then we need + //to warn them + for (auto const& enableUpdate : m_enabledUpdates) + { + OSL_ASSERT(enableUpdate.aInstalledPackage.is()); + //If the user has no write access to the shared folder then the update + //for a shared extension is disable, that is it cannot be in m_enabledUpdates + } + + + for (sal_uInt16 i = 0, nCount = m_xUpdates->n_children(); i < nCount; ++i) + { + UpdateDialog::Index const * p = + weld::fromId<UpdateDialog::Index const*>(m_xUpdates->get_id(i)); + if (p->m_eKind == ENABLED_UPDATE && m_xUpdates->get_toggle(i) == TRISTATE_TRUE) { + m_updateData.push_back( m_enabledUpdates[ p->m_nIndex ] ); + } + } + + m_xDialog->response(RET_OK); +} + +IMPL_LINK_NOARG(UpdateDialog, closeHandler, weld::Button&, void) +{ + m_thread->stop(); + m_xDialog->response(RET_CANCEL); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/gui/dp_gui_updatedialog.hxx b/desktop/source/deployment/gui/dp_gui_updatedialog.hxx new file mode 100644 index 0000000000..cbf376955d --- /dev/null +++ b/desktop/source/deployment/gui/dp_gui_updatedialog.hxx @@ -0,0 +1,168 @@ +/* -*- 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 <com/sun/star/uno/Reference.hxx> +#include <com/sun/star/uno/Sequence.hxx> +#include <rtl/ref.hxx> +#include <rtl/ustring.hxx> +#include <tools/link.hxx> +#include <vcl/weld.hxx> + +#include "dp_gui_updatedata.hxx" + +/// @HTML + +class Image; +class KeyEvent; +class MouseEvent; +class ResId; + +namespace com::sun::star { + namespace deployment { class XExtensionManager; + class XPackage; } + namespace uno { class XComponentContext; } +} + +namespace dp_gui { +/** + The modal “Check for Updates” dialog. +*/ +class UpdateDialog: public weld::GenericDialogController { +public: + /** + Create an instance. + + <p>Exactly one of <code>selectedPackages</code> and + <code>packageManagers</code> must be non-null.</p> + + @param context + a non-null component context + + @param parent + the parent window, may be null + + @param vExtensionList + check for updates for the contained extensions. There must only be one extension with + a particular identifier. If one extension is installed in several repositories, then the + one with the highest version must be used, because it contains the latest known update + information. + */ + UpdateDialog( + css::uno::Reference< css::uno::XComponentContext > const & context, + weld::Window * parent, + std::vector< css::uno::Reference< css::deployment::XPackage > > && vExtensionList, + std::vector< dp_gui::UpdateData > * updateData); + + virtual ~UpdateDialog() override; + + virtual short run() override; + + void notifyMenubar( bool bPrepareOnly, bool bRecheckOnly ); + static void createNotifyJob( bool bPrepareOnly, + css::uno::Sequence< css::uno::Sequence< OUString > > const &rItemList ); + +private: + UpdateDialog(UpdateDialog const &) = delete; + UpdateDialog& operator =(UpdateDialog const &) = delete; + + struct DisabledUpdate; + struct SpecificError; + struct IgnoredUpdate; + struct Index; + friend struct Index; + class Thread; + friend class Thread; + + friend class CheckListBox; + + void insertItem(const UpdateDialog::Index *pIndex, bool bEnableCheckBox); + void addAdditional(const UpdateDialog::Index *pIndex, bool bEnableCheckBox); + bool isIgnoredUpdate( UpdateDialog::Index *pIndex ); + + void addEnabledUpdate( OUString const & name, dp_gui::UpdateData const & data ); + void addDisabledUpdate( UpdateDialog::DisabledUpdate const & data ); + void addSpecificError( UpdateDialog::SpecificError const & data ); + + void checkingDone(); + + void enableOk(); + + void getIgnoredUpdates(); + + void initDescription(); + void clearDescription(); + bool showDescription(css::uno::Reference< + css::deployment::XPackage > const & aExtension); + bool showDescription(std::pair< OUString, OUString > const & pairPublisher, + OUString const & sReleaseNotes); + bool showDescription( css::uno::Reference< + css::xml::dom::XNode > const & aUpdateInfo); + bool showDescription( const OUString& rDescription); + + DECL_LINK(selectionHandler, weld::TreeView&, void); + DECL_LINK(allHandler, weld::Toggleable&, void); + DECL_LINK(okHandler, weld::Button&, void); + DECL_LINK(closeHandler, weld::Button&, void); + DECL_LINK(entryToggled, const weld::TreeView::iter_col&, void); + + css::uno::Reference< css::uno::XComponentContext > m_context; + OUString m_none; + OUString m_noInstallable; + OUString m_failure; + OUString m_unknownError; + OUString m_noDescription; + OUString m_noInstall; + OUString m_noDependency; + OUString m_noDependencyCurVer; + OUString m_browserbased; + OUString m_version; + OUString m_ignoredUpdate; + std::vector< dp_gui::UpdateData > m_enabledUpdates; + std::vector< UpdateDialog::DisabledUpdate > m_disabledUpdates; + std::vector< UpdateDialog::SpecificError > m_specificErrors; + std::vector< std::unique_ptr<UpdateDialog::IgnoredUpdate> > m_ignoredUpdates; + std::vector< std::unique_ptr<Index> > m_ListboxEntries; + std::vector< dp_gui::UpdateData > & m_updateData; + rtl::Reference< UpdateDialog::Thread > m_thread; + css::uno::Reference< css::deployment::XExtensionManager > m_xExtensionManager; + + std::unique_ptr<weld::Label> m_xChecking; + std::unique_ptr<weld::Spinner> m_xThrobber; + std::unique_ptr<weld::Label> m_xUpdate; + std::unique_ptr<weld::TreeView> m_xUpdates; + std::unique_ptr<weld::CheckButton> m_xAll; + std::unique_ptr<weld::Label> m_xDescription; + std::unique_ptr<weld::Label> m_xPublisherLabel; + std::unique_ptr<weld::LinkButton> m_xPublisherLink; + std::unique_ptr<weld::Label> m_xReleaseNotesLabel; + std::unique_ptr<weld::LinkButton> m_xReleaseNotesLink; + std::unique_ptr<weld::TextView> m_xDescriptions; + std::unique_ptr<weld::Button> m_xOk; + std::unique_ptr<weld::Button> m_xClose; + std::unique_ptr<weld::Button> m_xHelp; +}; + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/gui/dp_gui_updateinstalldialog.cxx b/desktop/source/deployment/gui/dp_gui_updateinstalldialog.cxx new file mode 100644 index 0000000000..0248a1537f --- /dev/null +++ b/desktop/source/deployment/gui/dp_gui_updateinstalldialog.cxx @@ -0,0 +1,662 @@ +/* -*- 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 "dp_gui_updatedata.hxx" + +#include <sal/config.h> +#include <osl/file.hxx> +#include <cppuhelper/exc_hlp.hxx> +#include <utility> +#include <vcl/svapp.hxx> +#include <cppuhelper/implbase.hxx> + +#include <com/sun/star/beans/NamedValue.hpp> +#include <com/sun/star/lang/WrappedTargetException.hpp> +#include <com/sun/star/ucb/NameClash.hpp> +#include <com/sun/star/ucb/XCommandEnvironment.hpp> +#include <com/sun/star/ucb/XProgressHandler.hpp> +#include <com/sun/star/deployment/DeploymentException.hpp> +#include <com/sun/star/deployment/ExtensionManager.hpp> +#include <com/sun/star/deployment/LicenseException.hpp> +#include <com/sun/star/deployment/VersionException.hpp> +#include <com/sun/star/task/XInteractionHandler.hpp> +#include <com/sun/star/task/XInteractionApprove.hpp> + +#include <dp_descriptioninfoset.hxx> +#include <strings.hrc> +#include "dp_gui_updateinstalldialog.hxx" +#include <dp_shared.hxx> +#include <dp_ucb.h> +#include <dp_misc.h> +#include "dp_gui_extensioncmdqueue.hxx" +#include <ucbhelper/content.hxx> +#include <rtl/ustrbuf.hxx> +#include <rtl/ref.hxx> +#include <salhelper/thread.hxx> +#include <com/sun/star/uno/Sequence.h> +#include <comphelper/anytostring.hxx> + +#include <string_view> +#include <vector> + +using dp_misc::StrTitle; + +namespace dp_gui { + +class UpdateInstallDialog::Thread: public salhelper::Thread { + friend class UpdateCommandEnv; +public: + Thread(css::uno::Reference< css::uno::XComponentContext > const & ctx, + UpdateInstallDialog & dialog, std::vector< dp_gui::UpdateData > & aVecUpdateData); + + void stop(); + +private: + virtual ~Thread() override; + + virtual void execute() override; + void downloadExtensions(); + bool download(OUString const & aUrls, UpdateData & aUpdatData); + void installExtensions(); + void removeTempDownloads(); + + UpdateInstallDialog & m_dialog; + + // guarded by Application::GetSolarMutex(): + css::uno::Reference< css::task::XAbortChannel > m_abort; + css::uno::Reference< css::uno::XComponentContext > m_xComponentContext; + std::vector< dp_gui::UpdateData > & m_aVecUpdateData; + ::rtl::Reference<UpdateCommandEnv> m_updateCmdEnv; + + //A folder which is created in the temp directory in which then the updates are downloaded + OUString m_sDownloadFolder; + + bool m_stop; + +}; + +class UpdateCommandEnv + : public ::cppu::WeakImplHelper< css::ucb::XCommandEnvironment, + css::task::XInteractionHandler, + css::ucb::XProgressHandler > +{ + friend class UpdateInstallDialog::Thread; + + ::rtl::Reference<UpdateInstallDialog::Thread> m_installThread; + css::uno::Reference< css::uno::XComponentContext > m_xContext; + +public: + UpdateCommandEnv( css::uno::Reference< css::uno::XComponentContext > xCtx, + ::rtl::Reference<UpdateInstallDialog::Thread> thread); + + // XCommandEnvironment + virtual css::uno::Reference<css::task::XInteractionHandler > SAL_CALL + getInteractionHandler() override; + virtual css::uno::Reference<css::ucb::XProgressHandler > + SAL_CALL getProgressHandler() override; + + // XInteractionHandler + virtual void SAL_CALL handle( + css::uno::Reference<css::task::XInteractionRequest > const & xRequest ) override; + + // XProgressHandler + virtual void SAL_CALL push( css::uno::Any const & Status ) override; + virtual void SAL_CALL update( css::uno::Any const & Status ) override; + virtual void SAL_CALL pop() override; +}; + + +UpdateInstallDialog::Thread::Thread( + css::uno::Reference< css::uno::XComponentContext> const & xCtx, + UpdateInstallDialog & dialog, + std::vector< dp_gui::UpdateData > & aVecUpdateData): + salhelper::Thread("dp_gui_updateinstalldialog"), + m_dialog(dialog), + m_xComponentContext(xCtx), + m_aVecUpdateData(aVecUpdateData), + m_updateCmdEnv(new UpdateCommandEnv(xCtx, this)), + m_stop(false) +{} + +void UpdateInstallDialog::Thread::stop() { + css::uno::Reference< css::task::XAbortChannel > abort; + { + SolarMutexGuard g; + abort = m_abort; + m_stop = true; + } + if (abort.is()) { + abort->sendAbort(); + } +} + +UpdateInstallDialog::Thread::~Thread() {} + +void UpdateInstallDialog::Thread::execute() +{ + try { + downloadExtensions(); + installExtensions(); + } + catch (...) + { + } + + //clean up the temp directories + try { + removeTempDownloads(); + } catch( ... ) { + } + + { + //make sure m_dialog is still alive + SolarMutexGuard g; + if (! m_stop) + m_dialog.updateDone(); + } + //UpdateCommandEnv keeps a reference to Thread and prevents destruction. Therefore remove it. + m_updateCmdEnv->m_installThread.clear(); +} + +UpdateInstallDialog::UpdateInstallDialog( + weld::Window* pParent, + std::vector<dp_gui::UpdateData> & aVecUpdateData, + css::uno::Reference< css::uno::XComponentContext > const & xCtx) + : GenericDialogController(pParent, "desktop/ui/updateinstalldialog.ui", + "UpdateInstallDialog") + , m_thread(new Thread(xCtx, *this, aVecUpdateData)) + , m_bError(false) + , m_bNoEntry(true) + , m_sInstalling(DpResId(RID_DLG_UPDATE_INSTALL_INSTALLING)) + , m_sFinished(DpResId(RID_DLG_UPDATE_INSTALL_FINISHED)) + , m_sNoErrors(DpResId(RID_DLG_UPDATE_INSTALL_NO_ERRORS)) + , m_sErrorDownload(DpResId(RID_DLG_UPDATE_INSTALL_ERROR_DOWNLOAD)) + , m_sErrorInstallation(DpResId(RID_DLG_UPDATE_INSTALL_ERROR_INSTALLATION)) + , m_sErrorLicenseDeclined(DpResId(RID_DLG_UPDATE_INSTALL_ERROR_LIC_DECLINED)) + , m_sNoInstall(DpResId(RID_DLG_UPDATE_INSTALL_EXTENSION_NOINSTALL)) + , m_sThisErrorOccurred(DpResId(RID_DLG_UPDATE_INSTALL_THIS_ERROR_OCCURRED)) + , m_xFt_action(m_xBuilder->weld_label("DOWNLOADING")) + , m_xStatusbar(m_xBuilder->weld_progress_bar("STATUSBAR")) + , m_xFt_extension_name(m_xBuilder->weld_label("EXTENSION_NAME")) + , m_xMle_info(m_xBuilder->weld_text_view("INFO")) + , m_xHelp(m_xBuilder->weld_button("help")) + , m_xOk(m_xBuilder->weld_button("ok")) + , m_xCancel(m_xBuilder->weld_button("cancel")) +{ + m_xMle_info->set_size_request(m_xMle_info->get_approximate_digit_width() * 52, + m_xMle_info->get_height_rows(5)); + + m_xExtensionManager = css::deployment::ExtensionManager::get( xCtx ); + + m_xCancel->connect_clicked(LINK(this, UpdateInstallDialog, cancelHandler)); + if ( ! dp_misc::office_is_running()) + m_xHelp->set_sensitive(false); +} + +UpdateInstallDialog::~UpdateInstallDialog() +{ +} + +short UpdateInstallDialog::run() +{ + m_thread->launch(); + short nRet = GenericDialogController::run(); + m_thread->stop(); + return nRet; +} + +// make sure the solar mutex is locked before calling +void UpdateInstallDialog::updateDone() +{ + if (!m_bError) + m_xMle_info->set_text(m_xMle_info->get_text() + m_sNoErrors); + m_xOk->set_sensitive(true); + m_xOk->grab_focus(); + m_xCancel->set_sensitive(false); +} + +// make sure the solar mutex is locked before calling +//sets an error message in the text area +void UpdateInstallDialog::setError(INSTALL_ERROR err, std::u16string_view sExtension, + std::u16string_view exceptionMessage) +{ + OUString sError; + m_bError = true; + + switch (err) + { + case ERROR_DOWNLOAD: + sError = m_sErrorDownload; + break; + case ERROR_INSTALLATION: + sError = m_sErrorInstallation; + break; + case ERROR_LICENSE_DECLINED: + sError = m_sErrorLicenseDeclined; + break; + + default: + OSL_ASSERT(false); + } + + OUString sMsg(m_xMle_info->get_text()); + sError = sError.replaceFirst("%NAME", sExtension); + //We want to have an empty line between the error messages. However, + //there shall be no empty line after the last entry. + if (m_bNoEntry) + m_bNoEntry = false; + else + sMsg += "\n"; + sMsg += sError; + //Insert more information about the error + if (!exceptionMessage.empty()) + sMsg += m_sThisErrorOccurred + exceptionMessage + "\n"; + + sMsg += m_sNoInstall + "\n"; + + m_xMle_info->set_text(sMsg); +} + +void UpdateInstallDialog::setError(std::u16string_view exceptionMessage) +{ + m_bError = true; + m_xMle_info->set_text(m_xMle_info->get_text() + exceptionMessage + "\n"); +} + +IMPL_LINK_NOARG(UpdateInstallDialog, cancelHandler, weld::Button&, void) +{ + m_xDialog->response(RET_CANCEL); +} + +void UpdateInstallDialog::Thread::downloadExtensions() +{ + try + { + //create the download directory in the temp folder + OUString sTempDir; + if (::osl::FileBase::getTempDirURL(sTempDir) != ::osl::FileBase::E_None) + throw css::uno::Exception("Could not get URL for the temp directory. No extensions will be installed.", nullptr); + + //create a unique name for the directory + OUString tempEntry, destFolder; + if (::osl::File::createTempFile(&sTempDir, nullptr, &tempEntry ) != ::osl::File::E_None) + throw css::uno::Exception("Could not create a temporary file in " + sTempDir + + ". No extensions will be installed", nullptr ); + + tempEntry = tempEntry.copy( tempEntry.lastIndexOf( '/' ) + 1 ); + + destFolder = dp_misc::makeURL( sTempDir, tempEntry ) + "_"; + m_sDownloadFolder = destFolder; + try + { + dp_misc::create_folder(nullptr, destFolder, m_updateCmdEnv ); + } catch (const css::uno::Exception & e) + { + css::uno::Any anyEx = cppu::getCaughtException(); + throw css::lang::WrappedTargetException( e.Message + " No extensions will be installed", + nullptr, anyEx ); + } + + + sal_uInt16 count = 0; + for (auto & updateData : m_aVecUpdateData) + { + if (!updateData.aUpdateInfo.is() || updateData.aUpdateSource.is()) + continue; + //We assume that m_aVecUpdateData contains only information about extensions which + //can be downloaded directly. + OSL_ASSERT(updateData.sWebsiteURL.isEmpty()); + + //update the name of the extension which is to be downloaded + { + SolarMutexGuard g; + if (m_stop) { + return; + } + m_dialog.m_xFt_extension_name->set_label(updateData.aInstalledPackage->getDisplayName()); + sal_uInt16 prog = (sal::static_int_cast<sal_uInt16>(100) * ++count) / + sal::static_int_cast<sal_uInt16>(m_aVecUpdateData.size()); + m_dialog.m_xStatusbar->set_percentage(prog); + } + dp_misc::DescriptionInfoset info(m_xComponentContext, updateData.aUpdateInfo); + //remember occurring exceptions in case we need to print out error information + std::vector< std::pair<OUString, css::uno::Exception> > vecExceptions; + css::uno::Sequence<OUString> seqDownloadURLs = info.getUpdateDownloadUrls(); + OSL_ENSURE(seqDownloadURLs.hasElements(), "No download URL provided!"); + for (sal_Int32 j = 0; j < seqDownloadURLs.getLength(); j++) + { + try + { + OSL_ENSURE(!seqDownloadURLs[j].isEmpty(), "Download URL is empty!"); + bool bCancelled = download(seqDownloadURLs[j], updateData); + if (bCancelled || !updateData.sLocalURL.isEmpty()) + break; + } + catch ( css::uno::Exception & e ) + { + vecExceptions.emplace_back(seqDownloadURLs[j], e); + //There can be several different errors, for example, the URL is wrong, webserver cannot be reached, + //name cannot be resolved. The UCB helper API does not specify different special exceptions for these + //cases. Therefore ignore and continue. + continue; + } + } + //update the progress and display download error + { + SolarMutexGuard g; + if (m_stop) { + return; + } + if (updateData.sLocalURL.isEmpty()) + { + //Construct a string of all messages contained in the exceptions plus the respective download URLs + OUStringBuffer buf(256); + size_t nPos = 0; + for (auto const& elem : vecExceptions) + { + if (nPos) + buf.append("\n"); + buf.append("Could not download " + elem.first + ". " + elem.second.Message); + ++nPos; + } + m_dialog.setError(UpdateInstallDialog::ERROR_DOWNLOAD, updateData.aInstalledPackage->getDisplayName(), + buf); + } + } + + } + } + catch (const css::uno::Exception & e) + { + SolarMutexGuard g; + if (m_stop) { + return; + } + m_dialog.setError(e.Message); + } +} + +void UpdateInstallDialog::Thread::installExtensions() +{ + //Update the fix text in the dialog to "Installing extensions..." + { + SolarMutexGuard g; + if (m_stop) { + return; + } + m_dialog.m_xFt_action->set_label(m_dialog.m_sInstalling); + m_dialog.m_xStatusbar->set_percentage(0); + } + + sal_uInt16 count = 0; + for (auto const& updateData : m_aVecUpdateData) + { + //update the name of the extension which is to be installed + { + SolarMutexGuard g; + if (m_stop) { + return; + } + //we only show progress after an extension has been installed. + if (count > 0) { + m_dialog.m_xStatusbar->set_percentage( + (sal::static_int_cast<sal_uInt16>(100) * count) / + sal::static_int_cast<sal_uInt16>(m_aVecUpdateData.size())); + } + m_dialog.m_xFt_extension_name->set_label(updateData.aInstalledPackage->getDisplayName()); + } + bool bError = false; + bool bLicenseDeclined = false; + css::uno::Reference<css::deployment::XPackage> xExtension; + css::uno::Exception exc; + try + { + css::uno::Reference< css::task::XAbortChannel > xAbortChannel( + updateData.aInstalledPackage->createAbortChannel() ); + { + SolarMutexGuard g; + if (m_stop) { + return; + } + m_abort = xAbortChannel; + } + if (!updateData.aUpdateSource.is() && !updateData.sLocalURL.isEmpty()) + { + css::beans::NamedValue prop("EXTENSION_UPDATE", css::uno::Any(OUString("1"))); + if (!updateData.bIsShared) + xExtension = m_dialog.getExtensionManager()->addExtension( + updateData.sLocalURL, css::uno::Sequence<css::beans::NamedValue>(&prop, 1), + "user", xAbortChannel, m_updateCmdEnv); + else + xExtension = m_dialog.getExtensionManager()->addExtension( + updateData.sLocalURL, css::uno::Sequence<css::beans::NamedValue>(&prop, 1), + "shared", xAbortChannel, m_updateCmdEnv); + } + else if (updateData.aUpdateSource.is()) + { + OSL_ASSERT(updateData.aUpdateSource.is()); + //I am not sure if we should obtain the install properties and pass them into + //add extension. Currently it contains only "SUPPRESS_LICENSE". So it could happen + //that a license is displayed when updating from the shared repository, although the + //shared extension was installed using "SUPPRESS_LICENSE". + css::beans::NamedValue prop("EXTENSION_UPDATE", css::uno::Any(OUString("1"))); + if (!updateData.bIsShared) + xExtension = m_dialog.getExtensionManager()->addExtension( + updateData.aUpdateSource->getURL(), css::uno::Sequence<css::beans::NamedValue>(&prop, 1), + "user", xAbortChannel, m_updateCmdEnv); + else + xExtension = m_dialog.getExtensionManager()->addExtension( + updateData.aUpdateSource->getURL(), css::uno::Sequence<css::beans::NamedValue>(&prop, 1), + "shared", xAbortChannel, m_updateCmdEnv); + } + } + catch (css::deployment::DeploymentException & de) + { + if (de.Cause.has<css::deployment::LicenseException>()) + { + bLicenseDeclined = true; + } + else + { + exc = de.Cause.get<css::uno::Exception>(); + bError = true; + } + } + catch (css::uno::Exception& e) + { + exc = e; + bError = true; + } + + if (bLicenseDeclined) + { + SolarMutexGuard g; + if (m_stop) { + return; + } + m_dialog.setError(UpdateInstallDialog::ERROR_LICENSE_DECLINED, + updateData.aInstalledPackage->getDisplayName(), std::u16string_view()); + } + else if (!xExtension.is() || bError) + { + SolarMutexGuard g; + if (m_stop) { + return; + } + m_dialog.setError(UpdateInstallDialog::ERROR_INSTALLATION, + updateData.aInstalledPackage->getDisplayName(), exc.Message); + } + ++count; + } + { + SolarMutexGuard g; + if (m_stop) { + return; + } + m_dialog.m_xStatusbar->set_percentage(100); + m_dialog.m_xFt_extension_name->set_label(OUString()); + m_dialog.m_xFt_action->set_label(m_dialog.m_sFinished); + } +} + +void UpdateInstallDialog::Thread::removeTempDownloads() +{ + if (!m_sDownloadFolder.isEmpty()) + { + dp_misc::erase_path(m_sDownloadFolder, + css::uno::Reference<css::ucb::XCommandEnvironment>(),false /* no throw: ignore errors */ ); + //remove also the temp file which we have used to create the unique name + OUString tempFile = m_sDownloadFolder.copy(0, m_sDownloadFolder.getLength() - 1); + dp_misc::erase_path(tempFile, css::uno::Reference<css::ucb::XCommandEnvironment>(),false); + m_sDownloadFolder.clear(); + } +} + +bool UpdateInstallDialog::Thread::download(OUString const & sDownloadURL, UpdateData & aUpdateData) +{ + { + SolarMutexGuard g; + if (m_stop) { + return m_stop; + } + } + + OSL_ASSERT(m_sDownloadFolder.getLength()); + OUString destFolder, tempEntry; + if (::osl::File::createTempFile( + &m_sDownloadFolder, + nullptr, &tempEntry ) != ::osl::File::E_None) + { + //ToDo feedback in window that download of this component failed + throw css::uno::Exception("Could not create temporary file in folder " + destFolder + ".", nullptr); + } + tempEntry = tempEntry.copy( tempEntry.lastIndexOf( '/' ) + 1 ); + + destFolder = dp_misc::makeURL( m_sDownloadFolder, tempEntry ) + "_"; + + ::ucbhelper::Content destFolderContent; + dp_misc::create_folder( &destFolderContent, destFolder, m_updateCmdEnv ); + + ::ucbhelper::Content sourceContent; + (void)dp_misc::create_ucb_content(&sourceContent, sDownloadURL, m_updateCmdEnv); + + const OUString sTitle( StrTitle::getTitle( sourceContent ) ); + + destFolderContent.transferContent( + sourceContent, ::ucbhelper::InsertOperation::Copy, + sTitle, css::ucb::NameClash::OVERWRITE ); + + { + //the user may have cancelled the dialog because downloading took too long + SolarMutexGuard g; + if (m_stop) { + return m_stop; + } + //all errors should be handled by the command environment. + aUpdateData.sLocalURL = destFolder + "/" + sTitle; + } + + return m_stop; +} + +UpdateCommandEnv::UpdateCommandEnv( css::uno::Reference< css::uno::XComponentContext > xCtx, + ::rtl::Reference<UpdateInstallDialog::Thread> thread) + : m_installThread(std::move(thread)), + m_xContext(std::move(xCtx)) +{ +} + +// XCommandEnvironment +css::uno::Reference<css::task::XInteractionHandler> UpdateCommandEnv::getInteractionHandler() +{ + return this; +} + +css::uno::Reference<css::ucb::XProgressHandler> UpdateCommandEnv::getProgressHandler() +{ + return this; +} + +// XInteractionHandler +void UpdateCommandEnv::handle( + css::uno::Reference< css::task::XInteractionRequest> const & xRequest ) +{ + css::uno::Any request( xRequest->getRequest() ); + OSL_ASSERT( request.getValueTypeClass() == css::uno::TypeClass_EXCEPTION ); + dp_misc::TRACE("[dp_gui_cmdenv.cxx] incoming request:\n" + + ::comphelper::anyToString(request) + "\n\n"); + + css::deployment::VersionException verExc; + bool approve = false; + + if (request >>= verExc) + { //We must catch the version exception during the update, + //because otherwise the user would be confronted with the dialogs, asking + //them if they want to replace an already installed version of the same extension. + //During an update we assume that we always want to replace the old version with the + //new version. + approve = true; + } + + if (!approve) + { + //forward to interaction handler for main dialog. + handleInteractionRequest( m_xContext, xRequest ); + } + else + { + // select: + css::uno::Sequence< css::uno::Reference< css::task::XInteractionContinuation > > conts( + xRequest->getContinuations() ); + css::uno::Reference< css::task::XInteractionContinuation > const * pConts = + conts.getConstArray(); + sal_Int32 len = conts.getLength(); + for ( sal_Int32 pos = 0; pos < len; ++pos ) + { + if (approve) { + css::uno::Reference< css::task::XInteractionApprove > xInteractionApprove( + pConts[ pos ], css::uno::UNO_QUERY ); + if (xInteractionApprove.is()) { + xInteractionApprove->select(); + // don't query again for ongoing continuations: + approve = false; + } + } + } + } +} + +// XProgressHandler +void UpdateCommandEnv::push( css::uno::Any const & /*Status*/ ) +{ +} + +void UpdateCommandEnv::update( css::uno::Any const & /*Status */) +{ +} + +void UpdateCommandEnv::pop() +{ +} + + +} //end namespace dp_gui + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/gui/dp_gui_updateinstalldialog.hxx b/desktop/source/deployment/gui/dp_gui_updateinstalldialog.hxx new file mode 100644 index 0000000000..39b37ed4bc --- /dev/null +++ b/desktop/source/deployment/gui/dp_gui_updateinstalldialog.hxx @@ -0,0 +1,112 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#pragma once + +#include <sal/config.h> +#include <vcl/weld.hxx> +#include <rtl/ref.hxx> + +#include <string_view> +#include <vector> + +/// @HTML +namespace com::sun::star::deployment { + class XExtensionManager; +} +namespace com::sun::star::uno { + class XComponentContext; +} + +namespace dp_gui { + + struct UpdateData; + class UpdateCommandEnv; + + +/** + The modal “Download and Installation” dialog. +*/ +class UpdateInstallDialog : public weld::GenericDialogController +{ +public: + /** + Create an instance. + + @param parent + the parent window, may be null + */ + UpdateInstallDialog(weld::Window* parent, std::vector<UpdateData> & aVecUpdateData, + css::uno::Reference< css::uno::XComponentContext > const & xCtx); + + virtual ~UpdateInstallDialog() override; + + virtual short run() override; + +private: + UpdateInstallDialog(UpdateInstallDialog const &) = delete; + UpdateInstallDialog& operator =(UpdateInstallDialog const &) = delete; + + class Thread; + friend class Thread; + friend class UpdateCommandEnv; + + DECL_LINK(cancelHandler, weld::Button&, void); + + //signals in the dialog that we have finished. + void updateDone(); + //Writes a particular error into the info listbox. + enum INSTALL_ERROR + { + ERROR_DOWNLOAD, + ERROR_INSTALLATION, + ERROR_LICENSE_DECLINED + }; + void setError(INSTALL_ERROR err, std::u16string_view sExtension, std::u16string_view exceptionMessage); + void setError(std::u16string_view exceptionMessage); + const css::uno::Reference< css::deployment::XExtensionManager >& getExtensionManager() const + { return m_xExtensionManager; } + + rtl::Reference< Thread > m_thread; + css::uno::Reference< css::deployment::XExtensionManager > m_xExtensionManager; + //Signals that an error occurred during download and installation + bool m_bError; + bool m_bNoEntry; + + OUString m_sInstalling; + OUString m_sFinished; + OUString m_sNoErrors; + OUString m_sErrorDownload; + OUString m_sErrorInstallation; + OUString m_sErrorLicenseDeclined; + OUString m_sNoInstall; + OUString m_sThisErrorOccurred; + + std::unique_ptr<weld::Label> m_xFt_action; + std::unique_ptr<weld::ProgressBar> m_xStatusbar; + std::unique_ptr<weld::Label> m_xFt_extension_name; + std::unique_ptr<weld::TextView> m_xMle_info; + std::unique_ptr<weld::Button> m_xHelp; + std::unique_ptr<weld::Button> m_xOk; + std::unique_ptr<weld::Button> m_xCancel; +}; + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/gui/license_dialog.cxx b/desktop/source/deployment/gui/license_dialog.cxx new file mode 100644 index 0000000000..23f184a333 --- /dev/null +++ b/desktop/source/deployment/gui/license_dialog.cxx @@ -0,0 +1,244 @@ +/* -*- 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 <comphelper/unwrapargs.hxx> +#include <vcl/event.hxx> +#include <vcl/idle.hxx> +#include <vcl/svapp.hxx> +#include <vcl/threadex.hxx> +#include <vcl/weld.hxx> +#include <cppuhelper/supportsservice.hxx> + +#include "license_dialog.hxx" + +#include <functional> +#include <string_view> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; + +namespace dp_gui { + +namespace { + +struct LicenseDialogImpl : public weld::GenericDialogController +{ + bool m_bLicenseRead; + Idle m_aResized; + AutoTimer m_aRepeat; + + std::unique_ptr<weld::Label> m_xFtHead; + std::unique_ptr<weld::Widget> m_xArrow1; + std::unique_ptr<weld::Widget> m_xArrow2; + std::unique_ptr<weld::TextView> m_xLicense; + std::unique_ptr<weld::Button> m_xDown; + std::unique_ptr<weld::Button> m_xAcceptButton; + std::unique_ptr<weld::Button> m_xDeclineButton; + + void PageDown(); + DECL_LINK(ScrollTimerHdl, Timer*, void); + DECL_LINK(ScrolledHdl, weld::TextView&, void); + DECL_LINK(ResizedHdl, Timer*, void); + DECL_LINK(CancelHdl, weld::Button&, void); + DECL_LINK(AcceptHdl, weld::Button&, void); + DECL_LINK(KeyInputHdl, const KeyEvent&, bool); + DECL_STATIC_LINK(LicenseDialogImpl, KeyReleaseHdl, const KeyEvent&, bool); + DECL_LINK(MousePressHdl, const MouseEvent&, bool); + DECL_LINK(MouseReleaseHdl, const MouseEvent&, bool); + DECL_LINK(SizeAllocHdl, const Size&, void); + + LicenseDialogImpl(weld::Window * pParent, + std::u16string_view sExtensionName, + const OUString & sLicenseText); + + bool IsEndReached() const; +}; + +} + +LicenseDialogImpl::LicenseDialogImpl( + weld::Window * pParent, + std::u16string_view sExtensionName, + const OUString & sLicenseText) + : GenericDialogController(pParent, "desktop/ui/licensedialog.ui", "LicenseDialog") + , m_bLicenseRead(false) + , m_aResized("desktop LicenseDialogImpl m_aResized") + , m_aRepeat("LicenseDialogImpl m_aRepeat") + , m_xFtHead(m_xBuilder->weld_label("head")) + , m_xArrow1(m_xBuilder->weld_widget("arrow1")) + , m_xArrow2(m_xBuilder->weld_widget("arrow2")) + , m_xLicense(m_xBuilder->weld_text_view("textview")) + , m_xDown(m_xBuilder->weld_button("down")) + , m_xAcceptButton(m_xBuilder->weld_button("ok")) + , m_xDeclineButton(m_xBuilder->weld_button("cancel")) +{ + m_xArrow1->show(); + m_xArrow2->hide(); + + m_xLicense->connect_size_allocate(LINK(this, LicenseDialogImpl, SizeAllocHdl)); + m_xLicense->set_size_request(m_xLicense->get_approximate_digit_width() * 72, + m_xLicense->get_height_rows(21)); + + m_xLicense->set_text(sLicenseText); + m_xFtHead->set_label(m_xFtHead->get_label() + "\n" + sExtensionName); + + m_xAcceptButton->connect_clicked( LINK(this, LicenseDialogImpl, AcceptHdl) ); + m_xDeclineButton->connect_clicked( LINK(this, LicenseDialogImpl, CancelHdl) ); + + m_xLicense->connect_vadjustment_changed(LINK(this, LicenseDialogImpl, ScrolledHdl)); + m_xDown->connect_mouse_press(LINK(this, LicenseDialogImpl, MousePressHdl)); + m_xDown->connect_mouse_release(LINK(this, LicenseDialogImpl, MouseReleaseHdl)); + m_xDown->connect_key_press(LINK(this, LicenseDialogImpl, KeyInputHdl)); + m_xDown->connect_key_release(LINK(this, LicenseDialogImpl, KeyReleaseHdl)); + + m_aRepeat.SetTimeout(Application::GetSettings().GetMouseSettings().GetButtonRepeat()); + m_aRepeat.SetInvokeHandler(LINK(this, LicenseDialogImpl, ScrollTimerHdl)); + + m_aResized.SetPriority(TaskPriority::LOWEST); + m_aResized.SetInvokeHandler(LINK(this, LicenseDialogImpl, ResizedHdl)); +} + +IMPL_LINK_NOARG(LicenseDialogImpl, SizeAllocHdl, const Size&, void) +{ + m_aResized.Start(); +} + +IMPL_LINK_NOARG(LicenseDialogImpl, AcceptHdl, weld::Button&, void) +{ + m_xDialog->response(RET_OK); +} + +IMPL_LINK_NOARG(LicenseDialogImpl, CancelHdl, weld::Button&, void) +{ + m_xDialog->response(RET_CANCEL); +} + +bool LicenseDialogImpl::IsEndReached() const +{ + return m_xLicense->vadjustment_get_value() + m_xLicense->vadjustment_get_page_size() >= m_xLicense->vadjustment_get_upper(); +} + +IMPL_LINK_NOARG(LicenseDialogImpl, ScrolledHdl, weld::TextView&, void) +{ + if (IsEndReached()) + { + m_xDown->set_sensitive(false); + m_aRepeat.Stop(); + + if (!m_bLicenseRead) + { + m_xAcceptButton->set_sensitive(true); + m_xAcceptButton->grab_focus(); + m_xArrow1->hide(); + m_xArrow2->show(); + m_bLicenseRead = true; + } + } + else + m_xDown->set_sensitive(true); +} + +void LicenseDialogImpl::PageDown() +{ + m_xLicense->vadjustment_set_value(m_xLicense->vadjustment_get_value() + + m_xLicense->vadjustment_get_page_size()); + ScrolledHdl(*m_xLicense); +} + +IMPL_LINK(LicenseDialogImpl, KeyInputHdl, const KeyEvent&, rKEvt, bool) +{ + vcl::KeyCode aKeyCode = rKEvt.GetKeyCode(); + if (aKeyCode.GetCode() == KEY_RETURN || aKeyCode.GetCode() == KEY_SPACE) + PageDown(); + return false; +} + +IMPL_LINK_NOARG(LicenseDialogImpl, ResizedHdl, Timer*, void) +{ + ScrolledHdl(*m_xLicense); +} + +IMPL_LINK_NOARG(LicenseDialogImpl, ScrollTimerHdl, Timer*, void) +{ + PageDown(); +} + +IMPL_STATIC_LINK_NOARG(LicenseDialogImpl, KeyReleaseHdl, const KeyEvent&, bool) +{ + return false; +} + +IMPL_LINK_NOARG(LicenseDialogImpl, MousePressHdl, const MouseEvent&, bool) +{ + PageDown(); + m_aRepeat.Start(); + return false; +} + +IMPL_LINK_NOARG(LicenseDialogImpl, MouseReleaseHdl, const MouseEvent&, bool) +{ + m_aRepeat.Stop(); + return false; +} + +LicenseDialog::LicenseDialog( Sequence<Any> const& args, + Reference<XComponentContext> const& ) +{ + comphelper::unwrapArgs( args, m_parent, m_sExtensionName, m_sLicenseText ); +} + +// XServiceInfo +OUString LicenseDialog::getImplementationName() +{ + return "com.sun.star.comp.deployment.ui.LicenseDialog"; +} + +sal_Bool LicenseDialog::supportsService( const OUString& ServiceName ) +{ + return cppu::supportsService(this, ServiceName); +} + +css::uno::Sequence< OUString > LicenseDialog::getSupportedServiceNames() +{ + return { "com.sun.star.deployment.ui.LicenseDialog" }; +} + + +// XExecutableDialog + +void LicenseDialog::setTitle( OUString const & ) +{ +} + +sal_Int16 LicenseDialog::execute() +{ + return vcl::solarthread::syncExecute( + std::bind(&LicenseDialog::solar_execute, this)); +} + +sal_Int16 LicenseDialog::solar_execute() +{ + LicenseDialogImpl dlg(Application::GetFrameWeld(m_parent), m_sExtensionName, m_sLicenseText); + return dlg.run(); +} + +} // namespace dp_gui + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/gui/license_dialog.hxx b/desktop/source/deployment/gui/license_dialog.hxx new file mode 100644 index 0000000000..3c628a880d --- /dev/null +++ b/desktop/source/deployment/gui/license_dialog.hxx @@ -0,0 +1,53 @@ +/* -*- 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/awt/XWindow.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <com/sun/star/ui/dialogs/XExecutableDialog.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> + +namespace dp_gui +{ +class LicenseDialog + : public ::cppu::WeakImplHelper<css::ui::dialogs::XExecutableDialog, css::lang::XServiceInfo> +{ + css::uno::Reference<css::awt::XWindow> /* const */ m_parent; + OUString m_sExtensionName; + OUString /* const */ m_sLicenseText; + + sal_Int16 solar_execute(); + +public: + LicenseDialog(css::uno::Sequence<css::uno::Any> const& args, + css::uno::Reference<css::uno::XComponentContext> const& xComponentContext); + + // 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; + + // XExecutableDialog + virtual void SAL_CALL setTitle(OUString const& title) override; + virtual sal_Int16 SAL_CALL execute() override; +}; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/inc/dp_dependencies.hxx b/desktop/source/deployment/inc/dp_dependencies.hxx new file mode 100644 index 0000000000..1c9872c785 --- /dev/null +++ b/desktop/source/deployment/inc/dp_dependencies.hxx @@ -0,0 +1,72 @@ +/* -*- 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 <com/sun/star/uno/Reference.hxx> +#include <com/sun/star/uno/Sequence.hxx> +#include "dp_misc_api.hxx" + +/// @HTML + +namespace com::sun::star::xml::dom { + class XElement; +} +namespace dp_misc { class DescriptionInfoset; } + +namespace dp_misc { + +/** + Dependency handling. +*/ +namespace Dependencies { + /** + Check for unsatisfied dependencies. + + @param infoset + the infoset containing the dependencies to check + + @return + a list of the unsatisfied dependencies from <code>infoset</code> (in no + specific order) + */ + DESKTOP_DEPLOYMENTMISC_DLLPUBLIC css::uno::Sequence< + css::uno::Reference< css::xml::dom::XElement > > + check(dp_misc::DescriptionInfoset const & infoset); + + /** + Obtain the (human-readable) error message of a failed dependency. + + @param dependency + a dependency represented as a non-null XML element + + @return + the name of the dependency; will never be empty, as a localized + “unknown” is substituted for an empty/missing name + */ + DESKTOP_DEPLOYMENTMISC_DLLPUBLIC OUString getErrorText( + css::uno::Reference< css::xml::dom::XElement > + const & dependency); +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/inc/dp_descriptioninfoset.hxx b/desktop/source/deployment/inc/dp_descriptioninfoset.hxx new file mode 100644 index 0000000000..08d533a79d --- /dev/null +++ b/desktop/source/deployment/inc/dp_descriptioninfoset.hxx @@ -0,0 +1,287 @@ +/* -*- 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 <optional> +#include <string_view> + +#include <com/sun/star/uno/Reference.hxx> +#include <com/sun/star/uno/Sequence.hxx> +#include <sal/types.h> +#include "dp_misc_api.hxx" + +/// @HTML + +namespace com::sun::star { + namespace uno { class XComponentContext; } + namespace xml { + namespace dom { + class XNode; + class XNodeList; + } + namespace xpath { class XXPathAPI; } + } +} + +namespace dp_misc { + +struct DESKTOP_DEPLOYMENTMISC_DLLPUBLIC SimpleLicenseAttributes +{ + OUString acceptBy; + //Attribute suppress-on-update. Default is false. + bool suppressOnUpdate; + //Attribute suppress-if-required. Default is false. + bool suppressIfRequired; +}; + + +/** + Access to the content of an XML <code>description</code> element. + + <p>This works for <code>description</code> elements in both the + <code>description.xml</code> file and online update information formats.</p> +*/ +class DESKTOP_DEPLOYMENTMISC_DLLPUBLIC DescriptionInfoset { +public: + /** + Create an instance. + + @param context + a non-null component context + + @param element + a <code>description</code> element; may be null (equivalent to an element + with no content) + */ + DescriptionInfoset( + css::uno::Reference< css::uno::XComponentContext > const & context, + css::uno::Reference< css::xml::dom::XNode > const & element); + + ~DescriptionInfoset(); + + /** + Return the identifier. + + @return + the identifier, or an empty <code>optional</code> if none is specified + */ + ::std::optional< OUString > getIdentifier() const; + + /** + Return the textual version representation. + + @return + textual version representation + */ + OUString getVersion() const; + + /** + Returns a list of supported platforms. + + If the extension does not specify a platform by leaving out the platform element + then we assume that the extension supports all platforms. In this case the returned + sequence will have one element, which is "all". + If the platform element is present but does not specify a platform then an empty + sequence is returned. Examples for invalid platform elements: + <pre> + <platform />, <platform value="" />, <platform value=","> + </pre> + + The value attribute can contain various platform tokens. They must be separated by + commas.Each token will be stripped from leading and trailing white space (trim()). + */ + css::uno::Sequence< OUString > getSupportedPlatforms() const; + + /** + Returns the localized publisher name and the corresponding URL. + + In case there is no publisher element then a pair of two empty strings is returned. + */ + std::pair< OUString, OUString > getLocalizedPublisherNameAndURL() const; + + /** + Returns the URL for the release notes corresponding to the office's locale. + + In case there is no release-notes element then an empty string is returned. + */ + OUString getLocalizedReleaseNotesURL() const; + + /** returns the relative path to the license file. + + In case there is no simple-license element then an empty string is returned. + */ + OUString getLocalizedLicenseURL() const; + + /** returns the attributes of the simple-license element + + As long as there is a simple-license element, the function will return + the structure. If it does not exist, then the optional object is uninitialized. + */ + ::std::optional<SimpleLicenseAttributes> getSimpleLicenseAttributes() const; + + /** returns the localized display name of the extensions. + + In case there is no localized display-name then an empty string is returned. + */ + OUString getLocalizedDisplayName() const; + + /** + returns the download website URL from the update information. + + There can be multiple URLs where each is assigned to a particular locale. + The function returns the URL which locale matches best the one used in the office. + + The return value is an optional because it may be necessary to find out if there + was a value provided or not. This is necessary to flag the extension in the update dialog + properly as "browser based update". The return value will only then not be initialized + if there is no <code><update-website></code>. If the element exists, then it must + have at least one child element containing a URL. + + The <code><update-website></code> and <code><update-download></code> + elements are mutually exclusive. + + @return + the download website URL, or an empty <code>optional</code> if none is + specified + */ + ::std::optional< OUString > getLocalizedUpdateWebsiteURL() const; + + /** returns the relative URL to the description. + + The URL is relative to the root directory of the extensions. + */ + OUString getLocalizedDescriptionURL() const; + /** + Return the dependencies. + + @return + dependencies; will never be null + */ + css::uno::Reference< css::xml::dom::XNodeList > + getDependencies() const; + + /** + Return the update information URLs. + + @return + update information URLs + */ + css::uno::Sequence< OUString > getUpdateInformationUrls() const; + + /** + Return the download URLs from the update information. + + Because the <code><update-download></code> and the <code><update-website></code> + elements are mutually exclusive one may need to determine exactly if the element + was provided. + + @return + download URLs + */ + css::uno::Sequence< OUString > getUpdateDownloadUrls() const; + + /** + Returns the URL for the icon image. + */ + OUString getIconURL( bool bHighContrast ) const; + + bool hasDescription() const; + +private: + SAL_DLLPRIVATE ::std::optional< OUString > getOptionalValue( + OUString const & expression) const; + + SAL_DLLPRIVATE css::uno::Sequence< OUString > getUrls( + OUString const & expression) const; + + /** Retrieves a child element which as lang attribute which matches the office locale. + + Only top-level children are taken into account. It is also assumed that they are all + of the same element type and have a lang attribute. The matching algorithm is according + to RFC 3066, with the exception that only one variant is allowed. + @param parent + the expression used to obtain the parent of the localized children. It can be null. + Then a null reference is returned. + */ + SAL_DLLPRIVATE css::uno::Reference< css::xml::dom::XNode > + getLocalizedChild( OUString const & sParent) const; + SAL_DLLPRIVATE css::uno::Reference< css::xml::dom::XNode> + matchLanguageTag( + css::uno::Reference< css::xml::dom::XNode > const & xParent, + std::u16string_view rTag) const; + + /** If there is no child element with a locale matching the office locale, then we use + the first child. In the case of the simple-license we also use the former default locale, which + was determined by the default-license-id (/description/registration/simple-license/@default-license-id) + and the license-id attributes (/description/registration/simple-license/license-text/@license-id). + However, since OOo 2.4 we use also the first child as default for the license + unless the two attributes are present. + */ + SAL_DLLPRIVATE css::uno::Reference< css::xml::dom::XNode> + getChildWithDefaultLocale( + css::uno::Reference< css::xml::dom::XNode > const & xParent) const; + /** + @param out_bParentExists + indicates if the element node specified in sXPathParent exists. + */ + SAL_DLLPRIVATE OUString getLocalizedHREFAttrFromChild( + OUString const & sXPathParent, bool * out_bParentExists) const; + + /** Gets the node value for a given expression. The expression is used in + m_xpath-selectSingleNode. The value of the returned node is return value + of this function. + */ + SAL_DLLPRIVATE OUString + getNodeValueFromExpression(OUString const & expression) const; + + /** Check the extensions denylist if additional extension meta data (e.g. dependencies) + are defined for this extension and have to be taken into account. + */ + SAL_DLLPRIVATE void + checkDenylist() const; + + /** Helper method to compare the versions with the current version + */ + SAL_DLLPRIVATE static bool + checkDenylistVersion(std::u16string_view currentversion, + css::uno::Sequence< OUString > const & versions); + + css::uno::Reference< css::uno::XComponentContext > m_context; + css::uno::Reference< css::xml::dom::XNode > m_element; + css::uno::Reference< css::xml::xpath::XXPathAPI > m_xpath; +}; + +inline bool DescriptionInfoset::hasDescription() const +{ + return m_element.is(); +} + +/** creates a DescriptionInfoset object. + + The argument sExtensionFolderURL is a file URL to extension folder containing + the description.xml. + */ +DESKTOP_DEPLOYMENTMISC_DLLPUBLIC +DescriptionInfoset getDescriptionInfoset(std::u16string_view sExtensionFolderURL); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/inc/dp_identifier.hxx b/desktop/source/deployment/inc/dp_identifier.hxx new file mode 100644 index 0000000000..bd11170b6a --- /dev/null +++ b/desktop/source/deployment/inc/dp_identifier.hxx @@ -0,0 +1,84 @@ +/* -*- 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 <optional> +#include <string_view> + +#include <com/sun/star/uno/Reference.hxx> + +#include "dp_misc_api.hxx" + +namespace com::sun::star::deployment { + class XPackage; +} + +namespace dp_misc { + +/** + Generates an identifier from an optional identifier. + + @param optional + an optional identifier + + @param fileName + a file name + + @return + the given optional identifier if present, otherwise a legacy identifier based + on the given file name +*/ +DESKTOP_DEPLOYMENTMISC_DLLPUBLIC OUString generateIdentifier( + ::std::optional< OUString > const & optional, + std::u16string_view fileName); + +/** + Gets the identifier of a package. + + @param package + a non-null package + + @return + the explicit identifier of the given package if present, otherwise the + implicit legacy identifier of the given package + + @throws css::uno::RuntimeException +*/ +DESKTOP_DEPLOYMENTMISC_DLLPUBLIC OUString getIdentifier( + css::uno::Reference< css::deployment::XPackage > + const & package); + +/** + Generates a legacy identifier based on a file name. + + @param fileName + a file name + + @return + a legacy identifier based on the given file name +*/ +DESKTOP_DEPLOYMENTMISC_DLLPUBLIC OUString generateLegacyIdentifier( + std::u16string_view fileName); + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/inc/dp_interact.h b/desktop/source/deployment/inc/dp_interact.h new file mode 100644 index 0000000000..6a041d99f1 --- /dev/null +++ b/desktop/source/deployment/inc/dp_interact.h @@ -0,0 +1,140 @@ +/* -*- 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 <config_options.h> +#include <rtl/ref.hxx> +#include <cppuhelper/implbase.hxx> +#include <com/sun/star/ucb/XCommandEnvironment.hpp> +#include <com/sun/star/task/XAbortChannel.hpp> +#include <utility> +#include "dp_misc_api.hxx" + +namespace dp_misc +{ + +inline void progressUpdate( + OUString const & status, + css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv ) +{ + if (xCmdEnv.is()) { + css::uno::Reference<css::ucb::XProgressHandler> xProgressHandler( + xCmdEnv->getProgressHandler() ); + if (xProgressHandler.is()) { + xProgressHandler->update( css::uno::Any(status) ); + } + } +} + + +class ProgressLevel +{ + css::uno::Reference<css::ucb::XProgressHandler> m_xProgressHandler; + +public: + inline ~ProgressLevel(); + inline ProgressLevel( + css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv, + OUString const & status ); + + inline void update( OUString const & status ) const; + inline void update( css::uno::Any const & status ) const; +}; + + +inline ProgressLevel::ProgressLevel( + css::uno::Reference< css::ucb::XCommandEnvironment > const & xCmdEnv, + OUString const & status ) +{ + if (xCmdEnv.is()) + m_xProgressHandler = xCmdEnv->getProgressHandler(); + if (m_xProgressHandler.is()) + m_xProgressHandler->push( css::uno::Any(status) ); +} + + +inline ProgressLevel::~ProgressLevel() +{ + if (m_xProgressHandler.is()) + m_xProgressHandler->pop(); +} + + +inline void ProgressLevel::update( OUString const & status ) const +{ + if (m_xProgressHandler.is()) + m_xProgressHandler->update( css::uno::Any(status) ); +} + + +inline void ProgressLevel::update( css::uno::Any const & status ) const +{ + if (m_xProgressHandler.is()) + m_xProgressHandler->update( status ); +} + + + +/** @return true if ia handler is present and any selection has been chosen + */ +DESKTOP_DEPLOYMENTMISC_DLLPUBLIC bool interactContinuation( + css::uno::Any const & request, + css::uno::Type const & continuation, + css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv, + bool * pcont, bool * pabort ); + + + + +class UNLESS_MERGELIBS(DESKTOP_DEPLOYMENTMISC_DLLPUBLIC) AbortChannel : + public ::cppu::WeakImplHelper<css::task::XAbortChannel> +{ + bool m_aborted; + css::uno::Reference<css::task::XAbortChannel> m_xNext; + +public: + AbortChannel() : m_aborted( false ) {} + static AbortChannel * get( + css::uno::Reference<css::task::XAbortChannel> const & xAbortChannel ) + { return static_cast<AbortChannel *>(xAbortChannel.get()); } + + bool isAborted() const { return m_aborted; } + + // XAbortChannel + virtual void SAL_CALL sendAbort() override; + + class SAL_DLLPRIVATE Chain + { + const ::rtl::Reference<AbortChannel> m_abortChannel; + public: + Chain( + ::rtl::Reference<AbortChannel> abortChannel, + css::uno::Reference<css::task::XAbortChannel> const & xNext ) + : m_abortChannel(std::move( abortChannel )) + { if (m_abortChannel.is()) m_abortChannel->m_xNext = xNext; } + ~Chain() + { if (m_abortChannel.is()) m_abortChannel->m_xNext.clear(); } + }; + friend class Chain; +}; + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/inc/dp_misc_api.hxx b/desktop/source/deployment/inc/dp_misc_api.hxx new file mode 100644 index 0000000000..ecb07dada3 --- /dev/null +++ b/desktop/source/deployment/inc/dp_misc_api.hxx @@ -0,0 +1,31 @@ +/* -*- 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 <sal/types.h> + +#if defined DESKTOP_DEPLOYMENTMISC_DLLIMPLEMENTATION +#define DESKTOP_DEPLOYMENTMISC_DLLPUBLIC SAL_DLLPUBLIC_EXPORT +#else +#define DESKTOP_DEPLOYMENTMISC_DLLPUBLIC SAL_DLLPUBLIC_IMPORT +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/inc/dp_package.hxx b/desktop/source/deployment/inc/dp_package.hxx new file mode 100644 index 0000000000..f287990f25 --- /dev/null +++ b/desktop/source/deployment/inc/dp_package.hxx @@ -0,0 +1,42 @@ +/* -*- 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 <com/sun/star/uno/Reference.hxx> +#include <rtl/ustring.hxx> + +namespace com::sun::star { + namespace deployment { class XPackageRegistry; } + namespace uno { class XComponentContext; } +} + +namespace dp_registry::backend::bundle { + +css::uno::Reference<css::deployment::XPackageRegistry> create( + css::uno::Reference<css::deployment::XPackageRegistry> const & + xRootRegistry, + OUString const & context, OUString const & cachePath, + css::uno::Reference<css::uno::XComponentContext> const & xComponentContext); + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/inc/dp_persmap.h b/desktop/source/deployment/inc/dp_persmap.h new file mode 100644 index 0000000000..c369798073 --- /dev/null +++ b/desktop/source/deployment/inc/dp_persmap.h @@ -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 . + */ + +#pragma once + +#include <rtl/ustring.hxx> +#include <osl/file.hxx> +#include <unordered_map> + +namespace dp_misc +{ +typedef std::unordered_map<OString, OString> t_string2string_map; + +// Class to read obsolete registered extensions +// should be removed for LibreOffice 4.0 +class PersistentMap final +{ + ::osl::File m_MapFile; + t_string2string_map m_entries; + bool m_bIsOpen; + bool m_bToBeCreated; + bool m_bIsDirty; + +public: + ~PersistentMap(); + PersistentMap(OUString const& url); + /** in mem db */ + PersistentMap(); + + bool has(OString const& key) const; + bool get(OString* value, OString const& key) const; + const t_string2string_map& getEntries() const { return m_entries; } + void put(OString const& key, OString const& value); + bool erase(OString const& key); + +private: + void open(); + void readAll(); + void add(OString const& key, OString const& value); + void flush(); +}; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/inc/dp_platform.hxx b/desktop/source/deployment/inc/dp_platform.hxx new file mode 100644 index 0000000000..5e454700d2 --- /dev/null +++ b/desktop/source/deployment/inc/dp_platform.hxx @@ -0,0 +1,41 @@ +/* -*- 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 "dp_misc_api.hxx" + +#include <com/sun/star/uno/Sequence.hxx> +#include <rtl/ustring.hxx> + +namespace dp_misc +{ +DESKTOP_DEPLOYMENTMISC_DLLPUBLIC OUString const& getPlatformString(); + +DESKTOP_DEPLOYMENTMISC_DLLPUBLIC +bool platform_fits(std::u16string_view platform_string); + +/** determines if the current platform corresponds to one of the platform strings. + +*/ +DESKTOP_DEPLOYMENTMISC_DLLPUBLIC +bool hasValidPlatform(css::uno::Sequence<OUString> const& platformStrings); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/inc/dp_registry.hxx b/desktop/source/deployment/inc/dp_registry.hxx new file mode 100644 index 0000000000..76742587ed --- /dev/null +++ b/desktop/source/deployment/inc/dp_registry.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 <sal/config.h> + +#include <com/sun/star/uno/Reference.hxx> +#include <rtl/ustring.hxx> + +namespace com::sun::star { + namespace deployment { class XPackageRegistry; } + namespace uno { class XComponentContext; } +} + +namespace dp_registry { + +css::uno::Reference<css::deployment::XPackageRegistry> create( + OUString const & context, OUString const & cachePath, + css::uno::Reference<css::uno::XComponentContext> const & xComponentContext); + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/inc/dp_resource.h b/desktop/source/deployment/inc/dp_resource.h new file mode 100644 index 0000000000..471960e8d4 --- /dev/null +++ b/desktop/source/deployment/inc/dp_resource.h @@ -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 <i18nlangtag/languagetag.hxx> +#include "dp_misc_api.hxx" + +namespace dp_misc +{ +DESKTOP_DEPLOYMENTMISC_DLLPUBLIC const LanguageTag& getOfficeLanguageTag(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/inc/dp_ucb.h b/desktop/source/deployment/inc/dp_ucb.h new file mode 100644 index 0000000000..e72a2cce93 --- /dev/null +++ b/desktop/source/deployment/inc/dp_ucb.h @@ -0,0 +1,94 @@ +/* -*- 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 <vector> +#include <com/sun/star/sdbc/XResultSet.hpp> +#include <com/sun/star/ucb/XCommandEnvironment.hpp> +#include "dp_misc_api.hxx" +#include <ucbhelper/content.hxx> + +namespace ucbhelper +{ +class Content; +} + +namespace dp_misc { + +struct DESKTOP_DEPLOYMENTMISC_DLLPUBLIC StrTitle +{ + static css::uno::Sequence< OUString > getTitleSequence() + { + css::uno::Sequence<OUString> aSeq { "Title" }; + return aSeq; + } + static OUString getTitle( ::ucbhelper::Content &rContent ) + { + return rContent.getPropertyValue("Title").get<OUString>(); + } + // just return titles - the ucbhelper should have a simpler API for this [!] + static css::uno::Reference< css::sdbc::XResultSet > + createCursor( ::ucbhelper::Content &rContent, + ucbhelper::ResultSetInclude eInclude ) + { + return rContent.createCursor( StrTitle::getTitleSequence(), eInclude ); + } +}; + + +DESKTOP_DEPLOYMENTMISC_DLLPUBLIC bool create_ucb_content( + ::ucbhelper::Content * ucb_content, + OUString const & url, + css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv, + bool throw_exc = true ); + + +/** @return true if previously non-existing folder has been created + */ +DESKTOP_DEPLOYMENTMISC_DLLPUBLIC bool create_folder( + ::ucbhelper::Content * ucb_content, + OUString const & url, + css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv, + bool throw_exc = true ); + + +DESKTOP_DEPLOYMENTMISC_DLLPUBLIC bool erase_path( + OUString const & url, + css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv, + bool throw_exc = true ); + + +DESKTOP_DEPLOYMENTMISC_DLLPUBLIC +std::vector<sal_Int8> readFile( ::ucbhelper::Content & ucb_content ); + + +DESKTOP_DEPLOYMENTMISC_DLLPUBLIC +bool readLine( OUString * res, std::u16string_view startingWith, + ::ucbhelper::Content & ucb_content, rtl_TextEncoding textenc ); + +DESKTOP_DEPLOYMENTMISC_DLLPUBLIC +bool readProperties( std::vector< std::pair< OUString, OUString> > & out_result, + ::ucbhelper::Content & ucb_content); + + + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/inc/dp_update.hxx b/desktop/source/deployment/inc/dp_update.hxx new file mode 100644 index 0000000000..f673d2f66a --- /dev/null +++ b/desktop/source/deployment/inc/dp_update.hxx @@ -0,0 +1,136 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#pragma once + + +#include <com/sun/star/deployment/XPackage.hpp> +#include <com/sun/star/deployment/XExtensionManager.hpp> +#include <com/sun/star/deployment/XUpdateInformationProvider.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <com/sun/star/xml/dom/XNode.hpp> + +#include "dp_misc_api.hxx" + +#include <map> +#include <vector> + +namespace dp_misc { + +/** returns the default update URL (for the update information) which + is used when an extension does not provide its own URL. +*/ +DESKTOP_DEPLOYMENTMISC_DLLPUBLIC +OUString getExtensionDefaultUpdateURL(); + +enum UPDATE_SOURCE +{ + UPDATE_SOURCE_NONE, + UPDATE_SOURCE_SHARED, + UPDATE_SOURCE_BUNDLED, + UPDATE_SOURCE_ONLINE +}; + +/* determine if an update is available which is installed in the + user repository. + + If the return value is UPDATE_SOURCE_NONE, then no update is + available, otherwise the return value determine from which the + repository the update is used. +*/ +DESKTOP_DEPLOYMENTMISC_DLLPUBLIC +UPDATE_SOURCE isUpdateUserExtension( + bool bReadOnlyShared, + OUString const & userVersion, + OUString const & sharedVersion, + OUString const & bundledVersion, + std::u16string_view onlineVersion); + +/* determine if an update is available which is installed in the + shared repository. + + If the return value is UPDATE_SOURCE_NONE, then no update is + available, otherwise the return value determine from which the + repository the update is used. +*/ +DESKTOP_DEPLOYMENTMISC_DLLPUBLIC +UPDATE_SOURCE isUpdateSharedExtension( + bool bReadOnlyShared, + OUString const & sharedVersion, + OUString const & bundledVersion, + std::u16string_view onlineVersion); + +/* determines the extension with the highest identifier and returns it + + */ +DESKTOP_DEPLOYMENTMISC_DLLPUBLIC +css::uno::Reference< css::deployment::XPackage> +getExtensionWithHighestVersion( + css::uno::Sequence< + css::uno::Reference< + css::deployment::XPackage> > const & seqExtensionsWithSameId); + + +struct UpdateInfo +{ + UpdateInfo( css::uno::Reference< css::deployment::XPackage> const & ext); + + css::uno::Reference< css::deployment::XPackage> extension; + //version of the update + OUString version; + css::uno::Reference< css::xml::dom::XNode > info; +}; + +typedef std::map< OUString, UpdateInfo > UpdateInfoMap; + +/* + @param extensionList + List of extension for which online update information is to be obtained. If NULL, then + for update information is obtained for all installed extension. There may be only one extension + with a particular identifier contained in the list. If one extension is installed + in several repositories, then the one with the highest version must be used, because it contains + the more recent URLs for getting the update information (if at all). + @param out_errors + the first member of the pair is the extension and the second the exception that was produced + when processing the extension. + + @return + A map of UpdateInfo instances. If the parameter extensionList was given, then the map contains + at only information for those extensions. + */ +DESKTOP_DEPLOYMENTMISC_DLLPUBLIC +UpdateInfoMap getOnlineUpdateInfos( + css::uno::Reference< css::uno::XComponentContext> const &xContext, + css::uno::Reference< css::deployment::XExtensionManager> const & xExtMgr, + css::uno::Reference< css::deployment::XUpdateInformationProvider > const & updateInformation, + std::vector< css::uno::Reference< css::deployment::XPackage > > const * extensionList, + std::vector< std::pair< css::uno::Reference< + css::deployment::XPackage>, css::uno::Any> > & out_errors); + +/* returns the highest version from the provided arguments. +*/ +DESKTOP_DEPLOYMENTMISC_DLLPUBLIC +OUString getHighestVersion( + OUString const & sharedVersion, + OUString const & bundledVersion, + OUString const & onlineVersion); + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/inc/dp_version.hxx b/desktop/source/deployment/inc/dp_version.hxx new file mode 100644 index 0000000000..f088b6861a --- /dev/null +++ b/desktop/source/deployment/inc/dp_version.hxx @@ -0,0 +1,36 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#pragma once + +#include <sal/config.h> + +#include <rtl/ustring.hxx> +#include "dp_misc_api.hxx" + + +namespace dp_misc { + +enum Order { LESS, EQUAL, GREATER }; + +DESKTOP_DEPLOYMENTMISC_DLLPUBLIC Order compareVersions( + std::u16string_view version1, std::u16string_view version2); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/inc/dp_xml.h b/desktop/source/deployment/inc/dp_xml.h new file mode 100644 index 0000000000..608073328b --- /dev/null +++ b/desktop/source/deployment/inc/dp_xml.h @@ -0,0 +1,42 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#pragma once + +#include <com/sun/star/uno/XComponentContext.hpp> +#include <com/sun/star/xml/sax/XDocumentHandler.hpp> + + +namespace ucbhelper +{ +class Content; +} + +namespace dp_misc +{ + + +void xml_parse( + css::uno::Reference< css::xml::sax::XDocumentHandler > const & xDocHandler, + ::ucbhelper::Content & ucb_content, + css::uno::Reference< css::uno::XComponentContext > const & xContext ); + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/inc/lockfile.hxx b/desktop/source/deployment/inc/lockfile.hxx new file mode 100644 index 0000000000..982a0c2f05 --- /dev/null +++ b/desktop/source/deployment/inc/lockfile.hxx @@ -0,0 +1,89 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +/* Information: + * This class implements a mechanism to lock a users installation directory, + * which is necessary because instances of staroffice could be running on + * different hosts while using the same directory thus causing data + * inconsistency. + * When an existing lock is detected, the user will be asked whether he wants + * to continue anyway, thus removing the lock and replacing it with a new one + * + * ideas: + * - store information about user and host and time in the lockfile and display + * these when asking whether to remove the lockfile. + * - periodically check the lockfile and warn the user when it gets replaced + * + */ + +#pragma once + +#include <rtl/ustring.hxx> + +#include "dp_misc_api.hxx" + +#define LOCKFILE_GROUP "Lockdata" +#define LOCKFILE_USERKEY "User" +#define LOCKFILE_HOSTKEY "Host" +#define LOCKFILE_STAMPKEY "Stamp" +#define LOCKFILE_TIMEKEY "Time" +#define LOCKFILE_IPCKEY "IPCServer" + +namespace desktop { + + class Lockfile; + bool Lockfile_execWarning( Lockfile const * that ); + + class DESKTOP_DEPLOYMENTMISC_DLLPUBLIC Lockfile + { + public: + + // constructs a new lockfile object + Lockfile( bool bIPCserver = true ); + + // separating GUI code: + typedef bool (* fpExecWarning)( Lockfile const * that ); + + // checks the lockfile, asks user when lockfile is + // found (iff gui) and returns false when we may not continue + bool check( fpExecWarning execWarning ); + + // removes the lockfile + ~Lockfile(); + + private: + bool m_bIPCserver; + // full qualified name (file://-url) of the lockfile + OUString m_aLockname; + // flag whether the d'tor should delete the lock + bool m_bRemove; + bool m_bIsLocked; + // ID + OUString m_aId; + OUString m_aDate; + // access to data in file + void syncToFile() const; + bool isStale() const; + friend bool Lockfile_execWarning( Lockfile const * that ); + + }; + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/manager/dp_activepackages.cxx b/desktop/source/deployment/manager/dp_activepackages.cxx new file mode 100644 index 0000000000..c1c5f2b28d --- /dev/null +++ b/desktop/source/deployment/manager/dp_activepackages.cxx @@ -0,0 +1,217 @@ +/* -*- 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_extensions.h> + +#include <sal/config.h> + +#include <string_view> +#include <utility> + +#include <osl/diagnose.h> +#include <rtl/string.hxx> +#include <rtl/textenc.h> +#include <rtl/ustring.hxx> + +#include <dp_identifier.hxx> +#include "dp_activepackages.hxx" + +// Old format of database entry: +// key: UTF8(filename) +// value: UTF8(tempname ";" mediatype) +// New format of database entry: +// key: 0xFF UTF8(identifier) +// value: UTF8(tempname) 0xFF UTF8(filename) 0xFF UTF8(mediatype) + +#if HAVE_FEATURE_EXTENSIONS + +namespace { + +constexpr const char separator[] = "\xff"; + +OString oldKey(std::u16string_view fileName) { + return OUStringToOString(fileName, RTL_TEXTENCODING_UTF8); +} + +OString newKey(std::u16string_view id) { + return separator + OUStringToOString(id, RTL_TEXTENCODING_UTF8); +} + +::dp_manager::ActivePackages::Data decodeOldData( + OUString const & fileName, OString const & value) +{ + ::dp_manager::ActivePackages::Data d; + sal_Int32 i = value.indexOf(';'); + OSL_ASSERT(i >= 0); + d.temporaryName = OUString(value.getStr(), i, RTL_TEXTENCODING_UTF8); + d.fileName = fileName; + d.mediaType = OUString( + value.getStr() + i + 1, value.getLength() - i - 1, + RTL_TEXTENCODING_UTF8); + return d; +} + +::dp_manager::ActivePackages::Data decodeNewData(OString const & value) { + ::dp_manager::ActivePackages::Data d; + sal_Int32 i1 = value.indexOf(separator); + OSL_ASSERT(i1 >= 0); + d.temporaryName = OUString( + value.getStr(), i1, RTL_TEXTENCODING_UTF8); + sal_Int32 i2 = value.indexOf(separator, i1 + 1); + OSL_ASSERT(i2 >= 0); + d.fileName = OUString( + value.getStr() + i1 + 1, i2 - i1 - 1, RTL_TEXTENCODING_UTF8); + sal_Int32 i3 = value.indexOf(separator, i2 + 1); + + if (i3 < 0) + { + //Before ActivePackages::Data::version was added + d.mediaType = OUString( + value.getStr() + i2 + 1, value.getLength() - i2 - 1, + RTL_TEXTENCODING_UTF8); + } + else + { + sal_Int32 i4 = value.indexOf(separator, i3 + 1); + d.mediaType = OUString( + value.getStr() + i2 + 1, i3 - i2 -1, RTL_TEXTENCODING_UTF8); + d.version = OUString( + value.getStr() + i3 + 1, i4 - i3 - 1, + RTL_TEXTENCODING_UTF8); + d.failedPrerequisites = OUString( + value.getStr() + i4 + 1, value.getLength() - i4 - 1, + RTL_TEXTENCODING_UTF8); + } + return d; +} + +} +#endif + +namespace dp_manager { + +ActivePackages::ActivePackages() {} + +ActivePackages::ActivePackages(OUString const & url) +#if HAVE_FEATURE_EXTENSIONS + : m_map(url) +#endif +{ +#if !HAVE_FEATURE_EXTENSIONS + (void) url; +#endif +} + +ActivePackages::~ActivePackages() {} + +bool ActivePackages::has( + OUString const & id, OUString const & fileName) const +{ + return get(nullptr, id, fileName); +} + +bool ActivePackages::get( + Data * data, OUString const & id, OUString const & fileName) + const +{ +#if HAVE_FEATURE_EXTENSIONS + OString v; + if (m_map.get(&v, newKey(id))) { + if (data != nullptr) { + *data = decodeNewData(v); + } + return true; + } else if (m_map.get(&v, oldKey(fileName))) { + if (data != nullptr) { + *data = decodeOldData(fileName, v); + } + return true; + } else { + return false; + } +#else + (void) data; + (void) id; + (void) fileName; + (void) this; + return false; +#endif +} + +ActivePackages::Entries ActivePackages::getEntries() const { + Entries es; +#if HAVE_FEATURE_EXTENSIONS + ::dp_misc::t_string2string_map m(m_map.getEntries()); + for (auto const& elem : m) + { + if (!elem.first.isEmpty() && elem.first[0] == separator[0]) { + es.emplace_back( + OUString( + elem.first.getStr() + 1, elem.first.getLength() - 1, + RTL_TEXTENCODING_UTF8), + decodeNewData(elem.second)); + } else { + OUString fn( + OStringToOUString(elem.first, RTL_TEXTENCODING_UTF8)); + es.emplace_back( + ::dp_misc::generateLegacyIdentifier(fn), + decodeOldData(fn, elem.second)); + } + } +#else + (void) this; +#endif + return es; +} + +void ActivePackages::put(OUString const & id, Data const & data) { +#if HAVE_FEATURE_EXTENSIONS + OString b = + OUStringToOString(data.temporaryName, RTL_TEXTENCODING_UTF8) + + separator + + OUStringToOString(data.fileName, RTL_TEXTENCODING_UTF8) + + separator + + OUStringToOString(data.mediaType, RTL_TEXTENCODING_UTF8) + + separator + + OUStringToOString(data.version, RTL_TEXTENCODING_UTF8) + + separator + + OUStringToOString(data.failedPrerequisites, RTL_TEXTENCODING_UTF8); + m_map.put(newKey(id), b); +#else + (void) id; + (void) data; + (void) this; +#endif +} + +void ActivePackages::erase( + OUString const & id, OUString const & fileName) +{ +#if HAVE_FEATURE_EXTENSIONS + m_map.erase(newKey(id)) || m_map.erase(oldKey(fileName)); +#else + (void) id; + (void) fileName; + (void) this; +#endif +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/manager/dp_activepackages.hxx b/desktop/source/deployment/manager/dp_activepackages.hxx new file mode 100644 index 0000000000..fae938019c --- /dev/null +++ b/desktop/source/deployment/manager/dp_activepackages.hxx @@ -0,0 +1,95 @@ +/* -*- 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 <config_extensions.h> + +#include <sal/config.h> +#include <rtl/ustring.hxx> + +#include <utility> +#include <vector> + +#if HAVE_FEATURE_EXTENSIONS +#include <dp_persmap.h> +#endif + + +namespace dp_manager { + +class ActivePackages { +public: + struct Data { + Data(): failedPrerequisites("0") + {} + /* name of the temporary file (shared, user extension) or the name of + the folder of the bundled extension. + It does not contain the trailing '_' of the folder. + UTF-8 encoded + */ + OUString temporaryName; + /* The file name (shared, user) or the folder name (bundled) + If the key is the file name, then file name is not encoded. + If the key is the identifier then the file name is UTF-8 encoded. + */ + OUString fileName; + OUString mediaType; + OUString version; + /* If this string contains the value according to + css::deployment::Prerequisites or "0". That is, if + the value is > 0 then + the call to XPackage::checkPrerequisites failed. + In this case the extension must not be registered. + */ + OUString failedPrerequisites; + }; + + typedef std::vector< std::pair< OUString, Data > > Entries; + + ActivePackages(); + + explicit ActivePackages(OUString const & url); + + ~ActivePackages(); + + bool has(OUString const & id, OUString const & fileName) + const; + + bool get( + Data * data, OUString const & id, + OUString const & fileName) const; + + Entries getEntries() const; + + void put(OUString const & id, Data const & value); + + void erase(OUString const & id, OUString const & fileName); + +private: + ActivePackages(ActivePackages const &) = delete; + ActivePackages& operator =(ActivePackages const &) = delete; +#if HAVE_FEATURE_EXTENSIONS + ::dp_misc::PersistentMap m_map; +#endif +}; + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/manager/dp_commandenvironments.cxx b/desktop/source/deployment/manager/dp_commandenvironments.cxx new file mode 100644 index 0000000000..0a25e042f0 --- /dev/null +++ b/desktop/source/deployment/manager/dp_commandenvironments.cxx @@ -0,0 +1,246 @@ +/* -*- 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 <com/sun/star/deployment/VersionException.hpp> +#include <com/sun/star/deployment/LicenseException.hpp> +#include <com/sun/star/deployment/InstallException.hpp> +#include <com/sun/star/deployment/DependencyException.hpp> +#include <com/sun/star/deployment/PlatformException.hpp> +#include <com/sun/star/task/XInteractionApprove.hpp> +#include <com/sun/star/task/XInteractionHandler.hpp> +#include <com/sun/star/ucb/XCommandEnvironment.hpp> +#include <utility> +#include "dp_commandenvironments.hxx" +#include <osl/diagnose.h> + +namespace deployment = com::sun::star::deployment; +namespace task = com::sun::star::task; +namespace ucb = com::sun::star::ucb; +namespace uno = com::sun::star::uno; + +using ::com::sun::star::uno::Reference; + +namespace dp_manager { + +BaseCommandEnv::BaseCommandEnv() +{ +} + +BaseCommandEnv::BaseCommandEnv( + Reference< task::XInteractionHandler> const & handler) + : m_forwardHandler(handler) +{ +} + +BaseCommandEnv::~BaseCommandEnv() +{ +} +// XCommandEnvironment + +Reference<task::XInteractionHandler> BaseCommandEnv::getInteractionHandler() +{ + return this; +} + + +Reference<ucb::XProgressHandler> BaseCommandEnv::getProgressHandler() +{ + return this; +} + +void BaseCommandEnv::handle( + Reference< task::XInteractionRequest> const & /*xRequest*/ ) +{ +} + +void BaseCommandEnv::handle_(bool approve, + Reference< task::XInteractionRequest> const & xRequest ) +{ + if (!approve) + { + //not handled so far -> forwarding + if (m_forwardHandler.is()) + m_forwardHandler->handle(xRequest); + else + return; //cannot handle + } + else + { + // select: + 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 (approve) { + Reference< task::XInteractionApprove > xInteractionApprove( + pConts[ pos ], uno::UNO_QUERY ); + if (xInteractionApprove.is()) { + xInteractionApprove->select(); + // don't query again for ongoing continuations: + approve = false; + } + } + } + } + +} + +// XProgressHandler +void BaseCommandEnv::push( uno::Any const & /*Status*/ ) +{ +} + +void BaseCommandEnv::update( uno::Any const & /*Status */) +{ +} + +void BaseCommandEnv::pop() +{ +} + + +TmpRepositoryCommandEnv::TmpRepositoryCommandEnv() +{ +} + +TmpRepositoryCommandEnv::TmpRepositoryCommandEnv( + css::uno::Reference< css::task::XInteractionHandler> const & handler): + BaseCommandEnv(handler) +{ +} +// XInteractionHandler +void TmpRepositoryCommandEnv::handle( + Reference< task::XInteractionRequest> const & xRequest ) +{ + uno::Any request( xRequest->getRequest() ); + OSL_ASSERT( request.getValueTypeClass() == uno::TypeClass_EXCEPTION ); + + deployment::VersionException verExc; + deployment::LicenseException licExc; + deployment::InstallException instExc; + + bool approve = false; + + if ((request >>= verExc) + || (request >>= licExc) + || (request >>= instExc)) + { + approve = true; + } + + handle_(approve, xRequest); +} + + +LicenseCommandEnv::LicenseCommandEnv( + css::uno::Reference< css::task::XInteractionHandler> const & handler, + bool bSuppressLicense, + OUString repository): + BaseCommandEnv(handler), m_repository(std::move(repository)), + m_bSuppressLicense(bSuppressLicense) +{ +} +// XInteractionHandler +void LicenseCommandEnv::handle( + Reference< task::XInteractionRequest> const & xRequest ) +{ + uno::Any request( xRequest->getRequest() ); + OSL_ASSERT( request.getValueTypeClass() == uno::TypeClass_EXCEPTION ); + + deployment::LicenseException licExc; + + bool approve = false; + + if (request >>= licExc) + { + if (m_bSuppressLicense + || m_repository == "bundled" + || licExc.AcceptBy == "admin") + { + //always approve in bundled case, because we do not support + //showing licenses anyway. + //The "admin" already accepted the license when installing the + // shared extension + approve = true; + } + } + + handle_(approve, xRequest); +} + + +NoLicenseCommandEnv::NoLicenseCommandEnv( + css::uno::Reference< css::task::XInteractionHandler> const & handler): + BaseCommandEnv(handler) +{ +} +// XInteractionHandler +void NoLicenseCommandEnv::handle( + Reference< task::XInteractionRequest> const & xRequest ) +{ + uno::Any request( xRequest->getRequest() ); + OSL_ASSERT( request.getValueTypeClass() == uno::TypeClass_EXCEPTION ); + + deployment::LicenseException licExc; + + bool approve = false; + + if (request >>= licExc) + { + approve = true; + } + handle_(approve, xRequest); +} + +SilentCheckPrerequisitesCommandEnv::SilentCheckPrerequisitesCommandEnv() +{ +} + +void SilentCheckPrerequisitesCommandEnv::handle( + Reference< task::XInteractionRequest> const & xRequest ) +{ + uno::Any request( xRequest->getRequest() ); + OSL_ASSERT( request.getValueTypeClass() == uno::TypeClass_EXCEPTION ); + + deployment::LicenseException licExc; + deployment::PlatformException platformExc; + deployment::DependencyException depExc; + + if (request >>= licExc) + { + handle_(true, xRequest); // approve = true + } + else if ((request >>= platformExc) + || (request >>= depExc)) + { + m_Exception = request; + } + else + { + m_UnknownException = request; + } +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/manager/dp_commandenvironments.hxx b/desktop/source/deployment/manager/dp_commandenvironments.hxx new file mode 100644 index 0000000000..6533d45b4f --- /dev/null +++ b/desktop/source/deployment/manager/dp_commandenvironments.hxx @@ -0,0 +1,139 @@ +/* -*- 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/task/XInteractionHandler.hpp> +#include <com/sun/star/task/XInteractionRequest.hpp> +#include <com/sun/star/ucb/XProgressHandler.hpp> +#include <com/sun/star/ucb/XCommandEnvironment.hpp> + + +namespace dp_manager { + +/** + This command environment is to be used when an extension is temporarily + stored in the "tmp" repository. It prevents all kind of user interaction. + */ +class BaseCommandEnv + : public ::cppu::WeakImplHelper< css::ucb::XCommandEnvironment, + css::task::XInteractionHandler, + css::ucb::XProgressHandler > +{ + css::uno::Reference< css::task::XInteractionHandler> m_forwardHandler; +protected: + void handle_(bool approve, + css::uno::Reference< css::task::XInteractionRequest> const & xRequest ); +public: + virtual ~BaseCommandEnv() override; + BaseCommandEnv(); + explicit BaseCommandEnv( + css::uno::Reference< css::task::XInteractionHandler> const & handler); + + // XCommandEnvironment + virtual css::uno::Reference<css::task::XInteractionHandler > SAL_CALL + getInteractionHandler() override; + virtual css::uno::Reference<css::ucb::XProgressHandler > + SAL_CALL getProgressHandler() override; + + // XInteractionHandler + virtual void SAL_CALL handle( + css::uno::Reference<css::task::XInteractionRequest > const & xRequest ) override; + + // XProgressHandler + virtual void SAL_CALL push( css::uno::Any const & Status ) override; + virtual void SAL_CALL update( css::uno::Any const & Status ) override; + virtual void SAL_CALL pop() override; +}; + +class TmpRepositoryCommandEnv : public BaseCommandEnv +{ +public: + TmpRepositoryCommandEnv(); + explicit TmpRepositoryCommandEnv(css::uno::Reference< css::task::XInteractionHandler> const & handler); + +// XInteractionHandler + virtual void SAL_CALL handle( + css::uno::Reference<css::task::XInteractionRequest > const & xRequest ) override; + +}; + +/** this class is for use in XPackageManager::synchronize. + + It handles particular license cases. + */ +class LicenseCommandEnv : public BaseCommandEnv +{ +private: + OUString m_repository; + bool m_bSuppressLicense; +public: + LicenseCommandEnv( + css::uno::Reference< css::task::XInteractionHandler> const & handler, + bool bSuppressLicense, + OUString repository); + +// XInteractionHandler + virtual void SAL_CALL handle( + css::uno::Reference<css::task::XInteractionRequest > const & xRequest ) override; + +}; + +/** this class is for use in XPackageManager::checkPrerequisites + + It always prohibits a license interaction + */ +class NoLicenseCommandEnv : public BaseCommandEnv +{ + +public: + explicit NoLicenseCommandEnv(css::uno::Reference< css::task::XInteractionHandler> const & handler); + +// XInteractionHandler + virtual void SAL_CALL handle( + css::uno::Reference<css::task::XInteractionRequest > const & xRequest ) override; + +}; + +/* For use in XExtensionManager::addExtension in the call to + XPackage::checkPrerequisites + It prevents all user interactions. The license is always accepted. + It remembers if there was a platform or a dependency exception in + the member m_bException. if there was any other exception then m_bUnknownException + is set. + + */ +class SilentCheckPrerequisitesCommandEnv : public BaseCommandEnv +{ +public: + SilentCheckPrerequisitesCommandEnv(); + // XInteractionHandler + virtual void SAL_CALL handle( + css::uno::Reference<css::task::XInteractionRequest > const & xRequest ) override; + + // Set to true if a PlatformException or a DependencyException were handled. + css::uno::Any m_Exception; + // Set to true if an unknown exception was handled. + css::uno::Any m_UnknownException; +}; + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/manager/dp_extensionmanager.cxx b/desktop/source/deployment/manager/dp_extensionmanager.cxx new file mode 100644 index 0000000000..f393a30c94 --- /dev/null +++ b/desktop/source/deployment/manager/dp_extensionmanager.cxx @@ -0,0 +1,1432 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + + +#include <cppuhelper/compbase.hxx> +#include <cppuhelper/supportsservice.hxx> + +#include <cppuhelper/exc_hlp.hxx> +#include <rtl/bootstrap.hxx> +#include <com/sun/star/deployment/DeploymentException.hpp> +#include <com/sun/star/deployment/XExtensionManager.hpp> +#include <com/sun/star/deployment/thePackageManagerFactory.hpp> +#include <com/sun/star/deployment/XPackageManager.hpp> +#include <com/sun/star/deployment/XPackage.hpp> +#include <com/sun/star/deployment/InstallException.hpp> +#include <com/sun/star/deployment/VersionException.hpp> +#include <com/sun/star/lang/IllegalArgumentException.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/beans/Optional.hpp> +#include <com/sun/star/task/XInteractionApprove.hpp> +#include <com/sun/star/beans/Ambiguous.hpp> +#include <com/sun/star/ucb/CommandAbortedException.hpp> +#include <com/sun/star/ucb/CommandFailedException.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <com/sun/star/io/XInputStream.hpp> +#include <com/sun/star/util/XModifyBroadcaster.hpp> +#include <comphelper/sequence.hxx> +#include <utility> +#include <xmlscript/xml_helper.hxx> +#include <osl/diagnose.h> +#include <dp_interact.h> +#include <dp_misc.h> +#include <dp_ucb.h> +#include <dp_identifier.hxx> +#include <dp_descriptioninfoset.hxx> +#include "dp_extensionmanager.hxx" +#include "dp_commandenvironments.hxx" +#include "dp_properties.hxx" + +#include <vector> +#include <algorithm> +#include <set> +#include <string_view> + +namespace lang = com::sun::star::lang; +namespace task = com::sun::star::task; +namespace ucb = com::sun::star::ucb; +namespace uno = com::sun::star::uno; +namespace beans = com::sun::star::beans; +namespace util = com::sun::star::util; + +using ::com::sun::star::uno::Reference; + +namespace { + +struct CompIdentifiers +{ + bool operator() (std::vector<Reference<css::deployment::XPackage> > const & a, + std::vector<Reference<css::deployment::XPackage> > const & b) + { + return getName(a).compareTo(getName(b)) < 0; + } + + static OUString getName(std::vector<Reference<css::deployment::XPackage> > const & a); +}; + +OUString CompIdentifiers::getName(std::vector<Reference<css::deployment::XPackage> > const & a) +{ + OSL_ASSERT(a.size() == 3); + //get the first non-null reference + Reference<css::deployment::XPackage> extension; + for (auto const& elem : a) + { + if (elem.is()) + { + extension = elem; + break; + } + } + OSL_ASSERT(extension.is()); + return extension->getDisplayName(); +} + +void writeLastModified(OUString & url, Reference<ucb::XCommandEnvironment> const & xCmdEnv, Reference< uno::XComponentContext > const & xContext) +{ + //Write the lastmodified file + try { + ::rtl::Bootstrap::expandMacros(url); + ::ucbhelper::Content ucbStamp(url, xCmdEnv, xContext); + dp_misc::erase_path( url, xCmdEnv ); + OString stamp("1"_ostr ); + Reference<css::io::XInputStream> xData( + ::xmlscript::createInputStream( + reinterpret_cast<sal_Int8 const *>(stamp.getStr()), + stamp.getLength() ) ); + ucbStamp.writeStream( xData, true /* replace existing */ ); + } + catch(...) + { + uno::Any exc(::cppu::getCaughtException()); + throw css::deployment::DeploymentException("Failed to update" + url, nullptr, exc); + } +} + +class ExtensionRemoveGuard +{ + css::uno::Reference<css::deployment::XPackage> m_extension; + css::uno::Reference<css::deployment::XPackageManager> m_xPackageManager; + +public: + ExtensionRemoveGuard(){}; + ExtensionRemoveGuard( + css::uno::Reference<css::deployment::XPackage> extension, + css::uno::Reference<css::deployment::XPackageManager> xPackageManager): + m_extension(std::move(extension)), m_xPackageManager(std::move(xPackageManager)) {} + ~ExtensionRemoveGuard(); + + void set(css::uno::Reference<css::deployment::XPackage> const & extension, + css::uno::Reference<css::deployment::XPackageManager> const & xPackageManager) { + m_extension = extension; + m_xPackageManager = xPackageManager; + } +}; + +ExtensionRemoveGuard::~ExtensionRemoveGuard() +{ + try { + OSL_ASSERT(!(m_extension.is() && !m_xPackageManager.is())); + if (m_xPackageManager.is() && m_extension.is()) + m_xPackageManager->removePackage( + dp_misc::getIdentifier(m_extension), OUString(), + css::uno::Reference<css::task::XAbortChannel>(), + css::uno::Reference<css::ucb::XCommandEnvironment>()); + } catch (...) { + OSL_ASSERT(false); + } +} + +} + +namespace dp_manager { + +//ToDo: bundled extension +ExtensionManager::ExtensionManager( Reference< uno::XComponentContext > const& xContext) : + ::cppu::WeakComponentImplHelper< css::deployment::XExtensionManager, css::lang::XServiceInfo >(m_aMutex) + , m_xContext(xContext) +{ + m_xPackageManagerFactory = css::deployment::thePackageManagerFactory::get(m_xContext); + OSL_ASSERT(m_xPackageManagerFactory.is()); + + m_repositoryNames.emplace_back("user"); + m_repositoryNames.emplace_back("shared"); + m_repositoryNames.emplace_back("bundled"); +} + +ExtensionManager::~ExtensionManager() +{ +} + +// XServiceInfo +OUString ExtensionManager::getImplementationName() +{ + return "com.sun.star.comp.deployment.ExtensionManager"; +} + +sal_Bool ExtensionManager::supportsService( const OUString& ServiceName ) +{ + return cppu::supportsService(this, ServiceName); +} + +css::uno::Sequence< OUString > ExtensionManager::getSupportedServiceNames() +{ + // a private one: + return { "com.sun.star.comp.deployment.ExtensionManager" }; +} + +Reference<css::deployment::XPackageManager> ExtensionManager::getUserRepository() +{ + return m_xPackageManagerFactory->getPackageManager("user"); +} +Reference<css::deployment::XPackageManager> ExtensionManager::getSharedRepository() +{ + return m_xPackageManagerFactory->getPackageManager("shared"); +} +Reference<css::deployment::XPackageManager> ExtensionManager::getBundledRepository() +{ + return m_xPackageManagerFactory->getPackageManager("bundled"); +} +Reference<css::deployment::XPackageManager> ExtensionManager::getTmpRepository() +{ + return m_xPackageManagerFactory->getPackageManager("tmp"); +} +Reference<css::deployment::XPackageManager> ExtensionManager::getBakRepository() +{ + return m_xPackageManagerFactory->getPackageManager("bak"); +} + +Reference<task::XAbortChannel> ExtensionManager::createAbortChannel() +{ + return new dp_misc::AbortChannel; +} + +css::uno::Reference<css::deployment::XPackageManager> +ExtensionManager::getPackageManager(std::u16string_view repository) +{ + Reference<css::deployment::XPackageManager> xPackageManager; + if (repository == u"user") + xPackageManager = getUserRepository(); + else if (repository == u"shared") + xPackageManager = getSharedRepository(); + else if (repository == u"bundled") + xPackageManager = getBundledRepository(); + else if (repository == u"tmp") + xPackageManager = getTmpRepository(); + else if (repository == u"bak") + xPackageManager = getBakRepository(); + else + throw lang::IllegalArgumentException( + "No valid repository name provided.", + static_cast<cppu::OWeakObject*>(this), 0); + return xPackageManager; +} + +/* + Enters the XPackage objects into a map. They must be all from the + same repository. The value type of the map is a vector, where each vector + represents an extension with a particular identifier. The first member + represents the user extension, the second the shared extension and the + third the bundled extension. + */ +void ExtensionManager::addExtensionsToMap( + id2extensions & mapExt, + uno::Sequence<Reference<css::deployment::XPackage> > const & seqExt, + std::u16string_view repository) +{ + //Determine the index in the vector where these extensions are to be + //added. + int index = 0; + for (auto const& repositoryName : m_repositoryNames) + { + if (repositoryName == repository) + break; + ++index; + } + + for (const Reference<css::deployment::XPackage>& xExtension : seqExt) + { + OUString id = dp_misc::getIdentifier(xExtension); + id2extensions::iterator ivec = mapExt.find(id); + if (ivec == mapExt.end()) + { + std::vector<Reference<css::deployment::XPackage> > vec(3); + vec[index] = xExtension; + mapExt[id] = vec; + } + else + { + ivec->second[index] = xExtension; + } + } +} + +/* + returns a list containing extensions with the same identifier from + all repositories (user, shared, bundled). If one repository does not + have this extension, then the list contains an empty Reference. The list + is ordered according to the priority of the repositories: + 1. user + 2. shared + 3. bundled + + The number of elements is always three, unless the number of repository + changes. + */ +std::vector<Reference<css::deployment::XPackage> > + ExtensionManager::getExtensionsWithSameId( + OUString const & identifier, OUString const & fileName) + +{ + std::vector<Reference<css::deployment::XPackage> > extensionList; + Reference<css::deployment::XPackageManager> lRepos[] = { + getUserRepository(), getSharedRepository(), getBundledRepository() }; + for (std::size_t i(0); i != std::size(lRepos); ++i) + { + Reference<css::deployment::XPackage> xPackage; + try + { + xPackage = lRepos[i]->getDeployedPackage( + identifier, fileName, Reference<ucb::XCommandEnvironment>()); + } + catch(const lang::IllegalArgumentException &) + { + // thrown if the extension does not exist in this repository + } + extensionList.push_back(xPackage); + } + OSL_ASSERT(extensionList.size() == 3); + return extensionList; +} + +uno::Sequence<Reference<css::deployment::XPackage> > +ExtensionManager::getExtensionsWithSameIdentifier( + OUString const & identifier, + OUString const & fileName, + Reference< ucb::XCommandEnvironment> const & /*xCmdEnv*/ ) +{ + try + { + std::vector<Reference<css::deployment::XPackage> > listExtensions = + getExtensionsWithSameId(identifier, fileName); + bool bHasExtension = false; + + //throw an IllegalArgumentException if there is no extension at all. + for (auto const& extension : listExtensions) + bHasExtension |= extension.is(); + if (!bHasExtension) + throw lang::IllegalArgumentException( + "Could not find extension: " + identifier + ", " + fileName, + static_cast<cppu::OWeakObject*>(this), -1); + + return comphelper::containerToSequence(listExtensions); + } + catch ( const css::deployment::DeploymentException & ) + { + throw; + } + catch ( const ucb::CommandFailedException & ) + { + throw; + } + catch (css::uno::RuntimeException &) + { + throw; + } + catch (...) + { + uno::Any exc = ::cppu::getCaughtException(); + throw css::deployment::DeploymentException( + "Extension Manager: exception during getExtensionsWithSameIdentifier", + static_cast<OWeakObject*>(this), exc); + } +} + +bool ExtensionManager::isUserDisabled( + OUString const & identifier, OUString const & fileName) +{ + std::vector<Reference<css::deployment::XPackage> > listExtensions; + + try { + listExtensions = getExtensionsWithSameId(identifier, fileName); + } catch ( const lang::IllegalArgumentException & ) { + } + OSL_ASSERT(listExtensions.size() == 3); + + return isUserDisabled( ::comphelper::containerToSequence(listExtensions) ); +} + +bool ExtensionManager::isUserDisabled( + uno::Sequence<Reference<css::deployment::XPackage> > const & seqExtSameId) +{ + OSL_ASSERT(seqExtSameId.getLength() == 3); + Reference<css::deployment::XPackage> const & userExtension = seqExtSameId[0]; + if (userExtension.is()) + { + beans::Optional<beans::Ambiguous<sal_Bool> > reg = + userExtension->isRegistered(Reference<task::XAbortChannel>(), + Reference<ucb::XCommandEnvironment>()); + //If the value is ambiguous, then we assume that the extension + //is enabled, but something went wrong during enabling. We do not + //automatically disable user extensions. + if (reg.IsPresent && + ! reg.Value.IsAmbiguous && ! reg.Value.Value) + return true; + } + return false; +} + +/* + This method determines the active extension (XPackage.registerPackage) with a + particular identifier. + + The parameter bUserDisabled determines if the user extension is disabled. + + When the user repository contains an extension with the given identifier and + it is not disabled by the user, then it is always registered. Otherwise an + extension is only registered when there is no registered extension in one of + the repositories with a higher priority. That is, if the extension is from + the shared repository and an active extension with the same identifier is in + the user repository, then the extension is not registered. Similarly a + bundled extension is not registered if there is an active extension with the + same identifier in the shared or user repository. +*/ +void ExtensionManager::activateExtension( + OUString const & identifier, OUString const & fileName, + bool bUserDisabled, + bool bStartup, + Reference<task::XAbortChannel> const & xAbortChannel, + Reference<ucb::XCommandEnvironment> const & xCmdEnv ) +{ + std::vector<Reference<css::deployment::XPackage> > listExtensions; + try { + listExtensions = getExtensionsWithSameId(identifier, fileName); + } catch (const lang::IllegalArgumentException &) { + } + OSL_ASSERT(listExtensions.size() == 3); + + activateExtension( + ::comphelper::containerToSequence(listExtensions), + bUserDisabled, bStartup, xAbortChannel, xCmdEnv); + + fireModified(); +} + +void ExtensionManager::activateExtension( + uno::Sequence<Reference<css::deployment::XPackage> > const & seqExt, + bool bUserDisabled, + bool bStartup, + Reference<task::XAbortChannel> const & xAbortChannel, + Reference<ucb::XCommandEnvironment> const & xCmdEnv ) +{ + bool bActive = false; + sal_Int32 len = seqExt.getLength(); + for (sal_Int32 i = 0; i < len; i++) + { + Reference<css::deployment::XPackage> const & aExt = seqExt[i]; + if (aExt.is()) + { + //get the registration value of the current iteration + beans::Optional<beans::Ambiguous<sal_Bool> > optReg = + aExt->isRegistered(xAbortChannel, xCmdEnv); + //If nothing can be registered then break + if (!optReg.IsPresent) + break; + + //Check if this is a disabled user extension, + if (i == 0 && bUserDisabled) + { + aExt->revokePackage(bStartup, xAbortChannel, xCmdEnv); + continue; + } + + //If we have already determined an active extension then we must + //make sure to unregister all extensions with the same id in + //repositories with a lower priority + if (bActive) + { + aExt->revokePackage(bStartup, xAbortChannel, xCmdEnv); + } + else + { + //This is the first extension in the ordered list, which becomes + //the active extension + bActive = true; + //Register if not already done. + //reregister if the value is ambiguous, which indicates that + //something went wrong during last registration. + aExt->registerPackage(bStartup, xAbortChannel, xCmdEnv); + } + } + } +} + +Reference<css::deployment::XPackage> ExtensionManager::backupExtension( + OUString const & identifier, OUString const & fileName, + Reference<css::deployment::XPackageManager> const & xPackageManager, + Reference<ucb::XCommandEnvironment> const & xCmdEnv ) +{ + Reference<css::deployment::XPackage> xBackup; + Reference<ucb::XCommandEnvironment> tmpCmdEnv( + new TmpRepositoryCommandEnv(xCmdEnv->getInteractionHandler())); + Reference<css::deployment::XPackage> xOldExtension = xPackageManager->getDeployedPackage( + identifier, fileName, tmpCmdEnv); + + if (xOldExtension.is()) + { + xBackup = getTmpRepository()->addPackage( + xOldExtension->getURL(), uno::Sequence<beans::NamedValue>(), + OUString(), Reference<task::XAbortChannel>(), tmpCmdEnv); + + OSL_ENSURE(xBackup.is(), "Failed to backup extension"); + } + return xBackup; +} + +//The supported package types are actually determined by the registry. However +//creating a registry +//(desktop/source/deployment/registry/dp_registry.cxx:PackageRegistryImpl) will +//create all the backends, so that the registry can obtain from them the package +//types. Creating the registry will also set up the registry folder containing +//all the subfolders for the respective backends. +//Because all repositories support the same backends, we can just delegate this +//call to one of the repositories. +uno::Sequence< Reference<css::deployment::XPackageTypeInfo> > +ExtensionManager::getSupportedPackageTypes() +{ + return getUserRepository()->getSupportedPackageTypes(); +} +//Do some necessary checks and user interaction. This function does not +//acquire the extension manager mutex and that mutex must not be acquired +//when this function is called. doChecksForAddExtension does synchronous +//user interactions which may require acquiring the solar mutex. +//Returns true if the extension can be installed. +bool ExtensionManager::doChecksForAddExtension( + Reference<css::deployment::XPackageManager> const & xPackageMgr, + uno::Sequence<beans::NamedValue> const & properties, + css::uno::Reference<css::deployment::XPackage> const & xTmpExtension, + Reference<task::XAbortChannel> const & xAbortChannel, + Reference<ucb::XCommandEnvironment> const & xCmdEnv, + Reference<css::deployment::XPackage> & out_existingExtension ) +{ + try + { + Reference<css::deployment::XPackage> xOldExtension; + const OUString sIdentifier = dp_misc::getIdentifier(xTmpExtension); + const OUString sFileName = xTmpExtension->getName(); + const OUString sDisplayName = xTmpExtension->getDisplayName(); + const OUString sVersion = xTmpExtension->getVersion(); + + try + { + xOldExtension = xPackageMgr->getDeployedPackage( + sIdentifier, sFileName, xCmdEnv); + out_existingExtension = xOldExtension; + } + catch (const lang::IllegalArgumentException &) + { + } + bool bCanInstall = false; + + //This part is not guarded against other threads removing, adding, disabling ... + //etc. the same extension. + //checkInstall is safe because it notifies the user if the extension is not yet + //installed in the same repository. Because addExtension has its own guard + //(m_addMutex), another thread cannot add the extension in the meantime. + //checkUpdate is called if the same extension exists in the same + //repository. The user is asked if they want to replace it. Another + //thread + //could already remove the extension. So asking the user was not + //necessary. No harm is done. The other thread may also ask the user + //if he wants to remove the extension. This depends on the + //XCommandEnvironment which it passes to removeExtension. + if (xOldExtension.is()) + { + //throws a CommandFailedException if the user cancels + //the action. + checkUpdate(sVersion, sDisplayName,xOldExtension, xCmdEnv); + } + else + { + //throws a CommandFailedException if the user cancels + //the action. + checkInstall(sDisplayName, xCmdEnv); + } + //Prevent showing the license if requested. + Reference<ucb::XCommandEnvironment> _xCmdEnv(xCmdEnv); + ExtensionProperties props(std::u16string_view(), properties, Reference<ucb::XCommandEnvironment>(), m_xContext); + + dp_misc::DescriptionInfoset info(dp_misc::getDescriptionInfoset(xTmpExtension->getURL())); + const ::std::optional<dp_misc::SimpleLicenseAttributes> licenseAttributes = + info.getSimpleLicenseAttributes(); + + if (licenseAttributes && licenseAttributes->suppressIfRequired + && props.isSuppressedLicense()) + _xCmdEnv.set(new NoLicenseCommandEnv(xCmdEnv->getInteractionHandler())); + + bCanInstall = xTmpExtension->checkPrerequisites( + xAbortChannel, _xCmdEnv, xOldExtension.is() || props.isExtensionUpdate()) == 0; + + return bCanInstall; + } + catch ( const css::deployment::DeploymentException& ) { + throw; + } catch ( const ucb::CommandFailedException & ) { + throw; + } catch ( const ucb::CommandAbortedException & ) { + throw; + } catch (const lang::IllegalArgumentException &) { + throw; + } catch (const uno::RuntimeException &) { + throw; + } catch (const uno::Exception &) { + uno::Any excOccurred = ::cppu::getCaughtException(); + css::deployment::DeploymentException exc( + "Extension Manager: exception in doChecksForAddExtension", + static_cast<OWeakObject*>(this), excOccurred); + throw exc; + } catch (...) { + throw uno::RuntimeException( + "Extension Manager: unexpected exception in doChecksForAddExtension", + static_cast<OWeakObject*>(this)); + } +} + +// Only add to shared and user repository +Reference<css::deployment::XPackage> ExtensionManager::addExtension( + OUString const & url, uno::Sequence<beans::NamedValue> const & properties, + OUString const & repository, + Reference<task::XAbortChannel> const & xAbortChannel, + Reference<ucb::XCommandEnvironment> const & xCmdEnv ) +{ + Reference<css::deployment::XPackage> xNewExtension; + //Determine the repository to use + Reference<css::deployment::XPackageManager> xPackageManager; + if (repository == "user") + xPackageManager = getUserRepository(); + else if (repository == "shared") + xPackageManager = getSharedRepository(); + else + throw lang::IllegalArgumentException( + "No valid repository name provided.", + static_cast<cppu::OWeakObject*>(this), 0); + //We must make sure that the xTmpExtension is not create twice, because this + //would remove the first one. + std::unique_lock addGuard(m_addMutex); + + Reference<css::deployment::XPackageManager> xTmpRepository(getTmpRepository()); + // make sure xTmpRepository is alive as long as xTmpExtension is; as + // the "tmp" manager is only held weakly by m_xPackageManagerFactory, it + // could otherwise be disposed early, which would in turn dispose + // xTmpExtension's PackageRegistryBackend behind its back + Reference<css::deployment::XPackage> xTmpExtension( + xTmpRepository->addPackage( + url, uno::Sequence<beans::NamedValue>(), OUString(), xAbortChannel, + new TmpRepositoryCommandEnv())); + if (!xTmpExtension.is()) { + throw css::deployment::DeploymentException( + ("Extension Manager: Failed to create temporary XPackage for url: " + + url), + static_cast<OWeakObject*>(this), uno::Any()); + } + + //Make sure the extension is removed from the tmp repository in case + //of an exception + ExtensionRemoveGuard tmpExtensionRemoveGuard(xTmpExtension, getTmpRepository()); + ExtensionRemoveGuard bakExtensionRemoveGuard; + const OUString sIdentifier = dp_misc::getIdentifier(xTmpExtension); + const OUString sFileName = xTmpExtension->getName(); + Reference<css::deployment::XPackage> xOldExtension; + Reference<css::deployment::XPackage> xExtensionBackup; + + uno::Any excOccurred2; + bool bCanInstall = doChecksForAddExtension( + xPackageManager, + properties, + xTmpExtension, + xAbortChannel, + xCmdEnv, + xOldExtension ); + + { + bool bUserDisabled = false; + // In this guarded section (getMutex) we must not use the argument xCmdEnv + // because it may bring up dialogs (XInteractionHandler::handle) this + // may potentially deadlock. See issue + // http://qa.openoffice.org/issues/show_bug.cgi?id=114933 + // By not providing xCmdEnv the underlying APIs will throw an exception if + // the XInteractionRequest cannot be handled. + ::osl::MutexGuard guard(m_aMutex); + + if (bCanInstall) + { + try + { + bUserDisabled = isUserDisabled(sIdentifier, sFileName); + if (xOldExtension.is()) + { + try + { + xOldExtension->revokePackage( + false, xAbortChannel, Reference<ucb::XCommandEnvironment>()); + //save the old user extension in case the user aborts + xExtensionBackup = getBakRepository()->importExtension( + xOldExtension, Reference<task::XAbortChannel>(), + Reference<ucb::XCommandEnvironment>()); + bakExtensionRemoveGuard.set(xExtensionBackup, getBakRepository()); + } + catch (const lang::DisposedException &) + { + //Another thread might have removed the extension meanwhile + } + } + //check again dependencies but prevent user interaction, + //We can disregard the license, because the user must have already + //accepted it, when we called checkPrerequisites the first time + rtl::Reference<SilentCheckPrerequisitesCommandEnv> pSilentCommandEnv = + new SilentCheckPrerequisitesCommandEnv(); + + sal_Int32 failedPrereq = xTmpExtension->checkPrerequisites( + xAbortChannel, pSilentCommandEnv, true); + if (failedPrereq == 0) + { + xNewExtension = xPackageManager->addPackage( + url, properties, OUString(), xAbortChannel, + Reference<ucb::XCommandEnvironment>()); + //If we add a user extension and there is already one which was + //disabled by a user, then the newly installed one is enabled. If we + //add to another repository then the user extension remains + //disabled. + bool bUserDisabled2 = bUserDisabled; + if (repository == "user") + bUserDisabled2 = false; + + // pass the two values via variables to workaround gcc-4.3.4 specific bug (bnc#655912) + OUString sNewExtensionIdentifier = dp_misc::getIdentifier(xNewExtension); + OUString sNewExtensionFileName = xNewExtension->getName(); + + activateExtension( + sNewExtensionIdentifier, sNewExtensionFileName, + bUserDisabled2, false, xAbortChannel, + Reference<ucb::XCommandEnvironment>()); + + // if reached this section, + // this means that either the licensedialog.ui didn't popup, + // or user accepted the license agreement. otherwise + // no need to call fireModified() because user declined + // the license agreement therefore no change made. + try + { + fireModified(); + + }catch ( const css::deployment::DeploymentException& ) { + throw; + } catch ( const ucb::CommandFailedException & ) { + throw; + } catch ( const ucb::CommandAbortedException & ) { + throw; + } catch (const lang::IllegalArgumentException &) { + throw; + } catch (const uno::RuntimeException &) { + throw; + } catch (const uno::Exception &) { + uno::Any excOccurred = ::cppu::getCaughtException(); + css::deployment::DeploymentException exc( + "Extension Manager: Exception on fireModified() " + "in the scope of 'if (failedPrereq == 0)'", + static_cast<OWeakObject*>(this), excOccurred); + throw exc; + } catch (...) { + throw uno::RuntimeException( + "Extension Manager: RuntimeException on fireModified() " + "in the scope of 'if (failedPrereq == 0)'", + static_cast<OWeakObject*>(this)); + } + } + else + { + if (pSilentCommandEnv->m_Exception.hasValue()) + ::cppu::throwException(pSilentCommandEnv->m_Exception); + else if ( pSilentCommandEnv->m_UnknownException.hasValue()) + ::cppu::throwException(pSilentCommandEnv->m_UnknownException); + else + throw css::deployment::DeploymentException ( + "Extension Manager: exception during addExtension, ckeckPrerequisites failed", + static_cast<OWeakObject*>(this), uno::Any()); + } + } + catch ( const css::deployment::DeploymentException& ) { + excOccurred2 = ::cppu::getCaughtException(); + } catch ( const ucb::CommandFailedException & ) { + excOccurred2 = ::cppu::getCaughtException(); + } catch ( const ucb::CommandAbortedException & ) { + excOccurred2 = ::cppu::getCaughtException(); + } catch (const lang::IllegalArgumentException &) { + excOccurred2 = ::cppu::getCaughtException(); + } catch (const uno::RuntimeException &) { + excOccurred2 = ::cppu::getCaughtException(); + } catch (...) { + excOccurred2 = ::cppu::getCaughtException(); + css::deployment::DeploymentException exc( + "Extension Manager: exception during addExtension, url: " + + url, static_cast<OWeakObject*>(this), excOccurred2); + excOccurred2 <<= exc; + } + } + + if (excOccurred2.hasValue()) + { + //It does not matter what exception is thrown. We try to + //recover the original status. + //If the user aborted installation then a ucb::CommandAbortedException + //is thrown. + //Use a private AbortChannel so the user cannot interrupt. + try + { + if (xExtensionBackup.is()) + { + xPackageManager->importExtension( + xExtensionBackup, Reference<task::XAbortChannel>(), + Reference<ucb::XCommandEnvironment>()); + } + activateExtension( + sIdentifier, sFileName, bUserDisabled, false, + Reference<task::XAbortChannel>(), Reference<ucb::XCommandEnvironment>()); + } + catch (...) + { + } + ::cppu::throwException(excOccurred2); + } + } // leaving the guarded section (getMutex()) + + return xNewExtension; +} + +void ExtensionManager::removeExtension( + OUString const & identifier, OUString const & fileName, + OUString const & repository, + Reference<task::XAbortChannel> const & xAbortChannel, + Reference<ucb::XCommandEnvironment> const & xCmdEnv ) +{ + uno::Any excOccurred1; + Reference<css::deployment::XPackage> xExtensionBackup; + Reference<css::deployment::XPackageManager> xPackageManager; + bool bUserDisabled = false; + ::osl::MutexGuard guard(m_aMutex); + try + { +//Determine the repository to use + if (repository == "user") + xPackageManager = getUserRepository(); + else if (repository == "shared") + xPackageManager = getSharedRepository(); + else + throw lang::IllegalArgumentException( + "No valid repository name provided.", + static_cast<cppu::OWeakObject*>(this), 0); + + bUserDisabled = isUserDisabled(identifier, fileName); + //Backup the extension, in case the user cancels the action + xExtensionBackup = backupExtension( + identifier, fileName, xPackageManager, xCmdEnv); + + //revoke the extension if it is active + Reference<css::deployment::XPackage> xOldExtension = + xPackageManager->getDeployedPackage( + identifier, fileName, xCmdEnv); + xOldExtension->revokePackage(false, xAbortChannel, xCmdEnv); + + xPackageManager->removePackage( + identifier, fileName, xAbortChannel, xCmdEnv); + activateExtension(identifier, fileName, bUserDisabled, false, + xAbortChannel, xCmdEnv); + fireModified(); + } + catch ( const css::deployment::DeploymentException& ) { + excOccurred1 = ::cppu::getCaughtException(); + } catch ( const ucb::CommandFailedException & ) { + excOccurred1 = ::cppu::getCaughtException(); + } catch ( const ucb::CommandAbortedException & ) { + excOccurred1 = ::cppu::getCaughtException(); + } catch (const lang::IllegalArgumentException &) { + excOccurred1 = ::cppu::getCaughtException(); + } catch (const uno::RuntimeException &) { + excOccurred1 = ::cppu::getCaughtException(); + } catch (...) { + excOccurred1 = ::cppu::getCaughtException(); + css::deployment::DeploymentException exc( + "Extension Manager: exception during removeExtension", + static_cast<OWeakObject*>(this), excOccurred1); + excOccurred1 <<= exc; + } + + if (excOccurred1.hasValue()) + { + //User aborted installation, restore the previous situation. + //Use a private AbortChannel so the user cannot interrupt. + try + { + Reference<ucb::XCommandEnvironment> tmpCmdEnv( + new TmpRepositoryCommandEnv(xCmdEnv->getInteractionHandler())); + if (xExtensionBackup.is()) + { + xPackageManager->importExtension( + xExtensionBackup, Reference<task::XAbortChannel>(), + tmpCmdEnv); + activateExtension( + identifier, fileName, bUserDisabled, false, + Reference<task::XAbortChannel>(), + tmpCmdEnv); + + getTmpRepository()->removePackage( + dp_misc::getIdentifier(xExtensionBackup), + xExtensionBackup->getName(), xAbortChannel, xCmdEnv); + fireModified(); + } + } + catch (...) + { + } + ::cppu::throwException(excOccurred1); + } + + if (xExtensionBackup.is()) + getTmpRepository()->removePackage( + dp_misc::getIdentifier(xExtensionBackup), + xExtensionBackup->getName(), xAbortChannel, xCmdEnv); +} + +// Only enable extensions from shared and user repository +void ExtensionManager::enableExtension( + Reference<css::deployment::XPackage> const & extension, + Reference<task::XAbortChannel> const & xAbortChannel, + Reference<ucb::XCommandEnvironment> const & xCmdEnv) +{ + ::osl::MutexGuard guard(m_aMutex); + bool bUserDisabled = false; + uno::Any excOccurred; + try + { + if (!extension.is()) + return; + OUString repository = extension->getRepositoryName(); + if (repository != "user") + throw lang::IllegalArgumentException( + "No valid repository name provided.", + static_cast<cppu::OWeakObject*>(this), 0); + + bUserDisabled = isUserDisabled(dp_misc::getIdentifier(extension), + extension->getName()); + + activateExtension(dp_misc::getIdentifier(extension), + extension->getName(), false, false, + xAbortChannel, xCmdEnv); + } + catch ( const css::deployment::DeploymentException& ) { + excOccurred = ::cppu::getCaughtException(); + } catch ( const ucb::CommandFailedException & ) { + excOccurred = ::cppu::getCaughtException(); + } catch ( const ucb::CommandAbortedException & ) { + excOccurred = ::cppu::getCaughtException(); + } catch (const lang::IllegalArgumentException &) { + excOccurred = ::cppu::getCaughtException(); + } catch (const uno::RuntimeException &) { + excOccurred = ::cppu::getCaughtException(); + } catch (...) { + excOccurred = ::cppu::getCaughtException(); + css::deployment::DeploymentException exc( + "Extension Manager: exception during enableExtension", + static_cast<OWeakObject*>(this), excOccurred); + excOccurred <<= exc; + } + + if (!excOccurred.hasValue()) + return; + + try + { + activateExtension(dp_misc::getIdentifier(extension), + extension->getName(), bUserDisabled, false, + xAbortChannel, xCmdEnv); + } + catch (...) + { + } + ::cppu::throwException(excOccurred); +} + +sal_Int32 ExtensionManager::checkPrerequisitesAndEnable( + Reference<css::deployment::XPackage> const & extension, + Reference<task::XAbortChannel> const & xAbortChannel, + Reference<ucb::XCommandEnvironment> const & xCmdEnv) +{ + try + { + if (!extension.is()) + return 0; + ::osl::MutexGuard guard(m_aMutex); + sal_Int32 ret = 0; + Reference<css::deployment::XPackageManager> mgr = + getPackageManager(extension->getRepositoryName()); + ret = mgr->checkPrerequisites(extension, xAbortChannel, xCmdEnv); + if (ret) + { + //There are some unfulfilled prerequisites, try to revoke + extension->revokePackage(false, xAbortChannel, xCmdEnv); + } + const OUString id(dp_misc::getIdentifier(extension)); + activateExtension(id, extension->getName(), + isUserDisabled(id, extension->getName()), false, + xAbortChannel, xCmdEnv); + return ret; + } + catch ( const css::deployment::DeploymentException& ) { + throw; + } catch ( const ucb::CommandFailedException & ) { + throw; + } catch ( const ucb::CommandAbortedException & ) { + throw; + } catch (const lang::IllegalArgumentException &) { + throw; + } catch (const uno::RuntimeException &) { + throw; + } catch (...) { + uno::Any excOccurred = ::cppu::getCaughtException(); + css::deployment::DeploymentException exc( + "Extension Manager: exception during disableExtension", + static_cast<OWeakObject*>(this), excOccurred); + throw exc; + } +} + +void ExtensionManager::disableExtension( + Reference<css::deployment::XPackage> const & extension, + Reference<task::XAbortChannel> const & xAbortChannel, + Reference<ucb::XCommandEnvironment> const & xCmdEnv ) +{ + ::osl::MutexGuard guard(m_aMutex); + uno::Any excOccurred; + bool bUserDisabled = false; + try + { + if (!extension.is()) + return; + const OUString repository( extension->getRepositoryName()); + if (repository != "user") + throw lang::IllegalArgumentException( + "No valid repository name provided.", + static_cast<cppu::OWeakObject*>(this), 0); + + const OUString id(dp_misc::getIdentifier(extension)); + bUserDisabled = isUserDisabled(id, extension->getName()); + + activateExtension(id, extension->getName(), true, false, + xAbortChannel, xCmdEnv); + } + catch ( const css::deployment::DeploymentException& ) { + excOccurred = ::cppu::getCaughtException(); + } catch ( const ucb::CommandFailedException & ) { + excOccurred = ::cppu::getCaughtException(); + } catch ( const ucb::CommandAbortedException & ) { + excOccurred = ::cppu::getCaughtException(); + } catch (const lang::IllegalArgumentException &) { + excOccurred = ::cppu::getCaughtException(); + } catch (const uno::RuntimeException &) { + excOccurred = ::cppu::getCaughtException(); + } catch (...) { + excOccurred = ::cppu::getCaughtException(); + css::deployment::DeploymentException exc( + "Extension Manager: exception during disableExtension", + static_cast<OWeakObject*>(this), excOccurred); + excOccurred <<= exc; + } + + if (!excOccurred.hasValue()) + return; + + try + { + activateExtension(dp_misc::getIdentifier(extension), + extension->getName(), bUserDisabled, false, + xAbortChannel, xCmdEnv); + } + catch (...) + { + } + ::cppu::throwException(excOccurred); +} + +uno::Sequence< Reference<css::deployment::XPackage> > + ExtensionManager::getDeployedExtensions( + OUString const & repository, + Reference<task::XAbortChannel> const &xAbort, + Reference<ucb::XCommandEnvironment> const & xCmdEnv ) +{ + return getPackageManager(repository)->getDeployedPackages( + xAbort, xCmdEnv); +} + +Reference<css::deployment::XPackage> + ExtensionManager::getDeployedExtension( + OUString const & repository, + OUString const & identifier, + OUString const & filename, + Reference<ucb::XCommandEnvironment> const & xCmdEnv ) +{ + return getPackageManager(repository)->getDeployedPackage( + identifier, filename, xCmdEnv); +} + +uno::Sequence< uno::Sequence<Reference<css::deployment::XPackage> > > + ExtensionManager::getAllExtensions( + Reference<task::XAbortChannel> const & xAbort, + Reference<ucb::XCommandEnvironment> const & xCmdEnv ) +{ + try + { + id2extensions mapExt; + + uno::Sequence<Reference<css::deployment::XPackage> > userExt = + getUserRepository()->getDeployedPackages(xAbort, xCmdEnv); + addExtensionsToMap(mapExt, userExt, u"user"); + uno::Sequence<Reference<css::deployment::XPackage> > sharedExt = + getSharedRepository()->getDeployedPackages(xAbort, xCmdEnv); + addExtensionsToMap(mapExt, sharedExt, u"shared"); + uno::Sequence<Reference<css::deployment::XPackage> > bundledExt = + getBundledRepository()->getDeployedPackages(xAbort, xCmdEnv); + addExtensionsToMap(mapExt, bundledExt, u"bundled"); + + // Create the tmp repository to trigger its clean up (deletion + // of old temporary data.) + getTmpRepository(); + + //copy the values of the map to a vector for sorting + std::vector< std::vector<Reference<css::deployment::XPackage> > > + vecExtensions; + for (auto const& elem : mapExt) + vecExtensions.push_back(elem.second); + + //sort the element according to the identifier + std::sort(vecExtensions.begin(), vecExtensions.end(), CompIdentifiers()); + + sal_Int32 j = 0; + uno::Sequence< uno::Sequence<Reference<css::deployment::XPackage> > > seqSeq(vecExtensions.size()); + auto seqSeqRange = asNonConstRange(seqSeq); + for (auto const& elem : vecExtensions) + { + seqSeqRange[j++] = comphelper::containerToSequence(elem); + } + return seqSeq; + + } catch ( const css::deployment::DeploymentException& ) { + throw; + } catch ( const ucb::CommandFailedException & ) { + throw; + } catch ( const ucb::CommandAbortedException & ) { + throw; + } catch (const lang::IllegalArgumentException &) { + throw; + } catch (const uno::RuntimeException &) { + throw; + } catch (...) { + uno::Any exc = ::cppu::getCaughtException(); + throw css::deployment::DeploymentException( + "Extension Manager: exception during enableExtension", + static_cast<OWeakObject*>(this), exc); + } +} + +// Only to be called from unopkg or soffice bootstrap (with force=true in the +// latter case): +void ExtensionManager::reinstallDeployedExtensions( + sal_Bool force, OUString const & repository, + Reference<task::XAbortChannel> const & xAbortChannel, + Reference<ucb::XCommandEnvironment> const & xCmdEnv ) +{ + try + { + Reference<css::deployment::XPackageManager> + xPackageManager = getPackageManager(repository); + + std::set< OUString > disabledExts; + { + const uno::Sequence< Reference<css::deployment::XPackage> > extensions( + xPackageManager->getDeployedPackages(xAbortChannel, xCmdEnv)); + for ( const Reference<css::deployment::XPackage>& package : extensions ) + { + try + { + beans::Optional< beans::Ambiguous< sal_Bool > > registered( + package->isRegistered(xAbortChannel, xCmdEnv)); + if (registered.IsPresent && + !(registered.Value.IsAmbiguous || + registered.Value.Value)) + { + const OUString id = dp_misc::getIdentifier(package); + OSL_ASSERT(!id.isEmpty()); + disabledExts.insert(id); + } + } + catch (const lang::DisposedException &) + { + } + } + } + + ::osl::MutexGuard guard(m_aMutex); + xPackageManager->reinstallDeployedPackages( + force, xAbortChannel, xCmdEnv); + //We must sync here, otherwise we will get exceptions when extensions + //are removed. + dp_misc::syncRepositories(force, xCmdEnv); + const uno::Sequence< Reference<css::deployment::XPackage> > extensions( + xPackageManager->getDeployedPackages(xAbortChannel, xCmdEnv)); + + for ( const Reference<css::deployment::XPackage>& package : extensions ) + { + try + { + const OUString id = dp_misc::getIdentifier(package); + const OUString fileName = package->getName(); + OSL_ASSERT(!id.isEmpty()); + activateExtension( + id, fileName, disabledExts.find(id) != disabledExts.end(), + true, xAbortChannel, xCmdEnv ); + } + catch (const lang::DisposedException &) + { + } + } + } catch ( const css::deployment::DeploymentException& ) { + throw; + } catch ( const ucb::CommandFailedException & ) { + throw; + } catch ( const ucb::CommandAbortedException & ) { + throw; + } catch (const lang::IllegalArgumentException &) { + throw; + } catch (const uno::RuntimeException &) { + throw; + } catch (...) { + uno::Any exc = ::cppu::getCaughtException(); + throw css::deployment::DeploymentException( + "Extension Manager: exception during enableExtension", + static_cast<OWeakObject*>(this), exc); + } +} + +sal_Bool ExtensionManager::synchronize( + Reference<task::XAbortChannel> const & xAbortChannel, + Reference<ucb::XCommandEnvironment> const & xCmdEnv ) +{ + try + { + ::osl::MutexGuard guard(m_aMutex); + OUString sSynchronizingShared(StrSyncRepository()); + sSynchronizingShared = sSynchronizingShared.replaceAll("%NAME", "shared"); + dp_misc::ProgressLevel progressShared(xCmdEnv, sSynchronizingShared); + bool bModified = getSharedRepository()->synchronize(xAbortChannel, xCmdEnv); + progressShared.update("\n\n"); + + OUString sSynchronizingBundled(StrSyncRepository()); + sSynchronizingBundled = sSynchronizingBundled.replaceAll("%NAME", "bundled"); + dp_misc::ProgressLevel progressBundled(xCmdEnv, sSynchronizingBundled); + bModified |= static_cast<bool>(getBundledRepository()->synchronize(xAbortChannel, xCmdEnv)); + progressBundled.update("\n\n"); + + //Always determine the active extension. + //TODO: Is this still necessary? (It used to be necessary for the + // first-start optimization: The setup created the registration data + // for the bundled extensions (share/prereg/bundled) which was copied to + // the user installation when a user started OOo for the first time + // after running setup. All bundled extensions were registered at that + // moment. However, extensions with the same identifier could be in the + // shared or user repository, in which case the respective bundled + // extensions had to be revoked.) + try + { + const uno::Sequence<uno::Sequence<Reference<css::deployment::XPackage> > > + seqSeqExt = getAllExtensions(xAbortChannel, xCmdEnv); + for (uno::Sequence<Reference<css::deployment::XPackage> > const & seqExt : seqSeqExt) + { + activateExtension(seqExt, isUserDisabled(seqExt), true, + xAbortChannel, xCmdEnv); + } + } + catch (...) + { + //We catch the exception, so we can write the lastmodified file + //so we will no repeat this every time OOo starts. + OSL_FAIL("Extensions Manager: synchronize"); + } + OUString lastSyncBundled("$BUNDLED_EXTENSIONS_USER/lastsynchronized"); + writeLastModified(lastSyncBundled, xCmdEnv, m_xContext); + OUString lastSyncShared("$SHARED_EXTENSIONS_USER/lastsynchronized"); + writeLastModified(lastSyncShared, xCmdEnv, m_xContext); + return bModified; + } catch ( const css::deployment::DeploymentException& ) { + throw; + } catch ( const ucb::CommandFailedException & ) { + throw; + } catch ( const ucb::CommandAbortedException & ) { + throw; + } catch (const lang::IllegalArgumentException &) { + throw; + } catch (const uno::RuntimeException &) { + throw; + } catch (...) { + uno::Any exc = ::cppu::getCaughtException(); + throw css::deployment::DeploymentException( + "Extension Manager: exception in synchronize", + static_cast<OWeakObject*>(this), exc); + } +} + +// Notify the user when a new extension is to be installed. This is only the +// case when one uses the system integration to install an extension (double +// clicking on .oxt file etc.)). The function must only be called if there is no +// extension with the same identifier already deployed. Then the checkUpdate +// function will inform the user that the extension is about to be installed In +// case the user cancels the installation a CommandFailed exception is +// thrown. +void ExtensionManager::checkInstall( + OUString const & displayName, + Reference<ucb::XCommandEnvironment> const & cmdEnv) +{ + uno::Any request( + css::deployment::InstallException( + "Extension " + displayName + + " is about to be installed.", + static_cast<OWeakObject *>(this), displayName)); + bool approve = false, abort = false; + if (! dp_misc::interactContinuation( + request, cppu::UnoType<task::XInteractionApprove>::get(), + cmdEnv, &approve, &abort )) + { + OSL_ASSERT( !approve && !abort ); + throw css::deployment::DeploymentException( + DpResId(RID_STR_ERROR_WHILE_ADDING) + displayName, + static_cast<OWeakObject *>(this), request ); + } + if (abort || !approve) + throw ucb::CommandFailedException( + DpResId(RID_STR_ERROR_WHILE_ADDING) + displayName, + static_cast<OWeakObject *>(this), request ); +} + +/* The function will make the user interaction in case there is an extension +installed with the same id. This function may only be called if there is already +an extension. +*/ +void ExtensionManager::checkUpdate( + OUString const & newVersion, + OUString const & newDisplayName, + Reference<css::deployment::XPackage> const & oldExtension, + Reference<ucb::XCommandEnvironment> const & xCmdEnv ) +{ + // package already deployed, interact --force: + uno::Any request( + (css::deployment::VersionException( + DpResId( + RID_STR_PACKAGE_ALREADY_ADDED ) + newDisplayName, + static_cast<OWeakObject *>(this), newVersion, newDisplayName, + oldExtension ) ) ); + bool replace = false, abort = false; + if (! dp_misc::interactContinuation( + request, cppu::UnoType<task::XInteractionApprove>::get(), + xCmdEnv, &replace, &abort )) { + OSL_ASSERT( !replace && !abort ); + throw css::deployment::DeploymentException( + DpResId( + RID_STR_ERROR_WHILE_ADDING) + newDisplayName, + static_cast<OWeakObject *>(this), request ); + } + if (abort || !replace) + throw ucb::CommandFailedException( + DpResId( + RID_STR_PACKAGE_ALREADY_ADDED) + newDisplayName, + static_cast<OWeakObject *>(this), request ); +} + +uno::Sequence<Reference<css::deployment::XPackage> > SAL_CALL +ExtensionManager::getExtensionsWithUnacceptedLicenses( + OUString const & repository, + Reference<ucb::XCommandEnvironment> const & xCmdEnv) +{ + Reference<css::deployment::XPackageManager> + xPackageManager = getPackageManager(repository); + ::osl::MutexGuard guard(m_aMutex); + return xPackageManager->getExtensionsWithUnacceptedLicenses(xCmdEnv); +} + +sal_Bool ExtensionManager::isReadOnlyRepository(OUString const & repository) +{ + return getPackageManager(repository)->isReadOnly(); +} + + +// XModifyBroadcaster + +void ExtensionManager::addModifyListener( + Reference<util::XModifyListener> const & xListener ) +{ + check(); + rBHelper.addListener( cppu::UnoType<decltype(xListener)>::get(), xListener ); +} + + +void ExtensionManager::removeModifyListener( + Reference<util::XModifyListener> const & xListener ) +{ + check(); + rBHelper.removeListener( cppu::UnoType<decltype(xListener)>::get(), xListener ); +} + +void ExtensionManager::check() +{ + ::osl::MutexGuard guard( m_aMutex ); + if (rBHelper.bInDispose || rBHelper.bDisposed) { + throw lang::DisposedException( + "ExtensionManager instance has already been disposed!", + static_cast<OWeakObject *>(this) ); + } +} + +void ExtensionManager::fireModified() +{ + ::cppu::OInterfaceContainerHelper * pContainer = rBHelper.getContainer( + cppu::UnoType<util::XModifyListener>::get() ); + if (pContainer != nullptr) { + pContainer->forEach<util::XModifyListener>( + [this] (uno::Reference<util::XModifyListener> const& xListener) + { return xListener->modified(lang::EventObject(static_cast<OWeakObject *>(this))); }); + } +} + +} // namespace dp_manager + + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +com_sun_star_comp_deployment_ExtensionManager_get_implementation( + css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const& ) +{ + return cppu::acquire(new dp_manager::ExtensionManager(context)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/manager/dp_extensionmanager.hxx b/desktop/source/deployment/manager/dp_extensionmanager.hxx new file mode 100644 index 0000000000..a70f4fbd2e --- /dev/null +++ b/desktop/source/deployment/manager/dp_extensionmanager.hxx @@ -0,0 +1,227 @@ +/* -*- 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 <strings.hrc> +#include <dp_shared.hxx> +#include <cppuhelper/basemutex.hxx> +#include <cppuhelper/compbase.hxx> +#include <com/sun/star/deployment/XExtensionManager.hpp> +#include <com/sun/star/deployment/XPackageManager.hpp> +#include <com/sun/star/deployment/XPackageManagerFactory.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <mutex> +#include <vector> +#include <unordered_map> + +namespace dp_manager { + +typedef std::unordered_map< + OUString, + std::vector<css::uno::Reference<css::deployment::XPackage> > > id2extensions; + +class ExtensionManager : private cppu::BaseMutex, + public ::cppu::WeakComponentImplHelper< css::deployment::XExtensionManager, css::lang::XServiceInfo > +{ +public: + explicit ExtensionManager( css::uno::Reference< css::uno::XComponentContext >const& xContext); + virtual ~ExtensionManager() override; + + void check(); + void fireModified(); + +public: + + // 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; + +// XModifyBroadcaster + virtual void SAL_CALL addModifyListener( + css::uno::Reference<css::util::XModifyListener> const & xListener ) override; + virtual void SAL_CALL removeModifyListener( + css::uno::Reference<css::util::XModifyListener> const & xListener ) override; + +//XExtensionManager + virtual css::uno::Sequence< + css::uno::Reference<css::deployment::XPackageTypeInfo> > SAL_CALL + getSupportedPackageTypes() override; + + virtual css::uno::Reference<css::task::XAbortChannel> SAL_CALL + createAbortChannel() override; + + virtual css::uno::Reference<css::deployment::XPackage> SAL_CALL addExtension( + OUString const & url, + css::uno::Sequence<css::beans::NamedValue> const & properties, + OUString const & repository, + css::uno::Reference<css::task::XAbortChannel> const & xAbortChannel, + css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv ) override; + + virtual void SAL_CALL removeExtension( + OUString const & identifier, + OUString const & filename, + OUString const & repository, + css::uno::Reference<css::task::XAbortChannel> const & xAbortChannel, + css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv ) override; + + virtual void SAL_CALL enableExtension( + css::uno::Reference<css::deployment::XPackage> const & extension, + css::uno::Reference<css::task::XAbortChannel> const & xAbortChannel, + css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv ) override; + + virtual void SAL_CALL disableExtension( + css::uno::Reference<css::deployment::XPackage> const & extension, + css::uno::Reference<css::task::XAbortChannel> const & xAbortChannel, + css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv ) override; + + virtual sal_Int32 SAL_CALL checkPrerequisitesAndEnable( + css::uno::Reference<css::deployment::XPackage> const & extension, + css::uno::Reference<css::task::XAbortChannel> const & xAbortChannel, + css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv ) override; + + virtual css::uno::Sequence< css::uno::Reference<css::deployment::XPackage> > + SAL_CALL getDeployedExtensions( + OUString const & repository, + css::uno::Reference<css::task::XAbortChannel> const &, + css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv ) override; + + virtual css::uno::Reference< css::deployment::XPackage> + SAL_CALL getDeployedExtension( + OUString const & repository, + OUString const & identifier, + OUString const & filename, + css::uno::Reference< css::ucb::XCommandEnvironment> const & xCmdEnv ) override; + + virtual css::uno::Sequence<css::uno::Reference<css::deployment::XPackage> > + SAL_CALL getExtensionsWithSameIdentifier( + OUString const & identifier, + OUString const & filename, + css::uno::Reference< css::ucb::XCommandEnvironment> const & xCmdEnv ) override; + + virtual css::uno::Sequence< css::uno::Sequence<css::uno::Reference<css::deployment::XPackage> > > + SAL_CALL getAllExtensions( + css::uno::Reference<css::task::XAbortChannel> const &, + css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv ) override; + + virtual void SAL_CALL reinstallDeployedExtensions( + sal_Bool force, OUString const & repository, + css::uno::Reference< css::task::XAbortChannel> const & xAbortChannel, + css::uno::Reference< css::ucb::XCommandEnvironment> const & xCmdEnv ) override; + + virtual sal_Bool SAL_CALL synchronize( + css::uno::Reference<css::task::XAbortChannel> const & xAbortChannel, + css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv ) override; + + virtual css::uno::Sequence<css::uno::Reference<css::deployment::XPackage> > SAL_CALL + getExtensionsWithUnacceptedLicenses( + OUString const & repository, + css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv) override; + + virtual sal_Bool SAL_CALL isReadOnlyRepository(OUString const & repository) override; + +private: + + static OUString StrSyncRepository() { return DpResId(RID_STR_SYNCHRONIZING_REPOSITORY); } + + css::uno::Reference< css::uno::XComponentContext> m_xContext; + css::uno::Reference<css::deployment::XPackageManagerFactory> m_xPackageManagerFactory; + + //only to be used within addExtension + std::mutex m_addMutex; + /* contains the names of all repositories (except tmp) in order of there + priority. That is, the first element is "user" followed by "shared" and + then "bundled" + */ + std::vector< OUString > m_repositoryNames; + + css::uno::Reference<css::deployment::XPackageManager> getUserRepository(); + css::uno::Reference<css::deployment::XPackageManager> getSharedRepository(); + css::uno::Reference<css::deployment::XPackageManager> getBundledRepository(); + css::uno::Reference<css::deployment::XPackageManager> getTmpRepository(); + css::uno::Reference<css::deployment::XPackageManager> getBakRepository(); + + bool isUserDisabled(OUString const & identifier, + OUString const & filename); + + static bool isUserDisabled( + css::uno::Sequence<css::uno::Reference<css::deployment::XPackage> > const & seqExtSameId); + + void activateExtension( + OUString const & identifier, + OUString const & fileName, + bool bUserDisabled, bool bStartup, + css::uno::Reference<css::task::XAbortChannel> const & xAbortChannel, + css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv); + + static void activateExtension( + css::uno::Sequence<css::uno::Reference<css::deployment::XPackage> > const & seqExt, + bool bUserDisabled, bool bStartup, + css::uno::Reference<css::task::XAbortChannel> const & xAbortChannel, + css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv ); + + std::vector<css::uno::Reference<css::deployment::XPackage> > + getExtensionsWithSameId(OUString const & identifier, + OUString const & fileName); + + css::uno::Reference<css::deployment::XPackage> backupExtension( + OUString const & identifier, OUString const & fileName, + css::uno::Reference<css::deployment::XPackageManager> const & xPackageManager, + css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv); + + void checkInstall( + OUString const & displayName, + css::uno::Reference<css::ucb::XCommandEnvironment> const & cmdEnv); + + void checkUpdate( + OUString const & newVersion, + OUString const & newDisplayName, + css::uno::Reference<css::deployment::XPackage> const & oldExtension, + css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv); + + void addExtensionsToMap( + id2extensions & mapExt, + css::uno::Sequence<css::uno::Reference<css::deployment::XPackage> > const & seqExt, + std::u16string_view repository); + + /// @throws css::lang::IllegalArgumentException + /// @throws css::uno::RuntimeException + css::uno::Reference<css::deployment::XPackageManager> + getPackageManager(std::u16string_view repository); + + /// @throws css::deployment::DeploymentException + /// @throws css::ucb::CommandFailedException + /// @throws css::ucb::CommandAbortedException + /// @throws css::lang::IllegalArgumentException + /// @throws css::uno::RuntimeException + bool doChecksForAddExtension( + css::uno::Reference<css::deployment::XPackageManager> const & xPackageMgr, + css::uno::Sequence<css::beans::NamedValue> const & properties, + css::uno::Reference<css::deployment::XPackage> const & xTmpExtension, + css::uno::Reference<css::task::XAbortChannel> const & xAbortChannel, + css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv, + css::uno::Reference<css::deployment::XPackage> & out_existingExtension ); + +}; + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/manager/dp_informationprovider.cxx b/desktop/source/deployment/manager/dp_informationprovider.cxx new file mode 100644 index 0000000000..5b6d6a92b9 --- /dev/null +++ b/desktop/source/deployment/manager/dp_informationprovider.cxx @@ -0,0 +1,338 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <cppuhelper/implbase.hxx> +#include <cppuhelper/supportsservice.hxx> + +#include <com/sun/star/deployment/UpdateInformationProvider.hpp> +#include <com/sun/star/deployment/XPackage.hpp> +#include <com/sun/star/deployment/XPackageInformationProvider.hpp> +#include <com/sun/star/deployment/ExtensionManager.hpp> +#include <com/sun/star/deployment/XUpdateInformationProvider.hpp> +#include <com/sun/star/lang/IllegalArgumentException.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/task/XAbortChannel.hpp> +#include <com/sun/star/ucb/ContentCreationException.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <com/sun/star/ucb/XCommandEnvironment.hpp> +#include <com/sun/star/xml/dom/XElement.hpp> + +#include <com/sun/star/uno/Reference.hxx> +#include <osl/diagnose.h> +#include <rtl/ustring.hxx> +#include <comphelper/diagnose_ex.hxx> +#include <ucbhelper/content.hxx> + +#include <dp_dependencies.hxx> +#include <dp_descriptioninfoset.hxx> +#include <dp_identifier.hxx> +#include <dp_version.hxx> +#include <dp_update.hxx> + +namespace beans = com::sun::star::beans ; +namespace deployment = com::sun::star::deployment ; +namespace lang = com::sun::star::lang ; +namespace task = com::sun::star::task ; +namespace css_ucb = com::sun::star::ucb ; +namespace uno = com::sun::star::uno ; +namespace xml = com::sun::star::xml ; + + +namespace dp_info { + +namespace { + +class PackageInformationProvider : + public ::cppu::WeakImplHelper< deployment::XPackageInformationProvider, lang::XServiceInfo > + +{ + public: + explicit PackageInformationProvider( uno::Reference< uno::XComponentContext >const& xContext); + + // 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; + + // XPackageInformationProvider + virtual OUString SAL_CALL getPackageLocation( const OUString& extensionId ) override; + virtual uno::Sequence< uno::Sequence< OUString > > SAL_CALL isUpdateAvailable( const OUString& extensionId ) override; + virtual uno::Sequence< uno::Sequence< OUString > > SAL_CALL getExtensionList() override; + +private: + + uno::Reference< uno::XComponentContext> mxContext; + + OUString getPackageLocation( const OUString& repository, + std::u16string_view _sExtensionId ); + + uno::Reference< deployment::XUpdateInformationProvider > mxUpdateInformation; +}; + +} + +PackageInformationProvider::PackageInformationProvider( uno::Reference< uno::XComponentContext > const& xContext) : + mxContext( xContext ), + mxUpdateInformation( deployment::UpdateInformationProvider::create( xContext ) ) +{ +} + +// XServiceInfo +OUString PackageInformationProvider::getImplementationName() +{ + return "com.sun.star.comp.deployment.PackageInformationProvider"; +} + +sal_Bool PackageInformationProvider::supportsService( const OUString& ServiceName ) +{ + return cppu::supportsService(this, ServiceName); +} + +css::uno::Sequence< OUString > PackageInformationProvider::getSupportedServiceNames() +{ + // a private one: + return { "com.sun.star.comp.deployment.PackageInformationProvider" }; +} + +OUString PackageInformationProvider::getPackageLocation( + const OUString & repository, + std::u16string_view _rExtensionId ) +{ + OUString aLocationURL; + uno::Reference<deployment::XExtensionManager> xManager = + deployment::ExtensionManager::get(mxContext); + + if ( xManager.is() ) + { + const uno::Sequence< uno::Reference< deployment::XPackage > > packages( + xManager->getDeployedExtensions( + repository, + uno::Reference< task::XAbortChannel >(), + uno::Reference< css_ucb::XCommandEnvironment > () ) ); + + for ( int pos = packages.getLength(); pos--; ) + { + try + { + const beans::Optional< OUString > aID = packages[ pos ]->getIdentifier(); + if ( aID.IsPresent && (aID.Value == _rExtensionId ) ) + { + aLocationURL = packages[ pos ]->getURL(); + break; + } + } + catch ( uno::RuntimeException & ) {} + } + } + + return aLocationURL; +} + + +OUString SAL_CALL +PackageInformationProvider::getPackageLocation( const OUString& _sExtensionId ) +{ + OUString aLocationURL = getPackageLocation( "user", _sExtensionId ); + + if ( aLocationURL.isEmpty() ) + { + aLocationURL = getPackageLocation( "shared", _sExtensionId ); + } + if ( aLocationURL.isEmpty() ) + { + aLocationURL = getPackageLocation( "bundled", _sExtensionId ); + } + if ( !aLocationURL.isEmpty() ) + { + try + { + ::ucbhelper::Content aContent( aLocationURL, nullptr, mxContext ); + aLocationURL = aContent.getURL(); + } + catch (const css::ucb::ContentCreationException&) + { + TOOLS_WARN_EXCEPTION("desktop.deployment", "ignoring"); + } + } + return aLocationURL; +} + +uno::Sequence< uno::Sequence< OUString > > SAL_CALL +PackageInformationProvider::isUpdateAvailable( const OUString& _sExtensionId ) +{ + uno::Sequence< uno::Sequence< OUString > > aList; + + uno::Reference<deployment::XExtensionManager> extMgr = + deployment::ExtensionManager::get(mxContext); + + if (!extMgr.is()) + { + OSL_ASSERT(false); + return aList; + } + std::vector<std::pair<uno::Reference<deployment::XPackage>, uno::Any > > errors; + dp_misc::UpdateInfoMap updateInfoMap; + if (!_sExtensionId.isEmpty()) + { + std::vector<uno::Reference<deployment::XPackage> > vecExtensions; + uno::Reference<deployment::XPackage> extension; + try + { + extension = dp_misc::getExtensionWithHighestVersion( + extMgr->getExtensionsWithSameIdentifier( + _sExtensionId, _sExtensionId, uno::Reference<css_ucb::XCommandEnvironment>())); + vecExtensions.push_back(extension); + } + catch (lang::IllegalArgumentException &) + { + OSL_ASSERT(false); + } + updateInfoMap = dp_misc::getOnlineUpdateInfos( + mxContext, extMgr, mxUpdateInformation, &vecExtensions, errors); + } + else + { + updateInfoMap = dp_misc::getOnlineUpdateInfos( + mxContext, extMgr, mxUpdateInformation, nullptr, errors); + } + + int nCount = 0; + for (auto const& updateInfo : updateInfoMap) + { + dp_misc::UpdateInfo const & info = updateInfo.second; + + OUString sOnlineVersion; + if (info.info.is()) + { + // check, if there are unsatisfied dependencies and ignore this online update + dp_misc::DescriptionInfoset infoset(mxContext, info.info); + uno::Sequence< uno::Reference< xml::dom::XElement > > + ds( dp_misc::Dependencies::check( infoset ) ); + if ( ! ds.hasElements() ) + sOnlineVersion = info.version; + } + + OUString sVersionUser; + OUString sVersionShared; + OUString sVersionBundled; + uno::Sequence< uno::Reference< deployment::XPackage> > extensions; + try { + extensions = extMgr->getExtensionsWithSameIdentifier( + dp_misc::getIdentifier(info.extension), info.extension->getName(), + uno::Reference<css_ucb::XCommandEnvironment>()); + } catch (const lang::IllegalArgumentException&) { + TOOLS_WARN_EXCEPTION("desktop.deployment", "ignoring"); + continue; + } + OSL_ASSERT(extensions.getLength() == 3); + if (extensions[0].is() ) + sVersionUser = extensions[0]->getVersion(); + if (extensions[1].is() ) + sVersionShared = extensions[1]->getVersion(); + if (extensions[2].is() ) + sVersionBundled = extensions[2]->getVersion(); + + bool bSharedReadOnly = extMgr->isReadOnlyRepository("shared"); + + dp_misc::UPDATE_SOURCE sourceUser = dp_misc::isUpdateUserExtension( + bSharedReadOnly, sVersionUser, sVersionShared, sVersionBundled, sOnlineVersion); + dp_misc::UPDATE_SOURCE sourceShared = dp_misc::isUpdateSharedExtension( + bSharedReadOnly, sVersionShared, sVersionBundled, sOnlineVersion); + + OUString updateVersionUser; + OUString updateVersionShared; + if (sourceUser != dp_misc::UPDATE_SOURCE_NONE) + updateVersionUser = dp_misc::getHighestVersion( + sVersionShared, sVersionBundled, sOnlineVersion); + if (sourceShared != dp_misc::UPDATE_SOURCE_NONE) + updateVersionShared = dp_misc::getHighestVersion( + OUString(), sVersionBundled, sOnlineVersion); + OUString updateVersion; + if (dp_misc::compareVersions(updateVersionUser, updateVersionShared) == dp_misc::GREATER) + updateVersion = updateVersionUser; + else + updateVersion = updateVersionShared; + if (!updateVersion.isEmpty()) + { + + OUString aNewEntry[2]; + aNewEntry[0] = updateInfo.first; + aNewEntry[1] = updateVersion; + aList.realloc( ++nCount ); + aList.getArray()[ nCount-1 ] = ::uno::Sequence< OUString >( aNewEntry, 2 ); + } + } + return aList; +} + + +uno::Sequence< uno::Sequence< OUString > > SAL_CALL PackageInformationProvider::getExtensionList() +{ + const uno::Reference<deployment::XExtensionManager> mgr = + deployment::ExtensionManager::get(mxContext); + + if (!mgr.is()) + return uno::Sequence< uno::Sequence< OUString > >(); + + const uno::Sequence< uno::Sequence< uno::Reference<deployment::XPackage > > > + allExt = mgr->getAllExtensions( + uno::Reference< task::XAbortChannel >(), + uno::Reference< css_ucb::XCommandEnvironment > () ); + + uno::Sequence< uno::Sequence< OUString > > retList; + + sal_Int32 cAllIds = allExt.getLength(); + retList.realloc(cAllIds); + auto pretList = retList.getArray(); + + for (sal_Int32 i = 0; i < cAllIds; i++) + { + //The inner sequence contains extensions with the same identifier from + //all the different repositories, that is user, share, bundled. + const uno::Sequence< uno::Reference< deployment::XPackage > > & + seqExtension = allExt[i]; + sal_Int32 cExt = seqExtension.getLength(); + OSL_ASSERT(cExt == 3); + for (sal_Int32 j = 0; j < cExt; j++) + { + //ToDo according to the old code the first found extension is used + //even if another one with the same id has a better version. + uno::Reference< deployment::XPackage > const & xExtension( seqExtension[j] ); + if (xExtension.is()) + { + pretList[i] = { dp_misc::getIdentifier(xExtension), xExtension->getVersion() }; + break; + } + } + } + return retList; +} + + +} // namespace dp_info + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +com_sun_star_comp_deployment_PackageInformationProvider_get_implementation( + css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const& ) +{ + return cppu::acquire(new dp_info::PackageInformationProvider(context)); +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/manager/dp_manager.cxx b/desktop/source/deployment/manager/dp_manager.cxx new file mode 100644 index 0000000000..d882b77baf --- /dev/null +++ b/desktop/source/deployment/manager/dp_manager.cxx @@ -0,0 +1,1597 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <config_features.h> + +#include <dp_interact.h> +#include <dp_misc.h> +#include <dp_registry.hxx> +#include <dp_shared.hxx> +#include <strings.hrc> +#include <dp_ucb.h> +#include <dp_platform.hxx> +#include "dp_manager.h" +#include <dp_identifier.hxx> +#include <rtl/ustrbuf.hxx> +#include <rtl/string.hxx> +#include <rtl/uri.hxx> +#include <rtl/bootstrap.hxx> +#include <sal/log.hxx> +#include <tools/urlobj.hxx> +#include <comphelper/diagnose_ex.hxx> +#include <osl/diagnose.h> +#include <osl/file.hxx> +#include <osl/security.hxx> +#include <cppuhelper/exc_hlp.hxx> +#include <comphelper/logging.hxx> +#include <comphelper/sequence.hxx> +#include <utility> +#include <xmlscript/xml_helper.hxx> +#include <svl/inettype.hxx> +#include <com/sun/star/lang/IllegalArgumentException.hpp> +#include <com/sun/star/lang/WrappedTargetRuntimeException.hpp> +#include <com/sun/star/beans/UnknownPropertyException.hpp> +#include <com/sun/star/logging/LogLevel.hpp> +#include <com/sun/star/logging/FileHandler.hpp> +#include <com/sun/star/logging/SimpleTextFormatter.hpp> +#include <com/sun/star/logging/XLogger.hpp> +#include <com/sun/star/util/XUpdatable.hpp> +#include <com/sun/star/sdbc/XResultSet.hpp> +#include <com/sun/star/sdbc/XRow.hpp> +#include <com/sun/star/ucb/CommandAbortedException.hpp> +#include <com/sun/star/ucb/CommandFailedException.hpp> +#include <com/sun/star/ucb/ContentCreationException.hpp> +#include <com/sun/star/ucb/XContentAccess.hpp> +#include <com/sun/star/ucb/NameClash.hpp> +#include <com/sun/star/deployment/DeploymentException.hpp> +#include <com/sun/star/deployment/InvalidRemovedParameterException.hpp> +#include <com/sun/star/deployment/Prerequisites.hpp> +#include <com/sun/star/ucb/UnsupportedCommandException.hpp> +#include <unotools/tempfile.hxx> + +#include <dp_descriptioninfoset.hxx> +#include "dp_commandenvironments.hxx" +#include "dp_properties.hxx" + +#include <vector> +#include <algorithm> + +using namespace ::dp_misc; +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::ucb; +using namespace ::com::sun::star::logging; + + +namespace dp_manager { + +namespace { + +struct MatchTempDir +{ + OUString m_str; + explicit MatchTempDir( OUString str ) : m_str(std::move( str )) {} + bool operator () ( ActivePackages::Entries::value_type const & v ) const { + return v.second.temporaryName.equalsIgnoreAsciiCase( m_str ); + } +}; + +OUString getExtensionFolder(OUString const & parentFolder, + Reference<ucb::XCommandEnvironment> const & xCmdEnv, + Reference<uno::XComponentContext> const & xContext) +{ + ::ucbhelper::Content tempFolder( parentFolder, xCmdEnv, xContext ); + Reference<sdbc::XResultSet> xResultSet( + StrTitle::createCursor (tempFolder, ::ucbhelper::INCLUDE_FOLDERS_ONLY ) ); + + OUString title; + if (xResultSet->next()) + { + title = Reference<sdbc::XRow>( + xResultSet, UNO_QUERY_THROW )->getString(1 /* Title */ ) ; + } + return title; +} +} + +void PackageManagerImpl::initActivationLayer( + Reference<XCommandEnvironment> const & xCmdEnv ) +{ + if (m_activePackages.isEmpty()) + { + OSL_ASSERT( m_registryCache.isEmpty() ); + // documents temp activation: + m_activePackagesDB.reset( new ActivePackages ); + ::ucbhelper::Content ucbContent; + if (create_ucb_content( &ucbContent, m_context, xCmdEnv, + false /* no throw */ )) + { + // scan for all entries in m_packagesDir: + Reference<sdbc::XResultSet> xResultSet( + StrTitle::createCursor (ucbContent, ::ucbhelper::INCLUDE_FOLDERS_AND_DOCUMENTS ) ); + + while (xResultSet->next()) + { + Reference<sdbc::XRow> xRow( xResultSet, UNO_QUERY_THROW ); + OUString title( xRow->getString( 1 /* Title */ ) ); + // xxx todo: remove workaround for tdoc + if ( title == "this_is_a_dummy_stream_just_there_as_a_workaround_for_a_temporary_limitation_of_the_storage_api_implementation" ) + continue; + if ( title == "META-INF" ) + continue; + + ::ucbhelper::Content sourceContent( + Reference<XContentAccess>( + xResultSet, UNO_QUERY_THROW )->queryContent(), + xCmdEnv, m_xComponentContext ); + + OUString mediaType( detectMediaType( sourceContent, + false /* no throw */) ); + if (!mediaType.isEmpty()) + { + ActivePackages::Data dbData; + insertToActivationLayer( + Sequence<css::beans::NamedValue>(),mediaType, sourceContent, + title, &dbData ); + + insertToActivationLayerDB( title, dbData ); + //TODO #i73136#: insertToActivationLayerDB needs id not + // title, but the whole m_activePackages.getLength()==0 + // case (i.e., document-relative deployment) currently + // does not work, anyway. + } + } + } + } + else + { + // user|share: + OSL_ASSERT( !m_activePackages.isEmpty() ); + m_activePackages_expanded = expandUnoRcUrl( m_activePackages ); + m_registrationData_expanded = expandUnoRcUrl(m_registrationData); + if (!m_readOnly) + create_folder( nullptr, m_activePackages_expanded, xCmdEnv); + + OUString dbName; + if (m_context == "user") + dbName = m_activePackages_expanded + ".pmap"; + else + { + // Create the extension data base in the user installation + create_folder( nullptr, m_registrationData_expanded, xCmdEnv); + dbName = m_registrationData_expanded + "/extensions.pmap"; + } + // The data base can always be written because it is always in the user installation + m_activePackagesDB.reset( new ActivePackages( dbName ) ); + + if (! m_readOnly && m_context != "bundled") + { + // clean up activation layer, scan for zombie temp dirs: + ActivePackages::Entries id2temp( m_activePackagesDB->getEntries() ); + + ::ucbhelper::Content tempFolder( m_activePackages_expanded, xCmdEnv, m_xComponentContext ); + Reference<sdbc::XResultSet> xResultSet( + StrTitle::createCursor (tempFolder, + ::ucbhelper::INCLUDE_DOCUMENTS_ONLY ) ); + + // get all temp directories: + std::vector<OUString> tempEntries; + std::vector<OUString> removedEntries; + while (xResultSet->next()) + { + OUString title( + Reference<sdbc::XRow>( + xResultSet, UNO_QUERY_THROW )->getString( + 1 /* Title */ ) ); + if (title.endsWith("removed", &title)) + { + //save the file name without the "removed" part + removedEntries.push_back(::rtl::Uri::encode( + title, rtl_UriCharClassPchar, + rtl_UriEncodeIgnoreEscapes, + RTL_TEXTENCODING_UTF8 ) ); + } + else + { + tempEntries.push_back( ::rtl::Uri::encode( + title, rtl_UriCharClassPchar, + rtl_UriEncodeIgnoreEscapes, + RTL_TEXTENCODING_UTF8 ) ); + } + } + + bool bShared = (m_context == "shared"); + for (const OUString & tempEntry : tempEntries) + { + const MatchTempDir match( tempEntry ); + if (std::none_of( id2temp.begin(), id2temp.end(), match )) + { + const OUString url( + makeURL(m_activePackages_expanded, tempEntry ) ); + + //In case of shared extensions, new entries are regarded as + //added extensions if there is no xxx.tmpremoved file. + if (bShared) + { + if (std::find(removedEntries.begin(), removedEntries.end(), tempEntry) == + removedEntries.end()) + { + continue; + } + else + { + //Make sure only the same user removes the extension, who + //previously unregistered it. This is avoid races if multiple instances + //of OOo are running which all have write access to the shared installation. + //For example, a user removes the extension, but keeps OOo + //running. Parts of the extension may still be loaded and used by OOo. + //Therefore the extension is only deleted the next time the extension manager is + //run after restarting OOo. While OOo is still running, another user starts OOo + //which would deleted the extension files. If the same user starts another + //instance of OOo then the lock file will prevent this. + OUString aUserName; + ::osl::Security aSecurity; + aSecurity.getUserName( aUserName ); + ucbhelper::Content remFileContent( + url + "removed", Reference<XCommandEnvironment>(), m_xComponentContext); + std::vector<sal_Int8> data = dp_misc::readFile(remFileContent); + std::string_view osData(reinterpret_cast<const char*>(data.data()), + data.size()); + OUString sData = OStringToOUString( + osData, RTL_TEXTENCODING_UTF8); + if (sData != aUserName) + continue; + } + } + // temp entry not needed anymore: + erase_path( url + "_", + Reference<XCommandEnvironment>(), + false /* no throw: ignore errors */ ); + erase_path( url, Reference<XCommandEnvironment>(), + false /* no throw: ignore errors */ ); + //delete the xxx.tmpremoved file + erase_path(url + "removed", + Reference<XCommandEnvironment>(), false); + } + } + } + } +} + + +void PackageManagerImpl::initRegistryBackends() +{ + if (!m_registryCache.isEmpty()) + create_folder( nullptr, m_registryCache, + Reference<XCommandEnvironment>(), false); + m_xRegistry.set( ::dp_registry::create( + m_context, m_registryCache, + m_xComponentContext ) ); +} + +namespace { + +osl::FileBase::RC createDirectory(OUString const & url) { + auto e = osl::Directory::create(url); + if (e != osl::FileBase::E_NOENT) { + return e; + } + INetURLObject o(url); + if (!o.removeSegment()) { + return osl::FileBase::E_INVAL; // anything but E_None/E_EXIST + } + e = createDirectory(o.GetMainURL(INetURLObject::DecodeMechanism::NONE)); + if (e != osl::FileBase::E_None && e != osl::FileBase::E_EXIST) { + return e; + } + return osl::Directory::create(url); +} + +bool isMacroURLReadOnly( const OUString &rMacro ) +{ + OUString aDirURL( rMacro ); + ::rtl::Bootstrap::expandMacros( aDirURL ); + + ::osl::FileBase::RC aErr = createDirectory( aDirURL ); + if ( aErr == ::osl::FileBase::E_None ) + return false; // it will be writeable + if ( aErr != ::osl::FileBase::E_EXIST ) + return true; // some serious problem creating it + + bool bError; + sal_uInt64 nWritten = 0; + OUString aFileURL( aDirURL + "/stamp.sys" ); + ::osl::File aFile( aFileURL ); + + bError = aFile.open( osl_File_OpenFlag_Read | + osl_File_OpenFlag_Write | + osl_File_OpenFlag_Create ) != ::osl::FileBase::E_None; + if (!bError) + bError = aFile.write( "1", 1, nWritten ) != ::osl::FileBase::E_None; + if (aFile.close() != ::osl::FileBase::E_None) + bError = true; + if (osl::File::remove( aFileURL ) != ::osl::FileBase::E_None) + bError = true; + + SAL_INFO( + "desktop.deployment", + "local url '" << rMacro << "' -> '" << aFileURL << "' " + << (bError ? "is" : "is not") << " readonly\n"); + return bError; +} + +} + +Reference<deployment::XPackageManager> PackageManagerImpl::create( + Reference<XComponentContext> const & xComponentContext, + OUString const & context ) +{ + rtl::Reference<PackageManagerImpl> that = new PackageManagerImpl( + xComponentContext, context ); + + OUString logFile, stamp; + if ( context == "user" ) { + that->m_activePackages = "vnd.sun.star.expand:$UNO_USER_PACKAGES_CACHE/uno_packages"; + that->m_registrationData = "vnd.sun.star.expand:$UNO_USER_PACKAGES_CACHE"; + that->m_registryCache = "vnd.sun.star.expand:$UNO_USER_PACKAGES_CACHE/registry"; + logFile = "$UNO_USER_PACKAGES_CACHE/log.txt"; + //We use the extension .sys for the file because on Windows Vista a sys + //(as well as exe and dll) file + //will not be written in the VirtualStore. For example if the process has no + //admin right once cannot write to the %programfiles% folder. However, when + //virtualization is used, the file will be written into the VirtualStore and + //it appears as if one could write to %programfiles%. When we test for write + //access to the office/shared folder for shared extensions then this typically + //fails because a normal user typically cannot write to this folder. However, + //using virtualization it appears that he/she can. Then a shared extension can + //be installed but is only visible for the user (because the extension is in + //the virtual store). + stamp = "$UNO_USER_PACKAGES_CACHE"; + } + else if ( context == "shared" ) { + that->m_activePackages = "vnd.sun.star.expand:$UNO_SHARED_PACKAGES_CACHE/uno_packages"; + that->m_registrationData = "vnd.sun.star.expand:$SHARED_EXTENSIONS_USER"; + that->m_registryCache = "vnd.sun.star.expand:$SHARED_EXTENSIONS_USER/registry"; + logFile = "$SHARED_EXTENSIONS_USER/log.txt"; +#if !HAVE_FEATURE_READONLY_INSTALLSET + // The "shared" extensions are read-only when we have a + // read-only installset. + stamp = "$UNO_SHARED_PACKAGES_CACHE"; +#endif + } + else if ( context == "bundled" ) { + that->m_activePackages = "vnd.sun.star.expand:$BUNDLED_EXTENSIONS"; + that->m_registrationData = "vnd.sun.star.expand:$BUNDLED_EXTENSIONS_USER"; + that->m_registryCache = "vnd.sun.star.expand:$BUNDLED_EXTENSIONS_USER/registry"; + logFile = "$BUNDLED_EXTENSIONS_USER/log.txt"; + //No stamp file. We assume that bundled is always readonly. It must not be + //modified from ExtensionManager but only by the installer + } + else if ( context == "tmp" ) { + that->m_activePackages = "vnd.sun.star.expand:$TMP_EXTENSIONS/extensions"; + that->m_registrationData = "vnd.sun.star.expand:$TMP_EXTENSIONS"; + that->m_registryCache = "vnd.sun.star.expand:$TMP_EXTENSIONS/registry"; + stamp = "$TMP_EXTENSIONS"; + } + else if (context == "bak") { + that->m_activePackages = "vnd.sun.star.expand:$BAK_EXTENSIONS/extensions"; + that->m_registrationData = "vnd.sun.star.expand:$BAK_EXTENSIONS"; + that->m_registryCache = "vnd.sun.star.expand:$BAK_EXTENSIONS/registry"; + stamp = "$BAK_EXTENSIONS"; + } + + else if (! context.match("vnd.sun.star.tdoc:/")) { + throw lang::IllegalArgumentException( + "invalid context given: " + context, + Reference<XInterface>(), static_cast<sal_Int16>(-1) ); + } + + Reference<XCommandEnvironment> xCmdEnv; + + try { + // There is no stamp for the bundled folder: + if (!stamp.isEmpty()) + that->m_readOnly = isMacroURLReadOnly( stamp ); + + if (!that->m_readOnly && !logFile.isEmpty()) + { + // Initialize logger which will be used in ProgressLogImpl (created below) + rtl::Bootstrap::expandMacros(logFile); + comphelper::EventLogger logger(xComponentContext, "unopkg"); + const Reference<XLogger> xLogger(logger.getLogger()); + Reference<XLogFormatter> xLogFormatter(SimpleTextFormatter::create(xComponentContext)); + Sequence < beans::NamedValue > aSeq2 { { "Formatter", Any(xLogFormatter) }, {"FileURL", Any(logFile)} }; + Reference<XLogHandler> xFileHandler(css::logging::FileHandler::createWithSettings(xComponentContext, aSeq2)); + xFileHandler->setLevel(LogLevel::WARNING); + xLogger->addLogHandler(xFileHandler); + + that->m_xLogFile.set( + that->m_xComponentContext->getServiceManager() + ->createInstanceWithArgumentsAndContext( + "com.sun.star.comp.deployment.ProgressLog", + Sequence<Any>(), + that->m_xComponentContext ), + UNO_QUERY_THROW ); + xCmdEnv.set( new CmdEnvWrapperImpl( xCmdEnv, that->m_xLogFile ) ); + } + + that->initRegistryBackends(); + that->initActivationLayer( xCmdEnv ); + + return that; + + } + catch (const RuntimeException &) { + throw; + } + catch (const Exception & e) { + Any exc( ::cppu::getCaughtException() ); + throw lang::WrappedTargetRuntimeException( + ("[context=\"" + context + "\"] caught unexpected " + + exc.getValueType().getTypeName() + ": " + e.Message), + Reference<XInterface>(), exc ); + } +} + + +PackageManagerImpl::~PackageManagerImpl() +{ +} + + +void PackageManagerImpl::fireModified() +{ + ::cppu::OInterfaceContainerHelper * pContainer = rBHelper.getContainer( + cppu::UnoType<util::XModifyListener>::get() ); + if (pContainer != nullptr) { + pContainer->forEach<util::XModifyListener>( + [this] (uno::Reference<util::XModifyListener> const& xListener) + { return xListener->modified(lang::EventObject(static_cast<OWeakObject *>(this))); }); + } +} + + +void PackageManagerImpl::disposing() +{ + try { +// // xxx todo: guarding? +// ::osl::MutexGuard guard( getMutex() ); + try_dispose( m_xLogFile ); + m_xLogFile.clear(); + try_dispose( m_xRegistry ); + m_xRegistry.clear(); + m_activePackagesDB.reset(); + m_xComponentContext.clear(); + + t_pm_helper::disposing(); + + } + catch (const RuntimeException &) { + throw; + } + catch (const Exception &) { + Any exc( ::cppu::getCaughtException() ); + throw lang::WrappedTargetRuntimeException( + "caught unexpected exception while disposing...", + static_cast<OWeakObject *>(this), exc ); + } +} + +// XComponent + +void PackageManagerImpl::dispose() +{ + //Do not call check here. We must not throw an exception here if the object + //is being disposed or is already disposed. See com.sun.star.lang.XComponent + WeakComponentImplHelperBase::dispose(); +} + + +void PackageManagerImpl::addEventListener( + Reference<lang::XEventListener> const & xListener ) +{ + //Do not call check here. We must not throw an exception here if the object + //is being disposed or is already disposed. See com.sun.star.lang.XComponent + WeakComponentImplHelperBase::addEventListener( xListener ); +} + + +void PackageManagerImpl::removeEventListener( + Reference<lang::XEventListener> const & xListener ) +{ + //Do not call check here. We must not throw an exception here if the object + //is being disposed or is already disposed. See com.sun.star.lang.XComponent + WeakComponentImplHelperBase::removeEventListener( xListener ); +} + +// XPackageManager + +OUString PackageManagerImpl::getContext() +{ + check(); + return m_context; +} + + +Sequence< Reference<deployment::XPackageTypeInfo> > +PackageManagerImpl::getSupportedPackageTypes() +{ + OSL_ASSERT( m_xRegistry.is() ); + return m_xRegistry->getSupportedPackageTypes(); +} + + +Reference<task::XAbortChannel> PackageManagerImpl::createAbortChannel() +{ + check(); + return new AbortChannel; +} + +// XModifyBroadcaster + +void PackageManagerImpl::addModifyListener( + Reference<util::XModifyListener> const & xListener ) +{ + check(); + rBHelper.addListener( cppu::UnoType<decltype(xListener)>::get(), xListener ); +} + + +void PackageManagerImpl::removeModifyListener( + Reference<util::XModifyListener> const & xListener ) +{ + check(); + rBHelper.removeListener( cppu::UnoType<decltype(xListener)>::get(), xListener ); +} + + +OUString PackageManagerImpl::detectMediaType( + ::ucbhelper::Content const & ucbContent_, bool throw_exc ) +{ + ::ucbhelper::Content ucbContent(ucbContent_); + OUString url( ucbContent.getURL() ); + OUString mediaType; + if (url.match( "vnd.sun.star.tdoc:" ) || url.match( "vnd.sun.star.pkg:" )) + { + try { + ucbContent.getPropertyValue( "MediaType" ) >>= mediaType; + } + catch (const beans::UnknownPropertyException &) { + } + OSL_ENSURE( !mediaType.isEmpty(), "### no media-type?!" ); + } + if (mediaType.isEmpty()) + { + try { + Reference<deployment::XPackage> xPackage( + m_xRegistry->bindPackage( + url, OUString(), false, OUString(), ucbContent.getCommandEnvironment() ) ); + const Reference<deployment::XPackageTypeInfo> xPackageType( + xPackage->getPackageType() ); + OSL_ASSERT( xPackageType.is() ); + if (xPackageType.is()) + mediaType = xPackageType->getMediaType(); + } + catch (const lang::IllegalArgumentException &) { + if (throw_exc) + throw; + css::uno::Any ex( cppu::getCaughtException() ); + SAL_WARN( "desktop", exceptionToString(ex) ); + } + } + return mediaType; +} + + +OUString PackageManagerImpl::insertToActivationLayer( + Sequence<beans::NamedValue> const & properties, + OUString const & mediaType, ::ucbhelper::Content const & sourceContent_, + OUString const & title, ActivePackages::Data * dbData ) +{ + ::ucbhelper::Content sourceContent(sourceContent_); + Reference<XCommandEnvironment> xCmdEnv( + sourceContent.getCommandEnvironment() ); + + OUString tempEntry = ::utl::CreateTempURL(&m_activePackages_expanded, false); + tempEntry = tempEntry.copy(tempEntry.lastIndexOf('/') + 1); + OUString destFolder = makeURL( m_activePackages, tempEntry) + "_"; + + // prepare activation folder: + ::ucbhelper::Content destFolderContent; + create_folder( &destFolderContent, destFolder, xCmdEnv ); + + // copy content into activation temp dir: + if (mediaType.matchIgnoreAsciiCase("application/vnd.sun.star.package-bundle") || + // xxx todo: more sophisticated parsing + mediaType.matchIgnoreAsciiCase("application/vnd.sun.star.legacy-package-bundle")) + { + // inflate content: + OUStringBuffer buf; + if (!sourceContent.isFolder()) + { + buf.append( "vnd.sun.star.zip://" ); + buf.append( ::rtl::Uri::encode( sourceContent.getURL(), + rtl_UriCharClassRegName, + rtl_UriEncodeIgnoreEscapes, + RTL_TEXTENCODING_UTF8 ) ); + } + else + { + //Folder. No need to unzip, just copy + buf.append(sourceContent.getURL()); + } + buf.append( '/' ); + sourceContent = ::ucbhelper::Content( + buf.makeStringAndClear(), xCmdEnv, m_xComponentContext ); + } + destFolderContent.transferContent( + sourceContent, ::ucbhelper::InsertOperation::Copy, + title, NameClash::OVERWRITE ); + + + // write to DB: + //bundled extensions should only be added by the synchronizeAddedExtensions + //functions. Moreover, there is no "temporary folder" for bundled extensions. + OSL_ASSERT(!(m_context == "bundled")); + OUString sFolderUrl = makeURLAppendSysPathSegment(destFolderContent.getURL(), title); + DescriptionInfoset info = + dp_misc::getDescriptionInfoset(sFolderUrl); + dbData->temporaryName = tempEntry; + dbData->fileName = title; + dbData->mediaType = mediaType; + dbData->version = info.getVersion(); + + //No write the properties file next to the extension + ExtensionProperties props(sFolderUrl, properties, xCmdEnv, m_xComponentContext); + props.write(); + return destFolder; +} + + +void PackageManagerImpl::insertToActivationLayerDB( + OUString const & id, ActivePackages::Data const & dbData ) +{ + //access to the database must be guarded. See removePackage + const ::osl::MutexGuard guard( m_aMutex ); + m_activePackagesDB->put( id, dbData ); +} + + +/* The function returns true if there is an extension with the same id already + installed which needs to be uninstalled, before the new extension can be installed. +*/ +bool PackageManagerImpl::isInstalled( + Reference<deployment::XPackage> const & package) +{ + OUString id(dp_misc::getIdentifier(package)); + OUString fn(package->getName()); + bool bInstalled = false; + if (m_activePackagesDB->has( id, fn )) + { + bInstalled = true; + } + return bInstalled; +} + +// XPackageManager + +Reference<deployment::XPackage> PackageManagerImpl::importExtension( + Reference<deployment::XPackage> const & extension, + Reference<task::XAbortChannel> const & xAbortChannel, + Reference<XCommandEnvironment> const & xCmdEnv_ ) +{ + return addPackage(extension->getURL(), Sequence<beans::NamedValue>(), + OUString(), xAbortChannel, xCmdEnv_); +} + +/* The function adds an extension but does not register it!!! + It may not do any user interaction. This is done in XExtensionManager::addExtension +*/ +Reference<deployment::XPackage> PackageManagerImpl::addPackage( + OUString const & url, + css::uno::Sequence<css::beans::NamedValue> const & properties, + OUString const & mediaType_, + Reference<task::XAbortChannel> const & xAbortChannel, + Reference<XCommandEnvironment> const & xCmdEnv_ ) +{ + check(); + if (m_readOnly) + { + OUString message; + if (m_context == "shared") + message = "You need write permissions to install a shared extension!"; + else + message = "You need write permissions to install this extension!"; + throw deployment::DeploymentException( + message, static_cast<OWeakObject *>(this), Any() ); + } + Reference<XCommandEnvironment> xCmdEnv; + if (m_xLogFile.is()) + xCmdEnv.set( new CmdEnvWrapperImpl( xCmdEnv_, m_xLogFile ) ); + else + xCmdEnv.set( xCmdEnv_ ); + + try { + ::ucbhelper::Content sourceContent; + (void)create_ucb_content( &sourceContent, url, xCmdEnv ); // throws exc + const OUString title( StrTitle::getTitle( sourceContent ) ); + const OUString title_enc( ::rtl::Uri::encode( + title, rtl_UriCharClassPchar, + rtl_UriEncodeIgnoreEscapes, + RTL_TEXTENCODING_UTF8 ) ); + OUString destFolder; + + OUString mediaType(mediaType_); + if (mediaType.isEmpty()) + mediaType = detectMediaType( sourceContent ); + + Reference<deployment::XPackage> xPackage; + // copy file: + progressUpdate( + DpResId(RID_STR_COPYING_PACKAGE) + title, xCmdEnv ); + if (m_activePackages.isEmpty()) + { + ::ucbhelper::Content docFolderContent; + create_folder( &docFolderContent, m_context, xCmdEnv ); + // copy into document, first: + docFolderContent.transferContent( + sourceContent, ::ucbhelper::InsertOperation::Copy, + OUString(), + NameClash::ASK /* xxx todo: ASK not needed? */); + // set media-type: + ::ucbhelper::Content docContent( + makeURL( m_context, title_enc ), xCmdEnv, m_xComponentContext ); + //TODO #i73136#: using title instead of id can lead to + // clashes, but the whole m_activePackages.getLength()==0 + // case (i.e., document-relative deployment) currently does + // not work, anyway. + docContent.setPropertyValue("MediaType", Any(mediaType) ); + + // xxx todo: obsolete in the future + try { + docFolderContent.executeCommand( "flush", Any() ); + } + catch (const UnsupportedCommandException &) { + } + } + ActivePackages::Data dbData; + destFolder = insertToActivationLayer( + properties, mediaType, sourceContent, title, &dbData ); + + + // bind activation package: + //Because every shared/user extension will be unpacked in a folder, + //which was created with a unique name we will always have two different + //XPackage objects, even if the second extension is the same. + //Therefore bindPackage does not need a guard here. + xPackage = m_xRegistry->bindPackage( + makeURL( destFolder, title_enc ), mediaType, false, OUString(), xCmdEnv ); + + OSL_ASSERT( xPackage.is() ); + if (xPackage.is()) + { + bool install = false; + try + { + OUString const id = dp_misc::getIdentifier( xPackage ); + + std::unique_lock g(m_addMutex); + if (isInstalled(xPackage)) + { + //Do not guard the complete function with the getMutex + removePackage(id, xPackage->getName(), xAbortChannel, + xCmdEnv); + } + install = true; + insertToActivationLayerDB(id, dbData); + } + catch (...) + { + deletePackageFromCache( xPackage, destFolder ); + throw; + } + if (!install) + { + deletePackageFromCache( xPackage, destFolder ); + } + //ToDo: We should notify only if the extension is registered + fireModified(); + } + return xPackage; + } + catch (const RuntimeException &) { + throw; + } + catch (const CommandFailedException & exc) { + logIntern( Any(exc) ); + throw; + } + catch (const CommandAbortedException & exc) { + logIntern( Any(exc) ); + throw; + } + catch (const deployment::DeploymentException & exc) { + logIntern( Any(exc) ); + throw; + } + catch (const Exception &) { + Any exc( ::cppu::getCaughtException() ); + logIntern( exc ); + throw deployment::DeploymentException( + DpResId(RID_STR_ERROR_WHILE_ADDING) + url, + static_cast<OWeakObject *>(this), exc ); + } +} +void PackageManagerImpl::deletePackageFromCache( + Reference<deployment::XPackage> const & xPackage, + OUString const & destFolder) +{ + try_dispose( xPackage ); + + //we remove the package from the uno cache + //no service from the package may be loaded at this time!!! + erase_path( destFolder, Reference<XCommandEnvironment>(), + false /* no throw: ignore errors */ ); + //rm last character '_' + OUString url = destFolder.copy(0, destFolder.getLength() - 1); + erase_path( url, Reference<XCommandEnvironment>(), + false /* no throw: ignore errors */ ); + +} + +void PackageManagerImpl::removePackage( + OUString const & id, OUString const & fileName, + Reference<task::XAbortChannel> const & /*xAbortChannel*/, + Reference<XCommandEnvironment> const & xCmdEnv_ ) +{ + check(); + + Reference<XCommandEnvironment> xCmdEnv; + if (m_xLogFile.is()) + xCmdEnv.set( new CmdEnvWrapperImpl( xCmdEnv_, m_xLogFile ) ); + else + xCmdEnv.set( xCmdEnv_ ); + + try { + Reference<deployment::XPackage> xPackage; + { + const ::osl::MutexGuard guard(m_aMutex); + //Check if this extension exist and throw an IllegalArgumentException + //if it does not + //If the files of the extension are already removed, or there is a + //different extension at the same place, for example after updating the + //extension, then the returned object is that which uses the database data. + xPackage = getDeployedPackage_(id, fileName, xCmdEnv ); + + + //Because the extension is only removed the next time the extension + //manager runs after restarting OOo, we need to indicate that a + //shared extension was "deleted". When a user starts OOo, then it + //will check if something changed in the shared repository. Based on + //the flag file it will then recognize, that the extension was + //deleted and can then update the extension database of the shared + //extensions in the user installation. + if ( xPackage.is() && !m_readOnly && !xPackage->isRemoved() && (m_context == "shared")) + { + ActivePackages::Data val; + m_activePackagesDB->get( & val, id, fileName); + OSL_ASSERT(!val.temporaryName.isEmpty()); + OUString url(makeURL(m_activePackages_expanded, + val.temporaryName + "removed")); + ::ucbhelper::Content contentRemoved(url, xCmdEnv, m_xComponentContext); + OUString aUserName; + ::osl::Security aSecurity; + aSecurity.getUserName( aUserName ); + + OString stamp = OUStringToOString(aUserName, RTL_TEXTENCODING_UTF8); + Reference<css::io::XInputStream> xData( + ::xmlscript::createInputStream( + reinterpret_cast<sal_Int8 const *>(stamp.getStr()), + stamp.getLength() ) ); + contentRemoved.writeStream( xData, true /* replace existing */ ); + } + m_activePackagesDB->erase( id, fileName ); // to be removed upon next start + //remove any cached data hold by the backend + m_xRegistry->packageRemoved(xPackage->getURL(), xPackage->getPackageType()->getMediaType()); + } + try_dispose( xPackage ); + + fireModified(); + } + catch (const RuntimeException &) { + throw; + } + catch (const CommandFailedException & exc) { + logIntern( Any(exc) ); + throw; + } + catch (const CommandAbortedException & exc) { + logIntern( Any(exc) ); + throw; + } + catch (const deployment::DeploymentException & exc) { + logIntern( Any(exc) ); + throw; + } + catch (const Exception &) { + Any exc( ::cppu::getCaughtException() ); + logIntern( exc ); + throw deployment::DeploymentException( + DpResId(RID_STR_ERROR_WHILE_REMOVING) + id, + static_cast<OWeakObject *>(this), exc ); + } +} + + +OUString PackageManagerImpl::getDeployPath( ActivePackages::Data const & data ) +{ + OUStringBuffer buf( data.temporaryName ); + //The bundled extensions are not contained in an additional folder + //with a unique name. data.temporaryName contains already the + //UTF8 encoded folder name. See PackageManagerImpl::synchronize + if (m_context != "bundled") + { + buf.append( "_/" + + ::rtl::Uri::encode( data.fileName, rtl_UriCharClassPchar, + rtl_UriEncodeIgnoreEscapes, + RTL_TEXTENCODING_UTF8 ) ); + } + return makeURL( m_activePackages, buf.makeStringAndClear() ); +} + + +Reference<deployment::XPackage> PackageManagerImpl::getDeployedPackage_( + OUString const & id, OUString const & fileName, + Reference<XCommandEnvironment> const & xCmdEnv ) +{ + ActivePackages::Data val; + if (m_activePackagesDB->get( &val, id, fileName )) + { + return getDeployedPackage_( id, val, xCmdEnv ); + } + throw lang::IllegalArgumentException( + DpResId(RID_STR_NO_SUCH_PACKAGE) + id, + static_cast<OWeakObject *>(this), static_cast<sal_Int16>(-1) ); +} + + +Reference<deployment::XPackage> PackageManagerImpl::getDeployedPackage_( + std::u16string_view id, ActivePackages::Data const & data, + Reference<XCommandEnvironment> const & xCmdEnv, bool ignoreAlienPlatforms ) +{ + if (ignoreAlienPlatforms) + { + OUString type, subType; + INetContentTypeParameterList params; + if (INetContentTypes::parse( data.mediaType, type, subType, ¶ms )) + { + auto const iter = params.find("platform"_ostr); + if (iter != params.end() && !platform_fits(iter->second.m_sValue)) + throw lang::IllegalArgumentException( + DpResId(RID_STR_NO_SUCH_PACKAGE) + id, + static_cast<OWeakObject *>(this), + static_cast<sal_Int16>(-1) ); + } + } + Reference<deployment::XPackage> xExtension; + try + { + //Ignore extensions where XPackage::checkPrerequisites failed. + //They must not be usable for this user. + if (data.failedPrerequisites == "0") + { + xExtension = m_xRegistry->bindPackage( + getDeployPath( data ), data.mediaType, false, OUString(), xCmdEnv ); + } + } + catch (const deployment::InvalidRemovedParameterException& e) + { + xExtension = e.Extension; + } + return xExtension; +} + + +Sequence< Reference<deployment::XPackage> > +PackageManagerImpl::getDeployedPackages_( + Reference<XCommandEnvironment> const & xCmdEnv ) +{ + std::vector< Reference<deployment::XPackage> > packages; + ActivePackages::Entries id2temp( m_activePackagesDB->getEntries() ); + for (auto const& elem : id2temp) + { + if (elem.second.failedPrerequisites != "0") + continue; + try { + packages.push_back( + getDeployedPackage_( + elem.first, elem.second, xCmdEnv, + true /* xxx todo: think of GUI: + ignore other platforms than the current one */ ) ); + } + catch (const lang::IllegalArgumentException &) { + // ignore + TOOLS_WARN_EXCEPTION( "desktop", "" ); + } + catch (const deployment::DeploymentException&) { + // ignore + TOOLS_WARN_EXCEPTION( "desktop", "" ); + } + } + return comphelper::containerToSequence(packages); +} + + +Reference<deployment::XPackage> PackageManagerImpl::getDeployedPackage( + OUString const & id, OUString const & fileName, + Reference<XCommandEnvironment> const & xCmdEnv_ ) +{ + check(); + Reference<XCommandEnvironment> xCmdEnv; + if (m_xLogFile.is()) + xCmdEnv.set( new CmdEnvWrapperImpl( xCmdEnv_, m_xLogFile ) ); + else + xCmdEnv.set( xCmdEnv_ ); + + try { + const ::osl::MutexGuard guard( m_aMutex ); + return getDeployedPackage_( id, fileName, xCmdEnv ); + } + catch (const RuntimeException &) { + throw; + } + catch (const CommandFailedException & exc) { + logIntern( Any(exc) ); + throw; + } + catch (const deployment::DeploymentException & exc) { + logIntern( Any(exc) ); + throw; + } + catch (const Exception &) { + Any exc( ::cppu::getCaughtException() ); + logIntern( exc ); + throw deployment::DeploymentException( + // ought never occur... + "error while accessing deployed package: " + id, + static_cast<OWeakObject *>(this), exc ); + } +} + + +Sequence< Reference<deployment::XPackage> > +PackageManagerImpl::getDeployedPackages( + Reference<task::XAbortChannel> const &, + Reference<XCommandEnvironment> const & xCmdEnv_ ) +{ + check(); + Reference<XCommandEnvironment> xCmdEnv; + if (m_xLogFile.is()) + xCmdEnv.set( new CmdEnvWrapperImpl( xCmdEnv_, m_xLogFile ) ); + else + xCmdEnv.set( xCmdEnv_ ); + + try { + const ::osl::MutexGuard guard( m_aMutex ); + return getDeployedPackages_( xCmdEnv ); + } + catch (const RuntimeException &) { + throw; + } + catch (const CommandFailedException & exc) { + logIntern( Any(exc) ); + throw; + } + catch (const CommandAbortedException & exc) { + logIntern( Any(exc) ); + throw; + } + catch (const deployment::DeploymentException & exc) { + logIntern( Any(exc) ); + throw; + } + catch (const Exception &) { + Any exc( ::cppu::getCaughtException() ); + logIntern( exc ); + throw deployment::DeploymentException( + // ought never occur... + "error while getting all deployed packages: " + m_context, + static_cast<OWeakObject *>(this), exc ); + } +} + + +//ToDo: the function must not call registerPackage, do this in +//XExtensionManager.reinstallDeployedExtensions +void PackageManagerImpl::reinstallDeployedPackages( + sal_Bool force, Reference<task::XAbortChannel> const & /*xAbortChannel*/, + Reference<XCommandEnvironment> const & xCmdEnv_ ) +{ + check(); + if (!force && office_is_running()) + throw RuntimeException( + "You must close any running Office process before reinstalling packages!", + static_cast<OWeakObject *>(this) ); + + Reference<XCommandEnvironment> xCmdEnv; + if (m_xLogFile.is()) + xCmdEnv.set( new CmdEnvWrapperImpl( xCmdEnv_, m_xLogFile ) ); + else + xCmdEnv.set( xCmdEnv_ ); + + try { + ProgressLevel progress( + xCmdEnv, "Reinstalling all deployed packages..." ); + + try_dispose( m_xRegistry ); + m_xRegistry.clear(); + if (!m_registryCache.isEmpty()) + erase_path( m_registryCache, xCmdEnv ); + initRegistryBackends(); + Reference<util::XUpdatable> xUpdatable( m_xRegistry, UNO_QUERY ); + if (xUpdatable.is()) + xUpdatable->update(); + + //registering is done by the ExtensionManager service. + } + catch (const RuntimeException &) { + throw; + } + catch (const CommandFailedException & exc) { + logIntern( Any(exc) ); + throw; + } + catch (const CommandAbortedException & exc) { + logIntern( Any(exc) ); + throw; + } + catch (const deployment::DeploymentException & exc) { + logIntern( Any(exc) ); + throw; + } + catch (const Exception &) { + Any exc( ::cppu::getCaughtException() ); + logIntern( exc ); + throw deployment::DeploymentException( + "Error while reinstalling all previously deployed packages of context " + m_context, + static_cast<OWeakObject *>(this), exc ); + } +} + + +sal_Bool SAL_CALL PackageManagerImpl::isReadOnly( ) +{ + return m_readOnly; +} +bool PackageManagerImpl::synchronizeRemovedExtensions( + Reference<task::XAbortChannel> const & xAbortChannel, + Reference<css::ucb::XCommandEnvironment> const & xCmdEnv) +{ + + //find all which are in the extension data base but which + //are removed already. + OSL_ASSERT(!(m_context == "user")); + bool bModified = false; + ActivePackages::Entries id2temp( m_activePackagesDB->getEntries() ); + + bool bShared = (m_context == "shared"); + + for (auto const& elem : id2temp) + { + try + { + //Get the URL to the extensions folder, first make the url for the + //shared repository including the temporary name + OUString url = makeURL(m_activePackages, elem.second.temporaryName); + if (bShared) + url = makeURLAppendSysPathSegment( Concat2View(url + "_"), elem.second.fileName); + + bool bRemoved = false; + //Check if the URL to the extension is still the same + ::ucbhelper::Content contentExtension; + + if (!create_ucb_content( + &contentExtension, url, + Reference<XCommandEnvironment>(), false)) + { + bRemoved = true; + } + + //The folder is in the extension database, but it can still be deleted. + //look for the xxx.tmpremoved file + //There can also be the case that a different extension was installed + //in a "temp" folder with name that is already used. + if (!bRemoved && bShared) + { + ::ucbhelper::Content contentRemoved; + + if (create_ucb_content( + &contentRemoved, + m_activePackages_expanded + "/" + + elem.second.temporaryName + "removed", + Reference<XCommandEnvironment>(), false)) + { + bRemoved = true; + } + } + + if (!bRemoved) + { + //There may be another extensions at the same place + dp_misc::DescriptionInfoset infoset = + dp_misc::getDescriptionInfoset(url); + OSL_ENSURE(infoset.hasDescription() && infoset.getIdentifier(), + "Extension Manager: bundled and shared extensions " + "must have an identifier and a version"); + if (infoset.hasDescription() && + infoset.getIdentifier() && + ( elem.first != *(infoset.getIdentifier()) + || elem.second.version != infoset.getVersion())) + { + bRemoved = true; + } + + } + if (bRemoved) + { + Reference<deployment::XPackage> xPackage = m_xRegistry->bindPackage( + url, elem.second.mediaType, true, elem.first, xCmdEnv ); + OSL_ASSERT(xPackage.is()); //Even if the files are removed, we must get the object. + xPackage->revokePackage(true, xAbortChannel, xCmdEnv); + removePackage(xPackage->getIdentifier().Value, xPackage->getName(), + xAbortChannel, xCmdEnv); + bModified = true; + } + } + catch( const uno::Exception & ) + { + TOOLS_WARN_EXCEPTION("desktop.deployment", ""); + } + } + return bModified; +} + + +bool PackageManagerImpl::synchronizeAddedExtensions( + Reference<task::XAbortChannel> const & xAbortChannel, + Reference<css::ucb::XCommandEnvironment> const & xCmdEnv) +{ + bool bModified = false; + OSL_ASSERT(!(m_context == "user")); + + ActivePackages::Entries id2temp( m_activePackagesDB->getEntries() ); + //check if the folder exist at all. The shared extension folder + //may not exist for a normal user. + bool bOk=true; + try + { + bOk = create_ucb_content( + nullptr, m_activePackages_expanded, Reference<css::ucb::XCommandEnvironment>(), false); + } + catch (const css::ucb::ContentCreationException&) + { + bOk = false; + } + + if (!bOk) + return bModified; + + ::ucbhelper::Content tempFolder( m_activePackages_expanded, xCmdEnv, m_xComponentContext ); + Reference<sdbc::XResultSet> xResultSet( + StrTitle::createCursor( tempFolder, + ::ucbhelper::INCLUDE_FOLDERS_ONLY ) ); + + while (xResultSet->next()) + { + try + { + OUString title( + Reference<sdbc::XRow>( + xResultSet, UNO_QUERY_THROW )->getString( + 1 /* Title */ ) ); + //The temporary folders of user and shared have an '_' at then end. + //But the name in ActivePackages.temporaryName is saved without. + OUString title2 = title; + bool bShared = (m_context == "shared"); + if (bShared) + { + OSL_ASSERT(title2.endsWith("_")); + title2 = title2.copy(0, title2.getLength() -1); + } + OUString titleEncoded = ::rtl::Uri::encode( + title2, rtl_UriCharClassPchar, + rtl_UriEncodeIgnoreEscapes, + RTL_TEXTENCODING_UTF8); + + //It is sufficient to check for the folder name, because when the administrator + //installed the extension it was already checked if there is one with the + //same identifier. + const MatchTempDir match(titleEncoded); + if (std::none_of( id2temp.begin(), id2temp.end(), match )) + { + + // The folder was not found in the data base, so it must be + // an added extension + OUString url(m_activePackages_expanded + "/" + titleEncoded); + OUString sExtFolder; + if (bShared) //that is, shared + { + //Check if the extension was not "deleted" already which is indicated + //by a xxx.tmpremoved file + ::ucbhelper::Content contentRemoved; + if (create_ucb_content(&contentRemoved, url + "removed", + Reference<XCommandEnvironment>(), false)) + continue; + sExtFolder = getExtensionFolder( + m_activePackages_expanded + "/" + titleEncoded + "_", + xCmdEnv, m_xComponentContext); + url = makeURLAppendSysPathSegment(m_activePackages_expanded, title); + url = makeURLAppendSysPathSegment(url, sExtFolder); + } + Reference<deployment::XPackage> xPackage = m_xRegistry->bindPackage( + url, OUString(), false, OUString(), xCmdEnv ); + if (xPackage.is()) + { + OUString id = dp_misc::getIdentifier( xPackage ); + + //Prepare the database entry + ActivePackages::Data dbData; + + dbData.temporaryName = titleEncoded; + if (bShared) + dbData.fileName = sExtFolder; + else + dbData.fileName = title; + dbData.mediaType = xPackage->getPackageType()->getMediaType(); + dbData.version = xPackage->getVersion(); + SAL_WARN_IF( + dbData.version.isEmpty(), "desktop.deployment", + "bundled/shared extension " << id << " at <" << url + << "> has no explicit version"); + + //We provide a special command environment that will prevent + //showing a license if simple-license/@accept-by = "admin" + //It will also prevent showing the license for bundled extensions + //which is not supported. + OSL_ASSERT(!(m_context == "user")); + + // shall the license be suppressed? + DescriptionInfoset info = + dp_misc::getDescriptionInfoset(url); + ::std::optional<dp_misc::SimpleLicenseAttributes> + attr = info.getSimpleLicenseAttributes(); + ExtensionProperties props(url, xCmdEnv, m_xComponentContext); + bool bNoLicense = false; + if (attr && attr->suppressIfRequired && props.isSuppressedLicense()) + bNoLicense = true; + + Reference<ucb::XCommandEnvironment> licCmdEnv( + new LicenseCommandEnv(xCmdEnv->getInteractionHandler(), + bNoLicense, m_context)); + sal_Int32 failedPrereq = xPackage->checkPrerequisites( + xAbortChannel, licCmdEnv, false); + //Remember that this failed. For example, the user + //could have declined the license. Then the next time the + //extension folder is investigated we do not want to + //try to install the extension again. + dbData.failedPrerequisites = OUString::number(failedPrereq); + insertToActivationLayerDB(id, dbData); + bModified = true; + } + } + } + catch (const uno::Exception &) + { + // Looks like exceptions being caught here is not an uncommon case. + TOOLS_WARN_EXCEPTION("desktop.deployment", ""); + } + } + return bModified; +} + +sal_Bool PackageManagerImpl::synchronize( + Reference<task::XAbortChannel> const & xAbortChannel, + Reference<css::ucb::XCommandEnvironment> const & xCmdEnv) +{ + check(); + bool bModified = false; + if (m_context == "user") + return bModified; + bModified |= + synchronizeRemovedExtensions(xAbortChannel, xCmdEnv); + bModified |= synchronizeAddedExtensions(xAbortChannel, xCmdEnv); + + return bModified; +} + +Sequence< Reference<deployment::XPackage> > PackageManagerImpl::getExtensionsWithUnacceptedLicenses( + Reference<ucb::XCommandEnvironment> const & xCmdEnv) +{ + std::vector<Reference<deployment::XPackage> > vec; + + try + { + const ::osl::MutexGuard guard( m_aMutex ); + // clean up activation layer, scan for zombie temp dirs: + ActivePackages::Entries id2temp( m_activePackagesDB->getEntries() ); + + bool bShared = (m_context == "shared"); + + for (auto const& elem : id2temp) + { + //Get the database entry + ActivePackages::Data const & dbData = elem.second; + sal_Int32 failedPrereq = dbData.failedPrerequisites.toInt32(); + //If the installation failed for other reason then the license then we + //ignore it. + if (failedPrereq ^ deployment::Prerequisites::LICENSE) + continue; + + //Prepare the URL to the extension + OUString url = makeURL(m_activePackages, elem.second.temporaryName); + if (bShared) + url = makeURLAppendSysPathSegment( Concat2View(url + "_"), elem.second.fileName); + + Reference<deployment::XPackage> p = m_xRegistry->bindPackage( + url, OUString(), false, OUString(), xCmdEnv ); + + if (p.is()) + vec.push_back(p); + + } + return ::comphelper::containerToSequence(vec); + } + catch (const deployment::DeploymentException &) + { + throw; + } + catch (const RuntimeException&) + { + throw; + } + catch (...) + { + Any exc = ::cppu::getCaughtException(); + deployment::DeploymentException de( + "PackageManagerImpl::getExtensionsWithUnacceptedLicenses", + static_cast<OWeakObject*>(this), exc); + exc <<= de; + ::cppu::throwException(exc); + } + + return ::comphelper::containerToSequence(vec); +} + +sal_Int32 PackageManagerImpl::checkPrerequisites( + css::uno::Reference<css::deployment::XPackage> const & extension, + css::uno::Reference<css::task::XAbortChannel> const & xAbortChannel, + css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv ) +{ + try + { + if (!extension.is()) + return 0; + if (m_context != extension->getRepositoryName()) + throw lang::IllegalArgumentException( + "PackageManagerImpl::checkPrerequisites: extension is not from this repository.", + nullptr, 0); + + ActivePackages::Data dbData; + OUString id = dp_misc::getIdentifier(extension); + if (!m_activePackagesDB->get( &dbData, id, OUString())) + { + throw lang::IllegalArgumentException( + "PackageManagerImpl::checkPrerequisites: unknown extension", + nullptr, 0); + + } + //If the license was already displayed, then do not show it again + Reference<ucb::XCommandEnvironment> _xCmdEnv = xCmdEnv; + sal_Int32 prereq = dbData.failedPrerequisites.toInt32(); + if ( !(prereq & deployment::Prerequisites::LICENSE)) + _xCmdEnv = new NoLicenseCommandEnv(xCmdEnv->getInteractionHandler()); + + sal_Int32 failedPrereq = extension->checkPrerequisites( + xAbortChannel, _xCmdEnv, false); + dbData.failedPrerequisites = OUString::number(failedPrereq); + insertToActivationLayerDB(id, dbData); + return 0; + } + catch ( const deployment::DeploymentException& ) { + throw; + } catch ( const ucb::CommandFailedException & ) { + throw; + } catch ( const ucb::CommandAbortedException & ) { + throw; + } catch (const lang::IllegalArgumentException &) { + throw; + } catch (const uno::RuntimeException &) { + throw; + } catch (...) { + uno::Any excOccurred = ::cppu::getCaughtException(); + deployment::DeploymentException exc( + "PackageManagerImpl::checkPrerequisites: exception ", + static_cast<OWeakObject*>(this), excOccurred); + throw exc; + } +} + + +PackageManagerImpl::CmdEnvWrapperImpl::~CmdEnvWrapperImpl() +{ +} + + +PackageManagerImpl::CmdEnvWrapperImpl::CmdEnvWrapperImpl( + Reference<XCommandEnvironment> const & xUserCmdEnv, + Reference<XProgressHandler> const & xLogFile ) + : m_xLogFile( xLogFile ) +{ + if (xUserCmdEnv.is()) { + m_xUserProgress.set( xUserCmdEnv->getProgressHandler() ); + m_xUserInteractionHandler.set( xUserCmdEnv->getInteractionHandler() ); + } +} + +// XCommandEnvironment + +Reference<task::XInteractionHandler> +PackageManagerImpl::CmdEnvWrapperImpl::getInteractionHandler() +{ + return m_xUserInteractionHandler; +} + + +Reference<XProgressHandler> +PackageManagerImpl::CmdEnvWrapperImpl::getProgressHandler() +{ + return this; +} + +// XProgressHandler + +void PackageManagerImpl::CmdEnvWrapperImpl::push( Any const & Status ) +{ + if (m_xLogFile.is()) + m_xLogFile->push( Status ); + if (m_xUserProgress.is()) + m_xUserProgress->push( Status ); +} + + +void PackageManagerImpl::CmdEnvWrapperImpl::update( Any const & Status ) +{ + if (m_xLogFile.is()) + m_xLogFile->update( Status ); + if (m_xUserProgress.is()) + m_xUserProgress->update( Status ); +} + + +void PackageManagerImpl::CmdEnvWrapperImpl::pop() +{ + if (m_xLogFile.is()) + m_xLogFile->pop(); + if (m_xUserProgress.is()) + m_xUserProgress->pop(); +} + +} // namespace dp_manager + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/manager/dp_manager.h b/desktop/source/deployment/manager/dp_manager.h new file mode 100644 index 0000000000..dce57d418e --- /dev/null +++ b/desktop/source/deployment/manager/dp_manager.h @@ -0,0 +1,233 @@ +/* -*- 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 "dp_activepackages.hxx" +#include <cppuhelper/basemutex.hxx> +#include <cppuhelper/compbase.hxx> +#include <cppuhelper/implbase.hxx> +#include <ucbhelper/content.hxx> +#include <com/sun/star/deployment/XPackageRegistry.hpp> +#include <com/sun/star/deployment/XPackageManager.hpp> +#include <memory> +#include <mutex> +#include <string_view> +#include <utility> + +namespace dp_manager { + +typedef ::cppu::WeakComponentImplHelper< + css::deployment::XPackageManager > t_pm_helper; + + +class PackageManagerImpl final : private cppu::BaseMutex, public t_pm_helper +{ + css::uno::Reference<css::uno::XComponentContext> m_xComponentContext; + OUString m_context; + OUString m_registrationData; + OUString m_registrationData_expanded; + OUString m_registryCache; + bool m_readOnly; + + OUString m_activePackages; + OUString m_activePackages_expanded; + std::unique_ptr< ActivePackages > m_activePackagesDB; + //This mutex is only used for synchronization in addPackage + std::mutex m_addMutex; + css::uno::Reference<css::ucb::XProgressHandler> m_xLogFile; + inline void logIntern( css::uno::Any const & status ); + void fireModified(); + + css::uno::Reference<css::deployment::XPackageRegistry> m_xRegistry; + + void initRegistryBackends(); + void initActivationLayer( + css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv ); + OUString detectMediaType( + ::ucbhelper::Content const & ucbContent, bool throw_exc = true ); + OUString insertToActivationLayer( + css::uno::Sequence<css::beans::NamedValue> const & properties, + OUString const & mediaType, + ::ucbhelper::Content const & sourceContent, + OUString const & title, ActivePackages::Data * dbData ); + void insertToActivationLayerDB( + OUString const & id, ActivePackages::Data const & dbData ); + + static void deletePackageFromCache( + css::uno::Reference<css::deployment::XPackage> const & xPackage, + OUString const & destFolder ); + + bool isInstalled( + css::uno::Reference<css::deployment::XPackage> const & package); + + bool synchronizeRemovedExtensions( + css::uno::Reference<css::task::XAbortChannel> const & xAbortChannel, + css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv); + + bool synchronizeAddedExtensions( + css::uno::Reference<css::task::XAbortChannel> const & xAbortChannel, + css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv); + + class CmdEnvWrapperImpl + : public ::cppu::WeakImplHelper< css::ucb::XCommandEnvironment, + css::ucb::XProgressHandler > + { + css::uno::Reference<css::ucb::XProgressHandler> m_xLogFile; + css::uno::Reference<css::ucb::XProgressHandler> m_xUserProgress; + css::uno::Reference<css::task::XInteractionHandler> + m_xUserInteractionHandler; + + public: + virtual ~CmdEnvWrapperImpl() override; + CmdEnvWrapperImpl( + css::uno::Reference<css::ucb::XCommandEnvironment> + const & xUserCmdEnv, + css::uno::Reference<css::ucb::XProgressHandler> const & xLogFile ); + + // XCommandEnvironment + virtual css::uno::Reference<css::task::XInteractionHandler> SAL_CALL + getInteractionHandler() override; + virtual css::uno::Reference<css::ucb::XProgressHandler> SAL_CALL + getProgressHandler() override; + + // XProgressHandler + virtual void SAL_CALL push( css::uno::Any const & Status ) override; + virtual void SAL_CALL update( css::uno::Any const & Status ) override; + virtual void SAL_CALL pop() override; + }; + + inline void check(); + virtual void SAL_CALL disposing() override; + + virtual ~PackageManagerImpl() override; + PackageManagerImpl( + css::uno::Reference<css::uno::XComponentContext> xComponentContext, OUString context ) + : t_pm_helper( m_aMutex ), + m_xComponentContext(std::move( xComponentContext )), + m_context(std::move( context )), + m_readOnly( true ) + {} + +public: + static css::uno::Reference<css::deployment::XPackageManager> create( + css::uno::Reference<css::uno::XComponentContext> + const & xComponentContext, OUString const & context ); + + // XComponent + virtual void SAL_CALL dispose() override; + virtual void SAL_CALL addEventListener( + css::uno::Reference<css::lang::XEventListener> const & xListener ) override; + virtual void SAL_CALL removeEventListener( + css::uno::Reference<css::lang::XEventListener> const & xListener ) override; + + // XModifyBroadcaster + virtual void SAL_CALL addModifyListener( + css::uno::Reference<css::util::XModifyListener> const & xListener ) override; + virtual void SAL_CALL removeModifyListener( + css::uno::Reference<css::util::XModifyListener> const & xListener ) override; + + // XPackageManager + virtual OUString SAL_CALL getContext() override; + virtual css::uno::Sequence< + css::uno::Reference<css::deployment::XPackageTypeInfo> > SAL_CALL + getSupportedPackageTypes() override; + + virtual css::uno::Reference<css::task::XAbortChannel> SAL_CALL + createAbortChannel() override; + + virtual css::uno::Reference<css::deployment::XPackage> SAL_CALL addPackage( + OUString const & url, + css::uno::Sequence<css::beans::NamedValue> const & properties, + OUString const & mediaType, + css::uno::Reference<css::task::XAbortChannel> const & xAbortChannel, + css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv ) override; + + virtual css::uno::Reference<css::deployment::XPackage> SAL_CALL importExtension( + css::uno::Reference<css::deployment::XPackage> const & extension, + css::uno::Reference<css::task::XAbortChannel> const & xAbortChannel, + css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv ) override; + + virtual void SAL_CALL removePackage( + OUString const & id, OUString const & fileName, + css::uno::Reference<css::task::XAbortChannel> const & xAbortChannel, + css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv ) override; + + OUString getDeployPath( ActivePackages::Data const & data ); + css::uno::Reference<css::deployment::XPackage> getDeployedPackage_( + OUString const & id, OUString const & fileName, + css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv ); + css::uno::Reference<css::deployment::XPackage> getDeployedPackage_( + std::u16string_view id, ActivePackages::Data const & data, + css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv, + bool ignoreAlienPlatforms = false ); + virtual css::uno::Reference<css::deployment::XPackage> SAL_CALL + getDeployedPackage( + OUString const & id, OUString const & fileName, + css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv ) override; + + css::uno::Sequence< css::uno::Reference<css::deployment::XPackage> > + getDeployedPackages_( + css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv ); + virtual css::uno::Sequence< css::uno::Reference<css::deployment::XPackage> > + SAL_CALL getDeployedPackages( + css::uno::Reference<css::task::XAbortChannel> const & xAbortChannel, + css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv ) override; + + virtual void SAL_CALL reinstallDeployedPackages( + sal_Bool force, + css::uno::Reference<css::task::XAbortChannel> const & xAbortChannel, + css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv ) override; + + virtual ::sal_Bool SAL_CALL isReadOnly( ) override; + + virtual ::sal_Bool SAL_CALL synchronize( + css::uno::Reference<css::task::XAbortChannel> const & xAbortChannel, + css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv ) override; + + virtual css::uno::Sequence<css::uno::Reference<css::deployment::XPackage> > SAL_CALL + getExtensionsWithUnacceptedLicenses( + css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv) override; + + virtual sal_Int32 SAL_CALL checkPrerequisites( + css::uno::Reference<css::deployment::XPackage> const & extension, + css::uno::Reference<css::task::XAbortChannel> const & xAbortChannel, + css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv ) override; + }; + + +inline void PackageManagerImpl::check() +{ + ::osl::MutexGuard guard( m_aMutex ); + if (rBHelper.bInDispose || rBHelper.bDisposed) + throw css::lang::DisposedException( + "PackageManager instance has already been disposed!", + static_cast< ::cppu::OWeakObject * >(this) ); +} + + +inline void PackageManagerImpl::logIntern( css::uno::Any const & status ) +{ + if (m_xLogFile.is()) + m_xLogFile->update( status ); +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/manager/dp_managerfac.cxx b/desktop/source/deployment/manager/dp_managerfac.cxx new file mode 100644 index 0000000000..79e0ea3588 --- /dev/null +++ b/desktop/source/deployment/manager/dp_managerfac.cxx @@ -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 . + */ + + +#include "dp_manager.h" +#include <dp_misc.h> +#include <cppuhelper/compbase.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <com/sun/star/deployment/XPackageManagerFactory.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <unordered_map> + +using namespace ::dp_misc; +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; + +namespace dp_manager::factory { + +typedef ::cppu::WeakComponentImplHelper< + deployment::XPackageManagerFactory, lang::XServiceInfo > t_pmfac_helper; + +namespace { + +class PackageManagerFactoryImpl : private cppu::BaseMutex, public t_pmfac_helper +{ + Reference<XComponentContext> m_xComponentContext; + + Reference<deployment::XPackageManager> m_xUserMgr; + Reference<deployment::XPackageManager> m_xSharedMgr; + Reference<deployment::XPackageManager> m_xBundledMgr; + Reference<deployment::XPackageManager> m_xTmpMgr; + Reference<deployment::XPackageManager> m_xBakMgr; + typedef std::unordered_map< + OUString, WeakReference<deployment::XPackageManager> > t_string2weakref; + t_string2weakref m_managers; + +protected: + inline void check(); + virtual void SAL_CALL disposing() override; + +public: + explicit PackageManagerFactoryImpl( + Reference<XComponentContext> const & xComponentContext ); + + // 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; + + // XPackageManagerFactory + virtual Reference<deployment::XPackageManager> SAL_CALL getPackageManager( + OUString const & context ) override; +}; + +} + +PackageManagerFactoryImpl::PackageManagerFactoryImpl( + Reference<XComponentContext> const & xComponentContext ) + : t_pmfac_helper( m_aMutex ), + m_xComponentContext( xComponentContext ) +{ +} + +// XServiceInfo +OUString PackageManagerFactoryImpl::getImplementationName() +{ + return "com.sun.star.comp.deployment.PackageManagerFactory"; +} + +sal_Bool PackageManagerFactoryImpl::supportsService( const OUString& ServiceName ) +{ + return cppu::supportsService(this, ServiceName); +} + +css::uno::Sequence< OUString > PackageManagerFactoryImpl::getSupportedServiceNames() +{ + // a private one: + return { "com.sun.star.comp.deployment.PackageManagerFactory" }; +} + +inline void PackageManagerFactoryImpl::check() +{ + ::osl::MutexGuard guard( m_aMutex ); + if (rBHelper.bInDispose || rBHelper.bDisposed) + { + throw lang::DisposedException( + "PackageManagerFactory instance has already been disposed!", + static_cast<OWeakObject *>(this) ); + } +} + + +void PackageManagerFactoryImpl::disposing() +{ + // dispose all managers: + ::osl::MutexGuard guard( m_aMutex ); + for (auto const& elem : m_managers) + try_dispose( elem.second ); + m_managers = t_string2weakref(); + // the below are already disposed: + m_xUserMgr.clear(); + m_xSharedMgr.clear(); + m_xBundledMgr.clear(); + m_xTmpMgr.clear(); + m_xBakMgr.clear(); +} + +// XPackageManagerFactory + +Reference<deployment::XPackageManager> +PackageManagerFactoryImpl::getPackageManager( OUString const & context ) +{ + Reference< deployment::XPackageManager > xRet; + ::osl::ResettableMutexGuard guard( m_aMutex ); + check(); + t_string2weakref::const_iterator const iFind( m_managers.find( context ) ); + if (iFind != m_managers.end()) { + xRet = iFind->second; + if (xRet.is()) + return xRet; + } + + guard.clear(); + xRet.set( PackageManagerImpl::create( m_xComponentContext, context ) ); + guard.reset(); + std::pair< t_string2weakref::iterator, bool > insertion( + m_managers.emplace( context, xRet ) ); + if (insertion.second) + { + OSL_ASSERT( insertion.first->second.get() == xRet ); + // hold user, shared mgrs for whole process: live deployment + if ( context == "user" ) + m_xUserMgr = xRet; + else if ( context == "shared" ) + m_xSharedMgr = xRet; + else if ( context == "bundled" ) + m_xBundledMgr = xRet; + else if ( context == "tmp" ) + m_xTmpMgr = xRet; + else if ( context == "bak" ) + m_xBakMgr = xRet; + } + else + { + Reference< deployment::XPackageManager > xAlreadyIn( + insertion.first->second ); + if (xAlreadyIn.is()) + { + guard.clear(); + try_dispose( xRet ); + xRet = xAlreadyIn; + } + else + { + insertion.first->second = xRet; + } + } + return xRet; +} + +} // namespace dp_manager::factory + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +com_sun_star_comp_deployment_PackageManagerFactory_get_implementation( + css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const& ) +{ + return cppu::acquire(new dp_manager::factory::PackageManagerFactoryImpl(context)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/manager/dp_properties.cxx b/desktop/source/deployment/manager/dp_properties.cxx new file mode 100644 index 0000000000..6dc1fff303 --- /dev/null +++ b/desktop/source/deployment/manager/dp_properties.cxx @@ -0,0 +1,145 @@ +/* -*- 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 <com/sun/star/ucb/XCommandEnvironment.hpp> +#include <com/sun/star/lang/IllegalArgumentException.hpp> +#include <xmlscript/xml_helper.hxx> +#include <ucbhelper/content.hxx> + +#include <dp_ucb.h> +#include "dp_properties.hxx" + +namespace lang = com::sun::star::lang; +namespace ucb = com::sun::star::ucb; +namespace uno = com::sun::star::uno; + + +using ::com::sun::star::uno::Reference; + +constexpr OUString PROP_SUPPRESS_LICENSE = u"SUPPRESS_LICENSE"_ustr; +constexpr OUStringLiteral PROP_EXTENSION_UPDATE = u"EXTENSION_UPDATE"; + +namespace dp_manager { + +//Reading the file +ExtensionProperties::ExtensionProperties( + std::u16string_view urlExtension, + Reference<ucb::XCommandEnvironment> const & xCmdEnv, + Reference<uno::XComponentContext> const & xContext) : + m_xCmdEnv(xCmdEnv), m_xContext(xContext) +{ + m_propFileUrl = OUString::Concat(urlExtension) + "properties"; + + std::vector< std::pair< OUString, OUString> > props; + if (! dp_misc::create_ucb_content(nullptr, m_propFileUrl, nullptr, false)) + return; + + ::ucbhelper::Content contentProps(m_propFileUrl, m_xCmdEnv, m_xContext); + dp_misc::readProperties(props, contentProps); + + for (auto const& prop : props) + { + if (prop.first == PROP_SUPPRESS_LICENSE) + m_prop_suppress_license = prop.second; + } +} + +//Writing the file +ExtensionProperties::ExtensionProperties( + std::u16string_view urlExtension, + uno::Sequence<css::beans::NamedValue> const & properties, + Reference<ucb::XCommandEnvironment> const & xCmdEnv, + Reference<uno::XComponentContext> const & xContext) : + m_xCmdEnv(xCmdEnv), m_xContext(xContext) +{ + m_propFileUrl = OUString::Concat(urlExtension) + "properties"; + + for (css::beans::NamedValue const & v : properties) + { + if (v.Name == PROP_SUPPRESS_LICENSE) + { + m_prop_suppress_license = getPropertyValue(v); + } + else if (v.Name == PROP_EXTENSION_UPDATE) + { + m_prop_extension_update = getPropertyValue(v); + } + else + { + throw lang::IllegalArgumentException( + "Extension Manager: unknown property", nullptr, -1); + } + } +} + +OUString ExtensionProperties::getPropertyValue(css::beans::NamedValue const & v) +{ + OUString value("0"); + if (! (v.Value >>= value) ) + { + throw lang::IllegalArgumentException( + "Extension Manager: wrong property value", nullptr, -1); + } + return value; +} +void ExtensionProperties::write() +{ + ::ucbhelper::Content contentProps(m_propFileUrl, m_xCmdEnv, m_xContext); + OUString buf; + + if (m_prop_suppress_license) + { + buf = OUString::Concat(PROP_SUPPRESS_LICENSE) + "=" + *m_prop_suppress_license; + } + + OString stamp = OUStringToOString(buf, RTL_TEXTENCODING_UTF8); + Reference<css::io::XInputStream> xData( + ::xmlscript::createInputStream( + reinterpret_cast<sal_Int8 const *>(stamp.getStr()), + stamp.getLength() ) ); + contentProps.writeStream( xData, true /* replace existing */ ); +} + +bool ExtensionProperties::isSuppressedLicense() const +{ + bool ret = false; + if (m_prop_suppress_license) + { + if (*m_prop_suppress_license == "1") + ret = true; + } + return ret; +} + +bool ExtensionProperties::isExtensionUpdate() const +{ + bool ret = false; + if (m_prop_extension_update) + { + if (*m_prop_extension_update == "1") + ret = true; + } + return ret; +} + +} // namespace dp_manager + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/manager/dp_properties.hxx b/desktop/source/deployment/manager/dp_properties.hxx new file mode 100644 index 0000000000..06139ece3c --- /dev/null +++ b/desktop/source/deployment/manager/dp_properties.hxx @@ -0,0 +1,58 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#pragma once + +#include <com/sun/star/beans/NamedValue.hpp> +#include <com/sun/star/ucb/XCommandEnvironment.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <optional> +#include <string_view> + +namespace dp_manager +{ +class ExtensionProperties final +{ + OUString m_propFileUrl; + const css::uno::Reference<css::ucb::XCommandEnvironment> m_xCmdEnv; + const css::uno::Reference<css::uno::XComponentContext> m_xContext; + ::std::optional<OUString> m_prop_suppress_license; + ::std::optional<OUString> m_prop_extension_update; + + static OUString getPropertyValue(css::beans::NamedValue const& v); + +public: + ExtensionProperties(std::u16string_view urlExtension, + css::uno::Reference<css::ucb::XCommandEnvironment> const& xCmdEnv, + css::uno::Reference<css::uno::XComponentContext> const& xContext); + + ExtensionProperties(std::u16string_view urlExtension, + css::uno::Sequence<css::beans::NamedValue> const& properties, + css::uno::Reference<css::ucb::XCommandEnvironment> const& xCmdEnv, + css::uno::Reference<css::uno::XComponentContext> const& xContext); + + void write(); + + bool isSuppressedLicense() const; + + bool isExtensionUpdate() const; +}; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/misc/dp_dependencies.cxx b/desktop/source/deployment/misc/dp_dependencies.cxx new file mode 100644 index 0000000000..eea3cfba9c --- /dev/null +++ b/desktop/source/deployment/misc/dp_dependencies.cxx @@ -0,0 +1,196 @@ +/* -*- 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 <sal/config.h> + +#include <com/sun/star/uno/Reference.hxx> +#include <com/sun/star/uno/Sequence.hxx> +#include <com/sun/star/xml/dom/XElement.hpp> +#include <com/sun/star/xml/dom/XNodeList.hpp> +#include <osl/diagnose.h> +#include <rtl/bootstrap.hxx> +#include <rtl/ustring.hxx> +#include <sal/types.h> +#include <unotools/configmgr.hxx> + +#include <strings.hrc> +#include <dp_shared.hxx> + +#include <dp_dependencies.hxx> +#include <dp_descriptioninfoset.hxx> +#include <dp_version.hxx> + +namespace { + +char const namespaceLibreOffice[] = + "http://libreoffice.org/extensions/description/2011"; + +constexpr OUString namespaceOpenOfficeOrg = + u"http://openoffice.org/extensions/description/2006"_ustr; + +char const minimalVersionLibreOffice[] = "LibreOffice-minimal-version"; +char const maximalVersionLibreOffice[] = "LibreOffice-maximal-version"; + +constexpr OUString minimalVersionOpenOfficeOrg = + u"OpenOffice.org-minimal-version"_ustr; + +char const maximalVersionOpenOfficeOrg[] = + "OpenOffice.org-maximal-version"; + +OUString getLibreOfficeMajorMinorMicro() { + return utl::ConfigManager::getAboutBoxProductVersion(); +} + +OUString getReferenceOpenOfficeOrgMajorMinor() { +#ifdef ANDROID + // just hardcode the version + OUString v("4.1"); +#else + OUString v( + "${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE("version") + ":Version:ReferenceOOoMajorMinor}"); + rtl::Bootstrap::expandMacros(v); //TODO: check for failure +#endif + return v; +} + +bool satisfiesMinimalVersion( + std::u16string_view actual, std::u16string_view specified) +{ + return dp_misc::compareVersions(actual, specified) != dp_misc::LESS; +} + +bool satisfiesMaximalVersion( + std::u16string_view actual, std::u16string_view specified) +{ + return dp_misc::compareVersions(actual, specified) != dp_misc::GREATER; +} + +OUString produceErrorText( + OUString const & reason, OUString const & version) +{ + return reason.replaceFirst("%VERSION", + (version.isEmpty() + ? DpResId(RID_DEPLOYMENT_DEPENDENCIES_UNKNOWN) + : version)); +} + +} + +namespace dp_misc::Dependencies { + +css::uno::Sequence< css::uno::Reference< css::xml::dom::XElement > > +check(dp_misc::DescriptionInfoset const & infoset) { + css::uno::Reference< css::xml::dom::XNodeList > deps( + infoset.getDependencies()); + sal_Int32 n = deps->getLength(); + css::uno::Sequence< css::uno::Reference< css::xml::dom::XElement > > + unsatisfied(n); + auto unsatisfiedRange = asNonConstRange(unsatisfied); + sal_Int32 unsat = 0; + // check first if minimalVersionLibreOffice is specified -- in that case ignore the legacy OOo dependencies + bool bIgnoreOoo = false; + for (sal_Int32 i = 0; i < n; ++i) { + css::uno::Reference< css::xml::dom::XElement > e( + deps->item(i), css::uno::UNO_QUERY_THROW); + if ( e->getNamespaceURI() == namespaceLibreOffice && e->getTagName() == minimalVersionLibreOffice) + { + bIgnoreOoo = true; + break; + } + } + for (sal_Int32 i = 0; i < n; ++i) { + css::uno::Reference< css::xml::dom::XElement > e( + deps->item(i), css::uno::UNO_QUERY_THROW); + bool sat = false; + if ( e->getNamespaceURI() == namespaceOpenOfficeOrg && e->getTagName() == minimalVersionOpenOfficeOrg ) + { + sat = bIgnoreOoo || satisfiesMinimalVersion( + getReferenceOpenOfficeOrgMajorMinor(), + e->getAttribute("value")); + } else if ( e->getNamespaceURI() == namespaceOpenOfficeOrg && e->getTagName() == maximalVersionOpenOfficeOrg ) + { + sat = bIgnoreOoo || satisfiesMaximalVersion( + getReferenceOpenOfficeOrgMajorMinor(), + e->getAttribute("value")); + } else if (e->getNamespaceURI() == namespaceLibreOffice && e->getTagName() == minimalVersionLibreOffice ) + { + sat = satisfiesMinimalVersion( + getLibreOfficeMajorMinorMicro(), + e->getAttribute("value")); + } else if (e->getNamespaceURI() == namespaceLibreOffice && e->getTagName() == maximalVersionLibreOffice ) + { + sat = satisfiesMaximalVersion(getLibreOfficeMajorMinorMicro(), e->getAttribute("value")); + } else if (e->hasAttributeNS(namespaceOpenOfficeOrg, + minimalVersionOpenOfficeOrg)) + { + sat = satisfiesMinimalVersion( + getReferenceOpenOfficeOrgMajorMinor(), + e->getAttributeNS(namespaceOpenOfficeOrg, + minimalVersionOpenOfficeOrg)); + } + if (!sat) { + unsatisfiedRange[unsat++] = e; + } + } + unsatisfied.realloc(unsat); + return unsatisfied; +} + +OUString getErrorText( + css::uno::Reference< css::xml::dom::XElement > const & dependency) +{ + OSL_ASSERT(dependency.is()); + if ( dependency->getNamespaceURI() == namespaceOpenOfficeOrg && dependency->getTagName() == minimalVersionOpenOfficeOrg ) + { + return produceErrorText( + DpResId(RID_DEPLOYMENT_DEPENDENCIES_OOO_MIN), + dependency->getAttribute("value")); + } else if (dependency->getNamespaceURI() == namespaceOpenOfficeOrg && dependency->getTagName() == maximalVersionOpenOfficeOrg ) + { + return produceErrorText( + DpResId(RID_DEPLOYMENT_DEPENDENCIES_OOO_MAX), + dependency->getAttribute("value")); + } else if (dependency->getNamespaceURI() == namespaceLibreOffice && dependency->getTagName() == minimalVersionLibreOffice ) + { + return produceErrorText( + DpResId(RID_DEPLOYMENT_DEPENDENCIES_LO_MIN), + dependency->getAttribute("value")); + } else if (dependency->getNamespaceURI() == namespaceLibreOffice && dependency->getTagName() == maximalVersionLibreOffice ) + { + return produceErrorText( + DpResId(RID_DEPLOYMENT_DEPENDENCIES_LO_MAX), + dependency->getAttribute("value")); + } else if (dependency->hasAttributeNS(namespaceOpenOfficeOrg, + minimalVersionOpenOfficeOrg)) + { + return produceErrorText( + DpResId(RID_DEPLOYMENT_DEPENDENCIES_OOO_MIN), + dependency->getAttributeNS(namespaceOpenOfficeOrg, + minimalVersionOpenOfficeOrg)); + } else { + return DpResId(RID_DEPLOYMENT_DEPENDENCIES_UNKNOWN); + } +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/misc/dp_descriptioninfoset.cxx b/desktop/source/deployment/misc/dp_descriptioninfoset.cxx new file mode 100644 index 0000000000..00b32c04f2 --- /dev/null +++ b/desktop/source/deployment/misc/dp_descriptioninfoset.cxx @@ -0,0 +1,809 @@ +/* -*- 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 <string_view> + +#include <dp_descriptioninfoset.hxx> + +#include <dp_resource.h> + +#include <comphelper/sequence.hxx> +#include <comphelper/processfactory.hxx> +#include <comphelper/propertysequence.hxx> +#include <optional> +#include <com/sun/star/configuration/theDefaultProvider.hpp> +#include <com/sun/star/container/XNameAccess.hpp> +#include <com/sun/star/deployment/DeploymentException.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/io/SequenceInputStream.hpp> +#include <com/sun/star/lang/XMultiComponentFactory.hpp> +#include <com/sun/star/lang/WrappedTargetRuntimeException.hpp> +#include <com/sun/star/task/XInteractionHandler.hpp> +#include <com/sun/star/ucb/XCommandEnvironment.hpp> +#include <com/sun/star/ucb/XProgressHandler.hpp> +#include <com/sun/star/uno/Reference.hxx> +#include <com/sun/star/uno/RuntimeException.hpp> +#include <com/sun/star/uno/Sequence.hxx> +#include <com/sun/star/uno/XInterface.hpp> +#include <com/sun/star/xml/dom/DOMException.hpp> +#include <com/sun/star/xml/dom/XNode.hpp> +#include <com/sun/star/xml/dom/XNodeList.hpp> +#include <com/sun/star/xml/dom/DocumentBuilder.hpp> +#include <com/sun/star/xml/xpath/XPathAPI.hpp> +#include <com/sun/star/xml/xpath/XPathException.hpp> +#include <com/sun/star/ucb/InteractiveIOException.hpp> +#include <cppuhelper/implbase.hxx> +#include <cppuhelper/weak.hxx> +#include <cppuhelper/exc_hlp.hxx> +#include <rtl/ustring.hxx> +#include <sal/types.h> +#include <ucbhelper/content.hxx> +#include <o3tl/string_view.hxx> + +namespace { + +using css::uno::Reference; + +class EmptyNodeList: + public cppu::WeakImplHelper<css::xml::dom::XNodeList> +{ +public: + EmptyNodeList(); + + EmptyNodeList(const EmptyNodeList&) = delete; + const EmptyNodeList& operator=(const EmptyNodeList&) = delete; + + virtual ::sal_Int32 SAL_CALL getLength() override; + + virtual css::uno::Reference< css::xml::dom::XNode > SAL_CALL + item(::sal_Int32 index) override; +}; + +EmptyNodeList::EmptyNodeList() {} + +::sal_Int32 EmptyNodeList::getLength() { + return 0; +} + +css::uno::Reference< css::xml::dom::XNode > EmptyNodeList::item(::sal_Int32) +{ + throw css::uno::RuntimeException("bad EmptyNodeList com.sun.star.xml.dom.XNodeList.item call", + static_cast< ::cppu::OWeakObject * >(this)); +} + +OUString getNodeValue( + css::uno::Reference< css::xml::dom::XNode > const & node) +{ + OSL_ASSERT(node.is()); + try { + return node->getNodeValue(); + } catch (const css::xml::dom::DOMException & e) { + css::uno::Any anyEx = cppu::getCaughtException(); + throw css::lang::WrappedTargetRuntimeException( + "com.sun.star.xml.dom.DOMException: " + e.Message, + nullptr, anyEx ); + } +} + +/**The class uses the UCB to access the description.xml file in an + extension. The UCB must have been initialized already. It also + requires that the extension has already be unzipped to a particular + location. + */ +class ExtensionDescription +{ +public: + /**throws an exception if the description.xml is not + available, cannot be read, does not contain the expected data, + or any other error occurred. Therefore it should only be used with + new extensions. + + Throws css::uno::RuntimeException, + css::deployment::DeploymentException, + dp_registry::backend::bundle::NoDescriptionException. + */ + ExtensionDescription( + const css::uno::Reference<css::uno::XComponentContext>& xContext, + std::u16string_view installDir, + const css::uno::Reference< css::ucb::XCommandEnvironment >& xCmdEnv); + + const css::uno::Reference<css::xml::dom::XNode>& getRootElement() const + { + return m_xRoot; + } + +private: + css::uno::Reference<css::xml::dom::XNode> m_xRoot; +}; + +class NoDescriptionException +{ +}; + +class FileDoesNotExistFilter + : public ::cppu::WeakImplHelper< css::ucb::XCommandEnvironment, + css::task::XInteractionHandler > + +{ + bool m_bExist; + css::uno::Reference< css::ucb::XCommandEnvironment > m_xCommandEnv; + +public: + explicit FileDoesNotExistFilter( + const css::uno::Reference< css::ucb::XCommandEnvironment >& xCmdEnv); + + bool exist() { return m_bExist;} + // XCommandEnvironment + virtual css::uno::Reference<css::task::XInteractionHandler > SAL_CALL + getInteractionHandler() override; + virtual css::uno::Reference<css::ucb::XProgressHandler > + SAL_CALL getProgressHandler() override; + + // XInteractionHandler + virtual void SAL_CALL handle( + css::uno::Reference<css::task::XInteractionRequest > const & xRequest ) override; +}; + +ExtensionDescription::ExtensionDescription( + const Reference<css::uno::XComponentContext>& xContext, + std::u16string_view installDir, + const Reference< css::ucb::XCommandEnvironment >& xCmdEnv) +{ + try { + //may throw css::ucb::ContentCreationException + //If there is no description.xml then ucb will start an interaction which + //brings up a dialog.We want to prevent this. Therefore we wrap the xCmdEnv + //and filter the respective exception out. + OUString sDescriptionUri(OUString::Concat(installDir) + "/description.xml"); + Reference<css::ucb::XCommandEnvironment> xFilter = new FileDoesNotExistFilter(xCmdEnv); + ::ucbhelper::Content descContent(sDescriptionUri, xFilter, xContext); + + //throws a css::uno::Exception if the file is not available + Reference<css::io::XInputStream> xIn; + try + { //throws com.sun.star.ucb.InteractiveIOException + xIn = descContent.openStream(); + } + catch ( const css::uno::Exception& ) + { + if ( ! static_cast<FileDoesNotExistFilter*>(xFilter.get())->exist()) + throw NoDescriptionException(); + throw; + } + if (!xIn.is()) + { + throw css::uno::Exception( + "Could not get XInputStream for description.xml of extension " + + sDescriptionUri, nullptr); + } + + //get root node of description.xml + Reference<css::xml::dom::XDocumentBuilder> xDocBuilder( + css::xml::dom::DocumentBuilder::create(xContext) ); + + if (!xDocBuilder->isNamespaceAware()) + { + throw css::uno::Exception( + "Service com.sun.star.xml.dom.DocumentBuilder is not namespace aware.", nullptr); + } + + Reference<css::xml::dom::XDocument> xDoc = xDocBuilder->parse(xIn); + if (!xDoc.is()) + { + throw css::uno::Exception(sDescriptionUri + " contains data which cannot be parsed. ", nullptr); + } + + //check for proper root element and namespace + Reference<css::xml::dom::XElement> xRoot = xDoc->getDocumentElement(); + if (!xRoot.is()) + { + throw css::uno::Exception( + sDescriptionUri + " contains no root element.", nullptr); + } + + if ( xRoot->getTagName() != "description") + { + throw css::uno::Exception( + sDescriptionUri + " does not contain the root element <description>.", nullptr); + } + + m_xRoot.set(xRoot, css::uno::UNO_QUERY_THROW); + OUString nsDescription = xRoot->getNamespaceURI(); + + //check if this namespace is supported + if ( nsDescription != "http://openoffice.org/extensions/description/2006") + { + throw css::uno::Exception(sDescriptionUri + " contains a root element with an unsupported namespace. ", nullptr); + } + } catch (const css::uno::RuntimeException &) { + throw; + } catch (const css::deployment::DeploymentException &) { + throw; + } catch (const css::uno::Exception & e) { + css::uno::Any a(cppu::getCaughtException()); + throw css::deployment::DeploymentException( + e.Message, Reference< css::uno::XInterface >(), a); + } +} + +FileDoesNotExistFilter::FileDoesNotExistFilter( + const Reference< css::ucb::XCommandEnvironment >& xCmdEnv): + m_bExist(true), m_xCommandEnv(xCmdEnv) +{} + + // XCommandEnvironment +Reference<css::task::XInteractionHandler > + FileDoesNotExistFilter::getInteractionHandler() +{ + return static_cast<css::task::XInteractionHandler*>(this); +} + +Reference<css::ucb::XProgressHandler > + FileDoesNotExistFilter::getProgressHandler() +{ + return m_xCommandEnv.is() + ? m_xCommandEnv->getProgressHandler() + : Reference<css::ucb::XProgressHandler>(); +} + +// XInteractionHandler +//If the interaction was caused by a non-existing file which is specified in the ctor +//of FileDoesNotExistFilter, then we do nothing +void FileDoesNotExistFilter::handle( + Reference<css::task::XInteractionRequest > const & xRequest ) +{ + css::uno::Any request( xRequest->getRequest() ); + + css::ucb::InteractiveIOException ioexc; + if ((request>>= ioexc) + && (ioexc.Code == css::ucb::IOErrorCode_NOT_EXISTING + || ioexc.Code == css::ucb::IOErrorCode_NOT_EXISTING_PATH)) + { + m_bExist = false; + return; + } + Reference<css::task::XInteractionHandler> xInteraction; + if (m_xCommandEnv.is()) { + xInteraction = m_xCommandEnv->getInteractionHandler(); + } + if (xInteraction.is()) { + xInteraction->handle(xRequest); + } +} + +} + +namespace dp_misc { + +DescriptionInfoset getDescriptionInfoset(std::u16string_view sExtensionFolderURL) +{ + Reference< css::xml::dom::XNode > root; + Reference<css::uno::XComponentContext> context( + comphelper::getProcessComponentContext()); + try { + root = + ExtensionDescription( + context, sExtensionFolderURL, + Reference< css::ucb::XCommandEnvironment >()). + getRootElement(); + } catch (const NoDescriptionException &) { + } catch (const css::deployment::DeploymentException & e) { + css::uno::Any anyEx = cppu::getCaughtException(); + throw css::lang::WrappedTargetRuntimeException( + "com.sun.star.deployment.DeploymentException: " + e.Message, + nullptr, anyEx ); + } + return DescriptionInfoset(context, root); +} + +DescriptionInfoset::DescriptionInfoset( + css::uno::Reference< css::uno::XComponentContext > const & context, + css::uno::Reference< css::xml::dom::XNode > const & element): + m_context(context), + m_element(element) +{ + if (m_element.is()) { + m_xpath = css::xml::xpath::XPathAPI::create(context); + m_xpath->registerNS("desc", element->getNamespaceURI()); + m_xpath->registerNS("xlink", "http://www.w3.org/1999/xlink"); + } +} + +DescriptionInfoset::~DescriptionInfoset() {} + +::std::optional< OUString > DescriptionInfoset::getIdentifier() const { + return getOptionalValue("desc:identifier/@value"); +} + +OUString DescriptionInfoset::getNodeValueFromExpression(OUString const & expression) const +{ + css::uno::Reference< css::xml::dom::XNode > n; + if (m_element.is()) { + try { + n = m_xpath->selectSingleNode(m_element, expression); + } catch (const css::xml::xpath::XPathException &) { + // ignore + } + } + return n.is() ? getNodeValue(n) : OUString(); +} + +void DescriptionInfoset::checkDenylist() const +{ + if (!m_element.is()) + return; + + std::optional< OUString > id(getIdentifier()); + if (!id) + return; // nothing to check + OUString currentversion(getVersion()); + if (currentversion.getLength() == 0) + return; // nothing to check + + css::uno::Sequence<css::uno::Any> args(comphelper::InitAnyPropertySequence( + { + {"nodepath", css::uno::Any(OUString("/org.openoffice.Office.ExtensionDependencies/Extensions"))} + })); + css::uno::Reference< css::container::XNameAccess > denylist( + (css::configuration::theDefaultProvider::get(m_context) + ->createInstanceWithArguments( + "com.sun.star.configuration.ConfigurationAccess", args)), + css::uno::UNO_QUERY_THROW); + + // check first if a denylist entry is available + if (!(denylist.is() && denylist->hasByName(*id))) return; + + css::uno::Reference< css::beans::XPropertySet > extProps( + denylist->getByName(*id), css::uno::UNO_QUERY_THROW); + + css::uno::Any anyValue = extProps->getPropertyValue("Versions"); + + css::uno::Sequence< OUString > blversions; + anyValue >>= blversions; + + // check if the current version requires further dependency checks from the denylist + if (!checkDenylistVersion(currentversion, blversions)) return; + + anyValue = extProps->getPropertyValue("Dependencies"); + OUString udeps; + anyValue >>= udeps; + + if (udeps.getLength() == 0) + return; // nothing todo + + OString xmlDependencies = OUStringToOString(udeps, RTL_TEXTENCODING_UNICODE); + + css::uno::Reference< css::xml::dom::XDocumentBuilder> docbuilder( + m_context->getServiceManager()->createInstanceWithContext("com.sun.star.xml.dom.DocumentBuilder", m_context), + css::uno::UNO_QUERY_THROW); + + css::uno::Sequence< sal_Int8 > byteSeq(reinterpret_cast<const sal_Int8*>(xmlDependencies.getStr()), xmlDependencies.getLength()); + + css::uno::Reference< css::io::XInputStream> inputstream( css::io::SequenceInputStream::createStreamFromSequence(m_context, byteSeq), + css::uno::UNO_QUERY_THROW); + + css::uno::Reference< css::xml::dom::XDocument > xDocument(docbuilder->parse(inputstream)); + css::uno::Reference< css::xml::dom::XElement > xElement(xDocument->getDocumentElement()); + css::uno::Reference< css::xml::dom::XNodeList > xDeps(xElement->getChildNodes()); + sal_Int32 nLen = xDeps->getLength(); + + // get the parent xml document of current description info for the import + css::uno::Reference< css::xml::dom::XDocument > xCurrentDescInfo(m_element->getOwnerDocument()); + + // get dependency node of current description info to merge the new dependencies from the denylist + css::uno::Reference< css::xml::dom::XNode > xCurrentDeps( + m_xpath->selectSingleNode(m_element, "desc:dependencies")); + + // if no dependency node exists, create a new one in the current description info + if (!xCurrentDeps.is()) { + css::uno::Reference< css::xml::dom::XNode > xNewDepNode( + xCurrentDescInfo->createElementNS( + "http://openoffice.org/extensions/description/2006", + "dependencies"), css::uno::UNO_QUERY_THROW); + m_element->appendChild(xNewDepNode); + xCurrentDeps = m_xpath->selectSingleNode(m_element, "desc:dependencies"); + } + + for (sal_Int32 i=0; i<nLen; i++) { + css::uno::Reference< css::xml::dom::XNode > xNode(xDeps->item(i)); + css::uno::Reference< css::xml::dom::XElement > xDep(xNode, css::uno::UNO_QUERY); + if (xDep.is()) { + // found valid denylist dependency, import the node first and append it to the existing dependency node + css::uno::Reference< css::xml::dom::XNode > importedNode = xCurrentDescInfo->importNode(xNode, true); + xCurrentDeps->appendChild(importedNode); + } + } +} + +bool DescriptionInfoset::checkDenylistVersion( + std::u16string_view currentversion, + css::uno::Sequence< OUString > const & versions) +{ + sal_Int32 nLen = versions.getLength(); + for (sal_Int32 i=0; i<nLen; i++) { + if (currentversion == versions[i]) + return true; + } + + return false; +} + +OUString DescriptionInfoset::getVersion() const +{ + return getNodeValueFromExpression( "desc:version/@value" ); +} + +css::uno::Sequence< OUString > DescriptionInfoset::getSupportedPlatforms() const +{ + //When there is no description.xml then we assume that we support all platforms + if (! m_element.is()) + { + return { OUString("all") }; + } + + //Check if the <platform> element was provided. If not the default is "all" platforms + css::uno::Reference< css::xml::dom::XNode > nodePlatform( + m_xpath->selectSingleNode(m_element, "desc:platform")); + if (!nodePlatform.is()) + { + return { OUString("all") }; + } + + //There is a platform element. + const OUString value = getNodeValueFromExpression("desc:platform/@value"); + //parse the string, it can contained multiple strings separated by commas + std::vector< OUString> vec; + sal_Int32 nIndex = 0; + do + { + const OUString aToken( o3tl::trim(o3tl::getToken(value, 0, ',', nIndex )) ); + if (!aToken.isEmpty()) + vec.push_back(aToken); + + } + while (nIndex >= 0); + + return comphelper::containerToSequence(vec); +} + +css::uno::Reference< css::xml::dom::XNodeList > +DescriptionInfoset::getDependencies() const { + if (m_element.is()) { + try { + // check the extension denylist first and expand the dependencies if applicable + checkDenylist(); + + return m_xpath->selectNodeList(m_element, "desc:dependencies/*"); + } catch (const css::xml::xpath::XPathException &) { + // ignore + } + } + return new EmptyNodeList; +} + +css::uno::Sequence< OUString > +DescriptionInfoset::getUpdateInformationUrls() const { + return getUrls("desc:update-information/desc:src/@xlink:href"); +} + +css::uno::Sequence< OUString > +DescriptionInfoset::getUpdateDownloadUrls() const +{ + return getUrls("desc:update-download/desc:src/@xlink:href"); +} + +OUString DescriptionInfoset::getIconURL( bool bHighContrast ) const +{ + css::uno::Sequence< OUString > aStrList = getUrls( "desc:icon/desc:default/@xlink:href" ); + css::uno::Sequence< OUString > aStrListHC = getUrls( "desc:icon/desc:high-contrast/@xlink:href" ); + + if ( bHighContrast && aStrListHC.hasElements() && !aStrListHC[0].isEmpty() ) + return aStrListHC[0]; + + if ( aStrList.hasElements() && !aStrList[0].isEmpty() ) + return aStrList[0]; + + return OUString(); +} + +::std::optional< OUString > DescriptionInfoset::getLocalizedUpdateWebsiteURL() + const +{ + bool bParentExists = false; + const OUString sURL (getLocalizedHREFAttrFromChild("/desc:description/desc:update-website", &bParentExists )); + + if (!sURL.isEmpty()) + return ::std::optional< OUString >(sURL); + else + return bParentExists ? ::std::optional< OUString >(OUString()) : + ::std::optional< OUString >(); +} + +::std::optional< OUString > DescriptionInfoset::getOptionalValue( + OUString const & expression) const +{ + css::uno::Reference< css::xml::dom::XNode > n; + if (m_element.is()) { + try { + n = m_xpath->selectSingleNode(m_element, expression); + } catch (const css::xml::xpath::XPathException &) { + // ignore + } + } + return n.is() + ? ::std::optional< OUString >(getNodeValue(n)) + : ::std::optional< OUString >(); +} + +css::uno::Sequence< OUString > DescriptionInfoset::getUrls( + OUString const & expression) const +{ + css::uno::Reference< css::xml::dom::XNodeList > ns; + if (m_element.is()) { + try { + ns = m_xpath->selectNodeList(m_element, expression); + } catch (const css::xml::xpath::XPathException &) { + // ignore + } + } + css::uno::Sequence< OUString > urls(ns.is() ? ns->getLength() : 0); + auto urlsRange = asNonConstRange(urls); + for (::sal_Int32 i = 0; i < urls.getLength(); ++i) { + urlsRange[i] = getNodeValue(ns->item(i)); + } + return urls; +} + +std::pair< OUString, OUString > DescriptionInfoset::getLocalizedPublisherNameAndURL() const +{ + css::uno::Reference< css::xml::dom::XNode > node = + getLocalizedChild("desc:publisher"); + + OUString sPublisherName; + OUString sURL; + if (node.is()) + { + css::uno::Reference< css::xml::dom::XNode > xPathName; + try { + xPathName = m_xpath->selectSingleNode(node, "text()"); + } catch (const css::xml::xpath::XPathException &) { + // ignore + } + OSL_ASSERT(xPathName.is()); + if (xPathName.is()) + sPublisherName = xPathName->getNodeValue(); + + css::uno::Reference< css::xml::dom::XNode > xURL; + try { + xURL = m_xpath->selectSingleNode(node, "@xlink:href"); + } catch (const css::xml::xpath::XPathException &) { + // ignore + } + OSL_ASSERT(xURL.is()); + if (xURL.is()) + sURL = xURL->getNodeValue(); + } + return std::make_pair(sPublisherName, sURL); +} + +OUString DescriptionInfoset::getLocalizedReleaseNotesURL() const +{ + return getLocalizedHREFAttrFromChild("/desc:description/desc:release-notes", nullptr); +} + +OUString DescriptionInfoset::getLocalizedDisplayName() const +{ + css::uno::Reference< css::xml::dom::XNode > node = + getLocalizedChild("desc:display-name"); + if (node.is()) + { + css::uno::Reference< css::xml::dom::XNode > xtext; + try { + xtext = m_xpath->selectSingleNode(node, "text()"); + } catch (const css::xml::xpath::XPathException &) { + // ignore + } + if (xtext.is()) + return xtext->getNodeValue(); + } + return OUString(); +} + +OUString DescriptionInfoset::getLocalizedLicenseURL() const +{ + return getLocalizedHREFAttrFromChild("/desc:description/desc:registration/desc:simple-license", nullptr); + +} + +::std::optional<SimpleLicenseAttributes> +DescriptionInfoset::getSimpleLicenseAttributes() const +{ + //Check if the node exist + css::uno::Reference< css::xml::dom::XNode > n; + if (m_element.is()) { + try { + n = m_xpath->selectSingleNode(m_element, "/desc:description/desc:registration/desc:simple-license/@accept-by"); + } catch (const css::xml::xpath::XPathException &) { + // ignore + } + if (n.is()) + { + SimpleLicenseAttributes attributes; + attributes.acceptBy = + getNodeValueFromExpression("/desc:description/desc:registration/desc:simple-license/@accept-by"); + + ::std::optional< OUString > suppressOnUpdate = getOptionalValue("/desc:description/desc:registration/desc:simple-license/@suppress-on-update"); + if (suppressOnUpdate) + attributes.suppressOnUpdate = o3tl::equalsIgnoreAsciiCase(o3tl::trim(*suppressOnUpdate), u"true"); + else + attributes.suppressOnUpdate = false; + + ::std::optional< OUString > suppressIfRequired = getOptionalValue("/desc:description/desc:registration/desc:simple-license/@suppress-if-required"); + if (suppressIfRequired) + attributes.suppressIfRequired = o3tl::equalsIgnoreAsciiCase(o3tl::trim(*suppressIfRequired), u"true"); + else + attributes.suppressIfRequired = false; + + return ::std::optional<SimpleLicenseAttributes>(attributes); + } + } + return ::std::optional<SimpleLicenseAttributes>(); +} + +OUString DescriptionInfoset::getLocalizedDescriptionURL() const +{ + return getLocalizedHREFAttrFromChild("/desc:description/desc:extension-description", nullptr); +} + +css::uno::Reference< css::xml::dom::XNode > +DescriptionInfoset::getLocalizedChild( const OUString & sParent) const +{ + if ( ! m_element.is() || sParent.isEmpty()) + return css::uno::Reference< css::xml::dom::XNode > (); + + css::uno::Reference< css::xml::dom::XNode > xParent; + try { + xParent = m_xpath->selectSingleNode(m_element, sParent); + } catch (const css::xml::xpath::XPathException &) { + // ignore + } + css::uno::Reference<css::xml::dom::XNode> nodeMatch; + if (xParent.is()) + { + nodeMatch = matchLanguageTag(xParent, getOfficeLanguageTag().getBcp47()); + + //office: en-DE, en, en-DE-altmark + if (! nodeMatch.is()) + { + // Already tried full tag, continue with first fallback. + const std::vector< OUString > aFallbacks( getOfficeLanguageTag().getFallbackStrings( false)); + for (auto const& fallback : aFallbacks) + { + nodeMatch = matchLanguageTag(xParent, fallback); + if (nodeMatch.is()) + break; + } + if (! nodeMatch.is()) + nodeMatch = getChildWithDefaultLocale(xParent); + } + } + + return nodeMatch; +} + +css::uno::Reference<css::xml::dom::XNode> +DescriptionInfoset::matchLanguageTag( + css::uno::Reference< css::xml::dom::XNode > const & xParent, std::u16string_view rTag) const +{ + OSL_ASSERT(xParent.is()); + css::uno::Reference<css::xml::dom::XNode> nodeMatch; + + //first try exact match for lang + const OUString exp1(OUString::Concat("*[@lang=\"") + rTag + "\"]"); + try { + nodeMatch = m_xpath->selectSingleNode(xParent, exp1); + } catch (const css::xml::xpath::XPathException &) { + // ignore + } + + //try to match in strings that also have a country and/or variant, for + //example en matches in en-US-montana, en-US, en-montana + if (!nodeMatch.is()) + { + const OUString exp2( + OUString::Concat("*[starts-with(@lang,\"") + rTag + "-\")]"); + try { + nodeMatch = m_xpath->selectSingleNode(xParent, exp2); + } catch (const css::xml::xpath::XPathException &) { + // ignore + } + } + return nodeMatch; +} + +css::uno::Reference<css::xml::dom::XNode> +DescriptionInfoset::getChildWithDefaultLocale(css::uno::Reference< css::xml::dom::XNode > + const & xParent) const +{ + OSL_ASSERT(xParent.is()); + if ( xParent->getNodeName() == "simple-license" ) + { + css::uno::Reference<css::xml::dom::XNode> nodeDefault; + try { + nodeDefault = m_xpath->selectSingleNode(xParent, "@default-license-id"); + } catch (const css::xml::xpath::XPathException &) { + // ignore + } + if (nodeDefault.is()) + { + //The old way + const OUString exp1("desc:license-text[@license-id = \"" + + nodeDefault->getNodeValue() + + "\"]"); + try { + return m_xpath->selectSingleNode(xParent, exp1); + } catch (const css::xml::xpath::XPathException &) { + // ignore + } + } + } + + try { + return m_xpath->selectSingleNode(xParent, "*[1]"); + } catch (const css::xml::xpath::XPathException &) { + // ignore + return nullptr; + } +} + +OUString DescriptionInfoset::getLocalizedHREFAttrFromChild( + OUString const & sXPathParent, bool * out_bParentExists) + const +{ + css::uno::Reference< css::xml::dom::XNode > node = + getLocalizedChild(sXPathParent); + + OUString sURL; + if (node.is()) + { + if (out_bParentExists) + *out_bParentExists = true; + css::uno::Reference< css::xml::dom::XNode > xURL; + try { + xURL = m_xpath->selectSingleNode(node, "@xlink:href"); + } catch (const css::xml::xpath::XPathException &) { + // ignore + } + OSL_ASSERT(xURL.is()); + if (xURL.is()) + sURL = xURL->getNodeValue(); + } + else + { + if (out_bParentExists) + *out_bParentExists = false; + } + return sURL; +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/misc/dp_identifier.cxx b/desktop/source/deployment/misc/dp_identifier.cxx new file mode 100644 index 0000000000..8669710c7b --- /dev/null +++ b/desktop/source/deployment/misc/dp_identifier.cxx @@ -0,0 +1,56 @@ +/* -*- 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 <optional> +#include <com/sun/star/beans/Optional.hpp> +#include <com/sun/star/deployment/XPackage.hpp> +#include <com/sun/star/uno/Reference.hxx> +#include <osl/diagnose.h> +#include <rtl/ustring.hxx> + +#include <dp_identifier.hxx> + +namespace dp_misc { + +OUString generateIdentifier( + ::std::optional< OUString > const & optional, + std::u16string_view fileName) +{ + return optional ? *optional : generateLegacyIdentifier(fileName); +} + +OUString getIdentifier( + css::uno::Reference< css::deployment::XPackage > const & package) +{ + OSL_ASSERT(package.is()); + css::beans::Optional< OUString > id(package->getIdentifier()); + return id.IsPresent + ? id.Value : generateLegacyIdentifier(package->getName()); +} + +OUString generateLegacyIdentifier(std::u16string_view fileName) { + return OUString::Concat("org.openoffice.legacy.") + fileName; +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/misc/dp_interact.cxx b/desktop/source/deployment/misc/dp_interact.cxx new file mode 100644 index 0000000000..ae928a28e8 --- /dev/null +++ b/desktop/source/deployment/misc/dp_interact.cxx @@ -0,0 +1,137 @@ +/* -*- 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 <dp_interact.h> + +#include <comphelper/interaction.hxx> + +#include <com/sun/star/task/XInteractionAbort.hpp> +#include <osl/diagnose.h> + + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::ucb; + +namespace dp_misc { +namespace { + + +class InteractionContinuationImpl : public ::cppu::OWeakObject, + public task::XInteractionContinuation +{ + const Type m_type; + bool * m_pselect; + +public: + InteractionContinuationImpl( Type const & type, bool * pselect ) + : m_type( type ), + m_pselect( pselect ) + { OSL_ASSERT( + cppu::UnoType<task::XInteractionContinuation>::get().isAssignableFrom(m_type) ); } + + // XInterface + virtual void SAL_CALL acquire() noexcept override; + virtual void SAL_CALL release() noexcept override; + virtual Any SAL_CALL queryInterface( Type const & type ) override; + + // XInteractionContinuation + virtual void SAL_CALL select() override; +}; + +// XInterface + +void InteractionContinuationImpl::acquire() noexcept +{ + OWeakObject::acquire(); +} + + +void InteractionContinuationImpl::release() noexcept +{ + OWeakObject::release(); +} + + +Any InteractionContinuationImpl::queryInterface( Type const & type ) +{ + if (type.isAssignableFrom( m_type )) { + Reference<task::XInteractionContinuation> xThis(this); + return Any( &xThis, type ); + } + else + return OWeakObject::queryInterface(type); +} + +// XInteractionContinuation + +void InteractionContinuationImpl::select() +{ + *m_pselect = true; +} + +} // anon namespace + + +bool interactContinuation( Any const & request, + Type const & continuation, + Reference<XCommandEnvironment> const & xCmdEnv, + bool * pcont, bool * pabort ) +{ + OSL_ASSERT( + cppu::UnoType<task::XInteractionContinuation>::get().isAssignableFrom( + continuation ) ); + if (!xCmdEnv) + return false; + + Reference<task::XInteractionHandler> xInteractionHandler( + xCmdEnv->getInteractionHandler() ); + if (!xInteractionHandler) + return false; + + bool cont = false; + bool abort = false; + std::vector< Reference<task::XInteractionContinuation> > conts { + new InteractionContinuationImpl(continuation, &cont ), + new InteractionContinuationImpl( cppu::UnoType<task::XInteractionAbort>::get(), &abort ) }; + xInteractionHandler->handle( + new ::comphelper::OInteractionRequest( request, std::move(conts) ) ); + if (cont || abort) { + if (pcont != nullptr) + *pcont = cont; + if (pabort != nullptr) + *pabort = abort; + return true; + } + return false; +} + +// XAbortChannel + +void AbortChannel::sendAbort() +{ + m_aborted = true; + if (m_xNext.is()) + m_xNext->sendAbort(); +} + +} // dp_misc + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/misc/dp_misc.cxx b/desktop/source/deployment/misc/dp_misc.cxx new file mode 100644 index 0000000000..01fb414a79 --- /dev/null +++ b/desktop/source/deployment/misc/dp_misc.cxx @@ -0,0 +1,562 @@ +/* -*- 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 <chrono> + +#include <dp_misc.h> +#include <dp_interact.h> +#include <dp_shared.hxx> +#include <o3tl/string_view.hxx> +#include <rtl/uri.hxx> +#include <rtl/digest.h> +#include <rtl/random.h> +#include <rtl/bootstrap.hxx> +#include <rtl/ustrbuf.hxx> +#include <sal/log.hxx> +#include <unotools/bootstrap.hxx> +#include <osl/file.hxx> +#include <osl/pipe.hxx> +#include <osl/security.hxx> +#include <osl/thread.hxx> +#include <com/sun/star/ucb/CommandAbortedException.hpp> +#include <com/sun/star/task/XInteractionHandler.hpp> +#include <com/sun/star/bridge/BridgeFactory.hpp> +#include <com/sun/star/bridge/UnoUrlResolver.hpp> +#include <com/sun/star/bridge/XUnoUrlResolver.hpp> +#include <com/sun/star/deployment/ExtensionManager.hpp> +#include <com/sun/star/lang/DisposedException.hpp> +#include <com/sun/star/task/OfficeRestartManager.hpp> +#include <memory> +#include <string_view> +#include <thread> +#include <comphelper/lok.hxx> +#include <comphelper/processfactory.hxx> +#include <salhelper/linkhelper.hxx> + +#ifdef _WIN32 +#include <prewin.h> +#include <postwin.h> +#endif + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; + +namespace dp_misc { +namespace { + +std::shared_ptr<rtl::Bootstrap> & UnoRc() +{ + static std::shared_ptr<rtl::Bootstrap> theRc = []() + { + OUString unorc( "$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE("louno") ); + ::rtl::Bootstrap::expandMacros( unorc ); + auto ret = std::make_shared<::rtl::Bootstrap>( unorc ); + OSL_ASSERT( ret->getHandle() != nullptr ); + return ret; + }(); + return theRc; +}; + +OUString generateOfficePipeId() +{ + OUString userPath; + ::utl::Bootstrap::PathStatus aLocateResult = + ::utl::Bootstrap::locateUserInstallation( userPath ); + if (aLocateResult != ::utl::Bootstrap::PATH_EXISTS && + aLocateResult != ::utl::Bootstrap::PATH_VALID) + { + throw Exception("Extension Manager: Could not obtain path for UserInstallation.", nullptr); + } + + rtlDigest digest = rtl_digest_create( rtl_Digest_AlgorithmMD5 ); + if (!digest) { + throw RuntimeException("cannot get digest rtl_Digest_AlgorithmMD5!", nullptr ); + } + + sal_uInt8 const * data = + reinterpret_cast<sal_uInt8 const *>(userPath.getStr()); + std::size_t size = userPath.getLength() * sizeof (sal_Unicode); + sal_uInt32 md5_key_len = rtl_digest_queryLength( digest ); + std::unique_ptr<sal_uInt8[]> md5_buf( new sal_uInt8 [ md5_key_len ] ); + + rtl_digest_init( digest, data, static_cast<sal_uInt32>(size) ); + rtl_digest_update( digest, data, static_cast<sal_uInt32>(size) ); + rtl_digest_get( digest, md5_buf.get(), md5_key_len ); + rtl_digest_destroy( digest ); + + // create hex-value string from the MD5 value to keep + // the string size minimal + OUStringBuffer buf( "SingleOfficeIPC_" ); + for ( sal_uInt32 i = 0; i < md5_key_len; ++i ) { + buf.append( static_cast<sal_Int32>(md5_buf[ i ]), 0x10 ); + } + return buf.makeStringAndClear(); +} + +bool existsOfficePipe() +{ + static OUString OfficePipeId = generateOfficePipeId(); + + OUString const & pipeId = OfficePipeId; + if (pipeId.isEmpty()) + return false; + ::osl::Security sec; + ::osl::Pipe pipe( pipeId, osl_Pipe_OPEN, sec ); + return pipe.is(); +} + +//get modification time +bool getModifyTimeTargetFile(const OUString &rFileURL, TimeValue &rTime) +{ + salhelper::LinkResolver aResolver(osl_FileStatus_Mask_ModifyTime); + + if (aResolver.fetchFileStatus(rFileURL) != osl::FileBase::E_None) + return false; + + rTime = aResolver.m_aStatus.getModifyTime(); + + return true; +} + +//Returns true if the Folder was more recently modified then +//the lastsynchronized file. That is the repository needs to +//be synchronized. +bool compareExtensionFolderWithLastSynchronizedFile( + OUString const & folderURL, OUString const & fileURL) +{ + bool bNeedsSync = false; + ::osl::DirectoryItem itemExtFolder; + ::osl::File::RC err1 = + ::osl::DirectoryItem::get(folderURL, itemExtFolder); + //If it does not exist, then there is nothing to be done + if (err1 == ::osl::File::E_NOENT) + { + return false; + } + else if (err1 != ::osl::File::E_None) + { + OSL_FAIL("Cannot access extension folder"); + return true; //sync just in case + } + + //If last synchronized does not exist, then OOo is started for the first time + ::osl::DirectoryItem itemFile; + ::osl::File::RC err2 = ::osl::DirectoryItem::get(fileURL, itemFile); + if (err2 == ::osl::File::E_NOENT) + { + return true; + + } + else if (err2 != ::osl::File::E_None) + { + OSL_FAIL("Cannot access file lastsynchronized"); + return true; //sync just in case + } + + //compare the modification time of the extension folder and the last + //modified file + TimeValue timeFolder; + if (getModifyTimeTargetFile(folderURL, timeFolder)) + { + TimeValue timeFile; + if (getModifyTimeTargetFile(fileURL, timeFile)) + { + if (timeFile.Seconds < timeFolder.Seconds) + bNeedsSync = true; + } + else + { + OSL_ASSERT(false); + bNeedsSync = true; + } + } + else + { + OSL_ASSERT(false); + bNeedsSync = true; + } + + return bNeedsSync; +} + +bool needToSyncRepository(std::u16string_view name) +{ + OUString folder; + OUString file; + if ( name == u"bundled" ) + { + folder = "$BUNDLED_EXTENSIONS"; + file = "$BUNDLED_EXTENSIONS_USER/lastsynchronized"; + } + else if ( name == u"shared" ) + { + folder = "$UNO_SHARED_PACKAGES_CACHE/uno_packages"; + file = "$SHARED_EXTENSIONS_USER/lastsynchronized"; + } + else + { + OSL_ASSERT(false); + return true; + } + ::rtl::Bootstrap::expandMacros(folder); + ::rtl::Bootstrap::expandMacros(file); + return compareExtensionFolderWithLastSynchronizedFile( + folder, file); +} + + +} // anon namespace + + +namespace { +OUString encodeForRcFile( std::u16string_view str ) +{ + // escape $\{} (=> rtl bootstrap files) + OUStringBuffer buf(64); + size_t pos = 0; + const size_t len = str.size(); + for ( ; pos < len; ++pos ) { + sal_Unicode c = str[ pos ]; + switch (c) { + case '$': + case '\\': + case '{': + case '}': + buf.append( '\\' ); + break; + } + buf.append( c ); + } + return buf.makeStringAndClear(); +} +} + + +OUString makeURL( std::u16string_view baseURL, OUString const & relPath_ ) +{ + OUStringBuffer buf(128); + if (baseURL.size() > 1 && baseURL[ baseURL.size() - 1 ] == '/') + buf.append( baseURL.substr(0, baseURL.size() - 1) ); + else + buf.append( baseURL ); + OUString relPath(relPath_); + if( relPath.startsWith("/") ) + relPath = relPath.copy( 1 ); + if (!relPath.isEmpty()) + { + buf.append( '/' ); + if (o3tl::starts_with(baseURL, u"vnd.sun.star.expand:" )) { + // encode for macro expansion: relPath is supposed to have no + // macros, so encode $, {} \ (bootstrap mimic) + relPath = encodeForRcFile(relPath); + + // encode once more for vnd.sun.star.expand schema: + // vnd.sun.star.expand:$UNO_... + // will expand to file-url + relPath = ::rtl::Uri::encode( relPath, rtl_UriCharClassUric, + rtl_UriEncodeIgnoreEscapes, + RTL_TEXTENCODING_UTF8 ); + } + buf.append( relPath ); + } + return buf.makeStringAndClear(); +} + +OUString makeURLAppendSysPathSegment( std::u16string_view baseURL, OUString const & segment ) +{ + OSL_ASSERT(segment.indexOf(u'/') == -1); + + ::rtl::Uri::encode( + segment, rtl_UriCharClassPchar, rtl_UriEncodeIgnoreEscapes, + RTL_TEXTENCODING_UTF8); + return makeURL(baseURL, segment); +} + + +OUString expandUnoRcTerm( OUString const & term_ ) +{ + OUString term(term_); + UnoRc()->expandMacrosFrom( term ); + return term; +} + +OUString makeRcTerm( OUString const & url ) +{ + OSL_ASSERT( url.match( "vnd.sun.star.expand:" )); + if (OUString rcterm; url.startsWithIgnoreAsciiCase("vnd.sun.star.expand:", &rcterm)) { + // decode uric class chars: + rcterm = ::rtl::Uri::decode( + rcterm, rtl_UriDecodeWithCharset, RTL_TEXTENCODING_UTF8 ); + return rcterm; + } + else + return url; +} + + +OUString expandUnoRcUrl( OUString const & url ) +{ + if (OUString rcurl; url.startsWithIgnoreAsciiCase("vnd.sun.star.expand:", &rcurl)) { + // decode uric class chars: + rcurl = ::rtl::Uri::decode( + rcurl, rtl_UriDecodeWithCharset, RTL_TEXTENCODING_UTF8 ); + // expand macro string: + UnoRc()->expandMacrosFrom( rcurl ); + return rcurl; + } + else { + return url; + } +} + + +bool office_is_running() +{ + //We need to check if we run within the office process. Then we must not use the pipe, because + //this could cause a deadlock. This is actually a workaround for i82778 + OUString sFile; + oslProcessError err = osl_getExecutableFile(& sFile.pData); + bool ret = false; + if (osl_Process_E_None == err) + { + sFile = sFile.copy(sFile.lastIndexOf('/') + 1); + if ( +#if defined _WIN32 + //osl_getExecutableFile should deliver "soffice.bin" on windows + //even if swriter.exe, scalc.exe etc. was started. This is a bug + //in osl_getExecutableFile + sFile == "soffice.bin" || sFile == "soffice.exe" || sFile == "soffice.com" + || sFile == "soffice" || sFile == "swriter.exe" || sFile == "swriter" + || sFile == "scalc.exe" || sFile == "scalc" || sFile == "simpress.exe" + || sFile == "simpress" || sFile == "sdraw.exe" || sFile == "sdraw" + || sFile == "sbase.exe" || sFile == "sbase" +#elif defined MACOSX + sFile == "soffice" +#elif defined UNIX + sFile == "soffice.bin" +#else +#error "Unsupported platform" +#endif + + ) + ret = true; + else + ret = existsOfficePipe(); + } + else + { + OSL_FAIL("NOT osl_Process_E_None "); + //if osl_getExecutable file then we take the risk of creating a pipe + ret = existsOfficePipe(); + } + return ret; +} + + +oslProcess raiseProcess( + OUString const & appURL, Sequence<OUString> const & args ) +{ + ::osl::Security sec; + oslProcess hProcess = nullptr; + oslProcessError rc = osl_executeProcess( + appURL.pData, + reinterpret_cast<rtl_uString **>( + const_cast<OUString *>(args.getConstArray()) ), + args.getLength(), + osl_Process_DETACHED, + sec.getHandle(), + nullptr, // => current working dir + nullptr, 0, // => no env vars + &hProcess ); + + switch (rc) { + case osl_Process_E_None: + break; + case osl_Process_E_NotFound: + throw RuntimeException( "image not found!", nullptr ); + case osl_Process_E_TimedOut: + throw RuntimeException( "timeout occurred!", nullptr ); + case osl_Process_E_NoPermission: + throw RuntimeException( "permission denied!", nullptr ); + case osl_Process_E_Unknown: + throw RuntimeException( "unknown error!", nullptr ); + case osl_Process_E_InvalidError: + default: + throw RuntimeException( "unmapped error!", nullptr ); + } + + return hProcess; +} + + +OUString generateRandomPipeId() +{ + // compute some good pipe id: + static rtlRandomPool s_hPool = rtl_random_createPool(); + if (s_hPool == nullptr) + throw RuntimeException( "cannot create random pool!?", nullptr ); + sal_uInt8 bytes[ 32 ]; + if (rtl_random_getBytes( + s_hPool, bytes, std::size(bytes) ) != rtl_Random_E_None) { + throw RuntimeException( "random pool error!?", nullptr ); + } + OUStringBuffer buf; + for (unsigned char byte : bytes) { + buf.append( static_cast<sal_Int32>(byte), 0x10 ); + } + return buf.makeStringAndClear(); +} + + +Reference<XInterface> resolveUnoURL( + OUString const & connectString, + Reference<XComponentContext> const & xLocalContext, + AbortChannel const * abortChannel ) +{ + Reference<bridge::XUnoUrlResolver> xUnoUrlResolver( + bridge::UnoUrlResolver::create( xLocalContext ) ); + + for (int i = 0; i <= 40; ++i) // 20 seconds + { + if (abortChannel != nullptr && abortChannel->isAborted()) { + throw ucb::CommandAbortedException( "abort!" ); + } + try { + return xUnoUrlResolver->resolve( connectString ); + } + catch (const connection::NoConnectException &) { + if (i < 40) + { + std::this_thread::sleep_for( std::chrono::milliseconds(500) ); + } + else throw; + } + } + return nullptr; // warning C4715 +} + +static void writeConsoleWithStream(std::u16string_view sText, FILE * stream) +{ + OString s = OUStringToOString(sText, osl_getThreadTextEncoding()); + fprintf(stream, "%s", s.getStr()); + fflush(stream); +} + +void writeConsole(std::u16string_view sText) +{ + writeConsoleWithStream(sText, stdout); +} + +void writeConsoleError(std::u16string_view sText) +{ + writeConsoleWithStream(sText, stderr); +} + +OUString readConsole() +{ + char buf[1024]; + memset(buf, 0, 1024); + // read one char less so that the last char in buf is always zero + if (fgets(buf, 1024, stdin) != nullptr) + { + OUString value = OStringToOUString(std::string_view(buf), osl_getThreadTextEncoding()); + return value.trim(); + } + throw css::uno::RuntimeException("reading from stdin failed"); +} + +void TRACE(OUString const & sText) +{ + SAL_INFO("desktop.deployment", sText); +} + +void syncRepositories( + bool force, Reference<ucb::XCommandEnvironment> const & xCmdEnv) +{ + OUString sDisable; + ::rtl::Bootstrap::get( "DISABLE_EXTENSION_SYNCHRONIZATION", sDisable, OUString() ); + if (!sDisable.isEmpty()) + return; + + Reference<deployment::XExtensionManager> xExtensionManager; + //synchronize shared before bundled otherwise there are + //more revoke and registration calls. + bool bModified = false; + if (force || needToSyncRepository(u"shared") || needToSyncRepository(u"bundled")) + { + xExtensionManager = + deployment::ExtensionManager::get( + comphelper::getProcessComponentContext()); + + if (xExtensionManager.is()) + { + bModified = xExtensionManager->synchronize( + Reference<task::XAbortChannel>(), xCmdEnv); + } + } +#if HAVE_FEATURE_MACOSX_SANDBOX + (void) bModified; +#else + if (bModified && !comphelper::LibreOfficeKit::isActive()) + { + Reference<task::XRestartManager> restarter(task::OfficeRestartManager::get(comphelper::getProcessComponentContext())); + if (restarter.is()) + { + restarter->requestRestart(xCmdEnv.is() ? xCmdEnv->getInteractionHandler() : + Reference<task::XInteractionHandler>()); + } + } +#endif +} + +void disposeBridges(Reference<css::uno::XComponentContext> const & ctx) +{ + if (!ctx.is()) + return; + + Reference<css::bridge::XBridgeFactory2> bridgeFac( css::bridge::BridgeFactory::create(ctx) ); + + const Sequence< Reference<css::bridge::XBridge> >seqBridges = bridgeFac->getExistingBridges(); + for (const Reference<css::bridge::XBridge>& bridge : seqBridges) + { + Reference<css::lang::XComponent> comp(bridge, UNO_QUERY); + if (comp.is()) + { + try { + comp->dispose(); + } + catch ( const css::lang::DisposedException& ) + { + } + } + } +} + +} + +OUString DpResId(TranslateId aId) +{ + static std::locale SINGLETON = Translate::Create("dkt"); + return Translate::get(aId, SINGLETON); +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/misc/dp_platform.cxx b/desktop/source/deployment/misc/dp_platform.cxx new file mode 100644 index 0000000000..b2af59f9b9 --- /dev/null +++ b/desktop/source/deployment/misc/dp_platform.cxx @@ -0,0 +1,210 @@ +/* -*- 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 <dp_platform.hxx> +#include <rtl/ustring.hxx> +#include <rtl/instance.hxx> +#include <rtl/bootstrap.hxx> +#include <osl/diagnose.h> +#include <o3tl/string_view.hxx> + +constexpr OUStringLiteral PLATFORM_ALL = u"all"; + + +namespace dp_misc +{ +namespace +{ + const OUString & StrOperatingSystem() + { + static const OUString theOS = []() + { + OUString os( "$_OS" ); + ::rtl::Bootstrap::expandMacros( os ); + return os; + }(); + return theOS; + }; + + const OUString & StrCPU() + { + static const OUString theCPU = []() + { + OUString arch( "$_ARCH" ); + ::rtl::Bootstrap::expandMacros( arch ); + return arch; + }(); + return theCPU; + }; + + + const OUString & StrPlatform() + { + static const OUString thePlatform = StrOperatingSystem() + "_" + StrCPU(); + return thePlatform; + }; + + bool checkOSandCPU(std::u16string_view os, std::u16string_view cpu) + { + return (os == StrOperatingSystem()) + && (cpu == StrCPU()); + } + + bool isPlatformSupported( std::u16string_view token ) + { + bool ret = false; + if (token == PLATFORM_ALL) + ret = true; + else if (token == u"windows_x86") + ret = checkOSandCPU(u"Windows", u"x86"); + else if (token == u"windows_x86_64") + ret = checkOSandCPU(u"Windows", u"X86_64"); + else if (token == u"windows_aarch64") + ret = checkOSandCPU(u"Windows", u"AARCH64"); + else if (token == u"solaris_sparc") + ret = checkOSandCPU(u"Solaris", u"SPARC"); + else if (token == u"solaris_sparc64") + ret = checkOSandCPU(u"Solaris", u"SPARC64"); + else if (token == u"solaris_x86") + ret = checkOSandCPU(u"Solaris", u"x86"); + else if (token == u"macosx_aarch64") + ret = checkOSandCPU(u"MacOSX", u"AARCH64"); + else if (token == u"macosx_x86_64") + ret = checkOSandCPU(u"MacOSX", u"X86_64"); + else if (token == u"linux_x86") + ret = checkOSandCPU(u"Linux", u"x86"); + else if (token == u"linux_x86_64") + ret = checkOSandCPU(u"Linux", u"X86_64"); + else if (token == u"linux_sparc") + ret = checkOSandCPU(u"Linux", u"SPARC"); + else if (token == u"linux_sparc64") + ret = checkOSandCPU(u"Linux", u"SPARC64"); + else if (token == u"linux_powerpc") + ret = checkOSandCPU(u"Linux", u"PowerPC"); + else if (token == u"linux_powerpc64") + ret = checkOSandCPU(u"Linux", u"PowerPC_64"); + else if (token == u"linux_powerpc64_le") + ret = checkOSandCPU(u"Linux", u"PowerPC_64_LE"); + else if (token == u"linux_arm_eabi") + ret = checkOSandCPU(u"Linux", u"ARM_EABI"); + else if (token == u"linux_arm_oabi") + ret = checkOSandCPU(u"Linux", u"ARM_OABI"); + else if (token == u"linux_mips_el") + ret = checkOSandCPU(u"Linux", u"MIPS_EL"); + else if (token == u"linux_mips64_el") + ret = checkOSandCPU(u"Linux", u"MIPS64_EL"); + else if (token == u"linux_mips_eb") + ret = checkOSandCPU(u"Linux", u"MIPS_EB"); + else if (token == u"linux_mips64_eb") + ret = checkOSandCPU(u"Linux", u"MIPS64_EB"); + else if (token == u"linux_ia64") + ret = checkOSandCPU(u"Linux", u"IA64"); + else if (token == u"linux_m68k") + ret = checkOSandCPU(u"Linux", u"M68K"); + else if (token == u"linux_s390x") + ret = checkOSandCPU(u"Linux", u"S390x"); + else if (token == u"linux_hppa") + ret = checkOSandCPU(u"Linux", u"HPPA"); + else if (token == u"linux_alpha") + ret = checkOSandCPU(u"Linux", u"ALPHA"); + else if (token == u"linux_aarch64") + ret = checkOSandCPU(u"Linux", u"AARCH64"); + else if (token == u"linux_riscv64") + ret = checkOSandCPU(u"Linux", u"RISCV64"); + else if (token == u"linux_loongarch64") + ret = checkOSandCPU(u"Linux", u"LOONGARCH64"); + else if (token == u"freebsd_x86") + ret = checkOSandCPU(u"FreeBSD", u"x86"); + else if (token == u"freebsd_x86_64") + ret = checkOSandCPU(u"FreeBSD", u"X86_64"); + else if (token == u"freebsd_powerpc") + ret = checkOSandCPU(u"FreeBSD", u"PowerPC"); + else if (token == u"freebsd_powerpc64") + ret = checkOSandCPU(u"FreeBSD", u"PowerPC64"); + else if (token == u"kfreebsd_x86") + ret = checkOSandCPU(u"kFreeBSD", u"x86"); + else if (token == u"kfreebsd_x86_64") + ret = checkOSandCPU(u"kFreeBSD", u"X86_64"); + else if (token == u"netbsd_x86") + ret = checkOSandCPU(u"NetBSD", u"x86"); + else if (token == u"netbsd_x86_64") + ret = checkOSandCPU(u"NetBSD", u"X86_64"); + else if (token == u"openbsd_x86") + ret = checkOSandCPU(u"OpenBSD", u"x86"); + else if (token == u"openbsd_x86_64") + ret = checkOSandCPU(u"OpenBSD", u"X86_64"); + else if (token == u"dragonfly_x86") + ret = checkOSandCPU(u"DragonFly", u"x86"); + else if (token == u"dragonfly_x86_64") + ret = checkOSandCPU(u"DragonFly", u"X86_64"); + else + { + OSL_FAIL("Extension Manager: The extension supports an unknown platform. " + "Check the platform in the description.xml"); + ret = false; + } + return ret; + } + +} // anon namespace + + +OUString const & getPlatformString() +{ + return StrPlatform(); +} + +bool platform_fits( std::u16string_view platform_string ) +{ + sal_Int32 index = 0; + for (;;) + { + const std::u16string_view token( + o3tl::trim(o3tl::getToken(platform_string, 0, ',', index )) ); + // check if this platform: + if (o3tl::equalsIgnoreAsciiCase( token, StrPlatform() ) || + (token.find( '_' ) == std::u16string_view::npos && /* check OS part only */ + o3tl::equalsIgnoreAsciiCase( token, StrOperatingSystem() ))) + { + return true; + } + if (index < 0) + break; + } + return false; +} + +bool hasValidPlatform( css::uno::Sequence<OUString> const & platformStrings) +{ + bool ret = false; + for (const OUString& s : platformStrings) + { + if ( isPlatformSupported( s ) ) + { + ret = true; + break; + } + } + return ret; +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/misc/dp_resource.cxx b/desktop/source/deployment/misc/dp_resource.cxx new file mode 100644 index 0000000000..682c90e524 --- /dev/null +++ b/desktop/source/deployment/misc/dp_resource.cxx @@ -0,0 +1,42 @@ +/* -*- 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 <dp_resource.h> +#include <unotools/configmgr.hxx> +#include <i18nlangtag/languagetag.hxx> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; + +namespace dp_misc +{ +const LanguageTag& getOfficeLanguageTag() +{ + static const LanguageTag OFFICE_LANG = []() { + OUString slang(utl::ConfigManager::getUILocale()); + //fallback, the locale is currently only set when the user starts the + //office for the first time. + if (slang.isEmpty()) + slang = "en-US"; + return LanguageTag(slang); + }(); + return OFFICE_LANG; +} +} +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/misc/dp_ucb.cxx b/desktop/source/deployment/misc/dp_ucb.cxx new file mode 100644 index 0000000000..5ca42f31ae --- /dev/null +++ b/desktop/source/deployment/misc/dp_ucb.cxx @@ -0,0 +1,304 @@ +/* -*- 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 <dp_misc.h> +#include <dp_ucb.h> +#include <rtl/uri.hxx> +#include <rtl/ustrbuf.hxx> +#include <ucbhelper/content.hxx> +#include <xmlscript/xml_helper.hxx> +#include <com/sun/star/io/XOutputStream.hpp> +#include <com/sun/star/ucb/CommandFailedException.hpp> +#include <com/sun/star/ucb/ContentInfo.hpp> +#include <com/sun/star/ucb/ContentInfoAttribute.hpp> +#include <com/sun/star/ucb/ContentCreationException.hpp> +#include <comphelper/processfactory.hxx> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::ucb; + +namespace dp_misc +{ + + +bool create_ucb_content( + ::ucbhelper::Content * ret_ucbContent, OUString const & url, + Reference<XCommandEnvironment> const & xCmdEnv, + bool throw_exc ) +{ + try { + // Existence check... + // content ctor/isFolder() will throw exception in case the resource + // does not exist. + + // dilemma: no chance to use the given handler here, because it would + // raise no such file dialogs, else no interaction for + // passwords, ...? xxx todo + ::ucbhelper::Content ucbContent( + url, Reference<XCommandEnvironment>(), + comphelper::getProcessComponentContext() ); + + ucbContent.isFolder(); + + if (ret_ucbContent != nullptr) + { + ucbContent.setCommandEnvironment( xCmdEnv ); + *ret_ucbContent = ucbContent; + } + return true; + } + catch (const RuntimeException &) { + throw; + } + catch (const Exception &) { + if (throw_exc) + throw; + } + return false; +} + + +bool create_folder( + ::ucbhelper::Content * ret_ucb_content, OUString const & url_, + Reference<XCommandEnvironment> const & xCmdEnv, bool throw_exc ) +{ + ::ucbhelper::Content ucb_content; + if (create_ucb_content( + &ucb_content, url_, xCmdEnv, false /* no throw */ )) + { + if (ucb_content.isFolder()) { + if (ret_ucb_content != nullptr) + *ret_ucb_content = ucb_content; + return true; + } + } + + OUString url( url_ ); + // xxx todo: find parent + sal_Int32 slash = url.lastIndexOf( '/' ); + if (slash < 0) { + // fallback: + url = expandUnoRcUrl( url ); + slash = url.lastIndexOf( '/' ); + } + if (slash < 0) { + // invalid: has to be at least "auth:/..." + if (throw_exc) + throw ContentCreationException( + "Cannot create folder (invalid path): '" + url + "'", + Reference<XInterface>(), ContentCreationError_UNKNOWN ); + return false; + } + ::ucbhelper::Content parentContent; + if (! create_folder( + &parentContent, url.copy( 0, slash ), xCmdEnv, throw_exc )) + return false; + const Any title( ::rtl::Uri::decode( url.copy( slash + 1 ), + rtl_UriDecodeWithCharset, + RTL_TEXTENCODING_UTF8 ) ); + const Sequence<ContentInfo> infos( + parentContent.queryCreatableContentsInfo() ); + for ( ContentInfo const & info : infos ) + { + // look KIND_FOLDER: + if ((info.Attributes & ContentInfoAttribute::KIND_FOLDER) != 0) + { + // make sure the only required bootstrap property is "Title": + Sequence<beans::Property> const & rProps = info.Properties; + if ( rProps.getLength() != 1 || rProps[ 0 ].Name != "Title" ) + continue; + + try { + if (parentContent.insertNewContent( + info.Type, + StrTitle::getTitleSequence(), + Sequence<Any>( &title, 1 ), + ucb_content )) { + if (ret_ucb_content != nullptr) + *ret_ucb_content = ucb_content; + return true; + } + } + catch (const RuntimeException &) { + throw; + } + catch (const CommandFailedException &) { + // Interaction Handler already handled the error + // that has occurred... + } + catch (const Exception &) { + if (throw_exc) + throw; + return false; + } + } + } + if (throw_exc) + throw ContentCreationException( + "Cannot create folder: '" + url + "'", + Reference<XInterface>(), ContentCreationError_UNKNOWN ); + return false; +} + + +bool erase_path( OUString const & url, + Reference<XCommandEnvironment> const & xCmdEnv, + bool throw_exc ) +{ + ::ucbhelper::Content ucb_content; + if (create_ucb_content( &ucb_content, url, xCmdEnv, false /* no throw */ )) + { + try { + ucb_content.executeCommand( + "delete", Any( true /* delete physically */ ) ); + } + catch (const RuntimeException &) { + throw; + } + catch (const Exception &) { + if (throw_exc) + throw; + return false; + } + } + return true; +} + + +std::vector<sal_Int8> readFile( ::ucbhelper::Content & ucb_content ) +{ + std::vector<sal_Int8> bytes; + Reference<io::XOutputStream> xStream( + ::xmlscript::createOutputStream( &bytes ) ); + if (! ucb_content.openStream( xStream )) + throw RuntimeException( + "::ucbhelper::Content::openStream( XOutputStream ) failed!", + nullptr ); + return bytes; +} + + +bool readLine( OUString * res, std::u16string_view startingWith, + ::ucbhelper::Content & ucb_content, rtl_TextEncoding textenc ) +{ + // read whole file: + std::vector<sal_Int8> bytes( readFile( ucb_content ) ); + OUString file( reinterpret_cast<char const *>(bytes.data()), + bytes.size(), textenc ); + sal_Int32 pos = 0; + for (;;) + { + if (file.match( startingWith, pos )) + { + OUStringBuffer buf; + sal_Int32 start = pos; + pos += startingWith.size(); + for (;;) + { + pos = file.indexOf( LF, pos ); + if (pos < 0) { // EOF + buf.append( file.subView(start) ); + } + else + { + if (pos > 0 && file[ pos - 1 ] == CR) + { + // consume extra CR + buf.append( file.subView(start, pos - start - 1) ); + ++pos; + } + else + buf.append( file.subView(start, pos - start) ); + ++pos; // consume LF + // check next line: + if (pos < file.getLength() && + (file[ pos ] == ' ' || file[ pos ] == '\t')) + { + buf.append( ' ' ); + ++pos; + start = pos; + continue; + } + } + break; + } + *res = buf.makeStringAndClear(); + return true; + } + // next line: + sal_Int32 next_lf = file.indexOf( LF, pos ); + if (next_lf < 0) // EOF + break; + pos = next_lf + 1; + } + return false; +} + +bool readProperties( std::vector< std::pair< OUString, OUString> > & out_result, + ::ucbhelper::Content & ucb_content ) +{ + // read whole file: + std::vector<sal_Int8> bytes( readFile( ucb_content ) ); + OUString file( reinterpret_cast<char const *>(bytes.data()), + bytes.size(), RTL_TEXTENCODING_UTF8); + sal_Int32 pos = 0; + + for (;;) + { + + OUStringBuffer buf; + sal_Int32 start = pos; + + bool bEOF = false; + pos = file.indexOf( LF, pos ); + if (pos < 0) { // EOF + buf.append( file.subView(start) ); + bEOF = true; + } + else + { + if (pos > 0 && file[ pos - 1 ] == CR) + // consume extra CR + buf.append( file.subView(start, pos - start - 1) ); + else + buf.append( file.subView(start, pos - start) ); + pos++; + } + OUString aLine = buf.makeStringAndClear(); + + sal_Int32 posEqual = aLine.indexOf('='); + if (posEqual > 0 && (posEqual + 1) < aLine.getLength()) + { + OUString name = aLine.copy(0, posEqual); + OUString value = aLine.copy(posEqual + 1); + out_result.emplace_back(name, value); + } + + if (bEOF) + break; + } + return false; +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/misc/dp_update.cxx b/desktop/source/deployment/misc/dp_update.cxx new file mode 100644 index 0000000000..650d648e8a --- /dev/null +++ b/desktop/source/deployment/misc/dp_update.cxx @@ -0,0 +1,408 @@ +/* -*- 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 <dp_update.hxx> +#include <dp_version.hxx> +#include <dp_identifier.hxx> +#include <dp_descriptioninfoset.hxx> + +#include <com/sun/star/ucb/CommandAbortedException.hpp> +#include <com/sun/star/ucb/CommandFailedException.hpp> +#include <osl/diagnose.h> +#include <rtl/bootstrap.hxx> +#include <sal/log.hxx> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; + + +namespace dp_misc { +namespace { + +int determineHighestVersion( + OUString const & userVersion, + OUString const & sharedVersion, + OUString const & bundledVersion, + std::u16string_view onlineVersion) +{ + int index = 0; + OUString greatest = userVersion; + if (dp_misc::compareVersions(sharedVersion, greatest) == dp_misc::GREATER) + { + index = 1; + greatest = sharedVersion; + } + if (dp_misc::compareVersions(bundledVersion, greatest) == dp_misc::GREATER) + { + index = 2; + greatest = bundledVersion; + } + if (dp_misc::compareVersions(onlineVersion, greatest) == dp_misc::GREATER) + { + index = 3; + } + return index; +} + +Sequence< Reference< xml::dom::XElement > > +getUpdateInformation( Reference<deployment::XUpdateInformationProvider > const & updateInformation, + Sequence< OUString > const & urls, + OUString const & identifier, + uno::Any & out_error) +{ + try { + return updateInformation->getUpdateInformation(urls, identifier); + } catch (const uno::RuntimeException &) { + throw; + } catch (const ucb::CommandFailedException & e) { + out_error = e.Reason; + } catch (const ucb::CommandAbortedException &) { + } catch (const uno::Exception & e) { + out_error <<= e; + } + return + Sequence<Reference< xml::dom::XElement > >(); +} + +void getOwnUpdateInfos( + Reference<uno::XComponentContext> const & xContext, + Reference<deployment::XUpdateInformationProvider > const & updateInformation, + UpdateInfoMap& inout_map, std::vector<std::pair<Reference<deployment::XPackage>, uno::Any> > & out_errors, + bool & out_allFound) +{ + bool bAllHaveOwnUpdateInformation = true; + for (auto & inout : inout_map) + { + OSL_ASSERT(inout.second.extension.is()); + Sequence<OUString> urls(inout.second.extension->getUpdateInformationURLs()); + if (urls.hasElements()) + { + const OUString search_id = dp_misc::getIdentifier(inout.second.extension); + SAL_INFO( "extensions.update", "Searching update for " << search_id ); + uno::Any anyError; + //It is unclear from the idl if there can be a null reference returned. + //However all valid information should be the same + const Sequence<Reference< xml::dom::XElement > > + infos(getUpdateInformation(updateInformation, urls, search_id, anyError)); + if (anyError.hasValue()) + out_errors.emplace_back(inout.second.extension, anyError); + + for (const Reference< xml::dom::XElement >& element : infos) + { + dp_misc::DescriptionInfoset infoset( + xContext, + Reference< xml::dom::XNode >(element, UNO_QUERY_THROW)); + if (!infoset.hasDescription()) + continue; + std::optional< OUString > result_id(infoset.getIdentifier()); + if (!result_id) + continue; + SAL_INFO( "extensions.update", " found version " + << infoset.getVersion() << " for " << *result_id ); + if (*result_id != search_id) + continue; + inout.second.version = infoset.getVersion(); + inout.second.info.set(element, UNO_QUERY_THROW); + break; + } + } + else + { + bAllHaveOwnUpdateInformation = false; + } + } + out_allFound = bAllHaveOwnUpdateInformation; +} + +void getDefaultUpdateInfos( + Reference<uno::XComponentContext> const & xContext, + Reference<deployment::XUpdateInformationProvider > const & updateInformation, + UpdateInfoMap& inout_map, + std::vector<std::pair<Reference<deployment::XPackage>, uno::Any> > & out_errors) +{ + const OUString sDefaultURL(dp_misc::getExtensionDefaultUpdateURL()); + OSL_ASSERT(!sDefaultURL.isEmpty()); + + Any anyError; + const Sequence< Reference< xml::dom::XElement > > + infos( + getUpdateInformation( + updateInformation, + Sequence< OUString >(&sDefaultURL, 1), OUString(), anyError)); + if (anyError.hasValue()) + out_errors.emplace_back(Reference<deployment::XPackage>(), anyError); + for (const Reference< xml::dom::XElement >& element : infos) + { + Reference< xml::dom::XNode > node(element, UNO_QUERY_THROW); + dp_misc::DescriptionInfoset infoset(xContext, node); + std::optional< OUString > id(infoset.getIdentifier()); + if (!id) { + continue; + } + UpdateInfoMap::iterator j = inout_map.find(*id); + if (j != inout_map.end()) + { + //skip those extension which provide its own update urls + if (j->second.extension->getUpdateInformationURLs().getLength()) + continue; + OUString v(infoset.getVersion()); + //look for the highest version in the online repository + if (dp_misc::compareVersions(v, j->second.version) == + dp_misc::GREATER) + { + j->second.version = v; + j->second.info = node; + } + } + } +} + +bool containsBundledOnly(Sequence<Reference<deployment::XPackage> > const & sameIdExtensions) +{ + OSL_ASSERT(sameIdExtensions.getLength() == 3); + return !sameIdExtensions[0].is() && !sameIdExtensions[1].is() && sameIdExtensions[2].is(); +} + +/** Returns true if the list of extensions are bundled extensions and there are no + other extensions with the same identifier in the shared or user repository. + If extensionList is NULL, then it is checked if there are only bundled extensions. +*/ +bool onlyBundledExtensions( + Reference<deployment::XExtensionManager> const & xExtMgr, + std::vector< Reference<deployment::XPackage > > const * extensionList) +{ + OSL_ASSERT(xExtMgr.is()); + bool bOnlyBundled = true; + if (extensionList) + { + for (auto const& elem : *extensionList) + { + Sequence<Reference<deployment::XPackage> > seqExt = xExtMgr->getExtensionsWithSameIdentifier( + dp_misc::getIdentifier(elem), elem->getName(), Reference<ucb::XCommandEnvironment>()); + + bOnlyBundled = containsBundledOnly(seqExt); + if (!bOnlyBundled) + break; + } + } + else + { + const uno::Sequence< uno::Sequence< Reference<deployment::XPackage > > > seqAllExt = + xExtMgr->getAllExtensions(Reference<task::XAbortChannel>(), Reference<ucb::XCommandEnvironment>()); + + for (int pos(0), nLen(seqAllExt.getLength()); bOnlyBundled && pos != nLen; ++pos) + { + bOnlyBundled = containsBundledOnly(seqAllExt[pos]); + } + } + return bOnlyBundled; +} + +} // anon namespace + + +OUString getExtensionDefaultUpdateURL() +{ + OUString sUrl( + "${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE("version") + ":Version:ExtensionUpdateURL}"); + ::rtl::Bootstrap::expandMacros(sUrl); + return sUrl; +} + +/* returns the index of the greatest version, starting with 0 + + */ +UPDATE_SOURCE isUpdateUserExtension( + bool bReadOnlyShared, + OUString const & userVersion, + OUString const & sharedVersion, + OUString const & bundledVersion, + std::u16string_view onlineVersion) +{ + UPDATE_SOURCE retVal = UPDATE_SOURCE_NONE; + if (bReadOnlyShared) + { + if (!userVersion.isEmpty()) + { + int index = determineHighestVersion( + userVersion, sharedVersion, bundledVersion, onlineVersion); + if (index == 1) + retVal = UPDATE_SOURCE_SHARED; + else if (index == 2) + retVal = UPDATE_SOURCE_BUNDLED; + else if (index == 3) + retVal = UPDATE_SOURCE_ONLINE; + } + else if (!sharedVersion.isEmpty()) + { + int index = determineHighestVersion( + OUString(), sharedVersion, bundledVersion, onlineVersion); + if (index == 2) + retVal = UPDATE_SOURCE_BUNDLED; + else if (index == 3) + retVal = UPDATE_SOURCE_ONLINE; + + } + } + else + { + if (!userVersion.isEmpty()) + { + int index = determineHighestVersion( + userVersion, sharedVersion, bundledVersion, onlineVersion); + if (index == 1) + retVal = UPDATE_SOURCE_SHARED; + else if (index == 2) + retVal = UPDATE_SOURCE_BUNDLED; + else if (index == 3) + retVal = UPDATE_SOURCE_ONLINE; + } + } + + return retVal; +} + +UPDATE_SOURCE isUpdateSharedExtension( + bool bReadOnlyShared, + OUString const & sharedVersion, + OUString const & bundledVersion, + std::u16string_view onlineVersion) +{ + if (bReadOnlyShared) + return UPDATE_SOURCE_NONE; + UPDATE_SOURCE retVal = UPDATE_SOURCE_NONE; + + if (!sharedVersion.isEmpty()) + { + int index = determineHighestVersion( + OUString(), sharedVersion, bundledVersion, onlineVersion); + if (index == 2) + retVal = UPDATE_SOURCE_BUNDLED; + else if (index == 3) + retVal = UPDATE_SOURCE_ONLINE; + } + return retVal; +} + +Reference<deployment::XPackage> +getExtensionWithHighestVersion( + Sequence<Reference<deployment::XPackage> > const & seqExt) +{ + if (!seqExt.hasElements()) + return Reference<deployment::XPackage>(); + + Reference<deployment::XPackage> greatest; + sal_Int32 len = seqExt.getLength(); + + for (sal_Int32 i = 0; i < len; i++) + { + if (!greatest.is()) + { + greatest = seqExt[i]; + continue; + } + Reference<deployment::XPackage> const & current = seqExt[i]; + //greatest has a value + if (! current.is()) + continue; + + if (dp_misc::compareVersions(current->getVersion(), greatest->getVersion()) == dp_misc::GREATER) + greatest = current; + } + return greatest; +} + +UpdateInfo::UpdateInfo( Reference< deployment::XPackage> const & ext): +extension(ext) +{ +} + + +UpdateInfoMap getOnlineUpdateInfos( + Reference<uno::XComponentContext> const &xContext, + Reference<deployment::XExtensionManager> const & xExtMgr, + Reference<deployment::XUpdateInformationProvider > const & updateInformation, + std::vector<Reference<deployment::XPackage > > const * extensionList, + std::vector<std::pair< Reference<deployment::XPackage>, uno::Any> > & out_errors) +{ + OSL_ASSERT(xExtMgr.is()); + UpdateInfoMap infoMap; + if (!xExtMgr.is() || onlyBundledExtensions(xExtMgr, extensionList)) + return infoMap; + + if (!extensionList) + { + const uno::Sequence< uno::Sequence< Reference<deployment::XPackage > > > seqAllExt = xExtMgr->getAllExtensions( + Reference<task::XAbortChannel>(), Reference<ucb::XCommandEnvironment>()); + + //fill the UpdateInfoMap. key = extension identifier, value = UpdateInfo + for (int pos = seqAllExt.getLength(); pos --; ) + { + uno::Sequence<Reference<deployment::XPackage> > const & seqExt = seqAllExt[pos]; + + Reference<deployment::XPackage> extension = getExtensionWithHighestVersion(seqExt); + OSL_ASSERT(extension.is()); + + std::pair<UpdateInfoMap::iterator, bool> insertRet = infoMap.emplace( + dp_misc::getIdentifier(extension), UpdateInfo(extension)); + OSL_ASSERT(insertRet.second); + } + } + else + { + for (auto const& elem : *extensionList) + { + OSL_ASSERT(elem.is()); + std::pair<UpdateInfoMap::iterator, bool> insertRet = infoMap.emplace( + dp_misc::getIdentifier(elem), UpdateInfo(elem)); + OSL_ASSERT(insertRet.second); + } + } + + //Now find the update information for the extensions which provide their own + //URLs to update information. + bool bAllInfosObtained = false; + getOwnUpdateInfos(xContext, updateInformation, infoMap, out_errors, bAllInfosObtained); + + if (!bAllInfosObtained) + getDefaultUpdateInfos(xContext, updateInformation, infoMap, out_errors); + return infoMap; +} +OUString getHighestVersion( + OUString const & sharedVersion, + OUString const & bundledVersion, + OUString const & onlineVersion) +{ + int index = determineHighestVersion(OUString(), sharedVersion, bundledVersion, onlineVersion); + switch (index) + { + case 1: return sharedVersion; + case 2: return bundledVersion; + case 3: return onlineVersion; + default: OSL_ASSERT(false); + } + + return OUString(); +} +} //namespace dp_misc + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/misc/dp_version.cxx b/desktop/source/deployment/misc/dp_version.cxx new file mode 100644 index 0000000000..8006e7b6cf --- /dev/null +++ b/desktop/source/deployment/misc/dp_version.cxx @@ -0,0 +1,64 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + + +#include <sal/config.h> + +#include <o3tl/string_view.hxx> +#include <rtl/ustring.hxx> + +#include <dp_version.hxx> + +namespace { + +std::u16string_view getElement(std::u16string_view version, std::size_t * index) +{ + while (*index < version.size() && version[*index] == '0') { + ++*index; + } + return o3tl::getToken(version, u'.', *index); +} + +} + +namespace dp_misc { + +::dp_misc::Order compareVersions( + std::u16string_view version1, std::u16string_view version2) +{ + for (size_t i1 = 0, i2 = 0; i1 != std::u16string_view::npos || i2 != std::u16string_view::npos;) { + std::u16string_view e1(i1 != std::u16string_view::npos ? getElement(version1, &i1) : std::u16string_view()); + std::u16string_view e2(i2 != std::u16string_view::npos ? getElement(version2, &i2) : std::u16string_view()); + if (e1.size() < e2.size()) { + return ::dp_misc::LESS; + } else if (e1.size() > e2.size()) { + return ::dp_misc::GREATER; + } else if (e1 < e2) { + return ::dp_misc::LESS; + } else if (e1 > e2) { + return ::dp_misc::GREATER; + } + } + return ::dp_misc::EQUAL; +} + + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/misc/lockfile.cxx b/desktop/source/deployment/misc/lockfile.cxx new file mode 100644 index 0000000000..1a87e8bc0f --- /dev/null +++ b/desktop/source/deployment/misc/lockfile.cxx @@ -0,0 +1,213 @@ +/* -*- 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 <memory> + +#include <time.h> +#ifndef _WIN32 +#include <unistd.h> +#else +#if !defined WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN +#endif +#include <windows.h> +#endif +#include <comphelper/random.hxx> +#include <sal/types.h> +#include <osl/file.hxx> +#include <osl/security.hxx> +#include <unotools/bootstrap.hxx> +#include <tools/config.hxx> + +#include <lockfile.hxx> + +using namespace ::osl; +using namespace ::utl; + + +static OString impl_getHostname() +{ + OString aHost; +#ifdef _WIN32 + /* + prevent windows from connecting to the net to get its own + hostname by using the netbios name + */ + DWORD sz = MAX_COMPUTERNAME_LENGTH + 1; + TCHAR szHost[MAX_COMPUTERNAME_LENGTH + 1]; + if (GetComputerNameA(szHost, &sz)) + aHost = OString(szHost); + else + aHost = OString("UNKNOWN"); +#else + /* Don't do dns lookup on Linux either */ + char pHostName[1024]; + + if ( gethostname( pHostName, sizeof( pHostName ) - 1 ) == 0 ) + { + pHostName[sizeof( pHostName ) - 1] = '\0'; + aHost = OString( pHostName ); + } + else + aHost = "UNKNOWN"_ostr; +#endif + + return aHost; +} + +namespace desktop { + + Lockfile::Lockfile( bool bIPCserver ) + :m_bIPCserver(bIPCserver) + ,m_bRemove(false) + ,m_bIsLocked(false) + { + // build the file-url to use for the lock + OUString aUserPath; + utl::Bootstrap::locateUserInstallation( aUserPath ); + m_aLockname = aUserPath + "/.lock"; + + // generate ID + const int nIdBytes = 16; + char tmpId[nIdBytes*2+1]; + time_t t = time(nullptr); + for (int i = 0; i<nIdBytes; i++) { + int tmpByte = comphelper::rng::uniform_int_distribution(0, 0xFF); + SAL_WNODEPRECATED_DECLARATIONS_PUSH // sprintf (macOS 13 SDK) + sprintf( tmpId+i*2, "%02X", tmpByte ); + SAL_WNODEPRECATED_DECLARATIONS_POP + } + tmpId[nIdBytes*2]=0x00; + m_aId = OUString::createFromAscii( tmpId ); + + // generate date string + char *tmpTime = ctime( &t ); + if (tmpTime != nullptr) { + m_aDate = OUString::createFromAscii( tmpTime ); + sal_Int32 i = m_aDate.indexOf('\n'); + if (i > 0) + m_aDate = m_aDate.copy(0, i); + } + + + // try to create file + File aFile(m_aLockname); + if (aFile.open( osl_File_OpenFlag_Create ) == File::E_EXIST) { + m_bIsLocked = true; + } else { + // new lock created + aFile.close( ); + syncToFile( ); + m_bRemove = true; + } + } + + bool Lockfile::check( fpExecWarning execWarning ) + { + + if (m_bIsLocked) { + // lock existed, ask user what to do + if (isStale() || + (execWarning != nullptr && (*execWarning)( this ))) { + // remove file and create new + File::remove( m_aLockname ); + File aFile(m_aLockname); + (void)aFile.open( osl_File_OpenFlag_Create ); + aFile.close( ); + syncToFile( ); + m_bRemove = true; + return true; + } else { + //leave alone and return false + m_bRemove = false; + return false; + } + } else { + // lock was created by us + return true; + } + } + + bool Lockfile::isStale() const + { + // this checks whether the lockfile was created on the same + // host by the same user. Should this be the case it is safe + // to assume that it is a stale lockfile which can be overwritten + OUString aLockname = m_aLockname; + Config aConfig(aLockname); + aConfig.SetGroup(LOCKFILE_GROUP ""_ostr); + OString aIPCserver = aConfig.ReadKey( LOCKFILE_IPCKEY ""_ostr ); + if (!aIPCserver.equalsIgnoreAsciiCase("true")) + return false; + + OString aHost = aConfig.ReadKey( LOCKFILE_HOSTKEY ""_ostr ); + OString aUser = aConfig.ReadKey( LOCKFILE_USERKEY ""_ostr ); + + // lockfile from same host? + OString myHost( impl_getHostname() ); + if (aHost == myHost) { + // lockfile by same UID + OUString myUserName; + Security aSecurity; + aSecurity.getUserName( myUserName ); + OString myUser(OUStringToOString(myUserName, RTL_TEXTENCODING_ASCII_US)); + if (aUser == myUser) + return true; + } + return false; + } + + void Lockfile::syncToFile() const + { + OUString aLockname = m_aLockname; + Config aConfig(aLockname); + aConfig.SetGroup(LOCKFILE_GROUP ""_ostr); + + // get information + OString aHost( impl_getHostname() ); + OUString aUserName; + Security aSecurity; + aSecurity.getUserName( aUserName ); + OString aUser = OUStringToOString( aUserName, RTL_TEXTENCODING_ASCII_US ); + OString aTime = OUStringToOString( m_aDate, RTL_TEXTENCODING_ASCII_US ); + OString aStamp = OUStringToOString( m_aId, RTL_TEXTENCODING_ASCII_US ); + + // write information + aConfig.WriteKey( LOCKFILE_USERKEY ""_ostr, aUser ); + aConfig.WriteKey( LOCKFILE_HOSTKEY ""_ostr, aHost ); + aConfig.WriteKey( LOCKFILE_STAMPKEY ""_ostr, aStamp ); + aConfig.WriteKey( LOCKFILE_TIMEKEY ""_ostr, aTime ); + aConfig.WriteKey( + LOCKFILE_IPCKEY ""_ostr, + m_bIPCserver ? "true"_ostr : "false"_ostr ); + aConfig.Flush( ); + } + + Lockfile::~Lockfile() + { + // unlock userdata by removing file + if ( m_bRemove ) + File::remove( m_aLockname ); + } +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/registry/component/dp_compbackenddb.cxx b/desktop/source/deployment/registry/component/dp_compbackenddb.cxx new file mode 100644 index 0000000000..fd9bb2c61b --- /dev/null +++ b/desktop/source/deployment/registry/component/dp_compbackenddb.cxx @@ -0,0 +1,131 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + + +#include <cppuhelper/exc_hlp.hxx> +#include <com/sun/star/deployment/DeploymentException.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> + +#include "dp_compbackenddb.hxx" + + +using namespace ::com::sun::star::uno; + +constexpr OUStringLiteral EXTENSION_REG_NS = u"http://openoffice.org/extensionmanager/component-registry/2010"; +constexpr OUStringLiteral NS_PREFIX = u"comp"; +constexpr OUStringLiteral ROOT_ELEMENT_NAME = u"component-backend-db"; +constexpr OUStringLiteral KEY_ELEMENT_NAME = u"component"; + +namespace dp_registry::backend::component { + +ComponentBackendDb::ComponentBackendDb( + Reference<XComponentContext> const & xContext, + OUString const & url):BackendDb(xContext, url) +{ + +} + +OUString ComponentBackendDb::getDbNSName() +{ + return EXTENSION_REG_NS; +} + +OUString ComponentBackendDb::getNSPrefix() +{ + return NS_PREFIX; +} + +OUString ComponentBackendDb::getRootElementName() +{ + return ROOT_ELEMENT_NAME; +} + +OUString ComponentBackendDb::getKeyElementName() +{ + return KEY_ELEMENT_NAME; +} + +void ComponentBackendDb::addEntry(OUString const & url, Data const & data) +{ + try{ + if (!activateEntry(url)) + { + Reference<css::xml::dom::XNode> componentNode = writeKeyElement(url); + writeSimpleElement(u"java-type-library", + OUString::boolean(data.javaTypeLibrary), + componentNode); + + writeSimpleList( + data.implementationNames, + u"implementation-names", + u"name", + componentNode); + + writeVectorOfPair( + data.singletons, + u"singletons", + u"item", + u"key", + u"value", + componentNode); + + save(); + } + } + catch(const css::uno::Exception &) + { + Any exc( ::cppu::getCaughtException() ); + throw css::deployment::DeploymentException( + "Extension Manager: failed to write data entry in backend db: " + + m_urlDb, nullptr, exc); + } +} + +ComponentBackendDb::Data ComponentBackendDb::getEntry(std::u16string_view url) +{ + try + { + ComponentBackendDb::Data retData; + Reference<css::xml::dom::XNode> aNode = getKeyElement(url); + if (aNode.is()) + { + bool bJava = readSimpleElement(u"java-type-library", aNode) == "true"; + retData.javaTypeLibrary = bJava; + + retData.implementationNames = + readList( aNode, u"implementation-names", u"name"); + + retData.singletons = + readVectorOfPair( aNode, u"singletons", u"item", u"key", u"value"); + } + return retData; + } + catch(const css::uno::Exception &) + { + Any exc( ::cppu::getCaughtException() ); + throw css::deployment::DeploymentException( + "Extension Manager: failed to read data entry in backend db: " + + m_urlDb, nullptr, exc); + } +} + + +} // namespace dp_registry::backend::component + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/registry/component/dp_compbackenddb.hxx b/desktop/source/deployment/registry/component/dp_compbackenddb.hxx new file mode 100644 index 0000000000..84153b6fa2 --- /dev/null +++ b/desktop/source/deployment/registry/component/dp_compbackenddb.hxx @@ -0,0 +1,95 @@ +/* -*- 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> +#include <rtl/string.hxx> +#include <vector> +#include <deque> +#include <string_view> + +#include <dp_backenddb.hxx> + +namespace com::sun::star::uno { class XComponentContext; } + +namespace dp_registry::backend::component { + +/* The XML file stores the extensions which are currently registered. + They will be removed when they are revoked. + The format looks like this: + +<?xml version="1.0"?> +<component-backend-db xmlns="http://openoffice.org/extensionmanager/component-registry/2010"> + <component url="vnd.sun.star.expand:$UNO_USER_PACKAGES_CACHE/uno_packages/5CD5.tmp_/leaves1.oxt/extensionoptions.jar"> + <name>FileName</name> + <java-type-library>true</java-type-library> + <implementation-names> + <name>com.sun.star.comp.extensionoptions.OptionsEventHandler$_OptionsEventHandler</name> + ... + </implementation-names> + <singletons> + <item> + <key>com.sun.star.java.theJavaVirtualMachine</key> + <value>com.sun.star.java.JavaVirtualMachine</value> + </item> + ... + </singletons> + </component> + + <component ...> + ... +</component-backend-db> + */ +class ComponentBackendDb: public dp_registry::backend::BackendDb +{ +protected: + virtual OUString getDbNSName() override; + virtual OUString getNSPrefix() override; + virtual OUString getRootElementName() override; + virtual OUString getKeyElementName() override; + +public: + struct Data + { + Data(): javaTypeLibrary(false) {}; + + std::deque< OUString> implementationNames; + std::vector< std::pair< OUString, OUString> >singletons; + // map from singleton names to implementation names + bool javaTypeLibrary; + }; + +public: + + ComponentBackendDb( css::uno::Reference<css::uno::XComponentContext> const & xContext, + OUString const & url); + + void addEntry(OUString const & url, Data const & data); + + Data getEntry(std::u16string_view url); + + +}; + + +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/registry/component/dp_component.cxx b/desktop/source/deployment/registry/component/dp_component.cxx new file mode 100644 index 0000000000..7a692ec8c6 --- /dev/null +++ b/desktop/source/deployment/registry/component/dp_component.cxx @@ -0,0 +1,1714 @@ +/* -*- 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 <strings.hrc> +#include <dp_misc.h> +#include <dp_shared.hxx> +#include <dp_backend.h> +#include <dp_platform.hxx> +#include <dp_ucb.h> +#include <rtl/string.hxx> +#include <rtl/strbuf.hxx> +#include <rtl/ustrbuf.hxx> +#include <rtl/uri.hxx> +#include <sal/log.hxx> +#include <cppuhelper/exc_hlp.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <ucbhelper/content.hxx> +#include <comphelper/sequence.hxx> +#include <utility> +#include <xmlscript/xml_helper.hxx> +#include <svl/inettype.hxx> +#include <comphelper/diagnose_ex.hxx> +#include <o3tl/string_view.hxx> +#include <com/sun/star/beans/NamedValue.hpp> +#include <com/sun/star/deployment/DeploymentException.hpp> +#include <com/sun/star/lang/WrappedTargetRuntimeException.hpp> +#include <com/sun/star/container/XNameContainer.hpp> +#include <com/sun/star/container/XSet.hpp> +#include <com/sun/star/registry/XSimpleRegistry.hpp> +#include <com/sun/star/registry/XImplementationRegistration.hpp> +#include <com/sun/star/loader/XImplementationLoader.hpp> +#include <com/sun/star/io/XInputStream.hpp> +#include <com/sun/star/ucb/NameClash.hpp> +#include <com/sun/star/util/theMacroExpander.hpp> +#include <algorithm> +#include <deque> +#include <memory> +#include <string_view> +#include <unordered_map> +#include <vector> +#include "dp_compbackenddb.hxx" + +using namespace ::dp_misc; +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::ucb; + +namespace dp_registry::backend::component { +namespace { + +/** return a vector of bootstrap variables which have been provided + as command arguments. +*/ +std::vector<OUString> getCmdBootstrapVariables() +{ + std::vector<OUString> ret; + sal_uInt32 count = osl_getCommandArgCount(); + for (sal_uInt32 i = 0; i < count; i++) + { + OUString arg; + osl_getCommandArg(i, &arg.pData); + if (arg.startsWith("-env:")) + ret.push_back(arg); + } + return ret; +} + +bool jarManifestHeaderPresent( + OUString const & url, std::u16string_view name, + Reference<XCommandEnvironment> const & xCmdEnv ) +{ + OUString buf = "vnd.sun.star.zip://" + + ::rtl::Uri::encode( + url, rtl_UriCharClassRegName, rtl_UriEncodeIgnoreEscapes, + RTL_TEXTENCODING_UTF8 ) + + "/META-INF/MANIFEST.MF"; + ::ucbhelper::Content manifestContent; + OUString line; + return + create_ucb_content( + &manifestContent, buf, xCmdEnv, + false /* no throw */ ) + && readLine( &line, name, manifestContent, RTL_TEXTENCODING_ASCII_US ); +} + + +class BackendImpl : public ::dp_registry::backend::PackageRegistryBackend +{ + class ComponentPackageImpl : public ::dp_registry::backend::Package + { + BackendImpl * getMyBackend() const; + + const OUString m_loader; + + enum class Reg { Uninit, Void, Registered, NotRegistered, MaybeRegistered }; + Reg m_registered; + + void getComponentInfo( + ComponentBackendDb::Data * data, + std::vector< css::uno::Reference< css::uno::XInterface > > * + factories, + Reference<XComponentContext> const & xContext ); + + void componentLiveInsertion( + ComponentBackendDb::Data const & data, + std::vector< css::uno::Reference< css::uno::XInterface > > const & + factories); + + void componentLiveRemoval(ComponentBackendDb::Data const & data); + + // Package + virtual beans::Optional< beans::Ambiguous<sal_Bool> > isRegistered_( + ::osl::ResettableMutexGuard & guard, + ::rtl::Reference<AbortChannel> const & abortChannel, + Reference<XCommandEnvironment> const & xCmdEnv ) override; + virtual void processPackage_( + ::osl::ResettableMutexGuard & guard, + bool registerPackage, + bool startup, + ::rtl::Reference<AbortChannel> const & abortChannel, + Reference<XCommandEnvironment> const & xCmdEnv ) override; + + Reference<registry::XSimpleRegistry> getRDB() const; + + public: + ComponentPackageImpl( + ::rtl::Reference<PackageRegistryBackend> const & myBackend, + OUString const & url, OUString const & name, + Reference<deployment::XPackageTypeInfo> const & xPackageType, + OUString loader, bool bRemoved, + OUString const & identifier); + }; + friend class ComponentPackageImpl; + + class ComponentsPackageImpl : public ::dp_registry::backend::Package + { + BackendImpl * getMyBackend() const; + + // Package + virtual beans::Optional< beans::Ambiguous<sal_Bool> > isRegistered_( + ::osl::ResettableMutexGuard & guard, + ::rtl::Reference<AbortChannel> const & abortChannel, + Reference<XCommandEnvironment> const & xCmdEnv ) override; + virtual void processPackage_( + ::osl::ResettableMutexGuard & guard, + bool registerPackage, + bool startup, + ::rtl::Reference<AbortChannel> const & abortChannel, + Reference<XCommandEnvironment> const & xCmdEnv ) override; + public: + ComponentsPackageImpl( + ::rtl::Reference<PackageRegistryBackend> const & myBackend, + OUString const & url, OUString const & name, + Reference<deployment::XPackageTypeInfo> const & xPackageType, + bool bRemoved, OUString const & identifier); + }; + friend class ComponentsPackageImpl; + + class TypelibraryPackageImpl : public ::dp_registry::backend::Package + { + BackendImpl * getMyBackend() const; + + const bool m_jarFile; + + // Package + virtual beans::Optional< beans::Ambiguous<sal_Bool> > isRegistered_( + ::osl::ResettableMutexGuard & guard, + ::rtl::Reference<AbortChannel> const & abortChannel, + Reference<XCommandEnvironment> const & xCmdEnv ) override; + virtual void processPackage_( + ::osl::ResettableMutexGuard & guard, + bool registerPackage, + bool startup, + ::rtl::Reference<AbortChannel> const & abortChannel, + Reference<XCommandEnvironment> const & xCmdEnv ) override; + + public: + TypelibraryPackageImpl( + ::rtl::Reference<PackageRegistryBackend> const & myBackend, + OUString const & url, OUString const & name, + Reference<deployment::XPackageTypeInfo> const & xPackageType, + bool jarFile, bool bRemoved, + OUString const & identifier); + }; + friend class TypelibraryPackageImpl; + + /** Serves for unregistering packages that were registered on a + different platform. This can happen if one has remotely mounted + /home, for example. + */ + class OtherPlatformPackageImpl : public ::dp_registry::backend::Package + { + public: + OtherPlatformPackageImpl( + ::rtl::Reference<PackageRegistryBackend> const & myBackend, + OUString const & url, OUString const & name, + Reference<deployment::XPackageTypeInfo> const & xPackageType, + bool bRemoved, OUString const & identifier, OUString platform); + + private: + BackendImpl * getMyBackend() const; + + Reference<registry::XSimpleRegistry> impl_openRDB() const; + Reference<XInterface> impl_createInstance(OUString const& rService) const; + + // Package + virtual beans::Optional< beans::Ambiguous<sal_Bool> > isRegistered_( + ::osl::ResettableMutexGuard & guard, + ::rtl::Reference<AbortChannel> const & abortChannel, + Reference<XCommandEnvironment> const & xCmdEnv ) override; + virtual void processPackage_( + ::osl::ResettableMutexGuard & guard, + bool registerPackage, + bool startup, + ::rtl::Reference<AbortChannel> const & abortChannel, + Reference<XCommandEnvironment> const & xCmdEnv ) override; + + private: + OUString const m_aPlatform; + }; + friend class OtherPlatformPackageImpl; + + std::deque<OUString> m_jar_typelibs; + std::deque<OUString> m_rdb_typelibs; + std::deque<OUString> m_components; + + enum RcItem { RCITEM_JAR_TYPELIB, RCITEM_RDB_TYPELIB, RCITEM_COMPONENTS }; + + std::deque<OUString> & getRcItemList( RcItem kind ) { + switch (kind) + { + case RCITEM_JAR_TYPELIB: + return m_jar_typelibs; + case RCITEM_RDB_TYPELIB: + return m_rdb_typelibs; + default: // case RCITEM_COMPONENTS + return m_components; + } + } + + bool m_unorc_inited; + bool m_unorc_modified; + bool bSwitchedRdbFiles; + + typedef std::unordered_map< OUString, Reference<XInterface> > t_string2object; + t_string2object m_backendObjects; + + // PackageRegistryBackend + virtual Reference<deployment::XPackage> bindPackage_( + OUString const & url, OUString const & mediaType, + bool bRemoved, OUString const & identifier, + Reference<XCommandEnvironment> const & xCmdEnv ) override; + + virtual void SAL_CALL disposing() override; + + const Reference<deployment::XPackageTypeInfo> m_xDynComponentTypeInfo; + const Reference<deployment::XPackageTypeInfo> m_xJavaComponentTypeInfo; + const Reference<deployment::XPackageTypeInfo> m_xPythonComponentTypeInfo; + const Reference<deployment::XPackageTypeInfo> m_xComponentsTypeInfo; + const Reference<deployment::XPackageTypeInfo> m_xRDBTypelibTypeInfo; + const Reference<deployment::XPackageTypeInfo> m_xJavaTypelibTypeInfo; + Sequence< Reference<deployment::XPackageTypeInfo> > m_typeInfos; + + OUString m_commonRDB; + OUString m_nativeRDB; + + //URLs of the original rdbs (before any switching): + OUString m_commonRDB_orig; + OUString m_nativeRDB_orig; + + std::unique_ptr<ComponentBackendDb> m_backendDb; + + void addDataToDb(OUString const & url, ComponentBackendDb::Data const & data); + ComponentBackendDb::Data readDataFromDb(std::u16string_view url); + void revokeEntryFromDb(std::u16string_view url); + + Reference<registry::XSimpleRegistry> m_xCommonRDB; + Reference<registry::XSimpleRegistry> m_xNativeRDB; + + void unorc_verify_init( Reference<XCommandEnvironment> const & xCmdEnv ); + void unorc_flush( Reference<XCommandEnvironment> const & xCmdEnv ); + + Reference<XInterface> getObject( OUString const & id ); + Reference<XInterface> insertObject( + OUString const & id, Reference<XInterface> const & xObject ); + void releaseObject( OUString const & id ); + + void addToUnoRc( RcItem kind, OUString const & url, + Reference<XCommandEnvironment> const & xCmdEnv ); + void removeFromUnoRc( RcItem kind, OUString const & url, + Reference<XCommandEnvironment> const & xCmdEnv ); + bool hasInUnoRc( RcItem kind, OUString const & url ); + + css::uno::Reference< css::uno::XComponentContext > getRootContext() const; + +public: + BackendImpl( Sequence<Any> const & args, + Reference<XComponentContext> const & xComponentContext ); + + // 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; + + // XPackageRegistry + virtual Sequence< Reference<deployment::XPackageTypeInfo> > SAL_CALL + getSupportedPackageTypes() override; + + virtual void SAL_CALL packageRemoved(OUString const & url, OUString const & mediaType) override; + + using PackageRegistryBackend::disposing; + + //Will be called from ComponentPackageImpl + void initServiceRdbFiles(); +}; + + +BackendImpl::ComponentPackageImpl::ComponentPackageImpl( + ::rtl::Reference<PackageRegistryBackend> const & myBackend, + OUString const & url, OUString const & name, + Reference<deployment::XPackageTypeInfo> const & xPackageType, + OUString loader, bool bRemoved, + OUString const & identifier) + : Package( myBackend, url, name, name /* display-name */, + xPackageType, bRemoved, identifier), + m_loader(std::move( loader )), + m_registered( Reg::Uninit ) +{} + +Reference<registry::XSimpleRegistry> +BackendImpl::ComponentPackageImpl::getRDB() const +{ + BackendImpl * that = getMyBackend(); + + //Late "initialization" of the services rdb files + //This is to prevent problems when running several + //instances of OOo with root rights in parallel. This + //would otherwise cause problems when copying the rdbs. + //See http://qa.openoffice.org/issues/show_bug.cgi?id=99257 + { + const ::osl::MutexGuard guard( m_aMutex ); + if (!that->bSwitchedRdbFiles) + { + that->bSwitchedRdbFiles = true; + that->initServiceRdbFiles(); + } + } + if ( m_loader == "com.sun.star.loader.SharedLibrary" ) + return that->m_xNativeRDB; + else + return that->m_xCommonRDB; +} + +BackendImpl * BackendImpl::ComponentPackageImpl::getMyBackend() const +{ + BackendImpl * pBackend = static_cast<BackendImpl *>(m_myBackend.get()); + if (nullptr == pBackend) + { + //Throws a DisposedException + check(); + //We should never get here... + throw RuntimeException( + "Failed to get the BackendImpl", + static_cast<OWeakObject*>(const_cast<ComponentPackageImpl *>(this))); + } + return pBackend; +} + + +void BackendImpl::disposing() +{ + try { + m_backendObjects = t_string2object(); + if (m_xNativeRDB.is()) { + m_xNativeRDB->close(); + m_xNativeRDB.clear(); + } + if (m_xCommonRDB.is()) { + m_xCommonRDB->close(); + m_xCommonRDB.clear(); + } + unorc_flush( Reference<XCommandEnvironment>() ); + + PackageRegistryBackend::disposing(); + } + catch (const RuntimeException &) { + throw; + } + catch (const Exception &) { + Any exc( ::cppu::getCaughtException() ); + throw lang::WrappedTargetRuntimeException( + "caught unexpected exception while disposing...", + static_cast<OWeakObject *>(this), exc ); + } +} + + +void BackendImpl::initServiceRdbFiles() +{ + const Reference<XCommandEnvironment> xCmdEnv; + + ::ucbhelper::Content cacheDir( getCachePath(), xCmdEnv, m_xComponentContext ); + ::ucbhelper::Content oldRDB; + // switch common rdb: + if (!m_commonRDB_orig.isEmpty()) + { + (void)create_ucb_content( + &oldRDB, makeURL( getCachePath(), m_commonRDB_orig), + xCmdEnv, false /* no throw */ ); + } + m_commonRDB = m_commonRDB_orig == "common.rdb" ? std::u16string_view(u"common_.rdb") : std::u16string_view(u"common.rdb"); + if (oldRDB.get().is()) + { + cacheDir.transferContent( + oldRDB, ::ucbhelper::InsertOperation::Copy, + m_commonRDB, NameClash::OVERWRITE ); + oldRDB = ::ucbhelper::Content(); + } + // switch native rdb: + if (!m_nativeRDB_orig.isEmpty()) + { + (void)create_ucb_content( + &oldRDB, makeURL(getCachePath(), m_nativeRDB_orig), + xCmdEnv, false /* no throw */ ); + } + const OUString plt_rdb( getPlatformString() + ".rdb" ); + const OUString plt_rdb_( getPlatformString() + "_.rdb" ); + m_nativeRDB = (m_nativeRDB_orig == plt_rdb ) ? plt_rdb_ : plt_rdb; + if (oldRDB.get().is()) + { + cacheDir.transferContent( + oldRDB, ::ucbhelper::InsertOperation::Copy, + m_nativeRDB, NameClash::OVERWRITE ); + } + + // UNO is bootstrapped, flush for next process start: + m_unorc_modified = true; + unorc_flush( Reference<XCommandEnvironment>() ); + + + // common rdb for java, native rdb for shared lib components + if (!m_commonRDB.isEmpty()) { + m_xCommonRDB.set( + m_xComponentContext->getServiceManager() + ->createInstanceWithContext( + "com.sun.star.registry.SimpleRegistry", + m_xComponentContext ), UNO_QUERY_THROW ); + m_xCommonRDB->open( + makeURL( expandUnoRcUrl(getCachePath()), m_commonRDB ), + false, true); + } + if (!m_nativeRDB.isEmpty()) { + m_xNativeRDB.set( + m_xComponentContext->getServiceManager() + ->createInstanceWithContext( + "com.sun.star.registry.SimpleRegistry", + m_xComponentContext ), UNO_QUERY_THROW ); + m_xNativeRDB->open( + makeURL( expandUnoRcUrl(getCachePath()), m_nativeRDB ), + false, true); + } +} + +BackendImpl::BackendImpl( + Sequence<Any> const & args, + Reference<XComponentContext> const & xComponentContext ) + : PackageRegistryBackend( args, xComponentContext ), + m_unorc_inited( false ), + m_unorc_modified( false ), + bSwitchedRdbFiles(false), + m_xDynComponentTypeInfo( new Package::TypeInfo( + "application/vnd.sun.star.uno-component;type=native;platform=" + + getPlatformString(), + "*" SAL_DLLEXTENSION, + DpResId(RID_STR_DYN_COMPONENT) + ) ), + m_xJavaComponentTypeInfo( new Package::TypeInfo( + "application/vnd.sun.star.uno-component;type=Java", + "*.jar", + DpResId(RID_STR_JAVA_COMPONENT) + ) ), + m_xPythonComponentTypeInfo( new Package::TypeInfo( + "application/vnd.sun.star.uno-component;type=Python", + "*.py", + DpResId( + RID_STR_PYTHON_COMPONENT) + ) ), + m_xComponentsTypeInfo( new Package::TypeInfo( + "application/vnd.sun.star.uno-components", + "*.components", + DpResId(RID_STR_COMPONENTS) + ) ), + m_xRDBTypelibTypeInfo( new Package::TypeInfo( + "application/vnd.sun.star.uno-typelibrary;type=RDB", + "*.rdb", + DpResId(RID_STR_RDB_TYPELIB) + ) ), + m_xJavaTypelibTypeInfo( new Package::TypeInfo( + "application/vnd.sun.star.uno-typelibrary;type=Java", + "*.jar", + DpResId(RID_STR_JAVA_TYPELIB) + ) ), + m_typeInfos{ m_xDynComponentTypeInfo, m_xJavaComponentTypeInfo, m_xPythonComponentTypeInfo, + m_xComponentsTypeInfo, m_xRDBTypelibTypeInfo, m_xJavaTypelibTypeInfo } +{ + const Reference<XCommandEnvironment> xCmdEnv; + + if (transientMode()) + { + // in-mem rdbs: + // common rdb for java, native rdb for shared lib components + m_xCommonRDB.set( + xComponentContext->getServiceManager()->createInstanceWithContext( + "com.sun.star.registry.SimpleRegistry", + xComponentContext ), UNO_QUERY_THROW ); + m_xCommonRDB->open( OUString() /* in-mem */, + false /* ! read-only */, true /* create */ ); + m_xNativeRDB.set( + xComponentContext->getServiceManager()->createInstanceWithContext( + "com.sun.star.registry.SimpleRegistry", + xComponentContext ), UNO_QUERY_THROW ); + m_xNativeRDB->open( OUString() /* in-mem */, + false /* ! read-only */, true /* create */ ); + } + else + { + unorc_verify_init( xCmdEnv ); + OUString dbFile = makeURL(getCachePath(), "backenddb.xml"); + m_backendDb.reset( + new ComponentBackendDb(getComponentContext(), dbFile)); + } +} + +// XServiceInfo +OUString BackendImpl::getImplementationName() +{ + return "com.sun.star.comp.deployment.component.PackageRegistryBackend"; +} + +sal_Bool BackendImpl::supportsService( const OUString& ServiceName ) +{ + return cppu::supportsService(this, ServiceName); +} + +css::uno::Sequence< OUString > BackendImpl::getSupportedServiceNames() +{ + return { BACKEND_SERVICE_NAME }; +} + +void BackendImpl::addDataToDb( + OUString const & url, ComponentBackendDb::Data const & data) +{ + if (m_backendDb) + m_backendDb->addEntry(url, data); +} + +ComponentBackendDb::Data BackendImpl::readDataFromDb(std::u16string_view url) +{ + ComponentBackendDb::Data data; + if (m_backendDb) + data = m_backendDb->getEntry(url); + return data; +} + +void BackendImpl::revokeEntryFromDb(std::u16string_view url) +{ + if (m_backendDb) + m_backendDb->revokeEntry(url); +} + +// XPackageRegistry + +Sequence< Reference<deployment::XPackageTypeInfo> > +BackendImpl::getSupportedPackageTypes() +{ + return m_typeInfos; +} + +void BackendImpl::packageRemoved(OUString const & url, OUString const & /*mediaType*/) +{ + if (m_backendDb) + m_backendDb->removeEntry(url); +} + +// PackageRegistryBackend + +Reference<deployment::XPackage> BackendImpl::bindPackage_( + OUString const & url, OUString const & mediaType_, + bool bRemoved, OUString const & identifier, + Reference<XCommandEnvironment> const & xCmdEnv ) +{ + OUString mediaType(mediaType_); + if ( mediaType.isEmpty() || mediaType == "application/vnd.sun.star.uno-component" || mediaType == "application/vnd.sun.star.uno-typelibrary" ) + { + // detect exact media-type: + ::ucbhelper::Content ucbContent; + if (create_ucb_content( &ucbContent, url, xCmdEnv )) { + const OUString title( StrTitle::getTitle( ucbContent ) ); + if (title.endsWithIgnoreAsciiCase(SAL_DLLEXTENSION)) + { + mediaType = "application/vnd.sun.star.uno-component;type=native;platform=" + + getPlatformString(); + } + else if (title.endsWithIgnoreAsciiCase(".jar")) + { + if (jarManifestHeaderPresent( + url, u"RegistrationClassName", xCmdEnv )) + mediaType = "application/vnd.sun.star.uno-component;type=Java"; + if (mediaType.isEmpty()) + mediaType = "application/vnd.sun.star.uno-typelibrary;type=Java"; + } + else if (title.endsWithIgnoreAsciiCase(".py")) + mediaType = "application/vnd.sun.star.uno-component;type=Python"; + else if (title.endsWithIgnoreAsciiCase(".rdb")) + mediaType = "application/vnd.sun.star.uno-typelibrary;type=RDB"; + } + if (mediaType.isEmpty()) + throw lang::IllegalArgumentException( + StrCannotDetectMediaType() + url, + static_cast<OWeakObject *>(this), static_cast<sal_Int16>(-1) ); + } + + OUString type, subType; + INetContentTypeParameterList params; + if (INetContentTypes::parse( mediaType, type, subType, ¶ms )) + { + if (type.equalsIgnoreAsciiCase("application")) + { + OUString name; + if (!bRemoved) + { + ::ucbhelper::Content ucbContent( url, xCmdEnv, m_xComponentContext ); + name = StrTitle::getTitle( ucbContent ); + } + + if (subType.equalsIgnoreAsciiCase("vnd.sun.star.uno-component")) + { + // xxx todo: probe and evaluate component xml description + + auto const iter = params.find("platform"_ostr); + bool bPlatformFits(iter == params.end()); + OUString aPlatform; + if (!bPlatformFits) // platform is specified, we have to check + { + aPlatform = iter->second.m_sValue; + bPlatformFits = platform_fits(aPlatform); + } + // If the package is being removed, do not care whether + // platform fits. We won't be using it anyway. + if (bPlatformFits || bRemoved) { + auto const iterType = params.find("type"_ostr); + if (iterType != params.end()) + { + OUString const & value = iterType->second.m_sValue; + if (value.equalsIgnoreAsciiCase("native")) { + if (bPlatformFits) + return new BackendImpl::ComponentPackageImpl( + this, url, name, m_xDynComponentTypeInfo, + "com.sun.star.loader.SharedLibrary", + bRemoved, identifier); + else + return new BackendImpl::OtherPlatformPackageImpl( + this, url, name, m_xDynComponentTypeInfo, + bRemoved, identifier, aPlatform); + } + if (value.equalsIgnoreAsciiCase("Java")) { + return new BackendImpl::ComponentPackageImpl( + this, url, name, m_xJavaComponentTypeInfo, + "com.sun.star.loader.Java2", + bRemoved, identifier); + } + if (value.equalsIgnoreAsciiCase("Python")) { + return new BackendImpl::ComponentPackageImpl( + this, url, name, m_xPythonComponentTypeInfo, + "com.sun.star.loader.Python", + bRemoved, identifier); + } + } + } + } + else if (subType.equalsIgnoreAsciiCase("vnd.sun.star.uno-components")) + { + auto const iter = params.find("platform"_ostr); + if (iter == params.end() || platform_fits(iter->second.m_sValue)) { + return new BackendImpl::ComponentsPackageImpl( + this, url, name, m_xComponentsTypeInfo, bRemoved, + identifier); + } + } + else if (subType.equalsIgnoreAsciiCase( "vnd.sun.star.uno-typelibrary")) + { + auto const iter = params.find("type"_ostr); + if (iter != params.end()) { + OUString const & value = iter->second.m_sValue; + if (value.equalsIgnoreAsciiCase("RDB")) + { + return new BackendImpl::TypelibraryPackageImpl( + this, url, name, m_xRDBTypelibTypeInfo, + false /* rdb */, bRemoved, identifier); + } + if (value.equalsIgnoreAsciiCase("Java")) { + return new BackendImpl::TypelibraryPackageImpl( + this, url, name, m_xJavaTypelibTypeInfo, + true /* jar */, bRemoved, identifier); + } + } + } + } + } + throw lang::IllegalArgumentException( + StrUnsupportedMediaType() + mediaType, + static_cast<OWeakObject *>(this), + static_cast<sal_Int16>(-1) ); +} + + +void BackendImpl::unorc_verify_init( + Reference<XCommandEnvironment> const & xCmdEnv ) +{ + if (transientMode()) + return; + const ::osl::MutexGuard guard( m_aMutex ); + if ( m_unorc_inited) + return; + + // common rc: + ::ucbhelper::Content ucb_content; + if (create_ucb_content( + &ucb_content, + makeURL( getCachePath(), "unorc" ), + xCmdEnv, false /* no throw */ )) + { + OUString line; + if (readLine( &line, u"UNO_JAVA_CLASSPATH=", ucb_content, + RTL_TEXTENCODING_UTF8 )) + { + sal_Int32 index = sizeof ("UNO_JAVA_CLASSPATH=") - 1; + do { + OUString token( o3tl::trim(o3tl::getToken(line, 0, ' ', index )) ); + if (!token.isEmpty()) + { + if (create_ucb_content( + nullptr, expandUnoRcTerm(token), xCmdEnv, + false /* no throw */ )) + { + //The jar file may not exist anymore if a shared or bundled + //extension was removed, but it can still be in the unorc + //After running XExtensionManager::synchronize, the unorc is + //cleaned up + m_jar_typelibs.push_back( token ); + } + } + } + while (index >= 0); + } + if (readLine( &line, u"UNO_TYPES=", ucb_content, + RTL_TEXTENCODING_UTF8 )) { + sal_Int32 index = sizeof ("UNO_TYPES=") - 1; + do { + OUString token( o3tl::trim(o3tl::getToken(line, 0, ' ', index )) ); + if (!token.isEmpty()) + { + if (token[ 0 ] == '?') + token = token.copy( 1 ); + if (create_ucb_content( + nullptr, expandUnoRcTerm(token), xCmdEnv, + false /* no throw */ )) + { + //The RDB file may not exist anymore if a shared or bundled + //extension was removed, but it can still be in the unorc. + //After running XExtensionManager::synchronize, the unorc is + //cleaned up + m_rdb_typelibs.push_back( token ); + } + } + } + while (index >= 0); + } + if (readLine( &line, u"UNO_SERVICES=", ucb_content, + RTL_TEXTENCODING_UTF8 )) + { + // The UNO_SERVICES line always has the BNF form + // "UNO_SERVICES=" + // ("?$ORIGIN/" <common-rdb>)? -- first + // "${$ORIGIN/${_OS}_${_ARCH}rc:UNO_SERVICES}"? -- second + // ("?" ("BUNDLED_EXTENSIONS" | -- third + // "UNO_SHARED_PACKAGES_CACHE" | "UNO_USER_PACKAGES_CACHE") + // ...)* + // so can unambiguously be split into its three parts: + int state = 1; + for (sal_Int32 i = RTL_CONSTASCII_LENGTH("UNO_SERVICES="); + i >= 0;) + { + OUString token(line.getToken(0, ' ', i)); + if (!token.isEmpty()) + { + if (state == 1 && token.match("?$ORIGIN/")) + { + m_commonRDB_orig = token.copy( + RTL_CONSTASCII_LENGTH("?$ORIGIN/")); + state = 2; + } + else if ( state <= 2 && token == "${$ORIGIN/${_OS}_${_ARCH}rc:UNO_SERVICES}" ) + { + state = 3; + } + else + { + if (token[0] == '?') + { + token = token.copy(1); + } + m_components.push_back(token); + state = 3; + } + } + } + } + + // native rc: + if (create_ucb_content( + &ucb_content, + makeURL( getCachePath(), getPlatformString() + "rc"), + xCmdEnv, false /* no throw */ )) { + if (readLine( &line, u"UNO_SERVICES=", ucb_content, + RTL_TEXTENCODING_UTF8 )) { + m_nativeRDB_orig = line.copy( + sizeof ("UNO_SERVICES=?$ORIGIN/") - 1 ); + } + } + } + m_unorc_modified = false; + m_unorc_inited = true; +} + + +void BackendImpl::unorc_flush( Reference<XCommandEnvironment> const & xCmdEnv ) +{ + if (transientMode()) + return; + if (!m_unorc_inited || !m_unorc_modified) + return; + + OUString sOrigin = dp_misc::makeRcTerm(m_cachePath); + OString osOrigin = OUStringToOString(sOrigin, RTL_TEXTENCODING_UTF8); + OStringBuffer buf("ORIGIN=" + osOrigin + OStringChar(LF)); + + if (! m_jar_typelibs.empty()) + { + auto iPos( m_jar_typelibs.cbegin() ); + auto const iEnd( m_jar_typelibs.cend() ); + buf.append( "UNO_JAVA_CLASSPATH=" ); + while (iPos != iEnd) { + // encoded ASCII file-urls: + const OString item( + OUStringToOString( *iPos, RTL_TEXTENCODING_ASCII_US ) ); + buf.append( item ); + ++iPos; + if (iPos != iEnd) + buf.append( ' ' ); + } + buf.append(LF); + } + if (! m_rdb_typelibs.empty()) + { + auto iPos( m_rdb_typelibs.cbegin() ); + auto const iEnd( m_rdb_typelibs.cend() ); + buf.append( "UNO_TYPES=" ); + while (iPos != iEnd) { + buf.append( '?' ); + // encoded ASCII file-urls: + const OString item( + OUStringToOString( *iPos, RTL_TEXTENCODING_ASCII_US ) ); + buf.append( item ); + ++iPos; + if (iPos != iEnd) + buf.append( ' ' ); + } + buf.append(LF); + } + + // If we duplicated the common or native rdb then we must use those urls + //otherwise we use those of the original files. That is, m_commonRDB_orig + //and m_nativeRDB_orig; + OUString sCommonRDB(m_commonRDB.isEmpty() ? m_commonRDB_orig : m_commonRDB ); + OUString sNativeRDB(m_nativeRDB.isEmpty() ? m_nativeRDB_orig : m_nativeRDB ); + + if (!sCommonRDB.isEmpty() || !sNativeRDB.isEmpty() || + !m_components.empty()) + { + buf.append( "UNO_SERVICES=" ); + bool space = false; + if (!sCommonRDB.isEmpty()) + { + buf.append( "?$ORIGIN/" + + OUStringToOString( sCommonRDB, RTL_TEXTENCODING_ASCII_US ) ); + space = true; + } + if (!sNativeRDB.isEmpty()) + { + if (space) + { + buf.append(' '); + } + buf.append( "${$ORIGIN/${_OS}_${_ARCH}rc:UNO_SERVICES}" ); + space = true; + + // write native rc: + OString buf2 = + "ORIGIN=" + + osOrigin + + OStringChar(LF) + + "UNO_SERVICES=?$ORIGIN/" + + OUStringToOString( sNativeRDB, RTL_TEXTENCODING_ASCII_US ) + + OStringChar(LF); + + const Reference<io::XInputStream> xData( + ::xmlscript::createInputStream( + reinterpret_cast<sal_Int8 const *>(buf2.getStr()), + buf2.getLength() ) ); + ::ucbhelper::Content ucb_content( + makeURL( getCachePath(), getPlatformString() + "rc" ), + xCmdEnv, m_xComponentContext ); + ucb_content.writeStream( xData, true /* replace existing */ ); + } + for (auto const& component : m_components) + { + if (space) + { + buf.append(' '); + } + buf.append("?" + OUStringToOString(component, RTL_TEXTENCODING_UTF8)); + space = true; + } + buf.append(LF); + } + + // write unorc: + const Reference<io::XInputStream> xData( + ::xmlscript::createInputStream( + reinterpret_cast<sal_Int8 const *>(buf.getStr()), + buf.getLength() ) ); + ::ucbhelper::Content ucb_content( + makeURL( getCachePath(), "unorc" ), xCmdEnv, m_xComponentContext ); + ucb_content.writeStream( xData, true /* replace existing */ ); + + m_unorc_modified = false; +} + + +void BackendImpl::addToUnoRc( RcItem kind, OUString const & url_, + Reference<XCommandEnvironment> const & xCmdEnv ) +{ + const OUString rcterm( dp_misc::makeRcTerm(url_) ); + const ::osl::MutexGuard guard( m_aMutex ); + unorc_verify_init( xCmdEnv ); + std::deque<OUString> & rSet = getRcItemList(kind); + if (std::find( rSet.begin(), rSet.end(), rcterm ) == rSet.end()) { + rSet.push_front( rcterm ); // prepend to list, thus overriding + // write immediately: + m_unorc_modified = true; + unorc_flush( xCmdEnv ); + } +} + + +void BackendImpl::removeFromUnoRc( + RcItem kind, OUString const & url_, + Reference<XCommandEnvironment> const & xCmdEnv ) +{ + const OUString rcterm( dp_misc::makeRcTerm(url_) ); + const ::osl::MutexGuard guard( m_aMutex ); + unorc_verify_init( xCmdEnv ); + std::deque<OUString> & aRcItemList = getRcItemList(kind); + std::erase(aRcItemList, rcterm); + // write immediately: + m_unorc_modified = true; + unorc_flush( xCmdEnv ); +} + + +bool BackendImpl::hasInUnoRc( + RcItem kind, OUString const & url_ ) +{ + const OUString rcterm( dp_misc::makeRcTerm(url_) ); + const ::osl::MutexGuard guard( m_aMutex ); + std::deque<OUString> const & rSet = getRcItemList(kind); + return std::find( rSet.begin(), rSet.end(), rcterm ) != rSet.end(); +} + +css::uno::Reference< css::uno::XComponentContext > BackendImpl::getRootContext() + const +{ + css::uno::Reference< css::uno::XComponentContext > rootContext( + getComponentContext()->getValueByName("_root"), + css::uno::UNO_QUERY); + return rootContext.is() ? rootContext : getComponentContext(); +} + + +void BackendImpl::releaseObject( OUString const & id ) +{ + const ::osl::MutexGuard guard( m_aMutex ); + m_backendObjects.erase( id ); +} + + +Reference<XInterface> BackendImpl::getObject( OUString const & id ) +{ + const ::osl::MutexGuard guard( m_aMutex ); + const t_string2object::const_iterator iFind( m_backendObjects.find( id ) ); + if (iFind == m_backendObjects.end()) + return Reference<XInterface>(); + else + return iFind->second; +} + + +Reference<XInterface> BackendImpl::insertObject( + OUString const & id, Reference<XInterface> const & xObject ) +{ + const ::osl::MutexGuard guard( m_aMutex ); + const std::pair<t_string2object::iterator, bool> insertion( + m_backendObjects.emplace( id, xObject ) ); + return insertion.first->second; +} + + +Reference<XComponentContext> raise_uno_process( + Reference<XComponentContext> const & xContext, + ::rtl::Reference<AbortChannel> const & abortChannel ) +{ + OSL_ASSERT( xContext.is() ); + + OUString url( util::theMacroExpander::get(xContext)->expandMacros( "$URE_BIN_DIR/uno" ) ); + + const OUString connectStr = "uno:pipe,name=" + generateRandomPipeId() + ";urp;uno.ComponentContext"; + + // raise core UNO process to register/run a component, + // javavm service uses unorc next to executable to retrieve deployed + // jar typelibs + + std::vector<OUString> args{ +#if OSL_DEBUG_LEVEL == 0 + "--quiet", +#endif + "--singleaccept", + "-u", + connectStr, + // don't inherit from unorc: + "-env:INIFILENAME=" }; + + //now add the bootstrap variables which were supplied on the command line + std::vector<OUString> bootvars = getCmdBootstrapVariables(); + args.insert(args.end(), bootvars.begin(), bootvars.end()); + + oslProcess hProcess; + try { + hProcess = raiseProcess( + url, comphelper::containerToSequence(args) ); + } + catch (...) { + OUStringBuffer sMsg = "error starting process: " + url; + for(const auto& arg : args) + sMsg.append(" " + arg); + throw uno::RuntimeException(sMsg.makeStringAndClear()); + } + try { + return Reference<XComponentContext>( + resolveUnoURL( connectStr, xContext, abortChannel.get() ), + UNO_QUERY_THROW ); + } + catch (...) { + // try to terminate process: + if ( osl_terminateProcess( hProcess ) != osl_Process_E_None ) + { + OSL_ASSERT( false ); + } + throw; + } +} + +void extractComponentData( + css::uno::Reference< css::uno::XComponentContext > const & context, + css::uno::Reference< css::registry::XRegistryKey > const & registry, + ComponentBackendDb::Data * data, + std::vector< css::uno::Reference< css::uno::XInterface > > * factories, + css::uno::Reference< css::loader::XImplementationLoader > const & + componentLoader, + OUString const & componentUrl) +{ + OSL_ASSERT( + context.is() && registry.is() && data != nullptr && componentLoader.is()); + OUString registryName(registry->getKeyName()); + sal_Int32 prefix = registryName.getLength(); + if (!registryName.endsWith("/")) { + prefix += RTL_CONSTASCII_LENGTH("/"); + } + const css::uno::Sequence< css::uno::Reference< css::registry::XRegistryKey > > + keys(registry->openKeys()); + css::uno::Reference< css::lang::XMultiComponentFactory > smgr( + context->getServiceManager(), css::uno::UNO_SET_THROW); + for (css::uno::Reference< css::registry::XRegistryKey > const & key : keys) { + OUString name(key->getKeyName().copy(prefix)); + data->implementationNames.push_back(name); + css::uno::Reference< css::registry::XRegistryKey > singletons( + key->openKey("UNO/SINGLETONS")); + if (singletons.is()) { + sal_Int32 prefix2 = key->getKeyName().getLength() + + RTL_CONSTASCII_LENGTH("/UNO/SINGLETONS/"); + const css::uno::Sequence< + css::uno::Reference< css::registry::XRegistryKey > > + singletonKeys(singletons->openKeys()); + for (css::uno::Reference< css::registry::XRegistryKey > const & singletonKey : singletonKeys) { + data->singletons.emplace_back( + singletonKey->getKeyName().copy(prefix2), name); + } + } + if (factories != nullptr) { + factories->push_back( + componentLoader->activate( + name, OUString(), componentUrl, key)); + } + } +} + +void BackendImpl::ComponentPackageImpl::getComponentInfo( + ComponentBackendDb::Data * data, + std::vector< css::uno::Reference< css::uno::XInterface > > * factories, + Reference<XComponentContext> const & xContext ) +{ + const Reference<loader::XImplementationLoader> xLoader( + xContext->getServiceManager()->createInstanceWithContext( + m_loader, xContext ), UNO_QUERY ); + if (! xLoader.is()) + { + throw css::deployment::DeploymentException( + "cannot instantiate loader " + m_loader, + static_cast< OWeakObject * >(this), Any()); + } + + // HACK: highly dependent on stoc/source/servicemanager + // and stoc/source/implreg implementation which rely on the same + // services.rdb format! + // .../UNO/LOCATION and .../UNO/ACTIVATOR appear not to be written by + // writeRegistryInfo, however, but are known, fixed values here, so + // can be passed into extractComponentData + OUString url(getURL()); + const Reference<registry::XSimpleRegistry> xMemReg( + xContext->getServiceManager()->createInstanceWithContext( + "com.sun.star.registry.SimpleRegistry", xContext ), + UNO_QUERY_THROW ); + xMemReg->open( OUString() /* in mem */, false, true ); + xLoader->writeRegistryInfo( xMemReg->getRootKey(), OUString(), url ); + extractComponentData( + xContext, xMemReg->getRootKey(), data, factories, xLoader, url); +} + +void BackendImpl::ComponentPackageImpl::componentLiveInsertion( + ComponentBackendDb::Data const & data, + std::vector< css::uno::Reference< css::uno::XInterface > > const & + factories) +{ + css::uno::Reference< css::uno::XComponentContext > rootContext( + getMyBackend()->getRootContext()); + css::uno::Reference< css::container::XSet > set( + rootContext->getServiceManager(), css::uno::UNO_QUERY_THROW); + std::vector< css::uno::Reference< css::uno::XInterface > >::const_iterator + factory(factories.begin()); + for (auto const& implementationName : data.implementationNames) + { + try { + set->insert(css::uno::Any(*factory++)); + } catch (const container::ElementExistException &) { + SAL_WARN("desktop.deployment", "implementation already registered " << implementationName); + } + } + if (data.singletons.empty()) return; + + css::uno::Reference< css::container::XNameContainer > cont( + rootContext, css::uno::UNO_QUERY_THROW); + for (auto const& singleton : data.singletons) + { + OUString name("/singletons/" + singleton.first); + //TODO: Update should be atomic: + try { + cont->removeByName( name + "/arguments"); + } catch (const container::NoSuchElementException &) {} + try { + cont->insertByName( name + "/service", css::uno::Any(singleton.second)); + } catch (const container::ElementExistException &) { + cont->replaceByName( name + "/service", css::uno::Any(singleton.second)); + } + try { + cont->insertByName(name, css::uno::Any()); + } catch (const container::ElementExistException &) { + SAL_WARN("desktop.deployment", "singleton already registered " << singleton.first); + cont->replaceByName(name, css::uno::Any()); + } + } +} + +void BackendImpl::ComponentPackageImpl::componentLiveRemoval( + ComponentBackendDb::Data const & data) +{ + css::uno::Reference< css::uno::XComponentContext > rootContext( + getMyBackend()->getRootContext()); + css::uno::Reference< css::container::XSet > set( + rootContext->getServiceManager(), css::uno::UNO_QUERY_THROW); + for (auto const& implementationName : data.implementationNames) + { + try { + set->remove(css::uno::Any(implementationName)); + } catch (const css::container::NoSuchElementException &) { + // ignore if factory has not been live deployed + } + } + if (data.singletons.empty()) + return; + + css::uno::Reference< css::container::XNameContainer > cont( + rootContext, css::uno::UNO_QUERY_THROW); + for (auto const& singleton : data.singletons) + { + OUString name("/singletons/" + singleton.first); + //TODO: Removal should be atomic: + try { + cont->removeByName(name); + } catch (const container::NoSuchElementException &) {} + try { + cont->removeByName( name + "/service" ); + } catch (const container::NoSuchElementException &) {} + try { + cont->removeByName( name + "/arguments" ); + } catch (const container::NoSuchElementException &) {} + } +} + +// Package + +//We could use here BackendImpl::hasActiveEntry. However, this check is just as well. +//And it also shows the problem if another extension has overwritten an implementation +//entry, because it contains the same service implementation +beans::Optional< beans::Ambiguous<sal_Bool> > +BackendImpl::ComponentPackageImpl::isRegistered_( + ::osl::ResettableMutexGuard &, + ::rtl::Reference<AbortChannel> const & abortChannel, + Reference<XCommandEnvironment> const & ) +{ + if (m_registered == Reg::Uninit) + { + m_registered = Reg::NotRegistered; + const Reference<registry::XSimpleRegistry> xRDB( getRDB() ); + if (xRDB.is()) + { + bool bAmbiguousComponentName = false; + // lookup rdb for location URL: + const Reference<registry::XRegistryKey> xRootKey( + xRDB->getRootKey() ); + const Reference<registry::XRegistryKey> xImplKey( + xRootKey->openKey( "IMPLEMENTATIONS" ) ); + Sequence<OUString> implNames; + if (xImplKey.is() && xImplKey->isValid()) + implNames = xImplKey->getKeyNames(); + OUString const * pImplNames = implNames.getConstArray(); + sal_Int32 pos = implNames.getLength(); + for ( ; pos--; ) + { + checkAborted( abortChannel ); + const OUString key( + pImplNames[ pos ] + "/UNO/LOCATION" ); + const Reference<registry::XRegistryKey> xKey( + xRootKey->openKey(key) ); + if (xKey.is() && xKey->isValid()) + { + const OUString location( xKey->getAsciiValue() ); + if (location.equalsIgnoreAsciiCase( getURL() )) + { + break; + } + else + { + //try to match only the file name + OUString thisUrl(getURL()); + std::u16string_view thisFileName(thisUrl.subView(thisUrl.lastIndexOf('/'))); + + std::u16string_view locationFileName(location.subView(location.lastIndexOf('/'))); + if (o3tl::equalsIgnoreAsciiCase(locationFileName, thisFileName)) + bAmbiguousComponentName = true; + } + } + } + if (pos >= 0) + m_registered = Reg::Registered; + else if (bAmbiguousComponentName) + m_registered = Reg::MaybeRegistered; + } + } + + //Different extensions can use the same service implementations. Then the extensions + //which was installed last will overwrite the one from the other extension. That is + //the registry will contain the path (the location) of the library or jar of the + //second extension. In this case isRegistered called for the lib of the first extension + //would return "not registered". That would mean that during uninstallation + //XPackage::registerPackage is not called, because it just was not registered. This is, + //however, necessary for jar files. Registering and unregistering update + //uno_packages/cache/registry/com.sun.star.comp.deployment.component.PackageRegistryBackend/unorc + //Therefore, we will return always "is ambiguous" if the path of this component cannot + //be found in the registry and if there is another path and both have the same file name (but + //the rest of the path is different). + //If the caller cannot precisely determine that this package was registered, then it must + //call registerPackage. + bool bAmbiguous = m_registered == Reg::Void // Reg::Void == we are in the progress of unregistration + || m_registered == Reg::MaybeRegistered; + return beans::Optional< beans::Ambiguous<sal_Bool> >( + true /* IsPresent */, + beans::Ambiguous<sal_Bool>( + m_registered == Reg::Registered, bAmbiguous) ); +} + + +void BackendImpl::ComponentPackageImpl::processPackage_( + ::osl::ResettableMutexGuard &, + bool doRegisterPackage, + bool startup, + ::rtl::Reference<AbortChannel> const & abortChannel, + Reference<XCommandEnvironment> const & xCmdEnv ) +{ + BackendImpl * that = getMyBackend(); + OUString url(getURL()); + if (doRegisterPackage) { + ComponentBackendDb::Data data; + css::uno::Reference< css::uno::XComponentContext > context; + if (startup) { + context = that->getComponentContext(); + } else { + context.set(that->getObject(url), css::uno::UNO_QUERY); + if (!context.is()) { + context.set( + that->insertObject( + url, + raise_uno_process( + that->getComponentContext(), abortChannel)), + css::uno::UNO_QUERY_THROW); + } + } + css::uno::Reference< css::registry::XImplementationRegistration> impreg( + context->getServiceManager()->createInstanceWithContext( + "com.sun.star.registry.ImplementationRegistration", + context), + css::uno::UNO_QUERY_THROW); + css::uno::Reference< css::registry::XSimpleRegistry > rdb(getRDB()); + impreg->registerImplementation(m_loader, url, rdb); + // Only write to unorc after successful registration; it may fail if + // there is no suitable java + if (m_loader == "com.sun.star.loader.Java2" && !jarManifestHeaderPresent(url, u"UNO-Type-Path", xCmdEnv)) + { + that->addToUnoRc(RCITEM_JAR_TYPELIB, url, xCmdEnv); + data.javaTypeLibrary = true; + } + std::vector< css::uno::Reference< css::uno::XInterface > > factories; + getComponentInfo(&data, startup ? nullptr : &factories, context); + if (!startup) { + try { + componentLiveInsertion(data, factories); + } catch (css::uno::Exception &) { + TOOLS_INFO_EXCEPTION("desktop.deployment", "caught"); + try { + impreg->revokeImplementation(url, rdb); + } catch (css::uno::RuntimeException &) { + TOOLS_WARN_EXCEPTION("desktop.deployment", "ignored"); + } + throw; + } + } + m_registered = Reg::Registered; + that->addDataToDb(url, data); + } else { // revoke + m_registered = Reg::Void; + ComponentBackendDb::Data data(that->readDataFromDb(url)); + css::uno::Reference< css::uno::XComponentContext > context( + that->getObject(url), css::uno::UNO_QUERY); + bool remoteContext = context.is(); + if (!remoteContext) { + context = that->getComponentContext(); + } + if (!startup) { + componentLiveRemoval(data); + } + css::uno::Reference< css::registry::XImplementationRegistration >( + context->getServiceManager()->createInstanceWithContext( + "com.sun.star.registry.ImplementationRegistration", + context), + css::uno::UNO_QUERY_THROW)->revokeImplementation(url, getRDB()); + if (data.javaTypeLibrary) { + that->removeFromUnoRc(RCITEM_JAR_TYPELIB, url, xCmdEnv); + } + if (remoteContext) { + that->releaseObject(url); + } + m_registered = Reg::NotRegistered; + getMyBackend()->revokeEntryFromDb(url); + } +} + +BackendImpl::TypelibraryPackageImpl::TypelibraryPackageImpl( + ::rtl::Reference<PackageRegistryBackend> const & myBackend, + OUString const & url, OUString const & name, + Reference<deployment::XPackageTypeInfo> const & xPackageType, + bool jarFile, bool bRemoved, OUString const & identifier) + : Package( myBackend, url, name, name /* display-name */, + xPackageType, bRemoved, identifier), + m_jarFile( jarFile ) +{ +} + +// Package +BackendImpl * BackendImpl::TypelibraryPackageImpl::getMyBackend() const +{ + BackendImpl * pBackend = static_cast<BackendImpl *>(m_myBackend.get()); + if (nullptr == pBackend) + { + //May throw a DisposedException + check(); + //We should never get here... + throw RuntimeException( "Failed to get the BackendImpl", + static_cast<OWeakObject*>(const_cast<TypelibraryPackageImpl *>(this))); + } + return pBackend; +} + +beans::Optional< beans::Ambiguous<sal_Bool> > +BackendImpl::TypelibraryPackageImpl::isRegistered_( + ::osl::ResettableMutexGuard &, + ::rtl::Reference<AbortChannel> const &, + Reference<XCommandEnvironment> const & ) +{ + BackendImpl * that = getMyBackend(); + return beans::Optional< beans::Ambiguous<sal_Bool> >( + true /* IsPresent */, + beans::Ambiguous<sal_Bool>( + that->hasInUnoRc( + m_jarFile ? RCITEM_JAR_TYPELIB : RCITEM_RDB_TYPELIB, getURL() ), + false /* IsAmbiguous */ ) ); +} + + +void BackendImpl::TypelibraryPackageImpl::processPackage_( + ::osl::ResettableMutexGuard &, + bool doRegisterPackage, + bool /*startup*/, + ::rtl::Reference<AbortChannel> const &, + Reference<XCommandEnvironment> const & xCmdEnv ) +{ + BackendImpl * that = getMyBackend(); + const OUString url( getURL() ); + + if (doRegisterPackage) + { + // live insertion: + if (m_jarFile) { + // xxx todo add to classpath at runtime: ??? + //SB: It is probably not worth it to add the live inserted type + // library JAR to the UnoClassLoader in the soffice process. Any + // live inserted component JAR that might reference this type + // library JAR runs in its own uno process, so there is probably no + // Java code in the soffice process that would see any UNO types + // introduced by this type library JAR. + } + else // RDB: + { + css::uno::Reference< css::container::XSet >( + that->getComponentContext()->getValueByName( + "/singletons" + "/com.sun.star.reflection.theTypeDescriptionManager"), + css::uno::UNO_QUERY_THROW)->insert( + css::uno::Any(expandUnoRcUrl(url))); + } + + that->addToUnoRc( m_jarFile ? RCITEM_JAR_TYPELIB : RCITEM_RDB_TYPELIB, + url, xCmdEnv ); + } + else // revokePackage() + { + that->removeFromUnoRc( + m_jarFile ? RCITEM_JAR_TYPELIB : RCITEM_RDB_TYPELIB, url, xCmdEnv ); + + // revoking types at runtime, possible, sensible? + if (!m_jarFile) { + css::uno::Reference< css::container::XSet >( + that->getComponentContext()->getValueByName( + "/singletons" + "/com.sun.star.reflection.theTypeDescriptionManager"), + css::uno::UNO_QUERY_THROW)->remove( + css::uno::Any(expandUnoRcUrl(url))); + } + } +} + +BackendImpl::OtherPlatformPackageImpl::OtherPlatformPackageImpl( + ::rtl::Reference<PackageRegistryBackend> const & myBackend, + OUString const & url, OUString const & name, + Reference<deployment::XPackageTypeInfo> const & xPackageType, + bool bRemoved, OUString const & identifier, OUString platform) + : Package(myBackend, url, name, name, xPackageType, bRemoved, identifier) + , m_aPlatform(std::move(platform)) +{ + OSL_PRECOND(bRemoved, "this class can only be used for removing packages!"); +} + +BackendImpl * +BackendImpl::OtherPlatformPackageImpl::getMyBackend() const +{ + BackendImpl * pBackend = static_cast<BackendImpl *>(m_myBackend.get()); + if (nullptr == pBackend) + { + //Throws a DisposedException + check(); + //We should never get here... + throw RuntimeException("Failed to get the BackendImpl", + static_cast<OWeakObject*>(const_cast<OtherPlatformPackageImpl*>(this))); + } + return pBackend; +} + +Reference<registry::XSimpleRegistry> +BackendImpl::OtherPlatformPackageImpl::impl_openRDB() const +{ + OUString const aRDB(m_aPlatform + ".rdb"); + OUString const aRDBPath(makeURL(getMyBackend()->getCachePath(), aRDB)); + + Reference<registry::XSimpleRegistry> xRegistry; + + try + { + xRegistry.set( + impl_createInstance("com.sun.star.registry.SimpleRegistry"), + UNO_QUERY) + ; + if (xRegistry.is()) + xRegistry->open(expandUnoRcUrl(aRDBPath), false, false); + } + catch (registry::InvalidRegistryException const&) + { + // If the registry does not exist, we do not need to bother at all + xRegistry.clear(); + } + + SAL_WARN_IF( !xRegistry.is(), "desktop.deployment", "could not create registry for the package's platform"); + return xRegistry; +} + +Reference<XInterface> +BackendImpl::OtherPlatformPackageImpl::impl_createInstance(OUString const& rService) +const +{ + Reference<XComponentContext> const xContext(getMyBackend()->getComponentContext()); + OSL_ASSERT(xContext.is()); + Reference<XInterface> xService; + if (xContext.is()) + xService.set(xContext->getServiceManager()->createInstanceWithContext(rService, xContext)); + return xService; +} + +beans::Optional<beans::Ambiguous<sal_Bool> > +BackendImpl::OtherPlatformPackageImpl::isRegistered_( + ::osl::ResettableMutexGuard& /* guard */, + ::rtl::Reference<AbortChannel> const& /* abortChannel */, + Reference<XCommandEnvironment> const& /* xCmdEnv */ ) +{ + return beans::Optional<beans::Ambiguous<sal_Bool> >(true, + beans::Ambiguous<sal_Bool>(true, false)); +} + +void +BackendImpl::OtherPlatformPackageImpl::processPackage_( + ::osl::ResettableMutexGuard& /* guard */, + bool bRegisterPackage, + bool /* bStartup */, + ::rtl::Reference<AbortChannel> const& /* abortChannel */, + Reference<XCommandEnvironment> const& /* xCmdEnv */) +{ + OSL_PRECOND(!bRegisterPackage, "this class can only be used for removing packages!"); + + OUString const aURL(getURL()); + + Reference<registry::XSimpleRegistry> const xServicesRDB(impl_openRDB()); + Reference<registry::XImplementationRegistration> const xImplReg( + impl_createInstance("com.sun.star.registry.ImplementationRegistration"), + UNO_QUERY) + ; + if (xImplReg.is() && xServicesRDB.is()) + xImplReg->revokeImplementation(aURL, xServicesRDB); + if (xServicesRDB.is()) + xServicesRDB->close(); + + getMyBackend()->revokeEntryFromDb(aURL); +} + +BackendImpl * BackendImpl::ComponentsPackageImpl::getMyBackend() const +{ + BackendImpl * pBackend = static_cast<BackendImpl *>(m_myBackend.get()); + if (nullptr == pBackend) + { + //Throws a DisposedException + check(); + //We should never get here... + throw RuntimeException("Failed to get the BackendImpl", + static_cast<OWeakObject*>(const_cast<ComponentsPackageImpl *>(this))); + } + return pBackend; +} + +beans::Optional< beans::Ambiguous<sal_Bool> > +BackendImpl::ComponentsPackageImpl::isRegistered_( + ::osl::ResettableMutexGuard &, + ::rtl::Reference<AbortChannel> const &, + Reference<XCommandEnvironment> const & ) +{ + return beans::Optional< beans::Ambiguous<sal_Bool> >( + true, + beans::Ambiguous<sal_Bool>( + getMyBackend()->hasInUnoRc(RCITEM_COMPONENTS, getURL()), false)); +} + +void BackendImpl::ComponentsPackageImpl::processPackage_( + ::osl::ResettableMutexGuard &, + bool doRegisterPackage, + bool startup, + ::rtl::Reference<AbortChannel> const & abortChannel, + Reference<XCommandEnvironment> const & xCmdEnv ) +{ + BackendImpl * that = getMyBackend(); + OUString url(getURL()); + if (doRegisterPackage) { + if (!startup) { + css::uno::Reference< css::uno::XComponentContext > context( + that->getObject(url), css::uno::UNO_QUERY); + if (!context.is()) { + context.set( + that->insertObject( + url, + raise_uno_process( + that->getComponentContext(), abortChannel)), + css::uno::UNO_QUERY_THROW); + } + // This relies on the root component context's service manager + // supporting the extended XSet semantics: + css::uno::Sequence< css::beans::NamedValue > args + { + { "uri", css::uno::Any(expandUnoRcUrl(url)) }, + { "component-context", css::uno::Any(context) } + }; + css::uno::Reference< css::container::XSet > smgr( + that->getRootContext()->getServiceManager(), + css::uno::UNO_QUERY_THROW); + smgr->insert(css::uno::Any(args)); + } + that->addToUnoRc(RCITEM_COMPONENTS, url, xCmdEnv); + } else { // revoke + that->removeFromUnoRc(RCITEM_COMPONENTS, url, xCmdEnv); + if (!startup) { + // This relies on the root component context's service manager + // supporting the extended XSet semantics: + css::uno::Sequence< css::beans::NamedValue > args { { "uri", css::uno::Any(expandUnoRcUrl(url)) } }; + css::uno::Reference< css::container::XSet > smgr( + that->getRootContext()->getServiceManager(), + css::uno::UNO_QUERY_THROW); + smgr->remove(css::uno::Any(args)); + } + that->releaseObject(url); + that->revokeEntryFromDb(url); // in case it got added with old code + } +} + +BackendImpl::ComponentsPackageImpl::ComponentsPackageImpl( + ::rtl::Reference<PackageRegistryBackend> const & myBackend, + OUString const & url, OUString const & name, + Reference<deployment::XPackageTypeInfo> const & xPackageType, + bool bRemoved, OUString const & identifier) + : Package( myBackend, url, name, name /* display-name */, + xPackageType, bRemoved, identifier) +{} + +} // anon namespace + +} // namespace dp_registry + + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +com_sun_star_comp_deployment_component_PackageRegistryBackend_get_implementation( + css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const& args) +{ + return cppu::acquire(new dp_registry::backend::component::BackendImpl(args, context)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/registry/configuration/dp_configuration.cxx b/desktop/source/deployment/registry/configuration/dp_configuration.cxx new file mode 100644 index 0000000000..6228142486 --- /dev/null +++ b/desktop/source/deployment/registry/configuration/dp_configuration.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 . + */ + +//TODO: Large parts of this file were copied from dp_component.cxx; those parts +// should be consolidated. + +#include <config_extensions.h> + +#include <dp_backend.h> +#if HAVE_FEATURE_EXTENSIONS +#include <dp_persmap.h> +#endif +#include <dp_misc.h> +#include <dp_ucb.h> +#include <rtl/string.hxx> +#include <rtl/strbuf.hxx> +#include <rtl/ustrbuf.hxx> +#include <cppuhelper/exc_hlp.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <ucbhelper/content.hxx> +#include <unotools/ucbhelper.hxx> +#include <xmlscript/xml_helper.hxx> +#include <comphelper/lok.hxx> +#include <comphelper/xmlencode.hxx> +#include <svl/inettype.hxx> +#include <o3tl/string_view.hxx> +#include <com/sun/star/configuration/Update.hpp> +#include <com/sun/star/lang/IllegalArgumentException.hpp> +#include <com/sun/star/lang/WrappedTargetRuntimeException.hpp> +#include <deque> +#include <memory> +#include <string_view> +#include <utility> + +#include "dp_configurationbackenddb.hxx" + +using namespace ::dp_misc; +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::ucb; + +namespace dp_registry::backend::configuration { +namespace { + +class BackendImpl : public ::dp_registry::backend::PackageRegistryBackend +{ + class PackageImpl : public ::dp_registry::backend::Package + { + BackendImpl * getMyBackend() const ; + + const bool m_isSchema; + + // Package + virtual beans::Optional< beans::Ambiguous<sal_Bool> > isRegistered_( + ::osl::ResettableMutexGuard & guard, + ::rtl::Reference<AbortChannel> const & abortChannel, + Reference<XCommandEnvironment> const & xCmdEnv ) override; + virtual void processPackage_( + ::osl::ResettableMutexGuard & guard, + bool registerPackage, + bool startup, + ::rtl::Reference<AbortChannel> const & abortChannel, + Reference<XCommandEnvironment> const & xCmdEnv ) override; + + public: + PackageImpl( + ::rtl::Reference<PackageRegistryBackend> const & myBackend, + OUString const & url, OUString const & name, + Reference<deployment::XPackageTypeInfo> const & xPackageType, + bool isSchema, bool bRemoved, OUString const & identifier) + : Package( myBackend, url, name, name /* display-name */, + xPackageType, bRemoved, identifier), + m_isSchema( isSchema ) + {} + }; + friend class PackageImpl; + + std::deque<OUString> m_xcs_files; + std::deque<OUString> m_xcu_files; + std::deque<OUString> & getFiles( bool xcs ) { + return xcs ? m_xcs_files : m_xcu_files; + } + + bool m_configmgrini_inited; + bool m_configmgrini_modified; + std::unique_ptr<ConfigurationBackendDb> m_backendDb; + + // PackageRegistryBackend + virtual Reference<deployment::XPackage> bindPackage_( + OUString const & url, OUString const & mediaType, bool bRemoved, + OUString const & identifier, + Reference<XCommandEnvironment> const & xCmdEnv ) override; + +#if HAVE_FEATURE_EXTENSIONS + // for backwards compatibility - nil if no (compatible) back-compat db present + std::unique_ptr<PersistentMap> m_registeredPackages; +#endif + + virtual void SAL_CALL disposing() override; + + const Reference<deployment::XPackageTypeInfo> m_xConfDataTypeInfo; + const Reference<deployment::XPackageTypeInfo> m_xConfSchemaTypeInfo; + Sequence< Reference<deployment::XPackageTypeInfo> > m_typeInfos; + + void configmgrini_verify_init( + Reference<XCommandEnvironment> const & xCmdEnv ); + void configmgrini_flush( Reference<XCommandEnvironment> const & xCmdEnv ); + + /* The parameter isURL is false in the case of adding the conf:ini-entry + value from the backend db. This entry already contains the path as it + is used in the configmgr.ini. + */ + void addToConfigmgrIni( bool isSchema, bool isURL, OUString const & url, + Reference<XCommandEnvironment> const & xCmdEnv ); +#if HAVE_FEATURE_EXTENSIONS + bool removeFromConfigmgrIni( bool isSchema, OUString const & url, + Reference<XCommandEnvironment> const & xCmdEnv ); +#endif + void addDataToDb(OUString const & url, ConfigurationBackendDb::Data const & data); + ::std::optional<ConfigurationBackendDb::Data> readDataFromDb(std::u16string_view url); + void revokeEntryFromDb(std::u16string_view url); + bool hasActiveEntry(std::u16string_view url); + bool activateEntry(std::u16string_view url); + +public: + BackendImpl( Sequence<Any> const & args, + Reference<XComponentContext> const & xComponentContext ); + + // 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; + + // XPackageRegistry + virtual Sequence< Reference<deployment::XPackageTypeInfo> > SAL_CALL + getSupportedPackageTypes() override; + virtual void SAL_CALL packageRemoved(OUString const & url, OUString const & mediaType) override; + + using PackageRegistryBackend::disposing; +}; + + +void BackendImpl::disposing() +{ + try { + configmgrini_flush( Reference<XCommandEnvironment>() ); + + PackageRegistryBackend::disposing(); + } + catch (const RuntimeException &) { + throw; + } + catch (const Exception &) { + Any exc( ::cppu::getCaughtException() ); + throw lang::WrappedTargetRuntimeException( + "caught unexpected exception while disposing...", + static_cast<OWeakObject *>(this), exc ); + } +} + + +BackendImpl::BackendImpl( + Sequence<Any> const & args, + Reference<XComponentContext> const & xComponentContext ) + : PackageRegistryBackend( args, xComponentContext ), + m_configmgrini_inited( false ), + m_configmgrini_modified( false ), + m_xConfDataTypeInfo( new Package::TypeInfo( + "application/vnd.sun.star.configuration-data", + "*.xcu", + DpResId(RID_STR_CONF_DATA) + ) ), + m_xConfSchemaTypeInfo( new Package::TypeInfo( + "application/vnd.sun.star.configuration-schema", + "*.xcs", + DpResId(RID_STR_CONF_SCHEMA) + ) ), + m_typeInfos{ m_xConfDataTypeInfo, m_xConfSchemaTypeInfo } +{ + const Reference<XCommandEnvironment> xCmdEnv; + + if (transientMode()) + { + // TODO + } + else + { + OUString dbFile = makeURL(getCachePath(), "backenddb.xml"); + m_backendDb.reset( + new ConfigurationBackendDb(getComponentContext(), dbFile)); + //clean up data folders which are no longer used. + //This must not be done in the same process where the help files + //are still registers. Only after revoking and restarting OOo the folders + //can be removed. This works now, because the extension manager is a singleton + //and the backends are only create once per process. + std::vector<OUString> folders = m_backendDb->getAllDataUrls(); + deleteUnusedFolders(folders); + + configmgrini_verify_init( xCmdEnv ); + +#if HAVE_FEATURE_EXTENSIONS + std::unique_ptr<PersistentMap> pMap; + OUString aCompatURL( makeURL( getCachePath(), "registered_packages.pmap" ) ); + + // Don't create it if it doesn't exist already + if ( ::utl::UCBContentHelper::Exists( expandUnoRcUrl( aCompatURL ) ) ) + { + try + { + pMap.reset( new PersistentMap( aCompatURL ) ); + } + catch (const Exception &e) + { + OUString aStr = "Exception loading legacy package database: '" + + e.Message + + "' - ignoring file, please remove it.\n"; + dp_misc::writeConsole( aStr ); + } + } + m_registeredPackages = std::move(pMap); +#endif + } +} + +// XServiceInfo +OUString BackendImpl::getImplementationName() +{ + return "com.sun.star.comp.deployment.configuration.PackageRegistryBackend"; +} + +sal_Bool BackendImpl::supportsService( const OUString& ServiceName ) +{ + return cppu::supportsService(this, ServiceName); +} + +css::uno::Sequence< OUString > BackendImpl::getSupportedServiceNames() +{ + return { BACKEND_SERVICE_NAME }; +} + +void BackendImpl::addDataToDb( + OUString const & url, ConfigurationBackendDb::Data const & data) +{ + if (m_backendDb) + m_backendDb->addEntry(url, data); +} + +::std::optional<ConfigurationBackendDb::Data> BackendImpl::readDataFromDb( + std::u16string_view url) +{ + ::std::optional<ConfigurationBackendDb::Data> data; + if (m_backendDb) + data = m_backendDb->getEntry(url); + return data; +} + +void BackendImpl::revokeEntryFromDb(std::u16string_view url) +{ + if (m_backendDb) + m_backendDb->revokeEntry(url); +} + +bool BackendImpl::hasActiveEntry(std::u16string_view url) +{ + if (m_backendDb) + return m_backendDb->hasActiveEntry(url); + return false; +} + +bool BackendImpl::activateEntry(std::u16string_view url) +{ + if (m_backendDb) + return m_backendDb->activateEntry(url); + return false; +} + + +// XPackageRegistry + +Sequence< Reference<deployment::XPackageTypeInfo> > +BackendImpl::getSupportedPackageTypes() +{ + return m_typeInfos; +} +void BackendImpl::packageRemoved(OUString const & url, OUString const & /*mediaType*/) +{ + if (m_backendDb) + m_backendDb->removeEntry(url); +} + +// PackageRegistryBackend + +Reference<deployment::XPackage> BackendImpl::bindPackage_( + OUString const & url, OUString const & mediaType_, + bool bRemoved, OUString const & identifier, + Reference<XCommandEnvironment> const & xCmdEnv ) +{ + OUString mediaType( mediaType_ ); + if (mediaType.isEmpty()) + { + // detect media-type: + ::ucbhelper::Content ucbContent; + if (create_ucb_content( &ucbContent, url, xCmdEnv )) + { + const OUString title( StrTitle::getTitle( ucbContent ) ); + if (title.endsWithIgnoreAsciiCase( ".xcu" )) { + mediaType = "application/vnd.sun.star.configuration-data"; + } + if (title.endsWithIgnoreAsciiCase( ".xcs" )) { + mediaType = "application/vnd.sun.star.configuration-schema"; + } + } + if (mediaType.isEmpty()) + throw lang::IllegalArgumentException( + StrCannotDetectMediaType() + url, + static_cast<OWeakObject *>(this), static_cast<sal_Int16>(-1) ); + } + + OUString type, subType; + INetContentTypeParameterList params; + if (INetContentTypes::parse( mediaType, type, subType, ¶ms )) + { + if (type.equalsIgnoreAsciiCase("application")) + { + OUString name; + if (!bRemoved) + { + ::ucbhelper::Content ucbContent( url, xCmdEnv, m_xComponentContext ); + name = StrTitle::getTitle( ucbContent ); + } + + if (subType.equalsIgnoreAsciiCase( "vnd.sun.star.configuration-data")) + { + return new PackageImpl( + this, url, name, m_xConfDataTypeInfo, false /* data file */, + bRemoved, identifier); + } + else if (subType.equalsIgnoreAsciiCase( "vnd.sun.star.configuration-schema")) { + return new PackageImpl( + this, url, name, m_xConfSchemaTypeInfo, true /* schema file */, + bRemoved, identifier); + } + } + } + throw lang::IllegalArgumentException( + StrUnsupportedMediaType() + mediaType, + static_cast<OWeakObject *>(this), + static_cast<sal_Int16>(-1) ); +} + + +void BackendImpl::configmgrini_verify_init( + Reference<XCommandEnvironment> const & xCmdEnv ) +{ + if (transientMode()) + return; + const ::osl::MutexGuard guard( m_aMutex ); + if ( m_configmgrini_inited) + return; + + // common rc: + ::ucbhelper::Content ucb_content; + if (create_ucb_content( + &ucb_content, + makeURL( getCachePath(), "configmgr.ini" ), + xCmdEnv, false /* no throw */ )) + { + OUString line; + if (readLine( &line, u"SCHEMA=", ucb_content, + RTL_TEXTENCODING_UTF8 )) + { + sal_Int32 index = RTL_CONSTASCII_LENGTH("SCHEMA="); + do { + OUString token( o3tl::trim(o3tl::getToken(line, 0, ' ', index )) ); + if (!token.isEmpty()) { + //The file may not exist anymore if a shared or bundled + //extension was removed, but it can still be in the configmgrini. + //After running XExtensionManager::synchronize, the configmgrini is + //cleaned up + m_xcs_files.push_back( token ); + } + } + while (index >= 0); + } + if (readLine( &line, u"DATA=", ucb_content, + RTL_TEXTENCODING_UTF8 )) { + sal_Int32 index = RTL_CONSTASCII_LENGTH("DATA="); + do { + std::u16string_view token( o3tl::trim(o3tl::getToken(line, 0, ' ', index )) ); + if (!token.empty()) + { + if (token[ 0 ] == '?') + token = token.substr( 1 ); + //The file may not exist anymore if a shared or bundled + //extension was removed, but it can still be in the configmgrini. + //After running XExtensionManager::synchronize, the configmgrini is + //cleaned up + m_xcu_files.push_back( OUString(token) ); + } + } + while (index >= 0); + } + } + m_configmgrini_modified = false; + m_configmgrini_inited = true; +} + + +void BackendImpl::configmgrini_flush( + Reference<XCommandEnvironment> const & xCmdEnv ) +{ + if (transientMode()) + return; + if (!m_configmgrini_inited || !m_configmgrini_modified) + return; + + OStringBuffer buf; + if (! m_xcs_files.empty()) + { + auto iPos( m_xcs_files.cbegin() ); + auto const iEnd( m_xcs_files.cend() ); + buf.append( "SCHEMA=" ); + while (iPos != iEnd) { + // encoded ASCII file-urls: + const OString item( + OUStringToOString( *iPos, RTL_TEXTENCODING_ASCII_US ) ); + buf.append( item ); + ++iPos; + if (iPos != iEnd) + buf.append( ' ' ); + } + buf.append(LF); + } + if (! m_xcu_files.empty()) + { + auto iPos( m_xcu_files.cbegin() ); + auto const iEnd( m_xcu_files.cend() ); + buf.append( "DATA=" ); + while (iPos != iEnd) { + // encoded ASCII file-urls: + const OString item( + OUStringToOString( *iPos, RTL_TEXTENCODING_ASCII_US ) ); + buf.append( item ); + ++iPos; + if (iPos != iEnd) + buf.append( ' ' ); + } + buf.append(LF); + } + + // write configmgr.ini: + const Reference<io::XInputStream> xData( + ::xmlscript::createInputStream( + reinterpret_cast<sal_Int8 const *>(buf.getStr()), + buf.getLength() ) ); + ::ucbhelper::Content ucb_content( + makeURL( getCachePath(), "configmgr.ini" ), xCmdEnv, m_xComponentContext ); + ucb_content.writeStream( xData, true /* replace existing */ ); + + m_configmgrini_modified = false; +} + + +void BackendImpl::addToConfigmgrIni( bool isSchema, bool isURL, OUString const & url_, + Reference<XCommandEnvironment> const & xCmdEnv ) +{ + const OUString rcterm( isURL ? dp_misc::makeRcTerm(url_) : url_ ); + const ::osl::MutexGuard guard( m_aMutex ); + configmgrini_verify_init( xCmdEnv ); + std::deque<OUString> & rSet = getFiles(isSchema); + if (std::find( rSet.begin(), rSet.end(), rcterm ) == rSet.end()) { + rSet.push_front( rcterm ); // prepend to list, thus overriding + // write immediately: + m_configmgrini_modified = true; + configmgrini_flush( xCmdEnv ); + } +} + +#if HAVE_FEATURE_EXTENSIONS +bool BackendImpl::removeFromConfigmgrIni( + bool isSchema, OUString const & url_, + Reference<XCommandEnvironment> const & xCmdEnv ) +{ + const OUString rcterm( dp_misc::makeRcTerm(url_) ); + const ::osl::MutexGuard guard( m_aMutex ); + configmgrini_verify_init( xCmdEnv ); + std::deque<OUString> & rSet = getFiles(isSchema); + auto i(std::find(rSet.begin(), rSet.end(), rcterm)); + if (i == rSet.end() && !isSchema) + { + //in case the xcu contained %origin% then the configmr.ini contains the + //url to the file in the user installation (e.g. $BUNDLED_EXTENSIONS_USER) + //However, m_url (getURL()) contains the URL for the file in the actual + //extension installation. + ::std::optional<ConfigurationBackendDb::Data> data = readDataFromDb(url_); + if (data) + i = std::find(rSet.begin(), rSet.end(), data->iniEntry); + } + if (i == rSet.end()) { + return false; + } + rSet.erase(i); + // write immediately: + m_configmgrini_modified = true; + configmgrini_flush( xCmdEnv ); + return true; +} +#endif + +// Package + + +BackendImpl * BackendImpl::PackageImpl::getMyBackend() const +{ + BackendImpl * pBackend = static_cast<BackendImpl *>(m_myBackend.get()); + if (nullptr == pBackend) + { + //May throw a DisposedException + check(); + //We should never get here... + throw RuntimeException( + "Failed to get the BackendImpl", + static_cast<OWeakObject*>(const_cast<PackageImpl *>(this))); + } + return pBackend; +} + +beans::Optional< beans::Ambiguous<sal_Bool> > +BackendImpl::PackageImpl::isRegistered_( + ::osl::ResettableMutexGuard &, + ::rtl::Reference<AbortChannel> const &, + Reference<XCommandEnvironment> const & ) +{ + BackendImpl * that = getMyBackend(); + + bool bReg = false; + if (that->hasActiveEntry(getURL())) + bReg = true; + +#if HAVE_FEATURE_EXTENSIONS + const OUString url(getURL()); + if (!bReg && that->m_registeredPackages) + { + // fallback for user extension registered in berkeley DB + bReg = that->m_registeredPackages->has( + OUStringToOString( url, RTL_TEXTENCODING_UTF8 )); + } +#endif + return beans::Optional< beans::Ambiguous<sal_Bool> >( + true, beans::Ambiguous<sal_Bool>( bReg, false ) ); +} + + +OUString replaceOrigin( + OUString const & url, std::u16string_view destFolder, Reference< XCommandEnvironment > const & xCmdEnv, Reference< XComponentContext > const & xContext, bool & out_replaced) +{ + // looking for %origin%: + ::ucbhelper::Content ucb_content( url, xCmdEnv, xContext ); + std::vector<sal_Int8> bytes( readFile( ucb_content ) ); + std::vector<sal_Int8> filtered( bytes.size() * 2 ); + bool use_filtered = false; + OString origin; + char const * pBytes = reinterpret_cast<char const *>( + bytes.data()); + std::size_t nBytes = bytes.size(); + size_t write_pos = 0; + while (nBytes > 0) + { + sal_Int32 index = rtl_str_indexOfChar_WithLength( pBytes, nBytes, '%' ); + if (index < 0) { + if (! use_filtered) // opt + break; + index = nBytes; + } + + if ((write_pos + index) > filtered.size()) + filtered.resize( (filtered.size() + index) * 2 ); + memcpy( filtered.data() + write_pos, pBytes, index ); + write_pos += index; + pBytes += index; + nBytes -= index; + if (nBytes == 0) + break; + + // consume %: + ++pBytes; + --nBytes; + char const * pAdd = "%"; + sal_Int32 nAdd = 1; + if (nBytes > 1 && pBytes[ 0 ] == '%') + { + // %% => % + ++pBytes; + --nBytes; + use_filtered = true; + } + else if (rtl_str_shortenedCompare_WithLength( + pBytes, nBytes, + "origin%", + RTL_CONSTASCII_LENGTH("origin%"), + RTL_CONSTASCII_LENGTH("origin%")) == 0) + { + if (origin.isEmpty()) { + // encode only once + origin = OUStringToOString( + comphelper::string::encodeForXml( url.subView( 0, url.lastIndexOf( '/' ) ) ), + // xxx todo: encode always for UTF-8? => lookup doc-header? + RTL_TEXTENCODING_UTF8 ); + } + pAdd = origin.getStr(); + nAdd = origin.getLength(); + pBytes += RTL_CONSTASCII_LENGTH("origin%"); + nBytes -= RTL_CONSTASCII_LENGTH("origin%"); + use_filtered = true; + } + if ((write_pos + nAdd) > filtered.size()) + filtered.resize( (filtered.size() + nAdd) * 2 ); + memcpy( filtered.data() + write_pos, pAdd, nAdd ); + write_pos += nAdd; + } + if (!use_filtered) + return url; + if (write_pos < filtered.size()) + filtered.resize( write_pos ); + OUString newUrl(url); + if (!destFolder.empty()) + { + //get the file name of the xcu and add it to the url of the temporary folder + sal_Int32 i = url.lastIndexOf('/'); + newUrl = OUString::Concat(destFolder) + url.subView(i); + } + + ucbhelper::Content(newUrl, xCmdEnv, xContext).writeStream( + xmlscript::createInputStream(std::move(filtered)), true); + out_replaced = true; + return newUrl; +} + + +void BackendImpl::PackageImpl::processPackage_( + ::osl::ResettableMutexGuard &, + bool doRegisterPackage, + bool startup, + ::rtl::Reference<AbortChannel> const &, + Reference<XCommandEnvironment> const & xCmdEnv ) +{ + BackendImpl * that = getMyBackend(); + OUString url( getURL() ); + + if (doRegisterPackage) + { + if (getMyBackend()->activateEntry(getURL())) + { + ::std::optional<ConfigurationBackendDb::Data> data = that->readDataFromDb(url); + OSL_ASSERT(data); + that->addToConfigmgrIni( m_isSchema, false, data->iniEntry, xCmdEnv ); + } + else + { + ConfigurationBackendDb::Data data; + if (!m_isSchema) + { + const OUString sModFolder = that->createFolder(xCmdEnv); + bool out_replaced = false; + url = replaceOrigin(url, sModFolder, xCmdEnv, that->getComponentContext(), out_replaced); + if (out_replaced) + data.dataUrl = sModFolder; + else + deleteTempFolder(sModFolder); + } + //No need for live-deployment for bundled extension, because OOo + //restarts after installation + if ((that->m_eContext != Context::Bundled && !startup) + || comphelper::LibreOfficeKit::isActive()) + { + if (m_isSchema) + { + css::configuration::Update::get( + that->m_xComponentContext)->insertExtensionXcsFile( + that->m_eContext == Context::Shared, expandUnoRcUrl(url)); + } + else + { + css::configuration::Update::get( + that->m_xComponentContext)->insertExtensionXcuFile( + that->m_eContext == Context::Shared, expandUnoRcUrl(url)); + } + } + that->addToConfigmgrIni( m_isSchema, true, url, xCmdEnv ); + data.iniEntry = dp_misc::makeRcTerm(url); + that->addDataToDb(getURL(), data); + } + } + else // revoke + { +#if HAVE_FEATURE_EXTENSIONS + if (!that->removeFromConfigmgrIni(m_isSchema, url, xCmdEnv) && + that->m_registeredPackages) { + // Obsolete package database handling - should be removed for LibreOffice 4.0 + t_string2string_map entries( + that->m_registeredPackages->getEntries()); + for (auto const& entry : entries) + { + //If the xcu file was installed before the configmgr was changed + //to use the configmgr.ini, one needed to rebuild to whole directory + //structure containing the xcu, xcs files from all extensions. Now, + //we just add all other xcu/xcs files to the configmgr.ini instead of + //rebuilding the directory structure. + OUString url2( + OStringToOUString(entry.first, RTL_TEXTENCODING_UTF8)); + if (url2 != url) { + bool schema = entry.second.equalsIgnoreAsciiCase( + "vnd.sun.star.configuration-schema"); + OUString url_replaced(url2); + ConfigurationBackendDb::Data data; + if (!schema) + { + const OUString sModFolder = that->createFolder(xCmdEnv); + bool out_replaced = false; + url_replaced = replaceOrigin( + url2, sModFolder, xCmdEnv, that->getComponentContext(), out_replaced); + if (out_replaced) + data.dataUrl = sModFolder; + else + deleteTempFolder(sModFolder); + } + that->addToConfigmgrIni(schema, true, url_replaced, xCmdEnv); + data.iniEntry = dp_misc::makeRcTerm(url_replaced); + that->addDataToDb(url2, data); + } + that->m_registeredPackages->erase(entry.first); + } + try + { + ::ucbhelper::Content( + makeURL( that->getCachePath(), "registry" ), + xCmdEnv, that->getComponentContext() ).executeCommand( + "delete", Any( true /* delete physically */ ) ); + } + catch(const Exception&) + { + OSL_ASSERT(false); + } + } +#endif + ::std::optional<ConfigurationBackendDb::Data> data = that->readDataFromDb(url); + //If an xcu file was life deployed then always a data entry is written. + //If the xcu file was already in the configmr.ini then there is also + //a data entry + if (!m_isSchema && data) + { + css::configuration::Update::get( + that->m_xComponentContext)->removeExtensionXcuFile(expandUnoRcTerm(data->iniEntry)); + } + that->revokeEntryFromDb(url); + } +} + +} // anon namespace + +} // namespace dp_registry + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +com_sun_star_comp_deployment_configuration_PackageRegistryBackend_get_implementation( + css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const& args) +{ + return cppu::acquire(new dp_registry::backend::configuration::BackendImpl(args, context)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/registry/configuration/dp_configurationbackenddb.cxx b/desktop/source/deployment/registry/configuration/dp_configurationbackenddb.cxx new file mode 100644 index 0000000000..afdc8112f2 --- /dev/null +++ b/desktop/source/deployment/registry/configuration/dp_configurationbackenddb.cxx @@ -0,0 +1,161 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + + +#include <cppuhelper/exc_hlp.hxx> +#include <com/sun/star/deployment/DeploymentException.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <com/sun/star/xml/xpath/XXPathAPI.hpp> + +#include "dp_configurationbackenddb.hxx" + + +using namespace ::com::sun::star::uno; + +constexpr OUStringLiteral EXTENSION_REG_NS = u"http://openoffice.org/extensionmanager/configuration-registry/2010"; +constexpr OUStringLiteral NS_PREFIX = u"conf"; +constexpr OUStringLiteral ROOT_ELEMENT_NAME = u"configuration-backend-db"; +constexpr OUStringLiteral KEY_ELEMENT_NAME = u"configuration"; + +namespace dp_registry::backend::configuration { + +ConfigurationBackendDb::ConfigurationBackendDb( + Reference<XComponentContext> const & xContext, + OUString const & url):BackendDb(xContext, url) +{ + +} + +OUString ConfigurationBackendDb::getDbNSName() +{ + return EXTENSION_REG_NS; +} + +OUString ConfigurationBackendDb::getNSPrefix() +{ + return NS_PREFIX; +} + +OUString ConfigurationBackendDb::getRootElementName() +{ + return ROOT_ELEMENT_NAME; +} + +OUString ConfigurationBackendDb::getKeyElementName() +{ + return KEY_ELEMENT_NAME; +} + + +void ConfigurationBackendDb::addEntry(OUString const & url, Data const & data) +{ + try{ + if (!activateEntry(url)) + { + Reference<css::xml::dom::XNode> helpNode + = writeKeyElement(url); + + writeSimpleElement(u"data-url", data.dataUrl, helpNode); + writeSimpleElement(u"ini-entry", data.iniEntry, helpNode); + save(); + } + } + catch ( const css::deployment::DeploymentException& ) + { + throw; + } + catch(const css::uno::Exception &) + { + Any exc( ::cppu::getCaughtException() ); + throw css::deployment::DeploymentException( + "Extension Manager: failed to write data entry in configuration backend db: " + + m_urlDb, nullptr, exc); + } +} + + +::std::optional<ConfigurationBackendDb::Data> +ConfigurationBackendDb::getEntry(std::u16string_view url) +{ + try + { + ConfigurationBackendDb::Data retData; + Reference<css::xml::dom::XNode> aNode = getKeyElement(url); + if (aNode.is()) + { + retData.dataUrl = readSimpleElement(u"data-url", aNode); + retData.iniEntry = readSimpleElement(u"ini-entry", aNode); + } + else + { + return ::std::optional<Data>(); + } + return ::std::optional<Data>(retData); + } + catch ( const css::deployment::DeploymentException& ) + { + throw; + } + catch(const css::uno::Exception &) + { + Any exc( ::cppu::getCaughtException() ); + throw css::deployment::DeploymentException( + "Extension Manager: failed to read data entry in configuration backend db: " + + m_urlDb, nullptr, exc); + } +} + +std::vector<OUString> ConfigurationBackendDb::getAllDataUrls() +{ + try + { + std::vector<OUString> listRet; + Reference<css::xml::dom::XDocument> doc = getDocument(); + Reference<css::xml::dom::XNode> root = doc->getFirstChild(); + + Reference<css::xml::xpath::XXPathAPI> xpathApi = getXPathAPI(); + const OUString sPrefix = getNSPrefix(); + OUString sExpression( + sPrefix + ":configuration/" + sPrefix + ":data-url/text()"); + Reference<css::xml::dom::XNodeList> nodes = + xpathApi->selectNodeList(root, sExpression); + if (nodes.is()) + { + sal_Int32 length = nodes->getLength(); + for (sal_Int32 i = 0; i < length; i++) + listRet.push_back(nodes->item(i)->getNodeValue()); + } + return listRet; + } + catch ( const css::deployment::DeploymentException& ) + { + throw; + } + catch(const css::uno::Exception &) + { + Any exc( ::cppu::getCaughtException() ); + throw css::deployment::DeploymentException( + "Extension Manager: failed to read data entry in configuration backend db: " + + m_urlDb, nullptr, exc); + } +} + +} // namespace dp_registry::backend::configuration + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/registry/configuration/dp_configurationbackenddb.hxx b/desktop/source/deployment/registry/configuration/dp_configurationbackenddb.hxx new file mode 100644 index 0000000000..bd48aab7b2 --- /dev/null +++ b/desktop/source/deployment/registry/configuration/dp_configurationbackenddb.hxx @@ -0,0 +1,74 @@ +/* -*- 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> +#include <rtl/string.hxx> +#include <vector> +#include <optional> +#include <string_view> + +#include <dp_backenddb.hxx> + +namespace com::sun::star::uno +{ +class XComponentContext; +} + +namespace dp_registry::backend::configuration +{ +/* The XML file stores the extensions which are currently registered. + They will be removed when they are revoked. + */ +class ConfigurationBackendDb : public dp_registry::backend::BackendDb +{ +protected: + virtual OUString getDbNSName() override; + + virtual OUString getNSPrefix() override; + + virtual OUString getRootElementName() override; + + virtual OUString getKeyElementName() override; + +public: + struct Data + { + /* the URL to the folder containing the xcu or xcs files which contained + %origin% + */ + OUString dataUrl; + /* the URL of the xcu or xcs file which is written in to the configmgr.ini + */ + OUString iniEntry; + }; + +public: + ConfigurationBackendDb(css::uno::Reference<css::uno::XComponentContext> const& xContext, + OUString const& url); + + void addEntry(OUString const& url, Data const& data); + + ::std::optional<Data> getEntry(std::u16string_view url); + std::vector<OUString> getAllDataUrls(); +}; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/registry/dp_backend.cxx b/desktop/source/deployment/registry/dp_backend.cxx new file mode 100644 index 0000000000..016ff66286 --- /dev/null +++ b/desktop/source/deployment/registry/dp_backend.cxx @@ -0,0 +1,769 @@ +/* -*- 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 <cassert> + +#include <dp_backend.h> +#include <dp_misc.h> +#include <dp_ucb.h> +#include <rtl/ustring.hxx> +#include <rtl/bootstrap.hxx> +#include <sal/log.hxx> +#include <cppuhelper/exc_hlp.hxx> +#include <comphelper/unwrapargs.hxx> +#include <ucbhelper/content.hxx> +#include <com/sun/star/lang/WrappedTargetRuntimeException.hpp> +#include <com/sun/star/deployment/DeploymentException.hpp> +#include <com/sun/star/deployment/ExtensionRemovedException.hpp> +#include <com/sun/star/deployment/InvalidRemovedParameterException.hpp> +#include <com/sun/star/ucb/ContentCreationException.hpp> +#include <com/sun/star/ucb/CommandAbortedException.hpp> +#include <com/sun/star/ucb/CommandFailedException.hpp> +#include <com/sun/star/ucb/InteractiveAugmentedIOException.hpp> +#include <com/sun/star/ucb/IOErrorCode.hpp> +#include <com/sun/star/beans/StringPair.hpp> +#include <com/sun/star/sdbc/XResultSet.hpp> +#include <com/sun/star/sdbc/XRow.hpp> +#include <comphelper/diagnose_ex.hxx> +#include <unotools/tempfile.hxx> +#include <optional> +#include <utility> + +using namespace ::dp_misc; +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::ucb; + +namespace dp_registry::backend { + + +PackageRegistryBackend::~PackageRegistryBackend() +{ +} + + +void PackageRegistryBackend::disposing( lang::EventObject const & event ) +{ + Reference<deployment::XPackage> xPackage( + event.Source, UNO_QUERY_THROW ); + OUString url( xPackage->getURL() ); + ::osl::MutexGuard guard( m_aMutex ); + if ( m_bound.erase( url ) != 1 ) + { + SAL_WARN("desktop.deployment", "erase(" << url << ") != 1"); + } +} + + +PackageRegistryBackend::PackageRegistryBackend( + Sequence<Any> const & args, + Reference<XComponentContext> const & xContext ) + : t_BackendBase( m_aMutex ), + m_xComponentContext( xContext ), + m_eContext( Context::Unknown ) +{ + assert(xContext.is()); + std::optional<OUString> cachePath; + std::optional<bool> readOnly; + comphelper::unwrapArgs( args, m_context, cachePath, readOnly ); + if (cachePath) + m_cachePath = *cachePath; + + if ( m_context == "user" ) + m_eContext = Context::User; + else if ( m_context == "shared" ) + m_eContext = Context::Shared; + else if ( m_context == "bundled" ) + m_eContext = Context::Bundled; + else if ( m_context == "tmp" ) + m_eContext = Context::Tmp; + else if (m_context.matchIgnoreAsciiCase("vnd.sun.star.tdoc:/")) + m_eContext = Context::Document; + else + m_eContext = Context::Unknown; +} + + +void PackageRegistryBackend::check() +{ + ::osl::MutexGuard guard( m_aMutex ); + if (rBHelper.bInDispose || rBHelper.bDisposed) { + throw lang::DisposedException( + "PackageRegistryBackend instance has already been disposed!", + static_cast<OWeakObject *>(this) ); + } +} + + +void PackageRegistryBackend::disposing() +{ + try { + for (auto const& elem : m_bound) + elem.second->removeEventListener(this); + m_bound.clear(); + m_xComponentContext.clear(); + WeakComponentImplHelperBase::disposing(); + } + catch (const RuntimeException &) { + throw; + } + catch (const Exception &) { + Any exc( ::cppu::getCaughtException() ); + throw lang::WrappedTargetRuntimeException( + "caught unexpected exception while disposing!", + static_cast<OWeakObject *>(this), exc ); + } +} + +// XPackageRegistry + +Reference<deployment::XPackage> PackageRegistryBackend::bindPackage( + OUString const & url, OUString const & mediaType, sal_Bool bRemoved, + OUString const & identifier, Reference<XCommandEnvironment> const & xCmdEnv ) +{ + ::osl::ResettableMutexGuard guard( m_aMutex ); + check(); + + t_string2ref::const_iterator const iFind( m_bound.find( url ) ); + if (iFind != m_bound.end()) + { + Reference<deployment::XPackage> xPackage( iFind->second ); + if (xPackage.is()) + { + if (!mediaType.isEmpty() && + mediaType != xPackage->getPackageType()->getMediaType()) + throw lang::IllegalArgumentException + ("XPackageRegistry::bindPackage: media type does not match", + static_cast<OWeakObject*>(this), 1); + if (xPackage->isRemoved() != bRemoved) + throw deployment::InvalidRemovedParameterException( + "XPackageRegistry::bindPackage: bRemoved parameter does not match", + static_cast<OWeakObject*>(this), xPackage->isRemoved(), xPackage); + return xPackage; + } + } + + guard.clear(); + + Reference<deployment::XPackage> xNewPackage; + try { + xNewPackage = bindPackage_( url, mediaType, bRemoved, + identifier, xCmdEnv ); + } + catch (const RuntimeException &) { + throw; + } + catch (const CommandFailedException &) { + throw; + } + catch (const deployment::DeploymentException &) { + throw; + } + catch (const Exception &) { + Any exc( ::cppu::getCaughtException() ); + throw deployment::DeploymentException( + "Error binding package: " + url, + static_cast<OWeakObject *>(this), exc ); + } + + guard.reset(); + + std::pair< t_string2ref::iterator, bool > insertion( + m_bound.emplace( url, xNewPackage ) ); + if (insertion.second) + { // first insertion + SAL_WARN_IF( + Reference<XInterface>(insertion.first->second) != xNewPackage, + "desktop.deployment", "mismatch"); + } + else + { // found existing entry + Reference<deployment::XPackage> xPackage( insertion.first->second ); + if (xPackage.is()) + return xPackage; + insertion.first->second = xNewPackage; + } + + guard.clear(); + xNewPackage->addEventListener( this ); // listen for disposing events + return xNewPackage; +} + +OUString PackageRegistryBackend::createFolder( + Reference<ucb::XCommandEnvironment> const & xCmdEnv) +{ + const OUString sDataFolder = makeURL(getCachePath(), OUString()); + //make sure the folder exist + ucbhelper::Content dataContent; + ::dp_misc::create_folder(&dataContent, sDataFolder, xCmdEnv); + + const OUString url = ::utl::CreateTempURL(&sDataFolder, true); + return sDataFolder + url.subView(url.lastIndexOf('/')); +} + +//folderURL can have the extension .tmp or .tmp_ +//Before OOo 3.4 the created a tmp file with osl_createTempFile and +//then created a Folder with a same name and a trailing '_' +//If the folderURL has no '_' then there is no corresponding tmp file. +void PackageRegistryBackend::deleteTempFolder( + OUString const & folderUrl) +{ + if (!folderUrl.isEmpty()) + { + erase_path( folderUrl, Reference<XCommandEnvironment>(), + false /* no throw: ignore errors */ ); + + if (folderUrl.endsWith("_")) + { + const OUString tempFile = folderUrl.copy(0, folderUrl.getLength() - 1); + erase_path( tempFile, Reference<XCommandEnvironment>(), + false /* no throw: ignore errors */ ); + } + } +} + +//usedFolders can contain folder names which have the extension .tmp or .tmp_ +//Before OOo 3.4 we created a tmp file with osl_createTempFile and +//then created a Folder with a same name and a trailing '_' +//If the folderURL has no '_' then there is no corresponding tmp file. +void PackageRegistryBackend::deleteUnusedFolders( + std::vector< OUString> const & usedFolders) +{ + try + { + const OUString sDataFolder = makeURL(getCachePath(), OUString()); + ::ucbhelper::Content tempFolder( + sDataFolder, Reference<ucb::XCommandEnvironment>(), m_xComponentContext); + + Reference<sdbc::XResultSet> xResultSet( + StrTitle::createCursor( tempFolder, ::ucbhelper::INCLUDE_FOLDERS_ONLY ) ); + + // get all temp directories: + std::vector<OUString> tempEntries; + + while (xResultSet->next()) + { + OUString title( + Reference<sdbc::XRow>( + xResultSet, UNO_QUERY_THROW )->getString( + 1 /* Title */ ) ); + + if (title.endsWith(".tmp")) + tempEntries.push_back( + makeURLAppendSysPathSegment(sDataFolder, title)); + } + + for (const OUString & tempEntrie : tempEntries) + { + if (std::find( usedFolders.begin(), usedFolders.end(), tempEntrie ) == + usedFolders.end()) + { + deleteTempFolder(tempEntrie); + } + } + } + catch (const ucb::InteractiveAugmentedIOException& e) + { + //In case the folder containing all the data folder does not + //exist yet, we ignore the exception + if (e.Code != ucb::IOErrorCode_NOT_EXISTING) + throw; + } + +} + + +Package::~Package() +{ +} + + +Package::Package( ::rtl::Reference<PackageRegistryBackend> myBackend, + OUString url, + OUString aName, + OUString displayName, + Reference<deployment::XPackageTypeInfo> const & xPackageType, + bool bRemoved, + OUString identifier) + : t_PackageBase( m_aMutex ), + m_myBackend(std::move( myBackend )), + m_url(std::move( url )), + m_name(std::move( aName )), + m_displayName(std::move( displayName )), + m_xPackageType( xPackageType ), + m_bRemoved(bRemoved), + m_identifier(std::move(identifier)) +{ + if (m_bRemoved) + { + //We use the last segment of the URL + SAL_WARN_IF( + !m_name.isEmpty(), "desktop.deployment", "non-empty m_name"); + OUString name = m_url; + rtl::Bootstrap::expandMacros(name); + sal_Int32 index = name.lastIndexOf('/'); + if (index != -1 && index < name.getLength()) + m_name = name.copy(index + 1); + } +} + + +void Package::disposing() +{ + m_myBackend.clear(); + WeakComponentImplHelperBase::disposing(); +} + + +void Package::check() const +{ + ::osl::MutexGuard guard( m_aMutex ); + if (rBHelper.bInDispose || rBHelper.bDisposed) { + throw lang::DisposedException( + "Package instance has already been disposed!", + static_cast<OWeakObject *>(const_cast<Package *>(this))); + } +} + +// XComponent + +void Package::dispose() +{ + //Do not call check here. We must not throw an exception here if the object + //is being disposed or is already disposed. See com.sun.star.lang.XComponent + WeakComponentImplHelperBase::dispose(); +} + + +void Package::addEventListener( + Reference<lang::XEventListener> const & xListener ) +{ + //Do not call check here. We must not throw an exception here if the object + //is being disposed or is already disposed. See com.sun.star.lang.XComponent + WeakComponentImplHelperBase::addEventListener( xListener ); +} + + +void Package::removeEventListener( + Reference<lang::XEventListener> const & xListener ) +{ + //Do not call check here. We must not throw an exception here if the object + //is being disposed or is already disposed. See com.sun.star.lang.XComponent + WeakComponentImplHelperBase::removeEventListener( xListener ); +} + +// XModifyBroadcaster + +void Package::addModifyListener( + Reference<util::XModifyListener> const & xListener ) +{ + check(); + rBHelper.addListener( cppu::UnoType<decltype(xListener)>::get(), xListener ); +} + + +void Package::removeModifyListener( + Reference<util::XModifyListener> const & xListener ) +{ + check(); + rBHelper.removeListener( cppu::UnoType<decltype(xListener)>::get(), xListener ); +} + + +void Package::checkAborted( + ::rtl::Reference<AbortChannel> const & abortChannel ) +{ + if (abortChannel.is() && abortChannel->isAborted()) { + throw CommandAbortedException( + "abort!", static_cast<OWeakObject *>(this) ); + } +} + +// XPackage + +Reference<task::XAbortChannel> Package::createAbortChannel() +{ + check(); + return new AbortChannel; +} + + +sal_Bool Package::isBundle() +{ + return false; // default +} + + +::sal_Int32 Package::checkPrerequisites( + const css::uno::Reference< css::task::XAbortChannel >&, + const css::uno::Reference< css::ucb::XCommandEnvironment >&, + sal_Bool) +{ + if (m_bRemoved) + throw deployment::ExtensionRemovedException(); + return 0; +} + + +sal_Bool Package::checkDependencies( + const css::uno::Reference< css::ucb::XCommandEnvironment >& ) +{ + if (m_bRemoved) + throw deployment::ExtensionRemovedException(); + return true; +} + + +Sequence< Reference<deployment::XPackage> > Package::getBundle( + Reference<task::XAbortChannel> const &, + Reference<XCommandEnvironment> const & ) +{ + return Sequence< Reference<deployment::XPackage> >(); +} + + +OUString Package::getName() +{ + return m_name; +} + +beans::Optional<OUString> Package::getIdentifier() +{ + if (m_bRemoved) + return beans::Optional<OUString>(true, m_identifier); + + return beans::Optional<OUString>(); +} + + +OUString Package::getVersion() +{ + if (m_bRemoved) + throw deployment::ExtensionRemovedException(); + return OUString(); +} + + +OUString Package::getURL() +{ + return m_url; +} + + +OUString Package::getDisplayName() +{ + if (m_bRemoved) + throw deployment::ExtensionRemovedException(); + return m_displayName; +} + + +OUString Package::getDescription() +{ + if (m_bRemoved) + throw deployment::ExtensionRemovedException(); + return OUString(); +} + + +OUString Package::getLicenseText() +{ + if (m_bRemoved) + throw deployment::ExtensionRemovedException(); + return OUString(); +} + + +Sequence<OUString> Package::getUpdateInformationURLs() +{ + if (m_bRemoved) + throw deployment::ExtensionRemovedException(); + return Sequence<OUString>(); +} + + +css::beans::StringPair Package::getPublisherInfo() +{ + if (m_bRemoved) + throw deployment::ExtensionRemovedException(); + css::beans::StringPair aEmptyPair; + return aEmptyPair; +} + + +uno::Reference< css::graphic::XGraphic > Package::getIcon( sal_Bool /*bHighContrast*/ ) +{ + if (m_bRemoved) + throw deployment::ExtensionRemovedException(); + + uno::Reference< css::graphic::XGraphic > aEmpty; + return aEmpty; +} + + +Reference<deployment::XPackageTypeInfo> Package::getPackageType() +{ + return m_xPackageType; +} + +void Package::exportTo( + OUString const & destFolderURL, OUString const & newTitle, + sal_Int32 nameClashAction, Reference<XCommandEnvironment> const & xCmdEnv ) +{ + if (m_bRemoved) + throw deployment::ExtensionRemovedException(); + + ::ucbhelper::Content destFolder( destFolderURL, xCmdEnv, getMyBackend()->getComponentContext() ); + ::ucbhelper::Content sourceContent( getURL(), xCmdEnv, getMyBackend()->getComponentContext() ); + bool bOk=true; + try + { + destFolder.transferContent( + sourceContent, ::ucbhelper::InsertOperation::Copy, + newTitle, nameClashAction); + } + catch (const css::ucb::ContentCreationException&) + { + bOk = false; + } + + if (!bOk) + throw RuntimeException( "UCB transferContent() failed!", nullptr ); +} + +void Package::fireModified() +{ + ::cppu::OInterfaceContainerHelper * container = rBHelper.getContainer( + cppu::UnoType<util::XModifyListener>::get() ); + if (container == nullptr) + return; + + const Sequence< Reference<XInterface> > elements( + container->getElements() ); + lang::EventObject evt( static_cast<OWeakObject *>(this) ); + for ( const Reference<XInterface>& x : elements ) + { + Reference<util::XModifyListener> xListener( x, UNO_QUERY ); + if (xListener.is()) + xListener->modified( evt ); + } +} + +// XPackage + +beans::Optional< beans::Ambiguous<sal_Bool> > Package::isRegistered( + Reference<task::XAbortChannel> const & xAbortChannel, + Reference<XCommandEnvironment> const & xCmdEnv ) +{ + try { + ::osl::ResettableMutexGuard guard( m_aMutex ); + return isRegistered_( guard, + AbortChannel::get(xAbortChannel), + xCmdEnv ); + } + catch (const RuntimeException &) { + throw; + } + catch (const CommandFailedException &) { + throw; + } + catch (const CommandAbortedException &) { + throw; + } + catch (const deployment::DeploymentException &) { + throw; + } + catch (const Exception & e) { + Any exc( ::cppu::getCaughtException() ); + throw deployment::DeploymentException( + "unexpected " + exc.getValueTypeName() + ": " + e.Message, + static_cast<OWeakObject *>(this), exc ); + } +} + + +void Package::processPackage_impl( + bool doRegisterPackage, + bool startup, + Reference<task::XAbortChannel> const & xAbortChannel, + Reference<XCommandEnvironment> const & xCmdEnv ) +{ + check(); + bool action = false; + + try { + try { + ::osl::ResettableMutexGuard guard( m_aMutex ); + beans::Optional< beans::Ambiguous<sal_Bool> > option( + isRegistered_( guard, AbortChannel::get(xAbortChannel), + xCmdEnv ) ); + action = (option.IsPresent && + (option.Value.IsAmbiguous || + (doRegisterPackage ? !option.Value.Value + : option.Value.Value))); + if (action) { + + OUString displayName = isRemoved() ? getName() : getDisplayName(); + ProgressLevel progress( + xCmdEnv, + (doRegisterPackage + ? PackageRegistryBackend::StrRegisteringPackage() + : PackageRegistryBackend::StrRevokingPackage()) + + displayName ); + processPackage_( guard, + doRegisterPackage, + startup, + AbortChannel::get(xAbortChannel), + xCmdEnv ); + } + } + catch (lang::IllegalArgumentException &) { + Any e(cppu::getCaughtException()); + throw deployment::DeploymentException( + ((doRegisterPackage + ? DpResId(RID_STR_ERROR_WHILE_REGISTERING) + : DpResId(RID_STR_ERROR_WHILE_REVOKING)) + + getDisplayName()), + static_cast< OWeakObject * >(this), e); + } + catch (const RuntimeException &) { + TOOLS_WARN_EXCEPTION("desktop.deployment", "unexpected"); + throw; + } + catch (const CommandFailedException &) { + throw; + } + catch (const CommandAbortedException &) { + throw; + } + catch (const deployment::DeploymentException &) { + throw; + } + catch (const Exception & e) { + Any exc( ::cppu::getCaughtException() ); + throw deployment::DeploymentException( + (doRegisterPackage + ? DpResId(RID_STR_ERROR_WHILE_REGISTERING) + : DpResId(RID_STR_ERROR_WHILE_REVOKING)) + + getDisplayName() + ": " + exc.getValueType().getTypeName() + " \"" + e.Message + + "\"", + static_cast<OWeakObject *>(this), exc ); + } + } + catch (...) { + if (action) + fireModified(); + throw; + } + if (action) + fireModified(); +} + + +void Package::registerPackage( + sal_Bool startup, + Reference<task::XAbortChannel> const & xAbortChannel, + Reference<XCommandEnvironment> const & xCmdEnv ) +{ + if (m_bRemoved) + throw deployment::ExtensionRemovedException(); + processPackage_impl( true /* register */, startup, xAbortChannel, xCmdEnv ); +} + + +void Package::revokePackage( + sal_Bool startup, + Reference<task::XAbortChannel> const & xAbortChannel, + Reference<XCommandEnvironment> const & xCmdEnv ) +{ + processPackage_impl( false /* revoke */, startup, xAbortChannel, xCmdEnv ); + +} + +PackageRegistryBackend * Package::getMyBackend() const +{ + PackageRegistryBackend * pBackend = m_myBackend.get(); + if (nullptr == pBackend) + { + //May throw a DisposedException + check(); + //We should never get here... + throw RuntimeException( + "Failed to get the BackendImpl", + static_cast<OWeakObject*>(const_cast<Package *>(this))); + } + return pBackend; +} + +OUString Package::getRepositoryName() +{ + PackageRegistryBackend * backEnd = getMyBackend(); + return backEnd->getContext(); +} + +beans::Optional< OUString > Package::getRegistrationDataURL() +{ + if (m_bRemoved) + throw deployment::ExtensionRemovedException(); + return beans::Optional<OUString>(); +} + +sal_Bool Package::isRemoved() +{ + return m_bRemoved; +} + +Package::TypeInfo::~TypeInfo() +{ +} + +// XPackageTypeInfo + +OUString Package::TypeInfo::getMediaType() +{ + return m_mediaType; +} + + +OUString Package::TypeInfo::getDescription() +{ + return getShortDescription(); +} + + +OUString Package::TypeInfo::getShortDescription() +{ + return m_shortDescr; +} + +OUString Package::TypeInfo::getFileFilter() +{ + return m_fileFilter; +} + +Any Package::TypeInfo::getIcon( sal_Bool /*highContrast*/, sal_Bool /*smallIcon*/ ) +{ + return Any(); +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/registry/dp_backenddb.cxx b/desktop/source/deployment/registry/dp_backenddb.cxx new file mode 100644 index 0000000000..28effd95c8 --- /dev/null +++ b/desktop/source/deployment/registry/dp_backenddb.cxx @@ -0,0 +1,655 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + + +#include <cppuhelper/exc_hlp.hxx> +#include <osl/file.hxx> +#include <com/sun/star/deployment/DeploymentException.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <com/sun/star/xml/dom/DocumentBuilder.hpp> +#include <com/sun/star/xml/xpath/XPathAPI.hpp> +#include <com/sun/star/io/XActiveDataSource.hpp> +#include <com/sun/star/io/XActiveDataControl.hpp> +#include <dp_misc.h> +#include <ucbhelper/content.hxx> +#include <xmlscript/xml_helper.hxx> +#include <dp_backenddb.hxx> + + +using namespace ::com::sun::star::uno; + + +namespace dp_registry::backend { + +BackendDb::BackendDb( + Reference<css::uno::XComponentContext> const & xContext, + OUString const & url): + m_xContext(xContext) +{ + m_urlDb = dp_misc::expandUnoRcUrl(url); +} + +void BackendDb::save() +{ + const Reference<css::io::XActiveDataSource> xDataSource(m_doc,css::uno::UNO_QUERY_THROW); + std::vector<sal_Int8> bytes; + xDataSource->setOutputStream(::xmlscript::createOutputStream(&bytes)); + const Reference<css::io::XActiveDataControl> xDataControl(m_doc,css::uno::UNO_QUERY_THROW); + xDataControl->start(); + + const Reference<css::io::XInputStream> xData( + ::xmlscript::createInputStream(std::move(bytes))); + ::ucbhelper::Content ucbDb(m_urlDb, nullptr, m_xContext); + ucbDb.writeStream(xData, true /*replace existing*/); +} + +css::uno::Reference<css::xml::dom::XDocument> const & BackendDb::getDocument() +{ + if (!m_doc.is()) + { + const Reference<css::xml::dom::XDocumentBuilder> xDocBuilder( + css::xml::dom::DocumentBuilder::create(m_xContext) ); + + ::osl::DirectoryItem item; + ::osl::File::RC err = ::osl::DirectoryItem::get(m_urlDb, item); + if (err == ::osl::File::E_None) + { + ::ucbhelper::Content descContent( + m_urlDb, css::uno::Reference<css::ucb::XCommandEnvironment>(), + m_xContext); + Reference<css::io::XInputStream> xIn = descContent.openStream(); + m_doc = xDocBuilder->parse(xIn); + } + else if (err == ::osl::File::E_NOENT) + { + //Create a new document and insert some basic stuff + m_doc = xDocBuilder->newDocument(); + const Reference<css::xml::dom::XElement> rootNode = + m_doc->createElementNS(getDbNSName(), getNSPrefix() + + ":" + getRootElementName()); + + m_doc->appendChild(Reference<css::xml::dom::XNode>( + rootNode, UNO_QUERY_THROW)); + save(); + } + else + throw css::uno::RuntimeException( + "Extension manager could not access database file:" + + m_urlDb, nullptr); + + if (!m_doc.is()) + throw css::uno::RuntimeException( + "Extension manager could not get root node of data base file: " + + m_urlDb, nullptr); + } + + return m_doc; +} + +Reference<css::xml::xpath::XXPathAPI> const & BackendDb::getXPathAPI() +{ + if (!m_xpathApi.is()) + { + m_xpathApi = css::xml::xpath::XPathAPI::create( m_xContext ); + + m_xpathApi->registerNS( getNSPrefix(), getDbNSName() ); + } + + return m_xpathApi; +} + +void BackendDb::removeElement(OUString const & sXPathExpression) +{ + try + { + const Reference<css::xml::dom::XDocument> doc = getDocument(); + const Reference<css::xml::dom::XNode> root = doc->getFirstChild(); + const Reference<css::xml::xpath::XXPathAPI> xpathApi = getXPathAPI(); + //find the extension element that is to be removed + const Reference<css::xml::dom::XNode> aNode = + xpathApi->selectSingleNode(root, sXPathExpression); + + if (aNode.is()) + { + root->removeChild(aNode); + save(); + } + +#if OSL_DEBUG_LEVEL > 0 + //There must not be any other entry with the same url + const Reference<css::xml::dom::XNode> nextNode = + xpathApi->selectSingleNode(root, sXPathExpression); + OSL_ASSERT(! nextNode.is()); +#endif + } + catch(const css::uno::Exception &) + { + Any exc( ::cppu::getCaughtException() ); + throw css::deployment::DeploymentException( + "Extension Manager: failed to write data entry in backend db: " + + m_urlDb, nullptr, exc); + } +} + +void BackendDb::removeEntry(std::u16string_view url) +{ + const OUString sKeyElement = getKeyElementName(); + const OUString sPrefix = getNSPrefix(); + OUString sExpression = + sPrefix + + ":" + + sKeyElement + + "[@url = \"" + + url + + "\"]"; + + removeElement(sExpression); +} + +void BackendDb::revokeEntry(std::u16string_view url) +{ + try + { + Reference<css::xml::dom::XElement> entry(getKeyElement(url), UNO_QUERY); + if (entry.is()) + { + entry->setAttribute("revoked", "true"); + save(); + } + } + catch(const css::uno::Exception &) + { + Any exc( ::cppu::getCaughtException() ); + throw css::deployment::DeploymentException( + "Extension Manager: failed to revoke data entry in backend db: " + + m_urlDb, nullptr, exc); + } +} + +bool BackendDb::activateEntry(std::u16string_view url) +{ + try + { + bool ret = false; + Reference<css::xml::dom::XElement> entry(getKeyElement(url), UNO_QUERY); + if (entry.is()) + { + //no attribute "active" means it is active, that is, registered. + entry->removeAttribute("revoked"); + save(); + ret = true; + } + return ret; + } + catch(const css::uno::Exception &) + { + Any exc( ::cppu::getCaughtException() ); + throw css::deployment::DeploymentException( + "Extension Manager: failed to revoke data entry in backend db: " + + m_urlDb, nullptr, exc); + } +} + +bool BackendDb::hasActiveEntry(std::u16string_view url) +{ + try + { + bool ret = false; + Reference<css::xml::dom::XElement> entry(getKeyElement(url), UNO_QUERY); + if (entry.is()) + { + OUString sActive = entry->getAttribute("revoked"); + if (!(sActive == "true")) + ret = true; + } + return ret; + + } + catch(const css::uno::Exception &) + { + Any exc( ::cppu::getCaughtException() ); + throw css::deployment::DeploymentException( + "Extension Manager: failed to determine an active entry in backend db: " + + m_urlDb, nullptr, exc); + } +} + +Reference<css::xml::dom::XNode> BackendDb::getKeyElement( + std::u16string_view url) +{ + try + { + const OUString sPrefix = getNSPrefix(); + const OUString sKeyElement = getKeyElementName(); + OUString sExpression = + sPrefix + + ":" + + sKeyElement + + "[@url = \"" + + url + + "\"]"; + + const Reference<css::xml::dom::XDocument> doc = getDocument(); + const Reference<css::xml::dom::XNode> root = doc->getFirstChild(); + const Reference<css::xml::xpath::XXPathAPI> xpathApi = getXPathAPI(); + return xpathApi->selectSingleNode(root, sExpression); + } + catch(const css::uno::Exception &) + { + Any exc( ::cppu::getCaughtException() ); + throw css::deployment::DeploymentException( + "Extension Manager: failed to read key element in backend db: " + + m_urlDb, nullptr, exc); + } +} + +//Only writes the data if there is at least one entry +void BackendDb::writeVectorOfPair( + std::vector< std::pair< OUString, OUString > > const & vecPairs, + std::u16string_view sVectorTagName, + std::u16string_view sPairTagName, + std::u16string_view sFirstTagName, + std::u16string_view sSecondTagName, + css::uno::Reference<css::xml::dom::XNode> const & xParent) +{ + try{ + if (vecPairs.empty()) + return; + const OUString sNameSpace = getDbNSName(); + OSL_ASSERT(!sNameSpace.isEmpty()); + const OUString sPrefix(getNSPrefix() + ":"); + const Reference<css::xml::dom::XDocument> doc = getDocument(); + + const Reference<css::xml::dom::XElement> vectorNode( + doc->createElementNS(sNameSpace, sPrefix + sVectorTagName)); + + xParent->appendChild( + Reference<css::xml::dom::XNode>( + vectorNode, css::uno::UNO_QUERY_THROW)); + for (auto const& vecPair : vecPairs) + { + const Reference<css::xml::dom::XElement> pairNode( + doc->createElementNS(sNameSpace, sPrefix + sPairTagName)); + + vectorNode->appendChild( + Reference<css::xml::dom::XNode>( + pairNode, css::uno::UNO_QUERY_THROW)); + + const Reference<css::xml::dom::XElement> firstNode( + doc->createElementNS(sNameSpace, sPrefix + sFirstTagName)); + + pairNode->appendChild( + Reference<css::xml::dom::XNode>( + firstNode, css::uno::UNO_QUERY_THROW)); + + const Reference<css::xml::dom::XText> firstTextNode( + doc->createTextNode( vecPair.first)); + + firstNode->appendChild( + Reference<css::xml::dom::XNode>( + firstTextNode, css::uno::UNO_QUERY_THROW)); + + const Reference<css::xml::dom::XElement> secondNode( + doc->createElementNS(sNameSpace, sPrefix + sSecondTagName)); + + pairNode->appendChild( + Reference<css::xml::dom::XNode>( + secondNode, css::uno::UNO_QUERY_THROW)); + + const Reference<css::xml::dom::XText> secondTextNode( + doc->createTextNode( vecPair.second)); + + secondNode->appendChild( + Reference<css::xml::dom::XNode>( + secondTextNode, css::uno::UNO_QUERY_THROW)); + } + } + catch(const css::uno::Exception &) + { + Any exc( ::cppu::getCaughtException() ); + throw css::deployment::DeploymentException( + "Extension Manager: failed to write data entry in backend db: " + + m_urlDb, nullptr, exc); + } +} + +std::vector< std::pair< OUString, OUString > > +BackendDb::readVectorOfPair( + Reference<css::xml::dom::XNode> const & parent, + std::u16string_view sListTagName, + std::u16string_view sPairTagName, + std::u16string_view sFirstTagName, + std::u16string_view sSecondTagName) +{ + try + { + OSL_ASSERT(parent.is()); + const OUString sPrefix(getNSPrefix() + ":"); + const Reference<css::xml::xpath::XXPathAPI> xpathApi = getXPathAPI(); + const OUString sExprPairs( + sPrefix + sListTagName + "/" + sPrefix + sPairTagName); + const Reference<css::xml::dom::XNodeList> listPairs = + xpathApi->selectNodeList(parent, sExprPairs); + + std::vector< std::pair< OUString, OUString > > retVector; + sal_Int32 length = listPairs->getLength(); + for (sal_Int32 i = 0; i < length; i++) + { + const Reference<css::xml::dom::XNode> aPair = listPairs->item(i); + const OUString sExprFirst(sPrefix + sFirstTagName + "/text()"); + const Reference<css::xml::dom::XNode> first = + xpathApi->selectSingleNode(aPair, sExprFirst); + + const OUString sExprSecond(sPrefix + sSecondTagName + "/text()"); + const Reference<css::xml::dom::XNode> second = + xpathApi->selectSingleNode(aPair, sExprSecond); + OSL_ASSERT(first.is() && second.is()); + + retVector.emplace_back( + first->getNodeValue(), second->getNodeValue()); + } + return retVector; + } + catch(const css::uno::Exception &) + { + Any exc( ::cppu::getCaughtException() ); + throw css::deployment::DeploymentException( + "Extension Manager: failed to read data entry in backend db: " + + m_urlDb, nullptr, exc); + } +} + +//Only writes the data if there is at least one entry +void BackendDb::writeSimpleList( + std::deque< OUString> const & list, + std::u16string_view sListTagName, + std::u16string_view sMemberTagName, + Reference<css::xml::dom::XNode> const & xParent) +{ + try + { + if (list.empty()) + return; + const OUString sNameSpace = getDbNSName(); + const OUString sPrefix(getNSPrefix() + ":"); + const Reference<css::xml::dom::XDocument> doc = getDocument(); + + const Reference<css::xml::dom::XElement> listNode( + doc->createElementNS(sNameSpace, sPrefix + sListTagName)); + + xParent->appendChild( + Reference<css::xml::dom::XNode>( + listNode, css::uno::UNO_QUERY_THROW)); + + for (auto const& elem : list) + { + const Reference<css::xml::dom::XNode> memberNode( + doc->createElementNS(sNameSpace, sPrefix + sMemberTagName), css::uno::UNO_QUERY_THROW); + + listNode->appendChild(memberNode); + + const Reference<css::xml::dom::XNode> textNode( + doc->createTextNode(elem), css::uno::UNO_QUERY_THROW); + + memberNode->appendChild(textNode); + } + } + catch(const css::uno::Exception &) + { + Any exc( ::cppu::getCaughtException() ); + throw css::deployment::DeploymentException( + "Extension Manager: failed to write data entry in backend db: " + + m_urlDb, nullptr, exc); + } +} + +//Writes only the element if is has a value. +//The prefix is automatically added to the element name +void BackendDb::writeSimpleElement( + std::u16string_view sElementName, OUString const & value, + Reference<css::xml::dom::XNode> const & xParent) +{ + try + { + if (value.isEmpty()) + return; + const OUString sPrefix = getNSPrefix(); + const Reference<css::xml::dom::XDocument> doc = getDocument(); + const OUString sNameSpace = getDbNSName(); + const Reference<css::xml::dom::XNode> dataNode( + doc->createElementNS(sNameSpace, sPrefix + ":" + sElementName), + UNO_QUERY_THROW); + xParent->appendChild(dataNode); + + const Reference<css::xml::dom::XNode> dataValue( + doc->createTextNode(value), UNO_QUERY_THROW); + dataNode->appendChild(dataValue); + } + catch(const css::uno::Exception &) + { + Any exc( ::cppu::getCaughtException() ); + throw css::deployment::DeploymentException( + "Extension Manager: failed to write data entry(writeSimpleElement) in backend db: " + + m_urlDb, nullptr, exc); + } + +} + +/// The key elements have a url attribute and are always children of the root element. +Reference<css::xml::dom::XNode> BackendDb::writeKeyElement( + OUString const & url) +{ + try + { + const OUString sNameSpace = getDbNSName(); + const OUString sPrefix = getNSPrefix(); + const OUString sElementName = getKeyElementName(); + const Reference<css::xml::dom::XDocument> doc = getDocument(); + const Reference<css::xml::dom::XNode> root = doc->getFirstChild(); + + //Check if there are an entry with the same url. This can be the case if the + //status of an XPackage is ambiguous. In this case a call to activateExtension + //(dp_extensionmanager.cxx), will register the package again. See also + //Package::processPackage_impl in dp_backend.cxx. + //A package can become + //invalid after its successful registration, for example if a second extension with + //the same service is installed. + const OUString sExpression( + sPrefix + ":" + sElementName + "[@url = \"" + url + "\"]"); + const Reference<css::xml::dom::XNode> existingNode = + getXPathAPI()->selectSingleNode(root, sExpression); + if (existingNode.is()) + { + OSL_ASSERT(false); + //replace the existing entry. + removeEntry(url); + } + + const Reference<css::xml::dom::XElement> keyElement( + doc->createElementNS(sNameSpace, sPrefix + ":" + sElementName)); + + keyElement->setAttribute("url", url); + + const Reference<css::xml::dom::XNode> keyNode( + keyElement, UNO_QUERY_THROW); + root->appendChild(keyNode); + return keyNode; + } + catch(const css::uno::Exception &) + { + Any exc( ::cppu::getCaughtException() ); + throw css::deployment::DeploymentException( + "Extension Manager: failed to write key element in backend db: " + + m_urlDb, nullptr, exc); + } +} + +OUString BackendDb::readSimpleElement( + std::u16string_view sElementName, Reference<css::xml::dom::XNode> const & xParent) +{ + try + { + const OUString sPrefix = getNSPrefix(); + const OUString sExpr(sPrefix + ":" + sElementName + "/text()"); + const Reference<css::xml::xpath::XXPathAPI> xpathApi = getXPathAPI(); + const Reference<css::xml::dom::XNode> val = + xpathApi->selectSingleNode(xParent, sExpr); + if (val.is()) + return val->getNodeValue(); + return OUString(); + } + catch(const css::uno::Exception &) + { + Any exc( ::cppu::getCaughtException() ); + throw css::deployment::DeploymentException( + "Extension Manager: failed to read data (readSimpleElement) in backend db: " + + m_urlDb, nullptr, exc); + } +} + + +std::deque< OUString> BackendDb::readList( + Reference<css::xml::dom::XNode> const & parent, + std::u16string_view sListTagName, + std::u16string_view sMemberTagName) +{ + try + { + OSL_ASSERT(parent.is()); + const OUString sPrefix(getNSPrefix() + ":"); + const Reference<css::xml::xpath::XXPathAPI> xpathApi = getXPathAPI(); + const OUString sExprList( + sPrefix + sListTagName + "/" + sPrefix + sMemberTagName + "/text()"); + const Reference<css::xml::dom::XNodeList> list = + xpathApi->selectNodeList(parent, sExprList); + + std::deque<OUString > retList; + sal_Int32 length = list->getLength(); + for (sal_Int32 i = 0; i < length; i++) + { + const Reference<css::xml::dom::XNode> member = list->item(i); + retList.push_back(member->getNodeValue()); + } + return retList; + } + catch(const css::uno::Exception &) + { + Any exc( ::cppu::getCaughtException() ); + throw css::deployment::DeploymentException( + "Extension Manager: failed to read data entry in backend db: " + + m_urlDb, nullptr, exc); + } +} + +std::vector<OUString> BackendDb::getOneChildFromAllEntries( + std::u16string_view name) +{ + try + { + std::vector<OUString> listRet; + Reference<css::xml::dom::XDocument> doc = getDocument(); + Reference<css::xml::dom::XNode> root = doc->getFirstChild(); + + Reference<css::xml::xpath::XXPathAPI> xpathApi = getXPathAPI(); + const OUString sPrefix = getNSPrefix(); + const OUString sKeyElement = getKeyElementName(); + OUString sNodeSelectExpr = + sPrefix + + ":" + + sKeyElement + + "/" + + sPrefix + + ":" + + name + + "/text()"; + + Reference<css::xml::dom::XNodeList> nodes = + xpathApi->selectNodeList(root, sNodeSelectExpr); + if (nodes.is()) + { + sal_Int32 length = nodes->getLength(); + for (sal_Int32 i = 0; i < length; i++) + listRet.push_back(nodes->item(i)->getNodeValue()); + } + return listRet; + } + catch ( const css::deployment::DeploymentException& ) + { + throw; + } + catch(const css::uno::Exception &) + { + Any exc( ::cppu::getCaughtException() ); + throw css::deployment::DeploymentException( + "Extension Manager: failed to read data entry in backend db: " + + m_urlDb, nullptr, exc); + } +} + + +RegisteredDb::RegisteredDb( + Reference<XComponentContext> const & xContext, + OUString const & url):BackendDb(xContext, url) +{ +} + +void RegisteredDb::addEntry(OUString const & url) +{ + try{ + if (!activateEntry(url)) + { + const OUString sNameSpace = getDbNSName(); + const OUString sPrefix = getNSPrefix(); + const OUString sEntry = getKeyElementName(); + + Reference<css::xml::dom::XDocument> doc = getDocument(); + Reference<css::xml::dom::XNode> root = doc->getFirstChild(); + +#if OSL_DEBUG_LEVEL > 0 + //There must not be yet an entry with the same url + OUString sExpression( + sPrefix + ":" + sEntry + "[@url = \"" + url + "\"]"); + Reference<css::xml::dom::XNode> _extensionNode = + getXPathAPI()->selectSingleNode(root, sExpression); + OSL_ASSERT(! _extensionNode.is()); +#endif + Reference<css::xml::dom::XElement> helpElement( + doc->createElementNS(sNameSpace, sPrefix + ":" + sEntry)); + + helpElement->setAttribute("url", url); + + Reference<css::xml::dom::XNode> helpNode( + helpElement, UNO_QUERY_THROW); + root->appendChild(helpNode); + + save(); + } + } + catch(const css::uno::Exception &) + { + Any exc( ::cppu::getCaughtException() ); + throw css::deployment::DeploymentException( + "Extension Manager: failed to write data entry in backend db: " + + m_urlDb, nullptr, exc); + } +} + +} // namespace dp_registry + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/registry/dp_registry.cxx b/desktop/source/deployment/registry/dp_registry.cxx new file mode 100644 index 0000000000..ac19bcd8e9 --- /dev/null +++ b/desktop/source/deployment/registry/dp_registry.cxx @@ -0,0 +1,526 @@ +/* -*- 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 <dp_shared.hxx> +#include <dp_package.hxx> +#include <strings.hrc> +#include <dp_registry.hxx> +#include <dp_misc.h> +#include <dp_ucb.h> +#include <osl/diagnose.h> +#include <rtl/ustrbuf.hxx> +#include <rtl/uri.hxx> +#include <cppuhelper/basemutex.hxx> +#include <cppuhelper/compbase.hxx> +#include <comphelper/sequence.hxx> +#include <ucbhelper/content.hxx> +#include <o3tl/string_view.hxx> +#include <com/sun/star/ucb/ContentCreationException.hpp> +#include <com/sun/star/uno/DeploymentException.hpp> +#include <com/sun/star/lang/DisposedException.hpp> +#include <com/sun/star/lang/IllegalArgumentException.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/lang/XSingleComponentFactory.hpp> +#include <com/sun/star/lang/XSingleServiceFactory.hpp> +#include <com/sun/star/util/XUpdatable.hpp> +#include <com/sun/star/container/XContentEnumerationAccess.hpp> +#include <com/sun/star/deployment/XPackageTypeInfo.hpp> +#include <com/sun/star/deployment/XPackageRegistry.hpp> +#include <set> +#include <string_view> +#include <unordered_map> +#include <unordered_set> + +using namespace ::dp_misc; +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::ucb; + + +namespace dp_registry { + +namespace { + +typedef ::cppu::WeakComponentImplHelper< + deployment::XPackageRegistry, util::XUpdatable > t_helper; + + +class PackageRegistryImpl : private cppu::BaseMutex, public t_helper +{ + struct ci_string_hash { + std::size_t operator () ( OUString const & str ) const { + return str.toAsciiLowerCase().hashCode(); + } + }; + struct ci_string_equals { + bool operator () ( std::u16string_view str1, std::u16string_view str2 ) const{ + return o3tl::equalsIgnoreAsciiCase( str1, str2 ); + } + }; + typedef std::unordered_map< + OUString, Reference<deployment::XPackageRegistry>, + ci_string_hash, ci_string_equals > t_string2registry; + typedef std::unordered_map< + OUString, OUString, + ci_string_hash, ci_string_equals > t_string2string; + typedef std::set< + Reference<deployment::XPackageRegistry> > t_registryset; + + t_string2registry m_mediaType2backend; + t_string2string m_filter2mediaType; + t_registryset m_ambiguousBackends; + t_registryset m_allBackends; + std::vector< Reference<deployment::XPackageTypeInfo> > m_typesInfos; + + void insertBackend( + Reference<deployment::XPackageRegistry> const & xBackend ); + +protected: + void check(); + virtual void SAL_CALL disposing() override; + + virtual ~PackageRegistryImpl() override; + PackageRegistryImpl() : t_helper( m_aMutex ) {} + + +public: + static Reference<deployment::XPackageRegistry> create( + OUString const & context, + OUString const & cachePath, + Reference<XComponentContext> const & xComponentContext ); + + // XUpdatable + virtual void SAL_CALL update() override; + + // XPackageRegistry + virtual Reference<deployment::XPackage> SAL_CALL bindPackage( + OUString const & url, OUString const & mediaType, sal_Bool bRemoved, + OUString const & identifier, Reference<XCommandEnvironment> const & xCmdEnv ) override; + virtual Sequence< Reference<deployment::XPackageTypeInfo> > SAL_CALL + getSupportedPackageTypes() override; + virtual void SAL_CALL packageRemoved(OUString const & url, OUString const & mediaType) override; + +}; + + +void PackageRegistryImpl::check() +{ + ::osl::MutexGuard guard( m_aMutex ); + if (rBHelper.bInDispose || rBHelper.bDisposed) { + throw lang::DisposedException( + "PackageRegistry instance has already been disposed!", + static_cast<OWeakObject *>(this) ); + } +} + + +void PackageRegistryImpl::disposing() +{ + // dispose all backends: + for (auto const& backend : m_allBackends) + { + try_dispose(backend); + } + m_mediaType2backend = t_string2registry(); + m_ambiguousBackends = t_registryset(); + m_allBackends = t_registryset(); + + t_helper::disposing(); +} + + +PackageRegistryImpl::~PackageRegistryImpl() +{ +} + + +OUString normalizeMediaType( std::u16string_view mediaType ) +{ + OUStringBuffer buf; + sal_Int32 index = 0; + for (;;) { + buf.append( o3tl::trim(o3tl::getToken(mediaType, 0, '/', index )) ); + if (index < 0) + break; + buf.append( '/' ); + } + return buf.makeStringAndClear(); +} + + +void PackageRegistryImpl::packageRemoved( + OUString const & url, OUString const & mediaType) +{ + const t_string2registry::const_iterator i = + m_mediaType2backend.find(mediaType); + + if (i != m_mediaType2backend.end()) + { + i->second->packageRemoved(url, mediaType); + } +} + +void PackageRegistryImpl::insertBackend( + Reference<deployment::XPackageRegistry> const & xBackend ) +{ + m_allBackends.insert( xBackend ); + std::unordered_set<OUString> ambiguousFilters; + + const Sequence< Reference<deployment::XPackageTypeInfo> > packageTypes( + xBackend->getSupportedPackageTypes() ); + for ( Reference<deployment::XPackageTypeInfo> const & xPackageType : packageTypes ) + { + m_typesInfos.push_back( xPackageType ); + + const OUString mediaType( normalizeMediaType( + xPackageType->getMediaType() ) ); + std::pair<t_string2registry::iterator, bool> a_insertion( + m_mediaType2backend.emplace( mediaType, xBackend ) ); + if (a_insertion.second) + { + // add parameterless media-type, too: + sal_Int32 semi = mediaType.indexOf( ';' ); + if (semi >= 0) { + m_mediaType2backend.emplace( mediaType.copy( 0, semi ), xBackend ); + } + const OUString fileFilter( xPackageType->getFileFilter() ); + //The package backend shall also be called to determine the mediatype + //(XPackageRegistry.bindPackage) when the URL points to a directory. + const bool bExtension = (mediaType == "application/vnd.sun.star.package-bundle"); + if (fileFilter.isEmpty() || fileFilter == "*.*" || fileFilter == "*" || bExtension) + { + m_ambiguousBackends.insert( xBackend ); + } + else + { + sal_Int32 nIndex = 0; + do { + OUString token( fileFilter.getToken( 0, ';', nIndex ) ); + if (token.match( "*." )) + token = token.copy( 1 ); + if (token.isEmpty()) + continue; + // mark any further wildcards ambig: + bool ambig = (token.indexOf('*') >= 0 || + token.indexOf('?') >= 0); + if (! ambig) { + std::pair<t_string2string::iterator, bool> ins( + m_filter2mediaType.emplace( + token, mediaType ) ); + ambig = !ins.second; + if (ambig) { + // filter has already been in: add previously + // added backend to ambig set + const t_string2registry::const_iterator iFind( + m_mediaType2backend.find( + /* media-type of pr. added backend */ + ins.first->second ) ); + OSL_ASSERT( + iFind != m_mediaType2backend.end() ); + if (iFind != m_mediaType2backend.end()) + m_ambiguousBackends.insert( iFind->second ); + } + } + if (ambig) { + m_ambiguousBackends.insert( xBackend ); + // mark filter to be removed later from filters map: + ambiguousFilters.insert( token ); + } + } + while (nIndex >= 0); + } + } +#if OSL_DEBUG_LEVEL > 0 + else + { + SAL_WARN( "desktop", "more than one PackageRegistryBackend for media-type=\"" + << mediaType + << "\" => " + << Reference<lang::XServiceInfo>( + xBackend, UNO_QUERY_THROW )->getImplementationName() + << "\"!" ); + } +#endif + } + + // cut out ambiguous filters: + for (auto const& ambiguousFilter : ambiguousFilters) + { + m_filter2mediaType.erase(ambiguousFilter); + } +} + + +Reference<deployment::XPackageRegistry> PackageRegistryImpl::create( + OUString const & context, + OUString const & cachePath, + Reference<XComponentContext> const & xComponentContext ) +{ + rtl::Reference<PackageRegistryImpl> that = new PackageRegistryImpl; + + // auto-detect all registered package registries: + Reference<container::XEnumeration> xEnum( + Reference<container::XContentEnumerationAccess>( + xComponentContext->getServiceManager(), + UNO_QUERY_THROW )->createContentEnumeration( + "com.sun.star.deployment.PackageRegistryBackend" ) ); + if (xEnum.is()) + { + while (xEnum->hasMoreElements()) + { + Any element( xEnum->nextElement() ); + Sequence<Any> registryArgs(cachePath.isEmpty() ? 1 : 3 ); + auto pregistryArgs = registryArgs.getArray(); + pregistryArgs[ 0 ] <<= context; + if (!cachePath.isEmpty()) + { + Reference<lang::XServiceInfo> xServiceInfo( + element, UNO_QUERY_THROW ); + OUString registryCachePath( + makeURL( cachePath, + ::rtl::Uri::encode( + xServiceInfo->getImplementationName(), + rtl_UriCharClassPchar, + rtl_UriEncodeIgnoreEscapes, + RTL_TEXTENCODING_UTF8 ) ) ); + pregistryArgs[ 1 ] <<= registryCachePath; + pregistryArgs[ 2 ] <<= false; // readOnly; + create_folder( nullptr, registryCachePath, + Reference<XCommandEnvironment>() ); + } + + Reference<deployment::XPackageRegistry> xBackend; + Reference<lang::XSingleComponentFactory> xFac( element, UNO_QUERY ); + if (xFac.is()) { + xBackend.set( + xFac->createInstanceWithArgumentsAndContext( + registryArgs, xComponentContext ), UNO_QUERY ); + } + else { + Reference<lang::XSingleServiceFactory> xSingleServiceFac( + element, UNO_QUERY_THROW ); + xBackend.set( + xSingleServiceFac->createInstanceWithArguments( + registryArgs ), UNO_QUERY ); + } + if (! xBackend.is()) { + throw DeploymentException( + "cannot instantiate PackageRegistryBackend service: " + + Reference<lang::XServiceInfo>( + element, UNO_QUERY_THROW )->getImplementationName(), + static_cast<OWeakObject *>(that.get()) ); + } + + that->insertBackend( xBackend ); + } + } + + // Insert bundle back-end. + // Always register as last, because we want to add extensions also as folders + // and as a default we accept every folder, which was not recognized by the other + // backends. + Reference<deployment::XPackageRegistry> extensionBackend = + ::dp_registry::backend::bundle::create( + that, context, cachePath, xComponentContext); + that->insertBackend(extensionBackend); + + Reference<lang::XServiceInfo> xServiceInfo( + extensionBackend, UNO_QUERY_THROW ); + + OSL_ASSERT(xServiceInfo.is()); + OUString registryCachePath( + makeURL( cachePath, + ::rtl::Uri::encode( + xServiceInfo->getImplementationName(), + rtl_UriCharClassPchar, + rtl_UriEncodeIgnoreEscapes, + RTL_TEXTENCODING_UTF8 ) ) ); + create_folder( nullptr, registryCachePath, Reference<XCommandEnvironment>()); + + +#if OSL_DEBUG_LEVEL > 0 + // dump tables: + { + t_registryset allBackends; + dp_misc::TRACE("> [dp_registry.cxx] media-type detection:\n\n" ); + for (auto const& elem : that->m_filter2mediaType) + { + const Reference<deployment::XPackageRegistry> xBackend( + that->m_mediaType2backend.find( elem.second )->second ); + allBackends.insert( xBackend ); + dp_misc::TRACE( + "extension \"" + elem.first + "\" maps to media-type \"" + elem.second + + "\" maps to backend " + + Reference<lang::XServiceInfo>( + xBackend, UNO_QUERY_THROW ) + ->getImplementationName() + + "\n"); + } + dp_misc::TRACE( "> [dp_registry.cxx] ambiguous backends:\n\n" ); + for (auto const& ambiguousBackend : that->m_ambiguousBackends) + { + OUStringBuffer buf; + buf.append( + Reference<lang::XServiceInfo>( + ambiguousBackend, UNO_QUERY_THROW )->getImplementationName() + + ": " ); + const Sequence< Reference<deployment::XPackageTypeInfo> > types( + ambiguousBackend->getSupportedPackageTypes() ); + for ( sal_Int32 pos = 0; pos < types.getLength(); ++pos ) { + Reference<deployment::XPackageTypeInfo> const & xInfo = + types[ pos ]; + buf.append( xInfo->getMediaType() ); + const OUString filter( xInfo->getFileFilter() ); + if (!filter.isEmpty()) { + buf.append( " (" + filter + ")" ); + } + if (pos < (types.getLength() - 1)) + buf.append( ", " ); + } + dp_misc::TRACE(buf + "\n\n"); + } + allBackends.insert( that->m_ambiguousBackends.begin(), + that->m_ambiguousBackends.end() ); + OSL_ASSERT( allBackends == that->m_allBackends ); + } +#endif + + return that; +} + +// XUpdatable: broadcast to backends + +void PackageRegistryImpl::update() +{ + check(); + for (auto const& backend : m_allBackends) + { + const Reference<util::XUpdatable> xUpdatable(backend, UNO_QUERY); + if (xUpdatable.is()) + xUpdatable->update(); + } +} + +// XPackageRegistry + +Reference<deployment::XPackage> PackageRegistryImpl::bindPackage( + OUString const & url, OUString const & mediaType_, sal_Bool bRemoved, + OUString const & identifier, Reference<XCommandEnvironment> const & xCmdEnv ) +{ + check(); + OUString mediaType(mediaType_); + if (mediaType.isEmpty()) + { + ::ucbhelper::Content ucbContent; + bool bOk=true; + + try + { + bOk = create_ucb_content( + &ucbContent, url, xCmdEnv, false /* no throw */ ) + && !ucbContent.isFolder(); + } + catch (const css::ucb::ContentCreationException&) + { + bOk = false; + } + + if (bOk) + { + OUString title( StrTitle::getTitle( ucbContent ) ); + for (;;) + { + const t_string2string::const_iterator iFind( + m_filter2mediaType.find(title) ); + if (iFind != m_filter2mediaType.end()) { + mediaType = iFind->second; + break; + } + sal_Int32 point = title.indexOf( '.', 1 /* consume . */ ); + if (point < 0) + break; + title = title.copy(point); + } + } + } + if (mediaType.isEmpty()) + { + // try ambiguous backends: + for (auto const& ambiguousBackend : m_ambiguousBackends) + { + try { + return ambiguousBackend->bindPackage( url, mediaType, bRemoved, + identifier, xCmdEnv ); + } + catch (const lang::IllegalArgumentException &) { + } + } + throw lang::IllegalArgumentException( + DpResId(RID_STR_CANNOT_DETECT_MEDIA_TYPE) + url, + static_cast<OWeakObject *>(this), static_cast<sal_Int16>(-1) ); + } + else + { + // get backend by media-type: + t_string2registry::const_iterator iFind( + m_mediaType2backend.find( normalizeMediaType(mediaType) ) ); + if (iFind == m_mediaType2backend.end()) { + // xxx todo: more sophisticated media-type argument parsing... + sal_Int32 q = mediaType.indexOf( ';' ); + if (q >= 0) { + iFind = m_mediaType2backend.find( + normalizeMediaType( + // cut parameters: + mediaType.subView( 0, q ) ) ); + } + } + if (iFind == m_mediaType2backend.end()) { + throw lang::IllegalArgumentException( + DpResId(RID_STR_UNSUPPORTED_MEDIA_TYPE) + mediaType, + static_cast<OWeakObject *>(this), static_cast<sal_Int16>(-1) ); + } + return iFind->second->bindPackage( url, mediaType, bRemoved, + identifier, xCmdEnv ); + } +} + + +Sequence< Reference<deployment::XPackageTypeInfo> > +PackageRegistryImpl::getSupportedPackageTypes() +{ + return comphelper::containerToSequence(m_typesInfos); +} +} // anon namespace + + +Reference<deployment::XPackageRegistry> create( + OUString const & context, + OUString const & cachePath, + Reference<XComponentContext> const & xComponentContext ) +{ + return PackageRegistryImpl::create( + context, cachePath, xComponentContext ); +} + +} // namespace dp_registry + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/registry/executable/dp_executable.cxx b/desktop/source/deployment/registry/executable/dp_executable.cxx new file mode 100644 index 0000000000..40b253587b --- /dev/null +++ b/desktop/source/deployment/registry/executable/dp_executable.cxx @@ -0,0 +1,336 @@ +/* -*- 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 <string_view> + +#include <dp_misc.h> +#include <dp_backend.h> +#include <dp_ucb.h> +#include <dp_interact.h> +#include <com/sun/star/lang/IllegalArgumentException.hpp> +#include <osl/file.hxx> +#include <ucbhelper/content.hxx> +#include <svl/inettype.hxx> +#include "dp_executablebackenddb.hxx" +#include <cppuhelper/supportsservice.hxx> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::ucb; +using namespace dp_misc; + +namespace dp_registry::backend::executable { +namespace { + +class BackendImpl : public ::dp_registry::backend::PackageRegistryBackend +{ + class ExecutablePackageImpl : public ::dp_registry::backend::Package + { + BackendImpl * getMyBackend() const; + + // Package + virtual beans::Optional< beans::Ambiguous<sal_Bool> > isRegistered_( + ::osl::ResettableMutexGuard & guard, + ::rtl::Reference<dp_misc::AbortChannel> const & abortChannel, + Reference<XCommandEnvironment> const & xCmdEnv ) override; + virtual void processPackage_( + ::osl::ResettableMutexGuard & guard, + bool registerPackage, + bool startup, + ::rtl::Reference<dp_misc::AbortChannel> const & abortChannel, + Reference<XCommandEnvironment> const & xCmdEnv ) override; + + bool getFileAttributes(sal_uInt64& out_Attributes); + bool isUrlTargetInExtension() const; + + public: + ExecutablePackageImpl( + ::rtl::Reference<PackageRegistryBackend> const & myBackend, + OUString const & url, OUString const & name, + Reference<deployment::XPackageTypeInfo> const & xPackageType, + bool bRemoved, OUString const & identifier) + : Package( myBackend, url, name, name /* display-name */, + xPackageType, bRemoved, identifier) + {} + }; + friend class ExecutablePackageImpl; + + // PackageRegistryBackend + virtual Reference<deployment::XPackage> bindPackage_( + OUString const & url, OUString const & mediaType, bool bRemoved, + OUString const & identifier, Reference<XCommandEnvironment> const & xCmdEnv ) override; + + void addDataToDb(OUString const & url); + bool hasActiveEntry(std::u16string_view url); + void revokeEntryFromDb(std::u16string_view url); + + Reference<deployment::XPackageTypeInfo> m_xExecutableTypeInfo; + std::unique_ptr<ExecutableBackendDb> m_backendDb; +public: + BackendImpl( Sequence<Any> const & args, + Reference<XComponentContext> const & xComponentContext ); + + // 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; + + // XPackageRegistry + virtual Sequence< Reference<deployment::XPackageTypeInfo> > SAL_CALL + getSupportedPackageTypes() override; + virtual void SAL_CALL packageRemoved(OUString const & url, OUString const & mediaType) override; + +}; + + +BackendImpl::BackendImpl( + Sequence<Any> const & args, + Reference<XComponentContext> const & xComponentContext ) + : PackageRegistryBackend( args, xComponentContext ), + m_xExecutableTypeInfo(new Package::TypeInfo( + "application/vnd.sun.star.executable", + "", "Executable" ) ) +{ + if (!transientMode()) + { + OUString dbFile = makeURL(getCachePath(), "backenddb.xml"); + m_backendDb.reset( + new ExecutableBackendDb(getComponentContext(), dbFile)); + } +} + +// XServiceInfo +OUString BackendImpl::getImplementationName() +{ + return "com.sun.star.comp.deployment.executable.PackageRegistryBackend"; +} + +sal_Bool BackendImpl::supportsService( const OUString& ServiceName ) +{ + return cppu::supportsService(this, ServiceName); +} + +css::uno::Sequence< OUString > BackendImpl::getSupportedServiceNames() +{ + return { BACKEND_SERVICE_NAME }; +} + +void BackendImpl::addDataToDb(OUString const & url) +{ + if (m_backendDb) + m_backendDb->addEntry(url); +} + +void BackendImpl::revokeEntryFromDb(std::u16string_view url) +{ + if (m_backendDb) + m_backendDb->revokeEntry(url); +} + +bool BackendImpl::hasActiveEntry(std::u16string_view url) +{ + if (m_backendDb) + return m_backendDb->hasActiveEntry(url); + return false; +} + + +// XPackageRegistry +Sequence< Reference<deployment::XPackageTypeInfo> > +BackendImpl::getSupportedPackageTypes() +{ + return Sequence<Reference<deployment::XPackageTypeInfo> >( + & m_xExecutableTypeInfo, 1); +} + +void BackendImpl::packageRemoved(OUString const & url, OUString const & /*mediaType*/) +{ + if (m_backendDb) + m_backendDb->removeEntry(url); +} + +// PackageRegistryBackend +Reference<deployment::XPackage> BackendImpl::bindPackage_( + OUString const & url, OUString const & mediaType, bool bRemoved, + OUString const & identifier, Reference<XCommandEnvironment> const & xCmdEnv ) +{ + if (mediaType.isEmpty()) + { + throw lang::IllegalArgumentException( + StrCannotDetectMediaType() + url, + static_cast<OWeakObject *>(this), static_cast<sal_Int16>(-1) ); + } + + OUString type, subType; + INetContentTypeParameterList params; + if (INetContentTypes::parse( mediaType, type, subType, ¶ms )) + { + if (type.equalsIgnoreAsciiCase("application")) + { + OUString name; + if (!bRemoved) + { + ::ucbhelper::Content ucbContent( + url, xCmdEnv, getComponentContext() ); + name = StrTitle::getTitle( ucbContent ); + } + if (subType.equalsIgnoreAsciiCase("vnd.sun.star.executable")) + { + return new BackendImpl::ExecutablePackageImpl( + this, url, name, m_xExecutableTypeInfo, bRemoved, + identifier); + } + } + } + return Reference<deployment::XPackage>(); +} + + +// Package +BackendImpl * BackendImpl::ExecutablePackageImpl::getMyBackend() const +{ + BackendImpl * pBackend = static_cast<BackendImpl *>(m_myBackend.get()); + if (nullptr == pBackend) + { + //May throw a DisposedException + check(); + //We should never get here... + throw RuntimeException( "Failed to get the BackendImpl", + static_cast<OWeakObject*>(const_cast<ExecutablePackageImpl *>(this))); + } + return pBackend; +} + +beans::Optional< beans::Ambiguous<sal_Bool> > +BackendImpl::ExecutablePackageImpl::isRegistered_( + ::osl::ResettableMutexGuard &, + ::rtl::Reference<dp_misc::AbortChannel> const &, + Reference<XCommandEnvironment> const & ) +{ + bool registered = getMyBackend()->hasActiveEntry(getURL()); + return beans::Optional< beans::Ambiguous<sal_Bool> >( + true /* IsPresent */, + beans::Ambiguous<sal_Bool>( + registered, false /* IsAmbiguous */ ) ); +} + +void BackendImpl::ExecutablePackageImpl::processPackage_( + ::osl::ResettableMutexGuard &, + bool doRegisterPackage, + bool /*startup*/, + ::rtl::Reference<dp_misc::AbortChannel> const & abortChannel, + Reference<XCommandEnvironment> const & /*xCmdEnv*/ ) +{ + checkAborted(abortChannel); + if (doRegisterPackage) + { + if (!isUrlTargetInExtension()) + { + OSL_ASSERT(false); + return; + } + sal_uInt64 attributes = 0; + //Setting the executable attribute does not affect executables on Windows + if (getFileAttributes(attributes)) + { + if(getMyBackend()->m_context == "user") + attributes |= osl_File_Attribute_OwnExe; + else if (getMyBackend()->m_context == "shared") + attributes |= (osl_File_Attribute_OwnExe | osl_File_Attribute_GrpExe + | osl_File_Attribute_OthExe); + else if (getMyBackend()->m_context != "bundled") + //Bundled extensions are required to be in the properly + //installed. That is an executable must have the right flags + OSL_ASSERT(false); + + //This won't have effect on Windows + osl::File::setAttributes( + dp_misc::expandUnoRcUrl(m_url), attributes); + } + getMyBackend()->addDataToDb(getURL()); + } + else + { + getMyBackend()->revokeEntryFromDb(getURL()); + } +} + +//We currently cannot check if this XPackage represents a content of a particular extension +//But we can check if we are within $UNO_USER_PACKAGES_CACHE etc. +//Done for security reasons. For example an extension manifest could contain a path to +//an executable outside the extension. +bool BackendImpl::ExecutablePackageImpl::isUrlTargetInExtension() const +{ + bool bSuccess = false; + OUString sExtensionDir; + if(getMyBackend()->m_context == "user") + sExtensionDir = dp_misc::expandUnoRcTerm("$UNO_USER_PACKAGES_CACHE"); + else if (getMyBackend()->m_context == "shared") + sExtensionDir = dp_misc::expandUnoRcTerm("$UNO_SHARED_PACKAGES_CACHE"); + else if (getMyBackend()->m_context == "bundled") + sExtensionDir = dp_misc::expandUnoRcTerm("$BUNDLED_EXTENSIONS"); + else + OSL_ASSERT(false); + //remove file ellipses + if (osl::File::E_None == osl::File::getAbsoluteFileURL(OUString(), sExtensionDir, sExtensionDir)) + { + OUString sFile; + if (osl::File::E_None == osl::File::getAbsoluteFileURL( + OUString(), dp_misc::expandUnoRcUrl(m_url), sFile)) + { + if (sFile.match(sExtensionDir)) + bSuccess = true; + } + } + return bSuccess; +} + +bool BackendImpl::ExecutablePackageImpl::getFileAttributes(sal_uInt64& out_Attributes) +{ + bool bSuccess = false; + const OUString url(dp_misc::expandUnoRcUrl(m_url)); + osl::DirectoryItem item; + if (osl::FileBase::E_None == osl::DirectoryItem::get(url, item)) + { + osl::FileStatus aStatus(osl_FileStatus_Mask_Attributes); + if( osl::FileBase::E_None == item.getFileStatus(aStatus)) + { + out_Attributes = aStatus.getAttributes(); + bSuccess = true; + } + } + return bSuccess; +} + + +} // anon namespace + + +} // namespace dp_registry::backend::executable + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +com_sun_star_comp_deployment_executable_PackageRegistryBackend_get_implementation( + css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const& args) +{ + return cppu::acquire(new dp_registry::backend::executable::BackendImpl(args, context)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/registry/executable/dp_executablebackenddb.cxx b/desktop/source/deployment/registry/executable/dp_executablebackenddb.cxx new file mode 100644 index 0000000000..29cbf85b33 --- /dev/null +++ b/desktop/source/deployment/registry/executable/dp_executablebackenddb.cxx @@ -0,0 +1,64 @@ +/* -*- 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 <com/sun/star/uno/XComponentContext.hpp> +#include "dp_executablebackenddb.hxx" + + +using namespace ::com::sun::star::uno; + +constexpr OUStringLiteral EXTENSION_REG_NS = u"http://openoffice.org/extensionmanager/executable-registry/2010"; +constexpr OUStringLiteral NS_PREFIX = u"exe"; +constexpr OUStringLiteral ROOT_ELEMENT_NAME = u"executable-backend-db"; +constexpr OUStringLiteral ENTRY_NAME = u"executable"; + +namespace dp_registry::backend::executable { + +ExecutableBackendDb::ExecutableBackendDb( + Reference<XComponentContext> const & xContext, + OUString const & url):RegisteredDb(xContext, url) +{ + +} + +OUString ExecutableBackendDb::getDbNSName() +{ + return EXTENSION_REG_NS; +} + +OUString ExecutableBackendDb::getNSPrefix() +{ + return NS_PREFIX; +} + +OUString ExecutableBackendDb::getRootElementName() +{ + return ROOT_ELEMENT_NAME; +} + +OUString ExecutableBackendDb::getKeyElementName() +{ + return ENTRY_NAME; +} + + +} // namespace dp_registry::backend::executable + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/registry/executable/dp_executablebackenddb.hxx b/desktop/source/deployment/registry/executable/dp_executablebackenddb.hxx new file mode 100644 index 0000000000..0561a8c546 --- /dev/null +++ b/desktop/source/deployment/registry/executable/dp_executablebackenddb.hxx @@ -0,0 +1,55 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#pragma once + +#include <rtl/ustring.hxx> +#include <dp_backenddb.hxx> + +namespace com::sun::star::uno +{ +class XComponentContext; +} + +namespace dp_registry::backend::executable +{ +/* The XML file stores the extensions which are currently registered. + They will be removed when they are revoked. + The format looks like this: + +<?xml version="1.0"?> + */ +class ExecutableBackendDb : public dp_registry::backend::RegisteredDb +{ +protected: + virtual OUString getDbNSName() override; + + virtual OUString getNSPrefix() override; + + virtual OUString getRootElementName() override; + + virtual OUString getKeyElementName() override; + +public: + ExecutableBackendDb(css::uno::Reference<css::uno::XComponentContext> const& xContext, + OUString const& url); +}; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/registry/help/README.md b/desktop/source/deployment/registry/help/README.md new file mode 100644 index 0000000000..24ea195181 --- /dev/null +++ b/desktop/source/deployment/registry/help/README.md @@ -0,0 +1 @@ +Support for help integrated in extensions. Also see /README.help.md. diff --git a/desktop/source/deployment/registry/help/dp_help.cxx b/desktop/source/deployment/registry/help/dp_help.cxx new file mode 100644 index 0000000000..a84bc28095 --- /dev/null +++ b/desktop/source/deployment/registry/help/dp_help.cxx @@ -0,0 +1,621 @@ +/* -*- 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 <strings.hrc> +#include <dp_backend.h> +#include <dp_misc.h> +#include "dp_helpbackenddb.hxx" +#include <dp_ucb.h> +#include <rtl/uri.hxx> +#include <osl/file.hxx> +#include <ucbhelper/content.hxx> +#include <svl/inettype.hxx> +#include <unotools/pathoptions.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <o3tl/string_view.hxx> + +#if HAVE_FEATURE_XMLHELP +#include <helpcompiler/compilehelp.hxx> +#include <helpcompiler/HelpIndexer.hxx> +#endif +#include <com/sun/star/deployment/DeploymentException.hpp> +#include <com/sun/star/deployment/ExtensionRemovedException.hpp> +#include <com/sun/star/ucb/SimpleFileAccess.hpp> +#include <com/sun/star/util/XMacroExpander.hpp> +#include <optional> +#include <string_view> + +using namespace ::dp_misc; +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::ucb; + +namespace dp_registry::backend::help { +namespace { + + +class BackendImpl : public ::dp_registry::backend::PackageRegistryBackend +{ + class PackageImpl : public ::dp_registry::backend::Package + { + BackendImpl * getMyBackend() const; + + // Package + virtual beans::Optional< beans::Ambiguous<sal_Bool> > isRegistered_( + ::osl::ResettableMutexGuard & guard, + ::rtl::Reference<AbortChannel> const & abortChannel, + Reference<XCommandEnvironment> const & xCmdEnv ) override; + virtual void processPackage_( + ::osl::ResettableMutexGuard & guard, + bool registerPackage, + bool startup, + ::rtl::Reference<AbortChannel> const & abortChannel, + Reference<XCommandEnvironment> const & xCmdEnv ) override; + + + public: + PackageImpl( + ::rtl::Reference<PackageRegistryBackend> const & myBackend, + OUString const & url, OUString const & name, + Reference<deployment::XPackageTypeInfo> const & xPackageType, + bool bRemoved, OUString const & identifier); + + bool extensionContainsCompiledHelp(); + + //XPackage + virtual css::beans::Optional< OUString > SAL_CALL getRegistrationDataURL() override; + }; + friend class PackageImpl; + + // PackageRegistryBackend + virtual Reference<deployment::XPackage> bindPackage_( + OUString const & url, OUString const & mediaType, + bool bRemoved, OUString const & identifier, + Reference<XCommandEnvironment> const & xCmdEnv ) override; + + void implProcessHelp( PackageImpl * package, bool doRegisterPackage, + Reference<ucb::XCommandEnvironment> const & xCmdEnv); + void implCollectXhpFiles( const OUString& aDir, + std::vector< OUString >& o_rXhpFileVector ); + + ::std::optional<HelpBackendDb::Data> readDataFromDb(std::u16string_view url); + bool hasActiveEntry(std::u16string_view url); + bool activateEntry(std::u16string_view url); + + Reference< ucb::XSimpleFileAccess3 > const & getFileAccess(); + Reference< ucb::XSimpleFileAccess3 > m_xSFA; + + const Reference<deployment::XPackageTypeInfo> m_xHelpTypeInfo; + Sequence< Reference<deployment::XPackageTypeInfo> > m_typeInfos; + std::unique_ptr<HelpBackendDb> m_backendDb; + +public: + BackendImpl( Sequence<Any> const & args, + Reference<XComponentContext> const & xComponentContext ); + + // 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; + + // XPackageRegistry + virtual Sequence< Reference<deployment::XPackageTypeInfo> > SAL_CALL + getSupportedPackageTypes() override; + virtual void SAL_CALL packageRemoved(OUString const & url, OUString const & mediaType) override; + +}; + + +BackendImpl::BackendImpl( + Sequence<Any> const & args, + Reference<XComponentContext> const & xComponentContext ) + : PackageRegistryBackend( args, xComponentContext ), + m_xHelpTypeInfo( new Package::TypeInfo("application/vnd.sun.star.help", + OUString(), + DpResId(RID_STR_HELP) + ) ), + m_typeInfos{ m_xHelpTypeInfo } +{ + if (transientMode()) + return; + + OUString dbFile = makeURL(getCachePath(), "backenddb.xml"); + m_backendDb.reset( + new HelpBackendDb(getComponentContext(), dbFile)); + + //clean up data folders which are no longer used. + //This must not be done in the same process where the help files + //are still registers. Only after revoking and restarting OOo the folders + //can be removed. This works now, because the extension manager is a singleton + //and the backends are only create once per process. + std::vector<OUString> folders = m_backendDb->getAllDataUrls(); + deleteUnusedFolders(folders); +} + +// XServiceInfo +OUString BackendImpl::getImplementationName() +{ + return "com.sun.star.comp.deployment.help.PackageRegistryBackend"; +} + +sal_Bool BackendImpl::supportsService( const OUString& ServiceName ) +{ + return cppu::supportsService(this, ServiceName); +} + +css::uno::Sequence< OUString > BackendImpl::getSupportedServiceNames() +{ + return { BACKEND_SERVICE_NAME }; +} + +// XPackageRegistry + +Sequence< Reference<deployment::XPackageTypeInfo> > +BackendImpl::getSupportedPackageTypes() +{ + return m_typeInfos; +} + +void BackendImpl::packageRemoved(OUString const & url, OUString const & /*mediaType*/) +{ + if (m_backendDb) + m_backendDb->removeEntry(url); +} + +// PackageRegistryBackend + +Reference<deployment::XPackage> BackendImpl::bindPackage_( + OUString const & url, OUString const & mediaType_, + bool bRemoved, OUString const & identifier, + Reference<XCommandEnvironment> const & xCmdEnv ) +{ + // we don't support auto detection: + if (mediaType_.isEmpty()) + throw lang::IllegalArgumentException( + StrCannotDetectMediaType() + url, + static_cast<OWeakObject *>(this), static_cast<sal_Int16>(-1) ); + + OUString type, subType; + INetContentTypeParameterList params; + if (INetContentTypes::parse( mediaType_, type, subType, ¶ms )) + { + if (type.equalsIgnoreAsciiCase("application")) + { + OUString name; + if (!bRemoved) + { + ::ucbhelper::Content ucbContent( + url, xCmdEnv, getComponentContext() ); + name = StrTitle::getTitle( ucbContent ); + } + + if (subType.equalsIgnoreAsciiCase( "vnd.sun.star.help")) + { + return new PackageImpl( + this, url, name, m_xHelpTypeInfo, bRemoved, + identifier); + } + } + } + throw lang::IllegalArgumentException( + StrUnsupportedMediaType() + mediaType_, + static_cast<OWeakObject *>(this), + static_cast<sal_Int16>(-1) ); +} + +::std::optional<HelpBackendDb::Data> BackendImpl::readDataFromDb( + std::u16string_view url) +{ + ::std::optional<HelpBackendDb::Data> data; + if (m_backendDb) + data = m_backendDb->getEntry(url); + return data; +} + +bool BackendImpl::hasActiveEntry(std::u16string_view url) +{ + if (m_backendDb) + return m_backendDb->hasActiveEntry(url); + return false; +} + +bool BackendImpl::activateEntry(std::u16string_view url) +{ + if (m_backendDb) + return m_backendDb->activateEntry(url); + return false; +} + + +BackendImpl::PackageImpl::PackageImpl( + ::rtl::Reference<PackageRegistryBackend> const & myBackend, + OUString const & url, OUString const & name, + Reference<deployment::XPackageTypeInfo> const & xPackageType, + bool bRemoved, OUString const & identifier) + : Package( myBackend, url, name, name, xPackageType, bRemoved, + identifier) +{ +} + +// Package +BackendImpl * BackendImpl::PackageImpl::getMyBackend() const +{ + BackendImpl * pBackend = static_cast<BackendImpl *>(m_myBackend.get()); + if (nullptr == pBackend) + { + //May throw a DisposedException + check(); + //We should never get here... + throw RuntimeException("Failed to get the BackendImpl", + static_cast<OWeakObject*>(const_cast<PackageImpl *>(this))); + } + return pBackend; +} + +bool BackendImpl::PackageImpl::extensionContainsCompiledHelp() +{ + bool bCompiled = true; + OUString aExpandedHelpURL = dp_misc::expandUnoRcUrl(getURL()); + + ::osl::Directory helpFolder(aExpandedHelpURL); + if ( helpFolder.open() == ::osl::File::E_None) + { + //iterate over the contents of the help folder + //We assume that all folders within the help folder contain language specific + //help files. If just one of them does not contain compiled help then this + //function returns false. + ::osl::DirectoryItem item; + ::osl::File::RC errorNext = ::osl::File::E_None; + while ((errorNext = helpFolder.getNextItem(item)) == ::osl::File::E_None) + { + //No find the language folders + ::osl::FileStatus stat(osl_FileStatus_Mask_Type | osl_FileStatus_Mask_FileName |osl_FileStatus_Mask_FileURL); + if (item.getFileStatus(stat) == ::osl::File::E_None) + { + if (stat.getFileType() != ::osl::FileStatus::Directory) + continue; + + //look if there is the folder help.idxl in the language folder + OUString compUrl(stat.getFileURL() + "/help.idxl"); + ::osl::Directory compiledFolder(compUrl); + if (compiledFolder.open() != ::osl::File::E_None) + { + bCompiled = false; + break; + } + } + else + { + //Error + OSL_ASSERT(false); + bCompiled = false; + break; + } + } + if (errorNext != ::osl::File::E_NOENT + && errorNext != ::osl::File::E_None) + { + //Error + OSL_ASSERT(false); + bCompiled = false; + } + } + return bCompiled; +} + + +beans::Optional< beans::Ambiguous<sal_Bool> > +BackendImpl::PackageImpl::isRegistered_( + ::osl::ResettableMutexGuard &, + ::rtl::Reference<AbortChannel> const &, + Reference<XCommandEnvironment> const & ) +{ + BackendImpl * that = getMyBackend(); + + bool bReg = false; + if (that->hasActiveEntry(getURL())) + bReg = true; + + return beans::Optional< beans::Ambiguous<sal_Bool> >( true, beans::Ambiguous<sal_Bool>( bReg, false ) ); +} + + +void BackendImpl::PackageImpl::processPackage_( + ::osl::ResettableMutexGuard &, + bool doRegisterPackage, + bool /* startup */, + ::rtl::Reference<AbortChannel> const &, + Reference<XCommandEnvironment> const & xCmdEnv ) +{ + BackendImpl* that = getMyBackend(); + that->implProcessHelp( this, doRegisterPackage, xCmdEnv); +} + +beans::Optional< OUString > BackendImpl::PackageImpl::getRegistrationDataURL() +{ + if (m_bRemoved) + throw deployment::ExtensionRemovedException(); + + ::std::optional<HelpBackendDb::Data> data = + getMyBackend()->readDataFromDb(getURL()); + + if (data && getMyBackend()->hasActiveEntry(getURL())) + return beans::Optional<OUString>(true, data->dataUrl); + + return beans::Optional<OUString>(true, OUString()); +} + +void BackendImpl::implProcessHelp( + PackageImpl * package, bool doRegisterPackage, + Reference<ucb::XCommandEnvironment> const & xCmdEnv) +{ + Reference< deployment::XPackage > xPackage(package); + OSL_ASSERT(xPackage.is()); + if (doRegisterPackage) + { + //revive already processed help if possible + if ( !activateEntry(xPackage->getURL())) + { + HelpBackendDb::Data data; + data.dataUrl = xPackage->getURL(); + if (!package->extensionContainsCompiledHelp()) + { +#if HAVE_FEATURE_XMLHELP + const OUString sHelpFolder = createFolder(xCmdEnv); + data.dataUrl = sHelpFolder; + + Reference< ucb::XSimpleFileAccess3 > xSFA = getFileAccess(); + OUString aHelpURL = xPackage->getURL(); + OUString aExpandedHelpURL = dp_misc::expandUnoRcUrl( aHelpURL ); + if( !xSFA->isFolder( aExpandedHelpURL ) ) + { + OUString aErrStr = DpResId( RID_STR_HELPPROCESSING_GENERAL_ERROR ) + + "No help folder"; + OWeakObject* oWeakThis = this; + throw deployment::DeploymentException( OUString(), oWeakThis, + Any( uno::Exception( aErrStr, oWeakThis ) ) ); + } + + // Scan languages + Sequence< OUString > aLanguageFolderSeq = xSFA->getFolderContents( aExpandedHelpURL, true ); + sal_Int32 nLangCount = aLanguageFolderSeq.getLength(); + const OUString* pSeq = aLanguageFolderSeq.getConstArray(); + for( sal_Int32 iLang = 0 ; iLang < nLangCount ; ++iLang ) + { + OUString aLangURL = pSeq[iLang]; + if( xSFA->isFolder( aLangURL ) ) + { + std::vector< OUString > aXhpFileVector; + + // calculate jar file URL + sal_Int32 indexStartSegment = aLangURL.lastIndexOf('/'); + // for example "/en" + OUString langFolderURLSegment( + aLangURL.copy( + indexStartSegment + 1, aLangURL.getLength() - indexStartSegment - 1)); + + //create the folder in the "temporary folder" + ::ucbhelper::Content langFolderContent; + const OUString langFolderDest = makeURL(sHelpFolder, langFolderURLSegment); + const OUString langFolderDestExpanded = ::dp_misc::expandUnoRcUrl(langFolderDest); + ::dp_misc::create_folder( + &langFolderContent, + langFolderDest, xCmdEnv); + + static constexpr OUString aHelpStr(u"help"_ustr); + + OUString aJarFile( + makeURL(sHelpFolder, langFolderURLSegment + "/" + aHelpStr + ".jar")); + aJarFile = ::dp_misc::expandUnoRcUrl(aJarFile); + + OUString aEncodedJarFilePath = rtl::Uri::encode( + aJarFile, rtl_UriCharClassPchar, + rtl_UriEncodeIgnoreEscapes, + RTL_TEXTENCODING_UTF8 ); + OUString aDestBasePath = "vnd.sun.star.zip://" + + aEncodedJarFilePath + "/" ; + + sal_Int32 nLenLangFolderURL = aLangURL.getLength() + 1; + + Sequence< OUString > aSubLangSeq = xSFA->getFolderContents( aLangURL, true ); + sal_Int32 nSubLangCount = aSubLangSeq.getLength(); + const OUString* pSubLangSeq = aSubLangSeq.getConstArray(); + for( sal_Int32 iSubLang = 0 ; iSubLang < nSubLangCount ; ++iSubLang ) + { + OUString aSubFolderURL = pSubLangSeq[iSubLang]; + if( !xSFA->isFolder( aSubFolderURL ) ) + continue; + + implCollectXhpFiles( aSubFolderURL, aXhpFileVector ); + + // Copy to package (later: move?) + std::u16string_view aPureFolderName = aSubFolderURL.subView( nLenLangFolderURL ); + OUString aDestPath = aDestBasePath + aPureFolderName; + xSFA->copy( aSubFolderURL, aDestPath ); + } + + // Call compiler + sal_Int32 nXhpFileCount = aXhpFileVector.size(); + std::unique_ptr<OUString[]> pXhpFiles(new OUString[nXhpFileCount]); + for( sal_Int32 iXhp = 0 ; iXhp < nXhpFileCount ; ++iXhp ) + { + OUString aXhpFile = aXhpFileVector[iXhp]; + OUString aXhpRelFile = aXhpFile.copy( nLenLangFolderURL ); + pXhpFiles[iXhp] = aXhpRelFile; + } + + OUString aOfficeHelpPath( SvtPathOptions().GetHelpPath() ); + OUString aOfficeHelpPathFileURL; + ::osl::File::getFileURLFromSystemPath( aOfficeHelpPath, aOfficeHelpPathFileURL ); + + HelpProcessingErrorInfo aErrorInfo; + bool bSuccess = compileExtensionHelp( + aOfficeHelpPathFileURL, aHelpStr, aLangURL, + nXhpFileCount, pXhpFiles.get(), + langFolderDestExpanded, aErrorInfo ); + + pXhpFiles.reset(); + + if( bSuccess ) + { + OUString aLang; + sal_Int32 nLastSlash = aLangURL.lastIndexOf( '/' ); + if( nLastSlash != -1 ) + aLang = aLangURL.copy( nLastSlash + 1 ); + else + aLang = "en"; + + HelpIndexer aIndexer(aLang, "help", langFolderDestExpanded, langFolderDestExpanded); + aIndexer.indexDocuments(); + } + + if( !bSuccess ) + { + TranslateId pErrStrId; + switch( aErrorInfo.m_eErrorClass ) + { + case HelpProcessingErrorClass::General: pErrStrId = RID_STR_HELPPROCESSING_GENERAL_ERROR; break; + case HelpProcessingErrorClass::XmlParsing: pErrStrId = RID_STR_HELPPROCESSING_XMLPARSING_ERROR; break; + default: ; + }; + + OUString aErrStr; + if (pErrStrId) + { + aErrStr = DpResId(pErrStrId); + + // Remove CR/LF + OUString aErrMsg( aErrorInfo.m_aErrorMsg ); + sal_Unicode const nCR = 13, nLF = 10; + sal_Int32 nSearchCR = aErrMsg.indexOf( nCR ); + sal_Int32 nSearchLF = aErrMsg.indexOf( nLF ); + if( nSearchCR != -1 || nSearchLF != -1 ) + { + sal_Int32 nCopy; + if( nSearchCR == -1 ) + nCopy = nSearchLF; + else if( nSearchLF == -1 ) + nCopy = nSearchCR; + else + nCopy = ( nSearchCR < nSearchLF ) ? nSearchCR : nSearchLF; + + aErrMsg = aErrMsg.copy( 0, nCopy ); + } + aErrStr += aErrMsg; + if (pErrStrId != RID_STR_HELPPROCESSING_XMLPARSING_ERROR && !aErrorInfo.m_aXMLParsingFile.isEmpty() ) + { + aErrStr += " in "; + + OUString aDecodedFile = rtl::Uri::decode( aErrorInfo.m_aXMLParsingFile, + rtl_UriDecodeWithCharset, RTL_TEXTENCODING_UTF8 ); + aErrStr += aDecodedFile; + if( aErrorInfo.m_nXMLParsingLine != -1 ) + { + aErrStr += ", line " + + OUString::number( aErrorInfo.m_nXMLParsingLine ); + } + } + } + + OWeakObject* oWeakThis = this; + throw deployment::DeploymentException( OUString(), oWeakThis, + Any( uno::Exception( aErrStr, oWeakThis ) ) ); + } + } + } +#else + (void) xCmdEnv; +#endif + } + // Writing the data entry replaces writing the flag file. If we got to this + // point the registration was successful. + if (m_backendDb) + m_backendDb->addEntry(xPackage->getURL(), data); + } + } //if (doRegisterPackage) + else + { + if (m_backendDb) + m_backendDb->revokeEntry(xPackage->getURL()); + } +} + +void BackendImpl::implCollectXhpFiles( const OUString& aDir, + std::vector< OUString >& o_rXhpFileVector ) +{ + Reference< ucb::XSimpleFileAccess3 > xSFA = getFileAccess(); + + // Scan xhp files recursively + Sequence< OUString > aSeq = xSFA->getFolderContents( aDir, true ); + sal_Int32 nCount = aSeq.getLength(); + const OUString* pSeq = aSeq.getConstArray(); + for( sal_Int32 i = 0 ; i < nCount ; ++i ) + { + OUString aURL = pSeq[i]; + if( xSFA->isFolder( aURL ) ) + { + implCollectXhpFiles( aURL, o_rXhpFileVector ); + } + else + { + sal_Int32 nLastDot = aURL.lastIndexOf( '.' ); + if( nLastDot != -1 ) + { + std::u16string_view aExt = aURL.subView( nLastDot + 1 ); + if( o3tl::equalsIgnoreAsciiCase( aExt, u"xhp" ) ) + o_rXhpFileVector.push_back( aURL ); + } + } + } +} + +Reference< ucb::XSimpleFileAccess3 > const & BackendImpl::getFileAccess() +{ + if( !m_xSFA.is() ) + { + Reference<XComponentContext> const & xContext = getComponentContext(); + if( xContext.is() ) + { + m_xSFA = ucb::SimpleFileAccess::create(xContext); + } + if( !m_xSFA.is() ) + { + throw RuntimeException( + "dp_registry::backend::help::BackendImpl::getFileAccess(), " + "could not instantiate SimpleFileAccess." ); + } + } + return m_xSFA; +} + +} // anon namespace + +} // namespace dp_registry + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +com_sun_star_comp_deployment_help_PackageRegistryBackend_get_implementation( + css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const& args) +{ + return cppu::acquire(new dp_registry::backend::help::BackendImpl(args, context)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/registry/help/dp_helpbackenddb.cxx b/desktop/source/deployment/registry/help/dp_helpbackenddb.cxx new file mode 100644 index 0000000000..19bb3c3ce5 --- /dev/null +++ b/desktop/source/deployment/registry/help/dp_helpbackenddb.cxx @@ -0,0 +1,126 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + + +#include <cppuhelper/exc_hlp.hxx> +#include <com/sun/star/deployment/DeploymentException.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> + +#include "dp_helpbackenddb.hxx" + + +using namespace ::com::sun::star::uno; + +constexpr OUStringLiteral EXTENSION_REG_NS = u"http://openoffice.org/extensionmanager/help-registry/2010"; +constexpr OUStringLiteral NS_PREFIX = u"help"; +constexpr OUStringLiteral ROOT_ELEMENT_NAME = u"help-backend-db"; +constexpr OUStringLiteral KEY_ELEMENT_NAME = u"help"; + +namespace dp_registry::backend::help { + +HelpBackendDb::HelpBackendDb( + Reference<XComponentContext> const & xContext, + OUString const & url):BackendDb(xContext, url) +{ + +} + +OUString HelpBackendDb::getDbNSName() +{ + return EXTENSION_REG_NS; +} + +OUString HelpBackendDb::getNSPrefix() +{ + return NS_PREFIX; +} + +OUString HelpBackendDb::getRootElementName() +{ + return ROOT_ELEMENT_NAME; +} + +OUString HelpBackendDb::getKeyElementName() +{ + return KEY_ELEMENT_NAME; +} + + +void HelpBackendDb::addEntry(OUString const & url, Data const & data) +{ + try{ + if (!activateEntry(url)) + { + Reference<css::xml::dom::XNode> helpNode + = writeKeyElement(url); + + writeSimpleElement(u"data-url", data.dataUrl, helpNode); + save(); + } + } + catch ( const css::deployment::DeploymentException& ) + { + throw; + } + catch(const css::uno::Exception &) + { + Any exc( ::cppu::getCaughtException() ); + throw css::deployment::DeploymentException( + "Extension Manager: failed to write data entry in help backend db: " + m_urlDb, nullptr, exc); + } +} + + +::std::optional<HelpBackendDb::Data> +HelpBackendDb::getEntry(std::u16string_view url) +{ + try + { + HelpBackendDb::Data retData; + Reference<css::xml::dom::XNode> aNode = getKeyElement(url); + if (aNode.is()) + { + retData.dataUrl = readSimpleElement(u"data-url", aNode); + } + else + { + return ::std::optional<Data>(); + } + return ::std::optional<Data>(retData); + } + catch ( const css::deployment::DeploymentException& ) + { + throw; + } + catch(const css::uno::Exception &) + { + Any exc( ::cppu::getCaughtException() ); + throw css::deployment::DeploymentException( + "Extension Manager: failed to read data entry in help backend db: " + m_urlDb, nullptr, exc); + } +} + +std::vector<OUString> HelpBackendDb::getAllDataUrls() +{ + return getOneChildFromAllEntries(u"data-url"); +} + +} // namespace dp_registry::backend::help + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/registry/help/dp_helpbackenddb.hxx b/desktop/source/deployment/registry/help/dp_helpbackenddb.hxx new file mode 100644 index 0000000000..a46bd8663c --- /dev/null +++ b/desktop/source/deployment/registry/help/dp_helpbackenddb.hxx @@ -0,0 +1,70 @@ +/* -*- 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> +#include <optional> +#include <string_view> + +#include <dp_backenddb.hxx> + +namespace com::sun::star::uno +{ +class XComponentContext; +} + +namespace dp_registry::backend::help +{ +/* The XML file stores the extensions which are currently registered. + They will be removed when they are revoked. + */ +class HelpBackendDb : public dp_registry::backend::BackendDb +{ +protected: + virtual OUString getDbNSName() override; + + virtual OUString getNSPrefix() override; + + virtual OUString getRootElementName() override; + + virtual OUString getKeyElementName() override; + +public: + struct Data + { + /* the URL to the folder containing the compiled help files, etc. + */ + OUString dataUrl; + }; + +public: + HelpBackendDb(css::uno::Reference<css::uno::XComponentContext> const& xContext, + OUString const& url); + + void addEntry(OUString const& url, Data const& data); + + ::std::optional<Data> getEntry(std::u16string_view url); + //must also return the data urls for entries with @active="false". That is, + //those are currently revoked. + std::vector<OUString> getAllDataUrls(); +}; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/registry/inc/dp_backend.h b/desktop/source/deployment/registry/inc/dp_backend.h new file mode 100644 index 0000000000..a68420d6b0 --- /dev/null +++ b/desktop/source/deployment/registry/inc/dp_backend.h @@ -0,0 +1,287 @@ +/* -*- 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 <dp_shared.hxx> +#include <dp_interact.h> +#include <rtl/ref.hxx> +#include <cppuhelper/basemutex.hxx> +#include <cppuhelper/implbase.hxx> +#include <cppuhelper/compbase.hxx> +#include <com/sun/star/lang/XEventListener.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/deployment/XPackageRegistry.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <unordered_map> +#include <strings.hrc> +#include <utility> + +namespace dp_registry::backend +{ + +class PackageRegistryBackend; + +inline constexpr OUString BACKEND_SERVICE_NAME = u"com.sun.star.deployment.PackageRegistryBackend"_ustr; + +typedef ::cppu::WeakComponentImplHelper< + css::deployment::XPackage > t_PackageBase; + + +class Package : protected cppu::BaseMutex, public t_PackageBase +{ + PackageRegistryBackend * getMyBackend() const; + void processPackage_impl( + bool registerPackage, + bool startup, + css::uno::Reference<css::task::XAbortChannel> const & xAbortChannel, + css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv ); + +protected: + ::rtl::Reference<PackageRegistryBackend> m_myBackend; + const OUString m_url; + OUString m_name; + OUString m_displayName; + const css::uno::Reference<css::deployment::XPackageTypeInfo> m_xPackageType; + const bool m_bRemoved; + //Only set if m_bRemoved = true; + const OUString m_identifier; + + void check() const; + void fireModified(); + virtual void SAL_CALL disposing() override; + + void checkAborted( + ::rtl::Reference< ::dp_misc::AbortChannel > const & abortChannel ); + + // @@@ to be implemented by specific backend: + virtual css::beans::Optional< css::beans::Ambiguous<sal_Bool> > + isRegistered_( + ::osl::ResettableMutexGuard & guard, + ::rtl::Reference< ::dp_misc::AbortChannel > const & abortChannel, + css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv ) + = 0; + virtual void processPackage_( + ::osl::ResettableMutexGuard & guard, + bool registerPackage, + bool startup, + ::rtl::Reference< ::dp_misc::AbortChannel > const & abortChannel, + css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv ) + = 0; + + virtual ~Package() override; + Package( ::rtl::Reference<PackageRegistryBackend> myBackend, + OUString url, + OUString name, + OUString displayName, + css::uno::Reference<css::deployment::XPackageTypeInfo> const & xPackageType, + bool bRemoved, + OUString identifier); + +public: + + class TypeInfo : + public ::cppu::WeakImplHelper<css::deployment::XPackageTypeInfo> + { + const OUString m_mediaType; + const OUString m_fileFilter; + const OUString m_shortDescr; + public: + virtual ~TypeInfo() override; + TypeInfo( OUString mediaType, + OUString fileFilter, + OUString shortDescr ) + : m_mediaType(std::move(mediaType)), m_fileFilter(std::move(fileFilter)), + m_shortDescr(std::move(shortDescr)) + {} + // XPackageTypeInfo + virtual OUString SAL_CALL getMediaType() override; + virtual OUString SAL_CALL getDescription() override; + virtual OUString SAL_CALL getShortDescription() override; + virtual OUString SAL_CALL getFileFilter() override; + virtual css::uno::Any SAL_CALL getIcon( sal_Bool highContrast, + sal_Bool smallIcon ) override; + }; + + // XComponent + virtual void SAL_CALL dispose() override; + virtual void SAL_CALL addEventListener( + css::uno::Reference<css::lang::XEventListener> const & xListener ) override; + virtual void SAL_CALL removeEventListener( + css::uno::Reference<css::lang::XEventListener> const & xListener ) override; + + // XModifyBroadcaster + virtual void SAL_CALL addModifyListener( + css::uno::Reference<css::util::XModifyListener> const & xListener ) override; + virtual void SAL_CALL removeModifyListener( + css::uno::Reference<css::util::XModifyListener> const & xListener ) override; + + // XPackage + virtual css::uno::Reference<css::task::XAbortChannel> SAL_CALL + createAbortChannel() override; + virtual css::beans::Optional< css::beans::Ambiguous<sal_Bool> > + SAL_CALL isRegistered( + css::uno::Reference<css::task::XAbortChannel> const & xAbortChannel, + css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv ) override; + + virtual ::sal_Int32 SAL_CALL checkPrerequisites( + const css::uno::Reference< css::task::XAbortChannel >& xAbortChannel, + const css::uno::Reference< css::ucb::XCommandEnvironment >& xCmdEnv, + sal_Bool noLicenseChecking) override; + + virtual ::sal_Bool SAL_CALL checkDependencies( + const css::uno::Reference< css::ucb::XCommandEnvironment >& xCmdEnv ) override; + + virtual void SAL_CALL registerPackage( + sal_Bool startup, + css::uno::Reference<css::task::XAbortChannel> const & xAbortChannel, + css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv ) override; + virtual void SAL_CALL revokePackage( + sal_Bool startup, + css::uno::Reference<css::task::XAbortChannel> const & xAbortChannel, + css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv ) override; + virtual sal_Bool SAL_CALL isBundle() override; + virtual css::uno::Sequence< css::uno::Reference<css::deployment::XPackage> > + SAL_CALL getBundle( + css::uno::Reference<css::task::XAbortChannel> const & xAbortChannel, + css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv ) override; + virtual OUString SAL_CALL getName() override; + virtual css::beans::Optional< OUString > SAL_CALL getIdentifier() override; + virtual OUString SAL_CALL getVersion() override; + virtual OUString SAL_CALL getURL() override; + virtual OUString SAL_CALL getDisplayName() override; + virtual OUString SAL_CALL getDescription() override; + virtual OUString SAL_CALL getLicenseText() override; + virtual css::uno::Sequence< OUString > SAL_CALL + getUpdateInformationURLs() override; + virtual css::beans::StringPair SAL_CALL getPublisherInfo() override; + virtual css::uno::Reference< css::graphic::XGraphic > SAL_CALL + getIcon( sal_Bool bHighContrast ) override; + virtual css::uno::Reference<css::deployment::XPackageTypeInfo> SAL_CALL + getPackageType() override; + virtual void SAL_CALL exportTo( + OUString const & destFolderURL, + OUString const & newTitle, + sal_Int32 nameClashAction, + css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv ) override; + virtual OUString SAL_CALL getRepositoryName() override; + virtual css::beans::Optional< OUString > SAL_CALL getRegistrationDataURL() override; + virtual sal_Bool SAL_CALL isRemoved() override; + +}; + +typedef ::cppu::WeakComponentImplHelper< + css::lang::XEventListener, + css::deployment::XPackageRegistry, + css::lang::XServiceInfo > t_BackendBase; + + +class PackageRegistryBackend + : protected cppu::BaseMutex, public t_BackendBase +{ + //The map held originally WeakReferences. The map entries are removed in the disposing + //function, which is called when the XPackages are destructed or they are + //explicitly disposed. The latter happens, for example, when an extension is + //removed (see dp_manager.cxx). However, because of how the help systems work, now + // XPackageManager::getDeployedPackages is called often. This results in a lot + //of bindPackage calls which are costly. Therefore we keep hard references in + //the map now. + typedef std::unordered_map< + OUString, css::uno::Reference<css::deployment::XPackage> > t_string2ref; + t_string2ref m_bound; + +protected: + OUString m_cachePath; + css::uno::Reference<css::uno::XComponentContext> m_xComponentContext; + + OUString m_context; + // currently only for library containers: + enum class Context { + Unknown, User, Shared, Bundled, Tmp, Document + } m_eContext; + + static OUString StrCannotDetectMediaType() { return DpResId(RID_STR_CANNOT_DETECT_MEDIA_TYPE); } + static OUString StrUnsupportedMediaType() { return DpResId(RID_STR_UNSUPPORTED_MEDIA_TYPE); } + + // @@@ to be implemented by specific backend: + virtual css::uno::Reference<css::deployment::XPackage> bindPackage_( + OUString const & url, OUString const & mediaType, + bool bRemoved, OUString const & identifier, + css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv ) + = 0; + + void check(); + virtual void SAL_CALL disposing() override; + + virtual ~PackageRegistryBackend() override; + PackageRegistryBackend( + css::uno::Sequence<css::uno::Any> const & args, + css::uno::Reference<css::uno::XComponentContext> const & xContext ); + + /* creates a folder with a unique name. + If url is empty then it is created in the backend folder, otherwise + at a location relative to that folder specified by url. + */ + OUString createFolder( + css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv); + /* deletes folders and files. + + All folder all files which end with ".tmp" or ".tmp_" and which are + not used are deleted. + */ + void deleteUnusedFolders( + std::vector< OUString> const & usedFolders); + /* deletes one folder with a "temporary" name and the corresponding + tmp file, which was used to derive the folder name. + */ + static void deleteTempFolder( + OUString const & folderUrl); + +public: + static OUString StrRegisteringPackage() { return DpResId(RID_STR_REGISTERING_PACKAGE); } + static OUString StrRevokingPackage() { return DpResId(RID_STR_REVOKING_PACKAGE); } + + css::uno::Reference<css::uno::XComponentContext> const & + getComponentContext() const { return m_xComponentContext; } + + OUString const & getCachePath() const { return m_cachePath; } + bool transientMode() const { return m_cachePath.isEmpty(); } + + const OUString& getContext() const {return m_context; } + + // XEventListener + virtual void SAL_CALL disposing( css::lang::EventObject const & evt ) override; + + // XPackageRegistry + virtual css::uno::Reference<css::deployment::XPackage> SAL_CALL bindPackage( + OUString const & url, OUString const & mediaType, + sal_Bool bRemoved, OUString const & identifier, + css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv ) override; + +// virtual void SAL_CALL packageRemoved( +// OUString const & url, OUString const & mediaType) +// throw (css::deployment::DeploymentException, +// css::uno::RuntimeException); + +}; + +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/registry/inc/dp_backenddb.hxx b/desktop/source/deployment/registry/inc/dp_backenddb.hxx new file mode 100644 index 0000000000..7852014667 --- /dev/null +++ b/desktop/source/deployment/registry/inc/dp_backenddb.hxx @@ -0,0 +1,165 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#pragma once + +#include <com/sun/star/uno/Reference.hxx> +#include <rtl/ustring.hxx> +#include <deque> +#include <string_view> +#include <vector> + +namespace com::sun::star { + namespace uno { + class XComponentContext; + } + namespace xml::dom { + class XDocument; + class XNode; + } + namespace xml::xpath { + class XXPathAPI; + } +} + +namespace dp_registry::backend { + +class BackendDb +{ +private: + + css::uno::Reference<css::xml::dom::XDocument> m_doc; + css::uno::Reference<css::xml::xpath::XXPathAPI> m_xpathApi; + + BackendDb(BackendDb const &) = delete; + BackendDb & operator = (BackendDb const &) = delete; + +protected: + const css::uno::Reference<css::uno::XComponentContext> m_xContext; + OUString m_urlDb; + +protected: + + /* caller must make sure that only one thread accesses the function + */ + css::uno::Reference<css::xml::dom::XDocument> const & getDocument(); + + /* the namespace prefix is "reg" (without quotes) + */ + css::uno::Reference<css::xml::xpath::XXPathAPI> const & getXPathAPI(); + void save(); + void removeElement(OUString const & sXPathExpression); + + css::uno::Reference<css::xml::dom::XNode> getKeyElement( + std::u16string_view url); + + void writeSimpleList( + std::deque< OUString> const & list, + std::u16string_view sListTagName, + std::u16string_view sMemberTagName, + css::uno::Reference<css::xml::dom::XNode> const & xParent); + + void writeVectorOfPair( + std::vector< std::pair< OUString, OUString > > const & vecPairs, + std::u16string_view sVectorTagName, + std::u16string_view sPairTagName, + std::u16string_view sFirstTagName, + std::u16string_view sSecondTagName, + css::uno::Reference<css::xml::dom::XNode> const & xParent); + + void writeSimpleElement( + std::u16string_view sElementName, OUString const & value, + css::uno::Reference<css::xml::dom::XNode> const & xParent); + + css::uno::Reference<css::xml::dom::XNode> writeKeyElement( + OUString const & url); + + OUString readSimpleElement( + std::u16string_view sElementName, + css::uno::Reference<css::xml::dom::XNode> const & xParent); + + std::vector< std::pair< OUString, OUString > > + readVectorOfPair( + css::uno::Reference<css::xml::dom::XNode> const & parent, + std::u16string_view sListTagName, + std::u16string_view sPairTagName, + std::u16string_view sFirstTagName, + std::u16string_view sSecondTagName); + + std::deque< OUString> readList( + css::uno::Reference<css::xml::dom::XNode> const & parent, + std::u16string_view sListTagName, + std::u16string_view sMemberTagName); + + /* returns the values of one particularly child element of all key elements. + */ + std::vector< OUString> getOneChildFromAllEntries( + std::u16string_view sElementName); + + + /* returns the namespace which is to be written as xmlns attribute + into the root element. + */ + virtual OUString getDbNSName()=0; + /* return the namespace prefix which is to be registered with the XPath API. + + The prefix can then be used in XPath expressions. + */ + virtual OUString getNSPrefix()=0; + /* returns the name of the root element without any namespace prefix. + */ + virtual OUString getRootElementName()=0; + /* returns the name of xml element for each entry + */ + virtual OUString getKeyElementName()=0; + +public: + BackendDb(css::uno::Reference<css::uno::XComponentContext> const & xContext, + OUString const & url); + virtual ~BackendDb() {}; + + void removeEntry(std::u16string_view url); + + /* This is called to write the "revoked" attribute to the entry. + This is done when XPackage::revokePackage is called. + */ + void revokeEntry(std::u16string_view url); + + /* returns false if the entry does not exist yet. + */ + bool activateEntry(std::u16string_view url); + + bool hasActiveEntry(std::u16string_view url); + +}; + +class RegisteredDb: public BackendDb +{ + +public: + RegisteredDb( css::uno::Reference<css::uno::XComponentContext> const & xContext, + OUString const & url); + + + void addEntry(OUString const & url); +}; + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/registry/package/dp_extbackenddb.cxx b/desktop/source/deployment/registry/package/dp_extbackenddb.cxx new file mode 100644 index 0000000000..8e656c5d7d --- /dev/null +++ b/desktop/source/deployment/registry/package/dp_extbackenddb.cxx @@ -0,0 +1,111 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + + +#include <cppuhelper/exc_hlp.hxx> +#include <com/sun/star/deployment/DeploymentException.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> + +#include "dp_extbackenddb.hxx" + + +using namespace ::com::sun::star::uno; + +constexpr OUStringLiteral EXTENSION_REG_NS = u"http://openoffice.org/extensionmanager/extension-registry/2010"; +constexpr OUStringLiteral NS_PREFIX = u"ext"; +constexpr OUStringLiteral ROOT_ELEMENT_NAME = u"extension-backend-db"; +constexpr OUStringLiteral KEY_ELEMENT_NAME = u"extension"; + +namespace dp_registry::backend::bundle { + +ExtensionBackendDb::ExtensionBackendDb( + Reference<XComponentContext> const & xContext, + OUString const & url):BackendDb(xContext, url) +{ + +} + +OUString ExtensionBackendDb::getDbNSName() +{ + return EXTENSION_REG_NS; +} + +OUString ExtensionBackendDb::getNSPrefix() +{ + return NS_PREFIX; +} + +OUString ExtensionBackendDb::getRootElementName() +{ + return ROOT_ELEMENT_NAME; +} + +OUString ExtensionBackendDb::getKeyElementName() +{ + return KEY_ELEMENT_NAME; +} + +void ExtensionBackendDb::addEntry(OUString const & url, Data const & data) +{ + try{ + //reactive revoked entry if possible. + if (!activateEntry(url)) + { + Reference<css::xml::dom::XNode> extensionNodeNode = writeKeyElement(url); + writeVectorOfPair( data.items, u"extension-items", u"item", + u"url", u"media-type", extensionNodeNode); + save(); + } + } + catch(const css::uno::Exception &) + { + Any exc( ::cppu::getCaughtException() ); + throw css::deployment::DeploymentException( + "Extension Manager: failed to write data entry in backend db: " + + m_urlDb, nullptr, exc); + } +} + +ExtensionBackendDb::Data ExtensionBackendDb::getEntry(std::u16string_view url) +{ + try + { + ExtensionBackendDb::Data retData; + Reference<css::xml::dom::XNode> aNode = getKeyElement(url); + + if (aNode.is()) + { + retData.items = + readVectorOfPair( aNode, u"extension-items", u"item", + u"url", u"media-type"); + } + return retData; + } + catch(const css::uno::Exception &) + { + Any exc( ::cppu::getCaughtException() ); + throw css::deployment::DeploymentException( + "Extension Manager: failed to read data entry in backend db: " + + m_urlDb, nullptr, exc); + } +} + +} // namespace dp_registry::backend::bundle + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/registry/package/dp_extbackenddb.hxx b/desktop/source/deployment/registry/package/dp_extbackenddb.hxx new file mode 100644 index 0000000000..fb736e6e26 --- /dev/null +++ b/desktop/source/deployment/registry/package/dp_extbackenddb.hxx @@ -0,0 +1,69 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#pragma once + +#include <sal/config.h> + +#include <string_view> +#include <utility> +#include <vector> + +#include <rtl/ustring.hxx> + +#include <dp_backenddb.hxx> + +namespace com::sun::star::uno +{ +class XComponentContext; +} + +namespace dp_registry::backend::bundle +{ +/* The XML file stores the extensions which are currently registered. + They will be removed when they are revoked. + */ +class ExtensionBackendDb : public dp_registry::backend::BackendDb +{ +protected: + virtual OUString getDbNSName() override; + virtual OUString getNSPrefix() override; + virtual OUString getRootElementName() override; + virtual OUString getKeyElementName() override; + +public: + struct Data + { + /* every element consists of a pair of the url to the item (jar,rdb, etc) + and the media type + */ + std::vector<std::pair<OUString, OUString>> items; + }; + +public: + ExtensionBackendDb(css::uno::Reference<css::uno::XComponentContext> const& xContext, + OUString const& url); + + void addEntry(OUString const& url, Data const& data); + + Data getEntry(std::u16string_view url); +}; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/registry/package/dp_package.cxx b/desktop/source/deployment/registry/package/dp_package.cxx new file mode 100644 index 0000000000..6af2fb5515 --- /dev/null +++ b/desktop/source/deployment/registry/package/dp_package.cxx @@ -0,0 +1,1594 @@ +/* -*- 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 <strings.hrc> +#include <dp_package.hxx> +#include <dp_backend.h> +#include <dp_misc.h> +#include <dp_ucb.h> +#include <dp_interact.h> +#include <dp_dependencies.hxx> +#include <dp_platform.hxx> +#include <dp_descriptioninfoset.hxx> +#include <dp_identifier.hxx> +#include <dp_resource.h> +#include <rtl/uri.hxx> +#include <rtl/ustrbuf.hxx> +#include <sal/log.hxx> +#include <o3tl/string_view.hxx> +#include <cppuhelper/exc_hlp.hxx> +#include <cppuhelper/implbase.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <ucbhelper/content.hxx> +#include <svl/inettype.hxx> +#include <comphelper/propertyvalue.hxx> +#include <comphelper/sequence.hxx> +#include <com/sun/star/lang/WrappedTargetException.hpp> +#include <com/sun/star/graphic/XGraphic.hpp> +#include <com/sun/star/graphic/GraphicProvider.hpp> +#include <com/sun/star/graphic/XGraphicProvider.hpp> +#include <com/sun/star/io/Pipe.hpp> +#include <com/sun/star/io/XOutputStream.hpp> +#include <com/sun/star/io/XInputStream.hpp> +#include <com/sun/star/task/InteractionClassification.hpp> +#include <com/sun/star/task/XInteractionApprove.hpp> +#include <com/sun/star/ucb/CommandAbortedException.hpp> +#include <com/sun/star/ucb/CommandFailedException.hpp> +#include <com/sun/star/ucb/ContentCreationException.hpp> +#include <com/sun/star/ucb/XInteractionReplaceExistingData.hpp> +#include <com/sun/star/ucb/NameClashResolveRequest.hpp> +#include <com/sun/star/ucb/XContentAccess.hpp> +#include <com/sun/star/ucb/NameClash.hpp> +#include <com/sun/star/ucb/UnsupportedCommandException.hpp> +#include <com/sun/star/sdbc/XResultSet.hpp> +#include <com/sun/star/sdbc/XRow.hpp> +#include <com/sun/star/packages/manifest/ManifestReader.hpp> +#include <com/sun/star/packages/manifest/ManifestWriter.hpp> +#include <com/sun/star/deployment/DependencyException.hpp> +#include <com/sun/star/deployment/DeploymentException.hpp> +#include <com/sun/star/deployment/ExtensionRemovedException.hpp> +#include <com/sun/star/deployment/LicenseException.hpp> +#include <com/sun/star/deployment/PlatformException.hpp> +#include <com/sun/star/deployment/Prerequisites.hpp> +#include <optional> +#include <comphelper/diagnose_ex.hxx> + +#include <algorithm> +#include <memory> +#include <string_view> +#include <utility> +#include <vector> + +#include "dp_extbackenddb.hxx" +using namespace ::dp_misc; +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; + + +namespace dp_registry::backend::bundle { +namespace { + +typedef cppu::ImplInheritanceHelper<PackageRegistryBackend> ImplBaseT; + + +class BackendImpl : public ImplBaseT +{ + class PackageImpl : public ::dp_registry::backend::Package + { + BackendImpl * getMyBackend() const; + /** contains the old tooltip description for the Extension Manager GUI in OOo v.2.x + We keep it for backward compatibility. + */ + OUString m_oldDescription; + OUString m_url_expanded; + const bool m_legacyBundle; + Sequence< Reference<deployment::XPackage> > m_bundle; + Sequence< Reference<deployment::XPackage> > * m_pBundle; + + ExtensionBackendDb::Data m_dbData; + + Reference<deployment::XPackage> bindBundleItem( + OUString const & url, OUString const & mediaType, + bool bRemoved, //that is, using data base information + OUString const & identifier, + Reference<ucb::XCommandEnvironment> const & xCmdEnv, + bool notifyDetectionError = true ); + + typedef std::vector< Reference<deployment::XPackage> > t_packagevec; + void scanBundle( + t_packagevec & bundle, + ::rtl::Reference<AbortChannel> const & abortChannel, + Reference<ucb::XCommandEnvironment> const & xCmdEnv ); + void scanLegacyBundle( + t_packagevec & bundle, + OUString const & url, + ::rtl::Reference<AbortChannel> const & abortChannel, + Reference<ucb::XCommandEnvironment> const & xCmdEnv, + bool skip_registration = false ); + std::vector<Reference<deployment::XPackage> > getPackagesFromDb( + Reference<ucb::XCommandEnvironment> const & xCmdEnv); + bool checkPlatform( + Reference<ucb::XCommandEnvironment > const & environment); + + bool checkDependencies( + Reference<ucb::XCommandEnvironment > const & + environment, + DescriptionInfoset const & description); + // throws css::uno::RuntimeException, + // css::deployment::DeploymentException + + /// @throws deployment::DeploymentException + /// @throws ucb::CommandFailedException + /// @throws ucb::CommandAbortedException + /// @throws RuntimeException + bool checkLicense( + Reference< ucb::XCommandEnvironment > const & xCmdEnv, + DescriptionInfoset const & description, bool bNoLicenseChecking); + // @throws DeploymentException + OUString getTextFromURL( + const Reference< ucb::XCommandEnvironment >& xCmdEnv, + const OUString& licenseUrl); + + DescriptionInfoset getDescriptionInfoset() const; + + // Package + virtual beans::Optional< beans::Ambiguous<sal_Bool> > isRegistered_( + ::osl::ResettableMutexGuard & guard, + ::rtl::Reference<AbortChannel> const & abortChannel, + Reference<ucb::XCommandEnvironment> const & xCmdEnv ) override; + virtual void processPackage_( + ::osl::ResettableMutexGuard & guard, + bool registerPackage, + bool startup, + ::rtl::Reference<AbortChannel> const & abortChannel, + Reference<ucb::XCommandEnvironment> const & xCmdEnv ) override; + + virtual void SAL_CALL disposing() override; + + + public: + PackageImpl( + ::rtl::Reference<PackageRegistryBackend> const & myBackend, + OUString const & url, + OUString const & name, + Reference<deployment::XPackageTypeInfo> const & xPackageType, + bool legacyBundle, + bool bRemoved, + OUString const & identifier); + + // XPackage + virtual sal_Bool SAL_CALL isBundle() override; + + virtual Sequence< Reference<deployment::XPackage> > SAL_CALL getBundle( + Reference<task::XAbortChannel> const & xAbortChannel, + Reference<ucb::XCommandEnvironment> const & xCmdEnv ) override; + virtual OUString SAL_CALL getDescription() override; + + virtual OUString SAL_CALL getLicenseText() override; + + virtual void SAL_CALL exportTo( + OUString const & destFolderURL, OUString const & newTitle, + sal_Int32 nameClashAction, + Reference<ucb::XCommandEnvironment> const & xCmdEnv ) override; + + virtual ::sal_Int32 SAL_CALL checkPrerequisites( + const Reference< task::XAbortChannel >& xAbortChannel, + const Reference< ucb::XCommandEnvironment >& xCmdEnv, + sal_Bool noLicenseChecking) override; + + virtual sal_Bool SAL_CALL checkDependencies( + const Reference< ucb::XCommandEnvironment >& xCmdEnv ) override; + + virtual beans::Optional<OUString> SAL_CALL getIdentifier() override; + + virtual OUString SAL_CALL getVersion() override; + + virtual Sequence<OUString> SAL_CALL getUpdateInformationURLs() override; + + virtual beans::StringPair SAL_CALL getPublisherInfo() override; + + virtual OUString SAL_CALL getDisplayName() override; + + virtual Reference< graphic::XGraphic > SAL_CALL + getIcon( sal_Bool bHighContrast ) override; + }; + friend class PackageImpl; + + Reference<deployment::XPackageRegistry> m_xRootRegistry; + const Reference<deployment::XPackageTypeInfo> m_xBundleTypeInfo; + const Reference<deployment::XPackageTypeInfo> m_xLegacyBundleTypeInfo; + Sequence< Reference<deployment::XPackageTypeInfo> > m_typeInfos; + + std::unique_ptr<ExtensionBackendDb> m_backendDb; + + void addDataToDb(OUString const & url, ExtensionBackendDb::Data const & data); + ExtensionBackendDb::Data readDataFromDb(std::u16string_view url); + void revokeEntryFromDb(std::u16string_view url); + + // PackageRegistryBackend + virtual Reference<deployment::XPackage> bindPackage_( + OUString const & url, OUString const & mediaType, + bool bRemoved, OUString const & identifier, + Reference<ucb::XCommandEnvironment> const & xCmdEnv ) override; + + virtual void SAL_CALL disposing() override; + +public: + BackendImpl( + Sequence<Any> const & args, + Reference<XComponentContext> const & xComponentContext, + Reference<deployment::XPackageRegistry> const & xRootRegistry ); + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName() override; + virtual sal_Bool SAL_CALL supportsService( OUString const& name ) override; + virtual Sequence<OUString> SAL_CALL getSupportedServiceNames() override; + + // XPackageRegistry + virtual Sequence< Reference<deployment::XPackageTypeInfo> > SAL_CALL + getSupportedPackageTypes() override; + virtual void SAL_CALL packageRemoved(OUString const & url, OUString const & mediaType) override; + + using ImplBaseT::disposing; +}; + +//Used to find a XPackage with a particular URL +class XPackage_eq +{ + OUString m_URL; +public: + explicit XPackage_eq(OUString s) : m_URL(std::move(s)) {} + bool operator() (const Reference<deployment::XPackage> & p) const + { + return m_URL == p->getURL(); + } +}; + + +BackendImpl::BackendImpl( + Sequence<Any> const & args, + Reference<XComponentContext> const & xComponentContext, + Reference<deployment::XPackageRegistry> const & xRootRegistry ) + : ImplBaseT( args, xComponentContext ), + m_xRootRegistry( xRootRegistry ), + m_xBundleTypeInfo( new Package::TypeInfo( + "application/vnd.sun.star.package-bundle", + "*.oxt;*.uno.pkg", + DpResId(RID_STR_PACKAGE_BUNDLE) + ) ), + m_xLegacyBundleTypeInfo( new Package::TypeInfo( + "application/vnd.sun.star.legacy-package-bundle", + "*.zip", + m_xBundleTypeInfo->getShortDescription() + ) ), + m_typeInfos{ m_xBundleTypeInfo, m_xLegacyBundleTypeInfo } +{ + if (!transientMode()) + { + OUString dbFile = makeURL(getCachePath(), getImplementationName()); + dbFile = makeURL(dbFile, "backenddb.xml"); + m_backendDb.reset( + new ExtensionBackendDb(getComponentContext(), dbFile)); + } +} + + +void BackendImpl::disposing() +{ + m_xRootRegistry.clear(); + PackageRegistryBackend::disposing(); +} + +// XServiceInfo +OUString BackendImpl::getImplementationName() +{ + return "com.sun.star.comp.deployment.bundle.PackageRegistryBackend"; +} + +sal_Bool BackendImpl::supportsService(OUString const & ServiceName) +{ + return cppu::supportsService(this, ServiceName); +} + +Sequence<OUString> BackendImpl::getSupportedServiceNames() +{ + return { BACKEND_SERVICE_NAME }; +} + +// XPackageRegistry + +Sequence< Reference<deployment::XPackageTypeInfo> > +BackendImpl::getSupportedPackageTypes() +{ + return m_typeInfos; +} + +void BackendImpl::packageRemoved(OUString const & url, OUString const & /*mediaType*/) +{ + //Notify the backend responsible for processing the different media + //types that this extension was removed. + ExtensionBackendDb::Data data = readDataFromDb(url); + for (auto const& item : data.items) + { + m_xRootRegistry->packageRemoved(item.first, item.second); + } + + if (m_backendDb) + m_backendDb->removeEntry(url); +} + + +// PackageRegistryBackend + +Reference<deployment::XPackage> BackendImpl::bindPackage_( + OUString const & url, OUString const & mediaType_, + bool bRemoved, OUString const & identifier, + Reference<ucb::XCommandEnvironment> const & xCmdEnv ) +{ + OUString mediaType( mediaType_ ); + if (mediaType.isEmpty()) + { + // detect media-type: + ::ucbhelper::Content ucbContent; + if (create_ucb_content( &ucbContent, url, xCmdEnv )) + { + if (ucbContent.isFolder()) + { + //Every .oxt, uno.pkg file must contain a META-INF folder + ::ucbhelper::Content metaInfContent; + if (create_ucb_content( + &metaInfContent, makeURL( url, "META-INF" ), + xCmdEnv, false /* no throw */ )) + { + mediaType = "application/vnd.sun.star.package-bundle"; + } + //No support of legacy bundles, because every folder could be one. + } + else + { + const OUString title( StrTitle::getTitle( ucbContent ) ); + if (title.endsWithIgnoreAsciiCase(".oxt") || + title.endsWithIgnoreAsciiCase(".uno.pkg")) + mediaType = "application/vnd.sun.star.package-bundle"; + else if (title.endsWithIgnoreAsciiCase(".zip")) + mediaType = "application/vnd.sun.star.legacy-package-bundle"; + } + } + if (mediaType.isEmpty()) + throw lang::IllegalArgumentException( + StrCannotDetectMediaType() + url, + static_cast<OWeakObject *>(this), static_cast<sal_Int16>(-1) ); + } + + OUString type, subType; + INetContentTypeParameterList params; + if (INetContentTypes::parse( mediaType, type, subType, ¶ms )) + { + if (type.equalsIgnoreAsciiCase("application")) + { + + //In case a XPackage is created for a removed extension, we cannot + //obtain the name + OUString name; + if (!bRemoved) + { + ::ucbhelper::Content ucbContent( + url, xCmdEnv, getComponentContext() ); + name = StrTitle::getTitle( ucbContent ); + } + if (subType.equalsIgnoreAsciiCase("vnd.sun.star.package-bundle")) + { + return new PackageImpl( + this, url, name, m_xBundleTypeInfo, false, bRemoved, + identifier); + } + else if (subType.equalsIgnoreAsciiCase( "vnd.sun.star.legacy-package-bundle")) + { + return new PackageImpl( + this, url, name, m_xLegacyBundleTypeInfo, true, bRemoved, + identifier); + } + } + } + throw lang::IllegalArgumentException( + StrUnsupportedMediaType() + mediaType, + static_cast<OWeakObject *>(this), + static_cast<sal_Int16>(-1) ); +} + +void BackendImpl::addDataToDb( + OUString const & url, ExtensionBackendDb::Data const & data) +{ + if (m_backendDb) + m_backendDb->addEntry(url, data); +} + +ExtensionBackendDb::Data BackendImpl::readDataFromDb( + std::u16string_view url) +{ + ExtensionBackendDb::Data data; + if (m_backendDb) + data = m_backendDb->getEntry(url); + return data; +} + +void BackendImpl::revokeEntryFromDb(std::u16string_view url) +{ + if (m_backendDb) + m_backendDb->revokeEntry(url); +} + + +BackendImpl::PackageImpl::PackageImpl( + ::rtl::Reference<PackageRegistryBackend> const & myBackend, + OUString const & url, + OUString const & name, + Reference<deployment::XPackageTypeInfo> const & xPackageType, + bool legacyBundle, bool bRemoved, OUString const & identifier) + : Package( myBackend, url, name, name /* display-name */, + xPackageType, bRemoved, identifier), + m_url_expanded( expandUnoRcUrl( url ) ), + m_legacyBundle( legacyBundle ), + m_pBundle( nullptr ) +{ + if (bRemoved) + m_dbData = getMyBackend()->readDataFromDb(url); +} + +BackendImpl * BackendImpl::PackageImpl::getMyBackend() const +{ + BackendImpl * pBackend = static_cast<BackendImpl *>(m_myBackend.get()); + if (nullptr == pBackend) + { + //May throw a DisposedException + check(); + //We should never get here... + throw RuntimeException("Failed to get the BackendImpl", + static_cast<OWeakObject*>(const_cast<PackageImpl *>(this))); + } + return pBackend; +} + +void BackendImpl::PackageImpl::disposing() +{ + sal_Int32 len = m_bundle.getLength(); + Reference<deployment::XPackage> const * p = m_bundle.getConstArray(); + for ( sal_Int32 pos = 0; pos < len; ++pos ) + try_dispose( p[ pos ] ); + m_bundle.realloc( 0 ); + + Package::disposing(); +} + +// Package + +beans::Optional< beans::Ambiguous<sal_Bool> > +BackendImpl::PackageImpl::isRegistered_( + ::osl::ResettableMutexGuard &, + ::rtl::Reference<AbortChannel> const & abortChannel, + Reference<ucb::XCommandEnvironment> const & xCmdEnv ) +{ + //In case the object was created for a removed extension (m_bRemoved = true) + //but the extension is not registered, then bundle will be empty. Then + //the return value will be Optional<...>.IsPresent= false. Although this is + //not true, this does not matter. Then registerPackage or revokePackage + //would never be called for the items. But since the extension is removed + //and not registered anyway, this does not matter. + const Sequence< Reference<deployment::XPackage> > bundle( + getBundle( abortChannel, xCmdEnv ) ); + + bool reg = false; + bool present = false; + bool ambig = false; + for ( sal_Int32 pos = bundle.getLength(); pos--; ) + { + Reference<deployment::XPackage> const & xPackage = bundle[ pos ]; + Reference<task::XAbortChannel> xSubAbortChannel( + xPackage->createAbortChannel() ); + AbortChannel::Chain chain( abortChannel, xSubAbortChannel ); + beans::Optional< beans::Ambiguous<sal_Bool> > option( + xPackage->isRegistered( xSubAbortChannel, xCmdEnv ) ); + + //present = true if at least one bundle item has this value. + //reg = true if all bundle items have an option value (option.IsPresent == 1) + //and all have value of true (option.Value.Value == true) + //If not, then the bundle has the status of not registered and ambiguous. + if (option.IsPresent) + { + beans::Ambiguous<sal_Bool> const & status = option.Value; + if (present) + { + //we never come here in the first iteration + if (reg != bool(status.Value)) { + + ambig = true; + reg = false; + break; + } + } + else + { + //we always come here in the first iteration + reg = status.Value; + present = true; + } + } + } + return beans::Optional< beans::Ambiguous<sal_Bool> >( + present, beans::Ambiguous<sal_Bool>(reg, ambig) ); +} + +OUString BackendImpl::PackageImpl::getTextFromURL( + const css::uno::Reference< css::ucb::XCommandEnvironment >& xCmdEnv, + const OUString& licenseUrl) +{ + try + { + ::ucbhelper::Content descContent( + licenseUrl, xCmdEnv, getMyBackend()->getComponentContext()); + std::vector<sal_Int8> seq = dp_misc::readFile(descContent); + return OUString( reinterpret_cast<char const *>( + seq.data()), seq.size(), RTL_TEXTENCODING_UTF8); + } + catch (const css::uno::Exception&) + { + Any exc( ::cppu::getCaughtException() ); + throw css::deployment::DeploymentException( + "Could not read file " + licenseUrl, nullptr, exc); + } + +} + +DescriptionInfoset BackendImpl::PackageImpl::getDescriptionInfoset() const +{ + return dp_misc::getDescriptionInfoset(m_url_expanded); +} + +bool BackendImpl::PackageImpl::checkPlatform( + css::uno::Reference< css::ucb::XCommandEnvironment > const & environment) +{ + bool ret = false; + DescriptionInfoset info(getDescriptionInfoset()); + Sequence<OUString> platforms(info.getSupportedPlatforms()); + if (hasValidPlatform(platforms)) + { + ret = true; + } + else + { + ret = false; + OUString msg( + "unsupported platform"); + Any e( + css::deployment::PlatformException( + msg, static_cast<OWeakObject *>(this), this)); + if (!interactContinuation( + e, cppu::UnoType< css::task::XInteractionApprove >::get(), + environment, nullptr, nullptr)) + { + throw css::deployment::DeploymentException( + msg, static_cast<OWeakObject *>(this), e); + } + } + return ret; +} + + +bool BackendImpl::PackageImpl::checkDependencies( + css::uno::Reference< css::ucb::XCommandEnvironment > const & environment, + DescriptionInfoset const & description) +{ + css::uno::Sequence< css::uno::Reference< css::xml::dom::XElement > > + unsatisfied(dp_misc::Dependencies::check(description)); + + if (!unsatisfied.hasElements()) { + return true; + } else { + OUString msg( + "unsatisfied dependencies"); + Any e( + css::deployment::DependencyException( + msg, static_cast<OWeakObject *>(this), unsatisfied)); + if (!interactContinuation( + e, cppu::UnoType< css::task::XInteractionApprove >::get(), + environment, nullptr, nullptr)) + { + throw css::deployment::DeploymentException( + msg, static_cast<OWeakObject *>(this), e); + } + return false; + } +} + +bool BackendImpl::PackageImpl::checkLicense( + css::uno::Reference< css::ucb::XCommandEnvironment > const & xCmdEnv, + DescriptionInfoset const & info, bool alreadyInstalled) +{ + try + { + ::std::optional<SimpleLicenseAttributes> simplLicAttr + = info.getSimpleLicenseAttributes(); + if (! simplLicAttr) + return true; + OUString sLic = info.getLocalizedLicenseURL(); + //If we do not get a localized licence then there is an error in the description.xml + //This should be handled by using a validating parser. Therefore we assume that no + //license is available. + if (sLic.isEmpty()) + throw css::deployment::DeploymentException( + "Could not obtain path to license. Possible error in description.xml", nullptr, Any()); + OUString sHref = m_url_expanded + "/" + sLic; + OUString sLicense = getTextFromURL(xCmdEnv, sHref); + ////determine who has to agree to the license + //check correct value for attribute + if ( simplLicAttr->acceptBy != "user" && simplLicAttr->acceptBy != "admin") + throw css::deployment::DeploymentException( + "Could not obtain attribute simple-license@accept-by or it has no valid value", nullptr, Any()); + + + //Only use interaction if there is no version of this extension already installed + //and the suppress-on-update flag is not set for the new extension + // alreadyInstalled | bSuppressOnUpdate | show license + + // 0 | 0 | 1 + // 0 | 1 | 1 + // 1 | 0 | 1 + // 1 | 1 | 0 + + if ( !(alreadyInstalled && simplLicAttr->suppressOnUpdate)) + { + css::deployment::LicenseException licExc( + OUString(), nullptr, getDisplayName(), sLicense, + simplLicAttr->acceptBy); + bool approve = false; + bool abort = false; + if (! interactContinuation( + Any(licExc), cppu::UnoType<task::XInteractionApprove>::get(), xCmdEnv, &approve, &abort )) + throw css::deployment::DeploymentException( + "Could not interact with user.", nullptr, Any()); + + return approve; + } + return true; + } catch (const css::ucb::CommandFailedException&) { + throw; + } catch (const css::ucb::CommandAbortedException&) { + throw; + } catch (const css::deployment::DeploymentException&) { + throw; + } catch (const css::uno::RuntimeException&) { + throw; + } catch (const css::uno::Exception&) { + Any anyExc = cppu::getCaughtException(); + throw css::deployment::DeploymentException("Unexpected exception", nullptr, anyExc); + } +} + +::sal_Int32 BackendImpl::PackageImpl::checkPrerequisites( + const css::uno::Reference< css::task::XAbortChannel >&, + const css::uno::Reference< css::ucb::XCommandEnvironment >& xCmdEnv, + sal_Bool alreadyInstalled) +{ + if (m_bRemoved) + throw deployment::ExtensionRemovedException(); + DescriptionInfoset info = getDescriptionInfoset(); + if (!info.hasDescription()) + return 0; + + //always return LICENSE as long as the user did not accept the license + //so that XExtensionManager::checkPrerequisitesAndEnable will again + //check the license + if (!checkPlatform(xCmdEnv)) + return deployment::Prerequisites::PLATFORM | + deployment::Prerequisites::LICENSE; + else if(!checkDependencies(xCmdEnv, info)) + return deployment::Prerequisites::DEPENDENCIES | + deployment::Prerequisites::LICENSE; + else if(!checkLicense(xCmdEnv, info, alreadyInstalled)) + return deployment::Prerequisites::LICENSE; + else + return 0; +} + +sal_Bool BackendImpl::PackageImpl::checkDependencies( + const css::uno::Reference< css::ucb::XCommandEnvironment >& xCmdEnv ) +{ + if (m_bRemoved) + throw deployment::ExtensionRemovedException(); + DescriptionInfoset info = getDescriptionInfoset(); + if (!info.hasDescription()) + return true; + + return checkDependencies(xCmdEnv, info); +} + +beans::Optional<OUString> BackendImpl::PackageImpl::getIdentifier() +{ + OUString identifier; + if (m_bRemoved) + identifier = m_identifier; + else + identifier = dp_misc::generateIdentifier( + getDescriptionInfoset().getIdentifier(), m_name); + + return beans::Optional<OUString>( + true, identifier); +} + +OUString BackendImpl::PackageImpl::getVersion() +{ + if (m_bRemoved) + throw deployment::ExtensionRemovedException(); + return getDescriptionInfoset().getVersion(); +} + +Sequence<OUString> BackendImpl::PackageImpl::getUpdateInformationURLs() +{ + if (m_bRemoved) + throw deployment::ExtensionRemovedException(); + return getDescriptionInfoset().getUpdateInformationUrls(); +} + +beans::StringPair BackendImpl::PackageImpl::getPublisherInfo() +{ + if (m_bRemoved) + throw deployment::ExtensionRemovedException(); + std::pair< OUString, OUString > aInfo = getDescriptionInfoset().getLocalizedPublisherNameAndURL(); + beans::StringPair aStrPair( aInfo.first, aInfo.second ); + return aStrPair; +} + + +uno::Reference< graphic::XGraphic > BackendImpl::PackageImpl::getIcon( sal_Bool bHighContrast ) +{ + if (m_bRemoved) + throw deployment::ExtensionRemovedException(); + + uno::Reference< graphic::XGraphic > xGraphic; + + OUString aIconURL = getDescriptionInfoset().getIconURL( bHighContrast ); + if ( !aIconURL.isEmpty() ) + { + OUString aFullIconURL = m_url_expanded + "/" + aIconURL; + + uno::Reference< XComponentContext > xContext( getMyBackend()->getComponentContext() ); + uno::Reference< graphic::XGraphicProvider > xGraphProvider( graphic::GraphicProvider::create(xContext) ); + + uno::Sequence< beans::PropertyValue > aMediaProps{ comphelper::makePropertyValue( + "URL", aFullIconURL) }; + xGraphic = xGraphProvider->queryGraphic( aMediaProps ); + } + + return xGraphic; +} + + +void BackendImpl::PackageImpl::processPackage_( + ::osl::ResettableMutexGuard &, + bool doRegisterPackage, + bool startup, + ::rtl::Reference<AbortChannel> const & abortChannel, + Reference<ucb::XCommandEnvironment> const & xCmdEnv ) +{ + const Sequence< Reference<deployment::XPackage> > bundle( + getBundle( abortChannel, xCmdEnv ) ); + + if (doRegisterPackage) + { + ExtensionBackendDb::Data data; + const sal_Int32 len = bundle.getLength(); + for ( sal_Int32 pos = 0; pos < len; ++pos ) + { + checkAborted(abortChannel); + Reference<deployment::XPackage> const & xPackage = bundle[ pos ]; + Reference<task::XAbortChannel> xSubAbortChannel( + xPackage->createAbortChannel() ); + AbortChannel::Chain chain( abortChannel, xSubAbortChannel ); + try { + xPackage->registerPackage( startup, xSubAbortChannel, xCmdEnv ); + } + catch (const Exception &) + { + //We even try a rollback if the user cancelled the action (CommandAbortedException) + //in order to prevent invalid database entries. + Any exc( ::cppu::getCaughtException() ); + // try to handle exception, notify: + bool approve = false, abort = false; + if (! interactContinuation( + Any( lang::WrappedTargetException( + "bundle item registration error!", + static_cast<OWeakObject *>(this), exc ) ), + cppu::UnoType<task::XInteractionApprove>::get(), xCmdEnv, + &approve, &abort )) { + OSL_ASSERT( !approve && !abort ); + if (m_legacyBundle) // default for legacy packages: ignore + continue; + // no selection at all, so rethrow; + // no C++ rethrow after getCaughtException(), + // see cppuhelper/exc_hlp.hxx: + ::cppu::throwException(exc); + } + if (approve && !abort) // ignore error, just continue + continue; + + { + ProgressLevel progress( xCmdEnv, "rollback..." ); + // try rollback + for ( ; pos--; ) + { + try { + bundle[ pos ]->revokePackage( + startup, xSubAbortChannel, xCmdEnv ); + } + catch (const Exception &) + { + TOOLS_WARN_EXCEPTION( "desktop", "" ); + // ignore any errors of rollback + } + } + progress.update( "rollback finished." ); + } + + deployment::DeploymentException dpExc; + if (exc >>= dpExc) { + throw ucb::CommandFailedException( + dpExc.Message, dpExc.Context, dpExc.Cause ); + } + else { + // rethrow CommandFailedException + ::cppu::throwException(exc); + } + } + data.items.emplace_back(xPackage->getURL(), + xPackage->getPackageType()->getMediaType()); + } + getMyBackend()->addDataToDb(getURL(), data); + } + else + { + // revoke in reverse order: + for ( sal_Int32 pos = bundle.getLength(); pos--; ) + { + checkAborted(abortChannel); + Reference<deployment::XPackage> const & xPackage = bundle[ pos ]; + Reference<task::XAbortChannel> xSubAbortChannel( + xPackage->createAbortChannel() ); + AbortChannel::Chain chain( abortChannel, xSubAbortChannel ); + try { + bundle[ pos ]->revokePackage( + startup, xSubAbortChannel, xCmdEnv ); + } + catch (const RuntimeException &) { + throw; + } + catch (const ucb::CommandAbortedException &) { + throw; + } + catch (const Exception &) { + // CommandFailedException, DeploymentException: + Any exc( ::cppu::getCaughtException() ); + // try to handle exception, notify: + bool approve = false, abort = false; + if (! interactContinuation( + Any( lang::WrappedTargetException( + "bundle item revocation error!", + static_cast<OWeakObject *>(this), exc ) ), + cppu::UnoType<task::XInteractionApprove>::get(), xCmdEnv, + &approve, &abort )) { + OSL_ASSERT( !approve && !abort ); + if (m_legacyBundle) // default for legacy packages: ignore + continue; + // no selection at all, so rethrow + // no C++ rethrow after getCaughtException(), + // see cppuhelper/exc_hlp.hxx: + ::cppu::throwException(exc); + } + // ignore errors when revoking, although abort may have been + // selected + } + } + getMyBackend()->revokeEntryFromDb(getURL()); + } +} + + +OUString BackendImpl::PackageImpl::getDescription() +{ + if (m_bRemoved) + throw deployment::ExtensionRemovedException(); + + const OUString sRelativeURL(getDescriptionInfoset().getLocalizedDescriptionURL()); + OUString sDescription; + if (!sRelativeURL.isEmpty()) + { + OUString sURL = m_url_expanded + "/" + sRelativeURL; + + try + { + sDescription = getTextFromURL( css::uno::Reference< css::ucb::XCommandEnvironment >(), sURL ); + } + catch ( const css::deployment::DeploymentException& ) + { + TOOLS_WARN_EXCEPTION( "desktop", "" ); + } + } + + if (!sDescription.isEmpty()) + return sDescription; + return m_oldDescription; +} + + +OUString BackendImpl::PackageImpl::getLicenseText() +{ + if (m_bRemoved) + throw deployment::ExtensionRemovedException(); + + OUString sLicense; + DescriptionInfoset aInfo = getDescriptionInfoset(); + + ::std::optional< SimpleLicenseAttributes > aSimplLicAttr = aInfo.getSimpleLicenseAttributes(); + if ( aSimplLicAttr ) + { + OUString aLicenseURL = aInfo.getLocalizedLicenseURL(); + + if ( !aLicenseURL.isEmpty() ) + { + OUString aFullURL = m_url_expanded + "/" + aLicenseURL; + sLicense = getTextFromURL( Reference< ucb::XCommandEnvironment >(), aFullURL); + } + } + + return sLicense; +} + + +void BackendImpl::PackageImpl::exportTo( + OUString const & destFolderURL, OUString const & newTitle, + sal_Int32 nameClashAction, Reference<ucb::XCommandEnvironment> const & xCmdEnv ) +{ + if (m_bRemoved) + throw deployment::ExtensionRemovedException(); + + ::ucbhelper::Content sourceContent( + m_url_expanded, xCmdEnv, getMyBackend()->getComponentContext() ); + OUString title(newTitle); + if (title.isEmpty()) + sourceContent.getPropertyValue( "Title" ) >>= title; + OUString destURL( makeURL( destFolderURL, ::rtl::Uri::encode( + title, rtl_UriCharClassPchar, + rtl_UriEncodeIgnoreEscapes, + RTL_TEXTENCODING_UTF8 ) ) ); + + if (nameClashAction == ucb::NameClash::ASK) + { + if (create_ucb_content( + nullptr, destURL, xCmdEnv, false /* no throw */ )) { + bool replace = false, abort = false; + if (! interactContinuation( + Any( ucb::NameClashResolveRequest( + "file already exists: " + title, + static_cast<OWeakObject *>(this), + task::InteractionClassification_QUERY, + destFolderURL, title, OUString() ) ), + cppu::UnoType<ucb::XInteractionReplaceExistingData>::get(), xCmdEnv, + &replace, &abort ) || !replace) { + return; + } + } + } + else if (nameClashAction != ucb::NameClash::OVERWRITE) { + throw ucb::CommandFailedException("unsupported nameClashAction!", + static_cast<OWeakObject *>(this), Any() ); + } + erase_path( destURL, xCmdEnv ); + + OUString destFolder = + "vnd.sun.star.zip://" + + ::rtl::Uri::encode( destURL, + rtl_UriCharClassRegName, + rtl_UriEncodeIgnoreEscapes, + RTL_TEXTENCODING_UTF8 ) + + "/"; + + ::ucbhelper::Content destFolderContent( + destFolder, xCmdEnv, getMyBackend()->getComponentContext() ); + { + // transfer every item of folder into zip: + Reference<sdbc::XResultSet> xResultSet( + sourceContent.createCursor( Sequence<OUString>() ) ); + ProgressLevel progress( xCmdEnv, OUString() ); + while (xResultSet->next()) + { + ::ucbhelper::Content subContent( + Reference<ucb::XContentAccess>( + xResultSet, UNO_QUERY_THROW )->queryContent(), + xCmdEnv, getMyBackend()->getComponentContext() ); + destFolderContent.transferContent( + subContent, ::ucbhelper::InsertOperation::Copy, + OUString(), ucb::NameClash::OVERWRITE ); + progress.update( Any() ); // animating progress bar + } + } + + // assure META-INF folder: + ::ucbhelper::Content metainfFolderContent; + create_folder( &metainfFolderContent, + makeURL( destFolderContent.getURL(), "META-INF" ), + xCmdEnv ); + + if (m_legacyBundle) + { + // easy to migrate legacy bundles to new format: + // just export them once using a .oxt name! + // set detected media-types of any bundle item: + + // collect all manifest entries: + Sequence< Reference<deployment::XPackage> > bundle; + try { + bundle = getBundle( Reference<task::XAbortChannel>(), xCmdEnv ); + } + // xxx todo: think about exception specs: + catch (const deployment::DeploymentException &) { + TOOLS_WARN_EXCEPTION( "desktop", "" ); + } + catch (const lang::IllegalArgumentException &) { + TOOLS_WARN_EXCEPTION( "desktop", "" ); + } + + std::vector< Sequence<beans::PropertyValue> > manifest; + manifest.reserve( bundle.getLength() ); + sal_Int32 baseURLlen = m_url_expanded.getLength(); + Reference<deployment::XPackage> const *pbundle = bundle.getConstArray(); + static constexpr OUStringLiteral strMediaType( u"MediaType" ); + static constexpr OUStringLiteral strFullPath( u"FullPath" ); + static constexpr OUStringLiteral strIsFolder( u"IsFolder" ); + for ( sal_Int32 pos = bundle.getLength(); pos--; ) + { + Reference<deployment::XPackage> const & xPackage = pbundle[ pos ]; + OUString url_( expandUnoRcUrl( xPackage->getURL() ) ); + OSL_ASSERT( url_.getLength() >= baseURLlen ); + OUString fullPath; + if (url_.getLength() > baseURLlen) + fullPath = url_.copy( baseURLlen + 1 ); + ::ucbhelper::Content ucbContent( + url_, xCmdEnv, getMyBackend()->getComponentContext() ); + if (ucbContent.getPropertyValue(strIsFolder).get<bool>()) + fullPath += "/"; + Sequence<beans::PropertyValue> attribs( 2 ); + beans::PropertyValue * pattribs = attribs.getArray(); + pattribs[ 0 ].Name = strFullPath; + pattribs[ 0 ].Value <<= fullPath; + pattribs[ 1 ].Name = strMediaType; + const Reference<deployment::XPackageTypeInfo> xPackageType( + xPackage->getPackageType() ); + OUString mediaType; + OSL_ASSERT( xPackageType.is() ); + if (xPackageType.is()) + mediaType = xPackageType->getMediaType(); + else + mediaType = "unknown"; + pattribs[ 1 ].Value <<= mediaType; + manifest.push_back( attribs ); + } + + // write into pipe: + Reference<XComponentContext> xContext( + getMyBackend()->getComponentContext() ); + Reference<packages::manifest::XManifestWriter> xManifestWriter = + packages::manifest::ManifestWriter::create( xContext ); + Reference<io::XOutputStream> xPipe( io::Pipe::create(xContext), UNO_QUERY_THROW ); + xManifestWriter->writeManifestSequence( + xPipe, comphelper::containerToSequence(manifest) ); + + // write buffered pipe data to content: + ::ucbhelper::Content manifestContent( + makeURL( metainfFolderContent.getURL(), "manifest.xml" ), + xCmdEnv, getMyBackend()->getComponentContext() ); + manifestContent.writeStream( + Reference<io::XInputStream>( xPipe, UNO_QUERY_THROW ), + true /* replace existing */ ); + } + else + { + bool bSuccess = false; + try + { + // overwrite manifest.xml: + ::ucbhelper::Content manifestContent; + if ( ! create_ucb_content( + &manifestContent, + makeURL( m_url_expanded, "META-INF/manifest.xml" ), + xCmdEnv, false ) ) + { + OSL_FAIL( "### missing META-INF/manifest.xml file!" ); + return; + } + + metainfFolderContent.transferContent( + manifestContent, ::ucbhelper::InsertOperation::Copy, + OUString(), ucb::NameClash::OVERWRITE ); + bSuccess = true; + } + catch (const css::ucb::ContentCreationException &) + { + TOOLS_WARN_EXCEPTION("desktop.deployment", "exception on overwriting manifest"); + } + + if (!bSuccess) + throw RuntimeException( "UCB transferContent() failed!", + static_cast<OWeakObject *>(this) ); + } + + // xxx todo: maybe obsolete in the future + try { + destFolderContent.executeCommand( "flush", Any() ); + } + catch (const ucb::UnsupportedCommandException &) { + } +} + + +sal_Bool BackendImpl::PackageImpl::isBundle() +{ + return true; +} + + +Sequence< Reference<deployment::XPackage> > BackendImpl::PackageImpl::getBundle( + Reference<task::XAbortChannel> const & xAbortChannel, + Reference<ucb::XCommandEnvironment> const & xCmdEnv ) +{ + Sequence< Reference<deployment::XPackage> > * pBundle = m_pBundle; + if (pBundle == nullptr) + { + t_packagevec bundle; + if (m_bRemoved) + { + bundle = getPackagesFromDb(xCmdEnv); + } + else + { + try { + if (m_legacyBundle) + { + // .zip legacy packages allow script.xlb, dialog.xlb in bundle + // root folder: + OUString mediaType; + // probe for script.xlb: + if (create_ucb_content( + nullptr, makeURL( m_url_expanded, "script.xlb" ), + xCmdEnv, false /* no throw */ )) { + mediaType = "application/vnd.sun.star.basic-library"; + } + // probe for dialog.xlb: + else if (create_ucb_content( + nullptr, makeURL( m_url_expanded, "dialog.xlb" ), + xCmdEnv, false /* no throw */ )) + mediaType = "application/vnd.sun.star.dialog-library"; + + if (!mediaType.isEmpty()) { + const Reference<deployment::XPackage> xPackage( + bindBundleItem( getURL(), mediaType, false, OUString(), + xCmdEnv ) ); + if (xPackage.is()) + bundle.push_back( xPackage ); + // continue scanning: + } + scanLegacyBundle( bundle, getURL(), + AbortChannel::get(xAbortChannel), xCmdEnv ); + } + else + { + // .oxt: + scanBundle( bundle, AbortChannel::get(xAbortChannel), xCmdEnv ); + } + + } + catch (const RuntimeException &) { + throw; + } + catch (const ucb::CommandFailedException &) { + throw; + } + catch (const ucb::CommandAbortedException &) { + throw; + } + catch (const deployment::DeploymentException &) { + throw; + } + catch (const Exception &) { + Any exc( ::cppu::getCaughtException() ); + throw deployment::DeploymentException( + "error scanning bundle: " + getURL(), + static_cast<OWeakObject *>(this), exc ); + } + } + + // sort: schema before config data, typelibs before components: + Sequence< Reference<deployment::XPackage> > ret( bundle.size() ); + Reference<deployment::XPackage> * pret = ret.getArray(); + sal_Int32 lower_end = 0; + sal_Int32 upper_end = ret.getLength(); + for (auto const& elem : bundle) + { + const Reference<deployment::XPackageTypeInfo> xPackageType( + elem->getPackageType() ); + OSL_ASSERT( xPackageType.is() ); + if (xPackageType.is()) + { + const OUString mediaType( xPackageType->getMediaType() ); + OUString type, subType; + INetContentTypeParameterList params; + if (INetContentTypes::parse( mediaType, type, subType, ¶ms ) && + type.equalsIgnoreAsciiCase("application") && + (subType.equalsIgnoreAsciiCase( "vnd.sun.star.uno-component") || + subType.equalsIgnoreAsciiCase( "vnd.sun.star.configuration-data"))) + { + --upper_end; + pret[ upper_end ] = elem; + continue; + } + } + pret[ lower_end ] = elem; + ++lower_end; + } + OSL_ASSERT( lower_end == upper_end ); + + const ::osl::MutexGuard guard( m_aMutex ); + pBundle = m_pBundle; + if (pBundle == nullptr) { + m_bundle = ret; + pBundle = &m_bundle; + OSL_DOUBLE_CHECKED_LOCKING_MEMORY_BARRIER(); + m_pBundle = pBundle; + } + } + else { + OSL_DOUBLE_CHECKED_LOCKING_MEMORY_BARRIER(); + } + return *pBundle; +} + +bool isBundle_( std::u16string_view mediaType ) +{ + // xxx todo: additional parsing? + return !mediaType.empty() && + (o3tl::matchIgnoreAsciiCase( mediaType, u"application/vnd.sun.star.package-bundle") || + o3tl::matchIgnoreAsciiCase( mediaType, u"application/vnd.sun.star.legacy-package-bundle")); +} + + +Reference<deployment::XPackage> BackendImpl::PackageImpl::bindBundleItem( + OUString const & url, OUString const & mediaType, + bool bRemoved, OUString const & identifier, + Reference<ucb::XCommandEnvironment> const & xCmdEnv, + bool notifyDetectionError ) +{ + // ignore any nested bundles: + if (isBundle_(mediaType)) + return Reference<deployment::XPackage>(); + + Reference<deployment::XPackage>xPackage; + try { + try { + xPackage.set( getMyBackend()->m_xRootRegistry->bindPackage( + url, mediaType, bRemoved, identifier, xCmdEnv ) ); + OSL_ASSERT( xPackage.is() ); + } catch (css::lang::IllegalArgumentException & e) { + css::uno::Any exc(cppu::getCaughtException()); + throw css::lang::WrappedTargetException( + "wrapped: " + e.Message, e.Context, exc); + } + } + catch (const RuntimeException &) { + throw; + } + catch (const ucb::CommandFailedException &) { + // ignore already handled error + } + catch (const Exception &) { + const Any exc( ::cppu::getCaughtException() ); + if (notifyDetectionError || + !exc.isExtractableTo( cppu::UnoType<lang::IllegalArgumentException>::get()) ) + { + (void)interactContinuation( + Any( lang::WrappedTargetException("bundle item error!", + static_cast<OWeakObject *>(this), exc ) ), + cppu::UnoType<task::XInteractionApprove>::get(), xCmdEnv, nullptr, nullptr ); + } + } + + if (xPackage.is()) { + const Reference<deployment::XPackageTypeInfo> xPackageType( + xPackage->getPackageType() ); + OSL_ASSERT( xPackageType.is() ); + // ignore any nested bundles: + if (xPackageType.is() && isBundle_( xPackageType->getMediaType() )) + xPackage.clear(); + } + return xPackage; +} + + +void BackendImpl::PackageImpl::scanBundle( + t_packagevec & bundle, + ::rtl::Reference<AbortChannel> const & abortChannel, + Reference<ucb::XCommandEnvironment> const & xCmdEnv ) +{ + OSL_ASSERT( !m_legacyBundle ); + + OUString mfUrl( makeURL( m_url_expanded, "META-INF/manifest.xml" ) ); + ::ucbhelper::Content manifestContent; + if (! create_ucb_content( + &manifestContent, mfUrl, xCmdEnv, false /* no throw */ )) + { + SAL_WARN( + "desktop.deployment", + "cannot create UCB Content for <" << mfUrl << ">" ); + return; + } + + + const LanguageTag& officeLocale = getOfficeLanguageTag(); + const std::vector< OUString > officeFallbacks( officeLocale.getFallbackStrings( true)); + const size_t nPenaltyMax = std::numeric_limits<size_t>::max(); + size_t descrPenalty = nPenaltyMax; + OUString descrFile; + + const Reference<XComponentContext> xContext( + getMyBackend()->getComponentContext() ); + Reference<packages::manifest::XManifestReader> xManifestReader = + packages::manifest::ManifestReader::create( xContext ); + const Sequence< Sequence<beans::PropertyValue> > manifestSeq( + xManifestReader->readManifestSequence( manifestContent.openStream() ) ); + const OUString packageRootURL( getURL() ); + for ( sal_Int32 pos = manifestSeq.getLength(); pos--; ) + { + OUString fullPath, mediaType; + Sequence<beans::PropertyValue> const & attribs = manifestSeq[ pos ]; + for ( sal_Int32 i = attribs.getLength(); i--; ) + { + if (!(fullPath.isEmpty() || mediaType.isEmpty())) + break; + if ( attribs[i].Name == "FullPath" ) + attribs[i].Value >>= fullPath; + else if ( attribs[i].Name == "MediaType" ) + attribs[i].Value >>= mediaType; + } + + if ( fullPath.isEmpty() || mediaType.isEmpty() || mediaType == "text/xml" )// opt: exclude common text/xml + continue; + + OUString type, subType; + INetContentTypeParameterList params; + if (! INetContentTypes::parse( mediaType, type, subType, ¶ms )) + continue; + + { + auto const iter = params.find("platform"_ostr); + if (iter != params.end() && !platform_fits(iter->second.m_sValue)) + continue; + } + const OUString url( makeURL( packageRootURL, fullPath ) ); + + // check for bundle description: + if (type.equalsIgnoreAsciiCase("application") && + subType.equalsIgnoreAsciiCase( "vnd.sun.star.package-bundle-description")) + { + // check locale: + auto const iter = params.find("locale"_ostr); + if (iter == params.end()) + { + if (descrFile.isEmpty()) + descrFile = url; + } + else { + // match best locale: + LanguageTag descrTag(iter->second.m_sValue); + if (officeLocale.getLanguage() == descrTag.getLanguage()) + { + size_t nPenalty = nPenaltyMax; + const std::vector< OUString > descrFallbacks( descrTag.getFallbackStrings( true)); + for (size_t o=0; o < officeFallbacks.size() && nPenalty == nPenaltyMax; ++o) + { + for (size_t d=0; d < descrFallbacks.size() && nPenalty == nPenaltyMax; ++d) + { + if (officeFallbacks[o] == descrFallbacks[d]) + { + // The last fallbacks are always language-only + // fallbacks, so we _will_ have _some_ match if + // we ever entered the overall if() condition. + nPenalty = o * 1000 + d; + if (descrPenalty > nPenalty) + { + descrPenalty = nPenalty; + descrFile = url; + } + } + } + } + } + // TODO: we could break here if descrPenalty==0 for an exact + // match of officeLocale, but the previous code didn't; are + // there side effects? + } + continue; + } + + checkAborted( abortChannel ); + + //We make sure that we only create one XPackage for a particular URL. + //Sometime programmers insert the same URL several times in the manifest + //which may lead to DisposedExceptions. + if (std::none_of(bundle.begin(), bundle.end(), XPackage_eq(url))) + { + const Reference<deployment::XPackage> xPackage( + bindBundleItem( url, mediaType, false, OUString(), xCmdEnv ) ); + if (xPackage.is()) + bundle.push_back( xPackage ); + } + else + { + SAL_WARN("desktop.deployment", "manifest.xml contains a duplicate entry (from " << url << ")"); + } + } + + if (descrFile.isEmpty()) + return; + + ::ucbhelper::Content descrFileContent; + if (!create_ucb_content( &descrFileContent, descrFile, + xCmdEnv, false /* no throw */ )) + return; + + // patch description: + std::vector<sal_Int8> bytes( readFile( descrFileContent ) ); + OUStringBuffer buf; + if ( !bytes.empty() ) + { + buf.append( OUString( reinterpret_cast<char const *>( + bytes.data() ), + bytes.size(), RTL_TEXTENCODING_UTF8 ) ); + } + else + { + buf.append( Package::getDescription() ); + } + m_oldDescription = buf.makeStringAndClear(); +} + + +void BackendImpl::PackageImpl::scanLegacyBundle( + t_packagevec & bundle, + OUString const & url, + ::rtl::Reference<AbortChannel> const & abortChannel, + Reference<ucb::XCommandEnvironment> const & xCmdEnv, + bool skip_registration ) +{ + ::ucbhelper::Content ucbContent( + url, xCmdEnv, getMyBackend()->getComponentContext() ); + + // check for platform paths: + const OUString title( StrTitle::getTitle( ucbContent ) ); + if (title.endsWithIgnoreAsciiCase( ".plt" ) && + !platform_fits( title.subView( 0, title.getLength() - 4 ) )) { + return; + } + if (title.endsWithIgnoreAsciiCase("skip_registration") ) + skip_registration = true; + + Sequence<OUString> ar { OUString("Title"), OUString("IsFolder") }; + Reference<sdbc::XResultSet> xResultSet( ucbContent.createCursor( ar ) ); + while (xResultSet->next()) + { + checkAborted( abortChannel ); + + const Reference<sdbc::XRow> xRow( xResultSet, UNO_QUERY_THROW ); + const OUString title_enc( ::rtl::Uri::encode( + xRow->getString( 1 /* Title */ ), + rtl_UriCharClassPchar, + rtl_UriEncodeIgnoreEscapes, + RTL_TEXTENCODING_UTF8 ) ); + const OUString path( makeURL( url, title_enc ) ); + + OUString mediaType; + const Reference<deployment::XPackage> xPackage( + bindBundleItem( path, OUString() /* detect */, false, OUString(), + xCmdEnv, false /* ignore detection errors */ ) ); + if (xPackage.is()) { + const Reference<deployment::XPackageTypeInfo> xPackageType( + xPackage->getPackageType() ); + OSL_ASSERT( xPackageType.is() ); + if (xPackageType.is()) + mediaType = xPackageType->getMediaType(); + + if (skip_registration && + // xxx todo: additional parsing? + mediaType.matchIgnoreAsciiCase("application/vnd.sun.star.uno-component")) + continue; + + bundle.push_back( xPackage ); + } + + if (mediaType.isEmpty() || + // script.xlb, dialog.xlb can be met everywhere: + mediaType.matchIgnoreAsciiCase("application/vnd.sun.star.basic-library") || + mediaType.matchIgnoreAsciiCase("application/vnd.sun.star.dialog-library")) + { + if (xRow->getBoolean( 2 /* IsFolder */ )) { // recurse into folder: + scanLegacyBundle( + bundle, path, abortChannel, xCmdEnv, skip_registration ); + } + } + } +} + +OUString BackendImpl::PackageImpl::getDisplayName() +{ + if (m_bRemoved) + throw deployment::ExtensionRemovedException(); + + OUString sName = getDescriptionInfoset().getLocalizedDisplayName(); + if (sName.isEmpty()) + return m_displayName; + else + return sName; +} + +std::vector<Reference<deployment::XPackage> > +BackendImpl::PackageImpl::getPackagesFromDb( + Reference<ucb::XCommandEnvironment> const & xCmdEnv) +{ + std::vector<Reference<deployment::XPackage> > retVector; + + for (auto const& item : m_dbData.items) + { + Reference<deployment::XPackage> xExtension = + bindBundleItem(item.first, item.second, true, m_identifier, xCmdEnv); + OSL_ASSERT(xExtension.is()); + if (xExtension.is()) + retVector.push_back(xExtension); + } + + return retVector; +} + +} // anon namespace + + +Reference<deployment::XPackageRegistry> create( + Reference<deployment::XPackageRegistry> const & xRootRegistry, + OUString const & context, OUString const & cachePath, + Reference<XComponentContext> const & xComponentContext ) +{ + Sequence<Any> args(cachePath.isEmpty() ? 1 : 3 ); + auto pArgs = args.getArray(); + pArgs[ 0 ] <<= context; + if (!cachePath.isEmpty()) { + pArgs[ 1 ] <<= cachePath; + pArgs[ 2 ] <<= false; // readOnly + } + return new BackendImpl( args, xComponentContext, xRootRegistry ); +} + +} // namespace dp_registry + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/registry/script/dp_lib_container.cxx b/desktop/source/deployment/registry/script/dp_lib_container.cxx new file mode 100644 index 0000000000..3a6f30253f --- /dev/null +++ b/desktop/source/deployment/registry/script/dp_lib_container.cxx @@ -0,0 +1,64 @@ +/* -*- 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 <com/sun/star/uno/XComponentContext.hpp> +#include <com/sun/star/ucb/XCommandEnvironment.hpp> + +#include <strings.hrc> +#include <dp_resource.h> +#include <dp_shared.hxx> +#include <dp_xml.h> +#include "dp_lib_container.h" + +#include <rtl/ustring.hxx> +#include <ucbhelper/content.hxx> +#include <xmlscript/xmllib_imexp.hxx> + + +using namespace ::dp_misc; +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::ucb; + +namespace dp_registry::backend::script { + +namespace { + OUString StrCannotDetermineLibName() { return DpResId(RID_STR_CANNOT_DETERMINE_LIBNAME); } +} + +OUString LibraryContainer::get_libname( + OUString const & url, + Reference<XCommandEnvironment> const & xCmdEnv, + Reference<XComponentContext> const & xContext ) +{ + ::xmlscript::LibDescriptor import; + ::ucbhelper::Content ucb_content( url, xCmdEnv, xContext ); + xml_parse( ::xmlscript::importLibrary( import ), ucb_content, xContext ); + + if (import.aName.isEmpty()) { + throw Exception( StrCannotDetermineLibName(), + Reference<XInterface>() ); + } + return import.aName; +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/registry/script/dp_lib_container.h b/desktop/source/deployment/registry/script/dp_lib_container.h new file mode 100644 index 0000000000..fbbedf8667 --- /dev/null +++ b/desktop/source/deployment/registry/script/dp_lib_container.h @@ -0,0 +1,48 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#pragma once + +#include <com/sun/star/uno/Reference.hxx> + +namespace com::sun::star { + namespace uno { + class XComponentContext; + } + namespace ucb { + class XCommandEnvironment; + } +} + + +namespace dp_registry::backend::script { + + +class LibraryContainer +{ +public: + static OUString get_libname( + OUString const & url, + css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv, + css::uno::Reference<css::uno::XComponentContext> const & xContext ); +}; + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/registry/script/dp_script.cxx b/desktop/source/deployment/registry/script/dp_script.cxx new file mode 100644 index 0000000000..47e41a364e --- /dev/null +++ b/desktop/source/deployment/registry/script/dp_script.cxx @@ -0,0 +1,480 @@ +/* -*- 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 <strings.hrc> +#include "dp_lib_container.h" +#include <dp_backend.h> +#include <dp_misc.h> +#include <dp_ucb.h> +#include <ucbhelper/content.hxx> +#include <cppuhelper/implbase.hxx> +#include <svl/inettype.hxx> +#include <com/sun/star/util/XUpdatable.hpp> +#include <com/sun/star/script/XLibraryContainer3.hpp> +#include <memory> +#include <string_view> + +#include "dp_scriptbackenddb.hxx" +#include <cppuhelper/supportsservice.hxx> + +using namespace ::dp_misc; +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::ucb; + +namespace dp_registry::backend::script { +namespace { + +typedef ::cppu::ImplInheritanceHelper< + ::dp_registry::backend::PackageRegistryBackend, util::XUpdatable > t_helper; + +class BackendImpl : public t_helper +{ + class PackageImpl : public ::dp_registry::backend::Package + { + BackendImpl * getMyBackend() const; + + const OUString m_scriptURL; + const OUString m_dialogURL; + OUString m_dialogName; + + // Package + virtual beans::Optional< beans::Ambiguous<sal_Bool> > isRegistered_( + ::osl::ResettableMutexGuard & guard, + ::rtl::Reference<AbortChannel> const & abortChannel, + Reference<XCommandEnvironment> const & xCmdEnv ) override; + virtual void processPackage_( + ::osl::ResettableMutexGuard & guard, + bool registerPackage, + bool startup, + ::rtl::Reference<AbortChannel> const & abortChannel, + Reference<XCommandEnvironment> const & xCmdEnv ) override; + + public: + PackageImpl( + ::rtl::Reference<BackendImpl> const & myBackend, + OUString const & url, + Reference<XCommandEnvironment> const &xCmdEnv, + OUString const & scriptURL, OUString const & dialogURL, + bool bRemoved, OUString const & identifier); + }; + friend class PackageImpl; + + // PackageRegistryBackend + virtual Reference<deployment::XPackage> bindPackage_( + OUString const & url, OUString const & mediaType, + bool bRemoved, OUString const & identifier, + Reference<XCommandEnvironment> const & xCmdEnv ) override; + + void addDataToDb(OUString const & url); + bool hasActiveEntry(std::u16string_view url); + void revokeEntryFromDb(std::u16string_view url); + + const Reference<deployment::XPackageTypeInfo> m_xBasicLibTypeInfo; + const Reference<deployment::XPackageTypeInfo> m_xDialogLibTypeInfo; + Sequence< Reference<deployment::XPackageTypeInfo> > m_typeInfos; + std::unique_ptr<ScriptBackendDb> m_backendDb; +public: + BackendImpl( Sequence<Any> const & args, + Reference<XComponentContext> const & xComponentContext ); + + // 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; + + // XUpdatable + virtual void SAL_CALL update() override; + + // XPackageRegistry + virtual Sequence< Reference<deployment::XPackageTypeInfo> > SAL_CALL + getSupportedPackageTypes() override; + virtual void SAL_CALL packageRemoved(OUString const & url, OUString const & mediaType) override; + +}; + + +BackendImpl::PackageImpl::PackageImpl( + ::rtl::Reference<BackendImpl> const & myBackend, + OUString const & url, + Reference<XCommandEnvironment> const &xCmdEnv, + OUString const & scriptURL, OUString const & dialogURL, bool bRemoved, + OUString const & identifier) + : Package( myBackend, url, + OUString(), OUString(), // will be late-initialized + !scriptURL.isEmpty() ? myBackend->m_xBasicLibTypeInfo + : myBackend->m_xDialogLibTypeInfo, bRemoved, identifier), + m_scriptURL( scriptURL ), + m_dialogURL( dialogURL ) +{ + // name, displayName: + if (!dialogURL.isEmpty()) { + m_dialogName = LibraryContainer::get_libname( + dialogURL, xCmdEnv, myBackend->getComponentContext() ); + } + if (!scriptURL.isEmpty()) { + assert(m_name.pData); + m_name = LibraryContainer::get_libname( + scriptURL, xCmdEnv, myBackend->getComponentContext() ); + } + else + m_name = m_dialogName; + m_displayName = m_name; +} + + +BackendImpl::BackendImpl( + Sequence<Any> const & args, + Reference<XComponentContext> const & xComponentContext ) + : t_helper( args, xComponentContext ), + m_xBasicLibTypeInfo( new Package::TypeInfo( + "application/vnd.sun.star.basic-library", + OUString() /* no file filter */, + DpResId(RID_STR_BASIC_LIB) + ) ), + m_xDialogLibTypeInfo( new Package::TypeInfo( + "application/vnd.sun.star.dialog-library", + OUString() /* no file filter */, + DpResId(RID_STR_DIALOG_LIB) + ) ), + m_typeInfos{ m_xBasicLibTypeInfo, m_xDialogLibTypeInfo } +{ + OSL_ASSERT( ! transientMode() ); + + if (!transientMode()) + { + OUString dbFile = makeURL(getCachePath(), "backenddb.xml"); + m_backendDb.reset( + new ScriptBackendDb(getComponentContext(), dbFile)); + } + +} + +// XServiceInfo +OUString BackendImpl::getImplementationName() +{ + return "com.sun.star.comp.deployment.script.PackageRegistryBackend"; +} + +sal_Bool BackendImpl::supportsService( const OUString& ServiceName ) +{ + return cppu::supportsService(this, ServiceName); +} + +css::uno::Sequence< OUString > BackendImpl::getSupportedServiceNames() +{ + return { BACKEND_SERVICE_NAME }; +} + +void BackendImpl::addDataToDb(OUString const & url) +{ + if (m_backendDb) + m_backendDb->addEntry(url); +} + +bool BackendImpl::hasActiveEntry(std::u16string_view url) +{ + if (m_backendDb) + return m_backendDb->hasActiveEntry(url); + return false; +} + +// XUpdatable + +void BackendImpl::update() +{ + // Nothing to do here after fixing i70283!? +} + +// XPackageRegistry + +Sequence< Reference<deployment::XPackageTypeInfo> > +BackendImpl::getSupportedPackageTypes() +{ + return m_typeInfos; +} +void BackendImpl::revokeEntryFromDb(std::u16string_view url) +{ + if (m_backendDb) + m_backendDb->revokeEntry(url); +} + +void BackendImpl::packageRemoved(OUString const & url, OUString const & /*mediaType*/) +{ + if (m_backendDb) + m_backendDb->removeEntry(url); +} + +// PackageRegistryBackend + +Reference<deployment::XPackage> BackendImpl::bindPackage_( + OUString const & url, OUString const & mediaType_, + bool bRemoved, OUString const & identifier, + Reference<XCommandEnvironment> const & xCmdEnv ) +{ + OUString mediaType( mediaType_ ); + if (mediaType.isEmpty()) + { + // detect media-type: + ::ucbhelper::Content ucbContent; + if (create_ucb_content( &ucbContent, url, xCmdEnv ) && + ucbContent.isFolder()) + { + // probe for script.xlb: + if (create_ucb_content( + nullptr, makeURL( url, "script.xlb" ), + xCmdEnv, false /* no throw */ )) + mediaType = "application/vnd.sun.star.basic-library"; + // probe for dialog.xlb: + else if (create_ucb_content( + nullptr, makeURL( url, "dialog.xlb" ), + xCmdEnv, false /* no throw */ )) + mediaType = "application/vnd.sun.star.dialog-library"; + } + if (mediaType.isEmpty()) + throw lang::IllegalArgumentException( + StrCannotDetectMediaType() + url, + static_cast<OWeakObject *>(this), static_cast<sal_Int16>(-1) ); + } + + OUString type, subType; + INetContentTypeParameterList params; + if (INetContentTypes::parse( mediaType, type, subType, ¶ms )) + { + if (type.equalsIgnoreAsciiCase("application")) + { + OUString dialogURL( makeURL( url, "dialog.xlb" ) ); + if (! create_ucb_content( + nullptr, dialogURL, xCmdEnv, false /* no throw */ )) { + dialogURL.clear(); + } + + if (subType.equalsIgnoreAsciiCase("vnd.sun.star.basic-library")) + { + OUString scriptURL( makeURL( url, "script.xlb")); + if (! create_ucb_content( + nullptr, scriptURL, xCmdEnv, false /* no throw */ )) { + scriptURL.clear(); + } + + return new PackageImpl( + this, url, xCmdEnv, scriptURL, + dialogURL, bRemoved, identifier); + } + else if (subType.equalsIgnoreAsciiCase( + "vnd.sun.star.dialog-library")) { + return new PackageImpl( + this, url, xCmdEnv, + OUString() /* no script lib */, + dialogURL, + bRemoved, identifier); + } + } + } + throw lang::IllegalArgumentException( + StrUnsupportedMediaType() + mediaType, + static_cast<OWeakObject *>(this), + static_cast<sal_Int16>(-1) ); +} + + +// Package +BackendImpl * BackendImpl::PackageImpl::getMyBackend() const +{ + BackendImpl * pBackend = static_cast<BackendImpl *>(m_myBackend.get()); + if (nullptr == pBackend) + { + //May throw a DisposedException + check(); + //We should never get here... + throw RuntimeException( + "Failed to get the BackendImpl", + static_cast<OWeakObject*>(const_cast<PackageImpl *>(this))); + } + return pBackend; +} + +beans::Optional< beans::Ambiguous<sal_Bool> > +BackendImpl::PackageImpl::isRegistered_( + ::osl::ResettableMutexGuard & /* guard */, + ::rtl::Reference<AbortChannel> const & /* abortChannel */, + Reference<XCommandEnvironment> const & /* xCmdEnv */ ) +{ + BackendImpl * that = getMyBackend(); + Reference< deployment::XPackage > xThisPackage( this ); + + bool registered = that->hasActiveEntry(getURL()); + return beans::Optional< beans::Ambiguous<sal_Bool> >( + true /* IsPresent */, + beans::Ambiguous<sal_Bool>( registered, false /* IsAmbiguous */ ) ); +} + +void +lcl_maybeRemoveScript( + bool const bExists, + OUString const& rName, + std::u16string_view rScriptURL, + Reference<css::script::XLibraryContainer3> const& xScriptLibs) +{ + if (bExists && xScriptLibs.is() && xScriptLibs->hasByName(rName)) + { + const OUString sScriptUrl = xScriptLibs->getOriginalLibraryLinkURL(rName); + if (sScriptUrl == rScriptURL) + xScriptLibs->removeLibrary(rName); + } +} + +bool +lcl_maybeAddScript( + bool const bExists, + OUString const& rName, + OUString const& rScriptURL, + Reference<css::script::XLibraryContainer3> const& xScriptLibs) +{ + if (!bExists || !xScriptLibs) + return false; + + bool bCanAdd = true; + if (xScriptLibs->hasByName(rName)) + { + const OUString sOriginalUrl = xScriptLibs->getOriginalLibraryLinkURL(rName); + //We assume here that library names in extensions are unique, which may not be the case + //ToDo: If the script exist in another extension, then both extensions must have the + //same id + if (sOriginalUrl.match("vnd.sun.star.expand:$UNO_USER_PACKAGES_CACHE") + || sOriginalUrl.match("vnd.sun.star.expand:$UNO_SHARED_PACKAGES_CACHE") + || sOriginalUrl.match("vnd.sun.star.expand:$BUNDLED_EXTENSIONS") + || sOriginalUrl.match("$(INST)/share/basic/Access2Base/")) + { + xScriptLibs->removeLibrary(rName); + bCanAdd = true; + } + else + { + bCanAdd = false; + } + } + + if (bCanAdd) + { + xScriptLibs->createLibraryLink(rName, rScriptURL, false); + return xScriptLibs->hasByName(rName); + } + + return false; +} + +void BackendImpl::PackageImpl::processPackage_( + ::osl::ResettableMutexGuard & /* guard */, + bool doRegisterPackage, + bool startup, + ::rtl::Reference<AbortChannel> const & /* abortChannel */, + Reference<XCommandEnvironment> const & /* xCmdEnv */ ) +{ + BackendImpl * that = getMyBackend(); + + Reference< deployment::XPackage > xThisPackage( this ); + Reference<XComponentContext> const & xComponentContext = that->getComponentContext(); + + bool bScript = !m_scriptURL.isEmpty(); + Reference<css::script::XLibraryContainer3> xScriptLibs; + + bool bDialog = !m_dialogURL.isEmpty(); + Reference<css::script::XLibraryContainer3> xDialogLibs; + + bool bRunning = !startup && office_is_running(); + if( bRunning ) + { + if( bScript ) + { + xScriptLibs.set( + xComponentContext->getServiceManager()->createInstanceWithContext( + "com.sun.star.script.ApplicationScriptLibraryContainer", + xComponentContext ), UNO_QUERY_THROW ); + } + + if( bDialog ) + { + xDialogLibs.set( + xComponentContext->getServiceManager()->createInstanceWithContext( + "com.sun.star.script.ApplicationDialogLibraryContainer", + xComponentContext ), UNO_QUERY_THROW ); + } + } + bool bRegistered = getMyBackend()->hasActiveEntry(getURL()); + if( !doRegisterPackage ) + { + //We cannot just call removeLibrary(name) because this could remove a + //script which was added by an extension in a different repository. For + //example, extension foo is contained in the bundled repository and then + //the user adds it to the user repository. The extension manager will + //then register the new script and revoke the script from the bundled + //extension. removeLibrary(name) would now remove the script from the + //user repository. That is, the script of the newly added user extension does + //not work anymore. Therefore we must check if the currently active + //script comes in fact from the currently processed extension. + + if (bRegistered) + { + //we also prevent and live deployment at startup + if (!isRemoved() && !startup) + { + lcl_maybeRemoveScript(bScript, m_name, m_scriptURL, xScriptLibs); + lcl_maybeRemoveScript(bDialog, m_dialogName, m_dialogURL, xDialogLibs); + } + getMyBackend()->revokeEntryFromDb(getURL()); + return; + } + } + if (bRegistered) + return; // Already registered + + // Update LibraryContainer + bool bScriptSuccess = false; + bool bDialogSuccess = false; + if (!startup) + { + //If there is a bundled extension, and the user installs the same extension + //then the script from the bundled extension must be removed. If this does not work + //then live deployment does not work for scripts. + bScriptSuccess = lcl_maybeAddScript(bScript, m_name, m_scriptURL, xScriptLibs); + bDialogSuccess = lcl_maybeAddScript(bDialog, m_dialogName, m_dialogURL, xDialogLibs); + } + bool bSuccess = bScript || bDialog; // Something must have happened + if( bRunning ) + if( (bScript && !bScriptSuccess) || (bDialog && !bDialogSuccess) ) + bSuccess = false; + + if (bSuccess) + getMyBackend()->addDataToDb(getURL()); +} + +} // anon namespace + +} // namespace dp_registry::backend::script + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +com_sun_star_comp_deployment_script_PackageRegistryBackend_get_implementation( + css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const& args) +{ + return cppu::acquire(new dp_registry::backend::script::BackendImpl(args, context)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/registry/script/dp_scriptbackenddb.cxx b/desktop/source/deployment/registry/script/dp_scriptbackenddb.cxx new file mode 100644 index 0000000000..476f43953a --- /dev/null +++ b/desktop/source/deployment/registry/script/dp_scriptbackenddb.cxx @@ -0,0 +1,64 @@ +/* -*- 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 <com/sun/star/uno/XComponentContext.hpp> +#include "dp_scriptbackenddb.hxx" + + +using namespace ::com::sun::star::uno; + +constexpr OUStringLiteral EXTENSION_REG_NS = u"http://openoffice.org/extensionmanager/script-registry/2010"; +constexpr OUStringLiteral NS_PREFIX = u"script"; +constexpr OUStringLiteral ROOT_ELEMENT_NAME = u"script-backend-db"; +constexpr OUStringLiteral KEY_ELEMENT_NAME = u"script"; + +namespace dp_registry::backend::script { + +ScriptBackendDb::ScriptBackendDb( + Reference<XComponentContext> const & xContext, + OUString const & url):RegisteredDb(xContext, url) +{ + +} + +OUString ScriptBackendDb::getDbNSName() +{ + return EXTENSION_REG_NS; +} + +OUString ScriptBackendDb::getNSPrefix() +{ + return NS_PREFIX; +} + +OUString ScriptBackendDb::getRootElementName() +{ + return ROOT_ELEMENT_NAME; +} + +OUString ScriptBackendDb::getKeyElementName() +{ + return KEY_ELEMENT_NAME; +} + + +} // namespace dp_registry::backend::script + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/registry/script/dp_scriptbackenddb.hxx b/desktop/source/deployment/registry/script/dp_scriptbackenddb.hxx new file mode 100644 index 0000000000..21d4b1f6b3 --- /dev/null +++ b/desktop/source/deployment/registry/script/dp_scriptbackenddb.hxx @@ -0,0 +1,55 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#pragma once + +#include <rtl/ustring.hxx> +#include <dp_backenddb.hxx> +#include <optional> + +namespace com::sun::star::uno { class XComponentContext; } + +namespace dp_registry::backend::script { + +/* The XML file stores the extensions which are currently registered. + They will be removed when they are revoked. + */ +class ScriptBackendDb: public dp_registry::backend::RegisteredDb +{ +protected: + virtual OUString getDbNSName() override; + + virtual OUString getNSPrefix() override; + + virtual OUString getRootElementName() override; + + virtual OUString getKeyElementName() override; + + +public: + + ScriptBackendDb( css::uno::Reference<css::uno::XComponentContext> const & xContext, + OUString const & url); +}; + + +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/registry/sfwk/dp_parceldesc.cxx b/desktop/source/deployment/registry/sfwk/dp_parceldesc.cxx new file mode 100644 index 0000000000..530924a078 --- /dev/null +++ b/desktop/source/deployment/registry/sfwk/dp_parceldesc.cxx @@ -0,0 +1,103 @@ +/* -*- 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 <dp_misc.h> +#include "dp_parceldesc.hxx" + + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; + + +namespace dp_registry::backend::sfwk +{ + + +// XDocumentHandler +void SAL_CALL +ParcelDescDocHandler::startDocument() +{ + m_bIsParsed = false; +} + +void SAL_CALL +ParcelDescDocHandler::endDocument() +{ + m_bIsParsed = true; +} + +void SAL_CALL +ParcelDescDocHandler::characters( const OUString & ) +{ +} + +void SAL_CALL +ParcelDescDocHandler::ignorableWhitespace( const OUString & ) +{ +} + +void SAL_CALL +ParcelDescDocHandler::processingInstruction( + const OUString &, const OUString & ) +{ +} + +void SAL_CALL +ParcelDescDocHandler::setDocumentLocator( + const Reference< xml::sax::XLocator >& ) +{ +} + +void SAL_CALL +ParcelDescDocHandler::startElement( const OUString& aName, + const Reference< xml::sax::XAttributeList > & xAttribs ) +{ + + dp_misc::TRACE("ParcelDescDocHandler::startElement() for " + + aName + "\n"); + if ( !skipIndex ) + { + if ( aName == "parcel" ) + { + m_sLang = xAttribs->getValueByName( "language" ); + } + ++skipIndex; + } + else + { + dp_misc::TRACE("ParcelDescDocHandler::startElement() skipping for " + + aName + "\n"); + } + +} + +void SAL_CALL ParcelDescDocHandler::endElement( const OUString & aName ) +{ + if ( skipIndex ) + { + --skipIndex; + dp_misc::TRACE("ParcelDescDocHandler::endElement() skipping for " + + aName + "\n"); + } +} + + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/registry/sfwk/dp_parceldesc.hxx b/desktop/source/deployment/registry/sfwk/dp_parceldesc.hxx new file mode 100644 index 0000000000..6b5bde8bdd --- /dev/null +++ b/desktop/source/deployment/registry/sfwk/dp_parceldesc.hxx @@ -0,0 +1,64 @@ +/* -*- 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/xml/sax/XAttributeList.hpp> +#include <com/sun/star/xml/sax/XDocumentHandler.hpp> + +namespace dp_registry::backend::sfwk +{ + +class ParcelDescDocHandler : public ::cppu::WeakImplHelper< css::xml::sax::XDocumentHandler > +{ +private: + bool m_bIsParsed; + OUString m_sLang; + sal_Int32 skipIndex; +public: + ParcelDescDocHandler():m_bIsParsed( false ), skipIndex( 0 ){} + const OUString& getParcelLanguage() const { return m_sLang; } + bool isParsed() const { return m_bIsParsed; } + // XDocumentHandler + virtual void SAL_CALL startDocument() override; + + virtual void SAL_CALL endDocument() override; + + virtual void SAL_CALL startElement( const OUString& aName, + const css::uno::Reference< css::xml::sax::XAttributeList > & xAttribs ) override; + + virtual void SAL_CALL endElement( const OUString & aName ) override; + + virtual void SAL_CALL characters( const OUString & aChars ) override; + + virtual void SAL_CALL ignorableWhitespace( const OUString & aWhitespaces ) override; + + virtual void SAL_CALL processingInstruction( + const OUString & aTarget, const OUString & aData ) override; + + virtual void SAL_CALL setDocumentLocator( + const css::uno::Reference< css::xml::sax::XLocator >& xLocator ) override; +}; +} + + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/registry/sfwk/dp_sfwk.cxx b/desktop/source/deployment/registry/sfwk/dp_sfwk.cxx new file mode 100644 index 0000000000..b617d7fa4e --- /dev/null +++ b/desktop/source/deployment/registry/sfwk/dp_sfwk.cxx @@ -0,0 +1,378 @@ +/* -*- 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 <strings.hrc> +#include <dp_backend.h> +#include <dp_misc.h> +#include <dp_ucb.h> +#include "dp_parceldesc.hxx" +#include <rtl/uri.hxx> +#include <ucbhelper/content.hxx> +#include <svl/inettype.hxx> +#include <com/sun/star/container/XNameContainer.hpp> +#include <com/sun/star/script/provider/theMasterScriptProviderFactory.hpp> +#include <com/sun/star/xml/sax/Parser.hpp> +#include <cppuhelper/supportsservice.hxx> +#include <utility> + + +using namespace ::dp_misc; +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::ucb; +using namespace ::com::sun::star::script; + + +namespace dp_registry::backend::sfwk +{ + +namespace { + +class BackendImpl : public ::dp_registry::backend::PackageRegistryBackend +{ + class PackageImpl : public ::dp_registry::backend::Package + { + BackendImpl * getMyBackend() const; + + Reference< container::XNameContainer > m_xNameCntrPkgHandler; + OUString m_descr; + + void initPackageHandler(); + + // Package + virtual beans::Optional< beans::Ambiguous<sal_Bool> > isRegistered_( + ::osl::ResettableMutexGuard & guard, + ::rtl::Reference<AbortChannel> const & abortChannel, + Reference<XCommandEnvironment> const & xCmdEnv ) override; + virtual void processPackage_( + ::osl::ResettableMutexGuard & guard, + bool registerPackage, + bool startup, + ::rtl::Reference<AbortChannel> const & abortChannel, + Reference<XCommandEnvironment> const & xCmdEnv ) override; + + public: + PackageImpl( + ::rtl::Reference<BackendImpl> const & myBackend, + OUString const & url, OUString libType, bool bRemoved, + OUString const & identifier); + // XPackage + virtual OUString SAL_CALL getDescription() override; + virtual OUString SAL_CALL getLicenseText() override; + }; + friend class PackageImpl; + + // PackageRegistryBackend + virtual Reference<deployment::XPackage> bindPackage_( + OUString const & url, OUString const & mediaType, + bool bRemoved, OUString const & identifier, + Reference<XCommandEnvironment> const & xCmdEnv ) override; + + const Reference<deployment::XPackageTypeInfo> m_xTypeInfo; + + +public: + BackendImpl( + Sequence<Any> const & args, + Reference<XComponentContext> const & xComponentContext ); + + // 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; + + // XPackageRegistry + virtual Sequence< Reference<deployment::XPackageTypeInfo> > SAL_CALL + getSupportedPackageTypes() override; + virtual void SAL_CALL packageRemoved(OUString const & url, OUString const & mediaType) override; +}; + +} + +BackendImpl * BackendImpl::PackageImpl::getMyBackend() const +{ + BackendImpl * pBackend = static_cast<BackendImpl *>(m_myBackend.get()); + if (nullptr == pBackend) + { + //May throw a DisposedException + check(); + //We should never get here... + throw RuntimeException("Failed to get the BackendImpl", + static_cast<OWeakObject*>(const_cast<PackageImpl *>(this))); + } + return pBackend; +} + +OUString BackendImpl::PackageImpl::getDescription() +{ + if (m_descr.isEmpty()) + return Package::getDescription(); + else + return m_descr; +} + +OUString BackendImpl::PackageImpl::getLicenseText() +{ + return Package::getDescription(); +} + +BackendImpl::PackageImpl::PackageImpl( + ::rtl::Reference<BackendImpl> const & myBackend, + OUString const & url, OUString libType, bool bRemoved, + OUString const & identifier) + : Package( myBackend, url, OUString(), OUString(), + myBackend->m_xTypeInfo, bRemoved, identifier), + m_descr(std::move(libType)) +{ + initPackageHandler(); + + sal_Int32 segmEnd = url.getLength(); + if ( url.endsWith("/") ) + --segmEnd; + sal_Int32 segmStart = url.lastIndexOf( '/', segmEnd ) + 1; + if (segmStart < 0) + segmStart = 0; + // name and display name default the same: + m_displayName = ::rtl::Uri::decode( + url.copy( segmStart, segmEnd - segmStart ), + rtl_UriDecodeWithCharset, RTL_TEXTENCODING_UTF8 ); + m_name = m_displayName; + + dp_misc::TRACE("PackageImpl displayName is " + m_displayName); +} + + +BackendImpl::BackendImpl( + Sequence<Any> const & args, + Reference<XComponentContext> const & xComponentContext ) + : PackageRegistryBackend( args, xComponentContext ), + m_xTypeInfo( new Package::TypeInfo( + "application/vnd.sun.star.framework-script", + OUString() /* no file filter */, + "Scripting Framework Script Library" + ) ) +{ +} + + +// XServiceInfo +OUString BackendImpl::getImplementationName() +{ + return "com.sun.star.comp.deployment.sfwk.PackageRegistryBackend"; +} + +sal_Bool BackendImpl::supportsService( const OUString& ServiceName ) +{ + return cppu::supportsService(this, ServiceName); +} + +css::uno::Sequence< OUString > BackendImpl::getSupportedServiceNames() +{ + return { BACKEND_SERVICE_NAME }; +} + +// XPackageRegistry + +Sequence< Reference<deployment::XPackageTypeInfo> > +BackendImpl::getSupportedPackageTypes() +{ + return Sequence< Reference<deployment::XPackageTypeInfo> >(&m_xTypeInfo, 1); +} + +void BackendImpl::packageRemoved(OUString const & /*url*/, OUString const & /*mediaType*/) +{ +} + +// PackageRegistryBackend + +Reference<deployment::XPackage> BackendImpl::bindPackage_( + OUString const & url, OUString const & mediaType_, bool bRemoved, + OUString const & identifier, Reference<XCommandEnvironment> const & xCmdEnv ) +{ + OUString mediaType( mediaType_ ); + if (mediaType.isEmpty()) + { + // detect media-type: + ::ucbhelper::Content ucbContent; + if (create_ucb_content( &ucbContent, url, xCmdEnv ) && + ucbContent.isFolder()) + { + // probe for parcel-descriptor.xml: + if (create_ucb_content( + nullptr, makeURL( url, "parcel-descriptor.xml" ), + xCmdEnv, false /* no throw */ )) + { + mediaType = "application/vnd.sun.star.framework-script"; + } + } + if (mediaType.isEmpty()) + throw lang::IllegalArgumentException( + StrCannotDetectMediaType() + url, + static_cast<OWeakObject *>(this), static_cast<sal_Int16>(-1) ); + } + + OUString type, subType; + INetContentTypeParameterList params; + if (INetContentTypes::parse( mediaType, type, subType, ¶ms )) + { + if (type.equalsIgnoreAsciiCase("application")) + { + if (subType.equalsIgnoreAsciiCase("vnd.sun.star.framework-script")) + { + OUString lang = "Script"; + OUString sParcelDescURL = makeURL( + url, "parcel-descriptor.xml" ); + + ::ucbhelper::Content ucb_content; + + if (create_ucb_content( &ucb_content, sParcelDescURL, + xCmdEnv, false /* no throw */ )) + { + rtl::Reference<ParcelDescDocHandler> pHandler = + new ParcelDescDocHandler(); + + Reference<XComponentContext> + xContext( getComponentContext() ); + + Reference< xml::sax::XParser > xParser = xml::sax::Parser::create(xContext); + + xParser->setDocumentHandler( pHandler ); + xml::sax::InputSource source; + source.aInputStream = ucb_content.openStream(); + source.sSystemId = ucb_content.getURL(); + xParser->parseStream( source ); + + if ( pHandler->isParsed() ) + { + lang = pHandler->getParcelLanguage(); + } + } + + OUString sfwkLibType = DpResId( RID_STR_SFWK_LIB ); + // replace %MACRONAME placeholder with language name + OUString MACRONAME( "%MACROLANG" ); + sal_Int32 startOfReplace = sfwkLibType.indexOf( MACRONAME ); + sal_Int32 charsToReplace = MACRONAME.getLength(); + sfwkLibType = sfwkLibType.replaceAt( startOfReplace, charsToReplace, lang ); + dp_misc::TRACE("******************************\n"); + dp_misc::TRACE(" BackEnd detected lang = " + lang + "\n"); + dp_misc::TRACE(" for url " + sParcelDescURL + "\n"); + dp_misc::TRACE("******************************\n"); + return new PackageImpl( this, url, sfwkLibType, bRemoved, identifier); + } + } + } + throw lang::IllegalArgumentException( + StrUnsupportedMediaType() + mediaType, + static_cast<OWeakObject *>(this), + static_cast<sal_Int16>(-1) ); +} + + +void BackendImpl::PackageImpl:: initPackageHandler() +{ + if (m_xNameCntrPkgHandler.is()) + return; + + BackendImpl * that = getMyBackend(); + Any aContext; + + if ( that->m_eContext == Context::User ) + { + aContext <<= OUString("user"); + } + else if ( that->m_eContext == Context::Shared ) + { + aContext <<= OUString("share"); + } + else if ( that->m_eContext == Context::Bundled ) + { + aContext <<= OUString("bundled"); + } + else + { + OSL_ASSERT( false ); + // NOT supported at the moment // TODO + } + + Reference< provider::XScriptProviderFactory > xFac = + provider::theMasterScriptProviderFactory::get( that->getComponentContext() ); + + Reference< container::XNameContainer > xName( xFac->createScriptProvider( aContext ), UNO_QUERY ); + if ( xName.is() ) + { + m_xNameCntrPkgHandler.set( xName ); + } + // TODO what happens if above fails?? +} + +// Package + +beans::Optional< beans::Ambiguous<sal_Bool> > +BackendImpl::PackageImpl::isRegistered_( + ::osl::ResettableMutexGuard &, + ::rtl::Reference<AbortChannel> const &, + Reference<XCommandEnvironment> const & ) +{ + return beans::Optional< beans::Ambiguous<sal_Bool> >( + true /* IsPresent */, + beans::Ambiguous<sal_Bool>( + m_xNameCntrPkgHandler.is() && m_xNameCntrPkgHandler->hasByName( + m_url ), + false /* IsAmbiguous */ ) ); +} + + +void BackendImpl::PackageImpl::processPackage_( + ::osl::ResettableMutexGuard &, + bool doRegisterPackage, + bool /* startup */, + ::rtl::Reference<AbortChannel> const &, + Reference<XCommandEnvironment> const & ) +{ + if ( !m_xNameCntrPkgHandler.is() ) + { + dp_misc::TRACE("no package handler!!!!\n"); + throw RuntimeException( "No package Handler " ); + } + + if (doRegisterPackage) + { + // will throw if it fails + m_xNameCntrPkgHandler->insertByName( m_url, Any( Reference< XPackage >(this) ) ); + + } + else // revokePackage() + { + m_xNameCntrPkgHandler->removeByName( m_url ); + } +} + +} // namespace dp_registry::backend::sfwk + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +com_sun_star_comp_deployment_sfwk_PackageRegistryBackend_get_implementation( + css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const& args) +{ + return cppu::acquire(new dp_registry::backend::sfwk::BackendImpl(args, context)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/inc/helpids.h b/desktop/source/inc/helpids.h new file mode 100644 index 0000000000..b7d7c43597 --- /dev/null +++ b/desktop/source/inc/helpids.h @@ -0,0 +1,27 @@ +/* -*- 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> + +inline constexpr OUString HID_EXTENSION_MANAGER_LISTBOX_ENABLE = u"DESKTOP_HID_EXTENSION_MANAGER_LISTBOX_ENABLE"_ustr; +inline constexpr OUString HID_EXTENSION_MANAGER_LISTBOX_DISABLE = u"DESKTOP_HID_EXTENSION_MANAGER_LISTBOX_DISABLE"_ustr; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/lib/init.cxx b/desktop/source/lib/init.cxx new file mode 100644 index 0000000000..ab12a160b9 --- /dev/null +++ b/desktop/source/lib/init.cxx @@ -0,0 +1,8129 @@ +/* -*- 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/. + */ + +#include <sfx2/lokhelper.hxx> +#include <sal/types.h> +#include <svx/sdr/contact/viewcontact.hxx> +#include <svx/svdpage.hxx> +#include <svx/svdpagv.hxx> +#include <config_buildconfig.h> +#include <config_cairo_rgba.h> +#include <config_features.h> + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> + +#ifdef IOS +#include <sys/mman.h> +#include <sys/stat.h> +#include <unicode/udata.h> +#include <unicode/ucnv.h> +#include <premac.h> +#import <Foundation/Foundation.h> +#import <CoreGraphics/CoreGraphics.h> +#include <postmac.h> +#endif + +#undef HAVE_MALLOC_TRIM + +#ifdef LINUX +#include <fcntl.h> +#if defined __GLIBC__ +# include <malloc.h> +# define HAVE_MALLOC_TRIM +#endif +#endif + +#ifdef ANDROID +#include <osl/detail/android-bootstrap.h> +#endif + +#ifdef EMSCRIPTEN +#include <osl/detail/emscripten-bootstrap.h> +#endif + +#include <algorithm> +#include <memory> +#include <iostream> +#include <string_view> +#include <queue> + +#include <boost/property_tree/json_parser.hpp> +#include <boost/algorithm/string.hpp> + +#include <LibreOfficeKit/LibreOfficeKit.h> +#include <LibreOfficeKit/LibreOfficeKitEnums.h> + +#include <sal/log.hxx> +#include <utility> +#include <vcl/errinf.hxx> +#include <vcl/lok.hxx> +#include <o3tl/any.hxx> +#include <o3tl/unit_conversion.hxx> +#include <o3tl/string_view.hxx> +#include <osl/file.hxx> +#include <osl/process.h> +#include <osl/thread.h> +#include <rtl/bootstrap.hxx> +#include <rtl/strbuf.hxx> +#include <rtl/uri.hxx> +#include <svl/zforlist.hxx> +#include <linguistic/misc.hxx> +#include <cppuhelper/bootstrap.hxx> +#include <comphelper/base64.hxx> +#include <comphelper/dispatchcommand.hxx> +#include <comphelper/lok.hxx> +#include <comphelper/processfactory.hxx> +#include <comphelper/string.hxx> +#include <comphelper/profilezone.hxx> +#include <comphelper/propertysequence.hxx> +#include <comphelper/propertyvalue.hxx> +#include <comphelper/scopeguard.hxx> +#include <comphelper/threadpool.hxx> +#include <comphelper/types.hxx> +#include <comphelper/servicehelper.hxx> +#include <comphelper/sequenceashashmap.hxx> + +#include <com/sun/star/connection/XConnection.hpp> +#include <com/sun/star/document/MacroExecMode.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/container/XNameAccess.hpp> +#include <com/sun/star/document/XDocumentLanguages.hpp> +#include <com/sun/star/frame/Desktop.hpp> +#include <com/sun/star/frame/DispatchResultEvent.hpp> +#include <com/sun/star/frame/DispatchResultState.hpp> +#include <com/sun/star/frame/XDispatchProvider.hpp> +#include <com/sun/star/frame/XDispatchResultListener.hpp> +#include <com/sun/star/frame/XSynchronousDispatch.hpp> +#include <com/sun/star/frame/XStorable.hpp> +#include <com/sun/star/lang/Locale.hpp> +#include <com/sun/star/lang/XComponent.hpp> +#include <com/sun/star/lang/XMultiServiceFactory.hpp> +#include <com/sun/star/style/XStyleFamiliesSupplier.hpp> +#include <com/sun/star/util/URLTransformer.hpp> +#include <com/sun/star/datatransfer/clipboard/XClipboard.hpp> +#include <com/sun/star/datatransfer/UnsupportedFlavorException.hpp> +#include <com/sun/star/datatransfer/XTransferable2.hpp> +#include <com/sun/star/text/TextContentAnchorType.hpp> +#include <com/sun/star/document/XRedlinesSupplier.hpp> +#include <com/sun/star/ui/GlobalAcceleratorConfiguration.hpp> +#include <com/sun/star/bridge/BridgeFactory.hpp> +#include <com/sun/star/bridge/XBridgeFactory.hpp> +#include <com/sun/star/bridge/XBridge.hpp> +#include <com/sun/star/uno/XNamingService.hpp> + +#include <com/sun/star/xml/crypto/SEInitializer.hpp> +#include <com/sun/star/xml/crypto/XSEInitializer.hpp> +#include <com/sun/star/xml/crypto/XSecurityEnvironment.hpp> +#include <com/sun/star/xml/crypto/XCertificateCreator.hpp> +#include <com/sun/star/security/XCertificate.hpp> + +#include <com/sun/star/linguistic2/DictionaryList.hpp> +#include <com/sun/star/linguistic2/LanguageGuessing.hpp> +#include <com/sun/star/linguistic2/LinguServiceManager.hpp> +#include <com/sun/star/linguistic2/XSpellChecker.hpp> +#include <com/sun/star/linguistic2/XProofreader.hpp> +#include <com/sun/star/i18n/LocaleCalendar2.hpp> +#include <com/sun/star/i18n/ScriptType.hpp> +#include <com/sun/star/lang/DisposedException.hpp> + +#include <editeng/flstitem.hxx> +#ifdef IOS +#include <sfx2/app.hxx> +#endif +#include <sfx2/objsh.hxx> +#include <sfx2/docfilt.hxx> +#include <sfx2/docfile.hxx> +#include <sfx2/viewsh.hxx> +#include <sfx2/viewfrm.hxx> +#include <sfx2/msgpool.hxx> +#include <sfx2/dispatch.hxx> +#include <sfx2/lokcomponenthelpers.hxx> +#include <sfx2/DocumentSigner.hxx> +#include <sfx2/sidebar/SidebarDockingWindow.hxx> +#include <sfx2/sidebar/SidebarController.hxx> +#include <svl/numformat.hxx> +#include <svx/dialmgr.hxx> +#include <svx/strings.hrc> +#include <svx/svdview.hxx> +#include <svx/svxids.hrc> +#include <svx/ucsubset.hxx> +#include <vcl/vclevent.hxx> +#include <vcl/GestureEventPan.hxx> +#include <vcl/svapp.hxx> +#include <unotools/resmgr.hxx> +#include <tools/fract.hxx> +#include <tools/json_writer.hxx> +#include <svtools/ctrltool.hxx> +#include <svtools/langtab.hxx> +#include <vcl/fontcharmap.hxx> +#ifdef IOS +#include <vcl/sysdata.hxx> +#endif +#include <vcl/virdev.hxx> +#include <vcl/ImageTree.hxx> +#include <vcl/ITiledRenderable.hxx> +#include <vcl/dialoghelper.hxx> +#ifdef _WIN32 +#include <vcl/BitmapReadAccess.hxx> +#endif +#include <unicode/uchar.h> +#include <unotools/securityoptions.hxx> +#include <unotools/confignode.hxx> +#include <unotools/syslocaleoptions.hxx> +#include <unotools/mediadescriptor.hxx> +#include <unotools/pathoptions.hxx> +#include <unotools/tempfile.hxx> +#include <unotools/streamwrap.hxx> +#include <osl/module.hxx> +#include <comphelper/sequence.hxx> +#include <sfx2/sfxbasemodel.hxx> +#include <svl/undo.hxx> +#include <unotools/datetime.hxx> +#include <i18nlangtag/mslangid.hxx> +#include <i18nlangtag/languagetag.hxx> +#include <vcl/abstdlg.hxx> +#include <comphelper/diagnose_ex.hxx> +#include <vcl/uitest/uiobject.hxx> +#include <vcl/jsdialog/executor.hxx> + +// Needed for getUndoManager() +#include <com/sun/star/document/XUndoManager.hpp> +#include <com/sun/star/document/XUndoManagerSupplier.hpp> +#include <com/sun/star/document/XLinkTargetSupplier.hpp> +#include <editeng/sizeitem.hxx> +#include <svx/rulritem.hxx> +#include <svx/pageitem.hxx> + +#include <app.hxx> + +#include "../app/cmdlineargs.hxx" +// We also need to hackily be able to start the main libreoffice thread: +#include "../app/sofficemain.h" +#include "../app/officeipcthread.hxx" +#include <lib/init.hxx> + +#include "lokinteractionhandler.hxx" +#include "lokclipboard.hxx" +#include <officecfg/Office/Common.hxx> +#include <officecfg/Office/Impress.hxx> +#include <officecfg/Office/Linguistic.hxx> +#include <officecfg/Office/UI/ToolbarMode.hxx> +#include <unotools/optionsdlg.hxx> +#include <svl/ctloptions.hxx> +#include <svtools/accessibilityoptions.hxx> +#include <svtools/colorcfg.hxx> +#include <svtools/miscopt.hxx> +#include <svtools/slidesorterbaropt.hxx> +#include <unotools/cmdoptions.hxx> +#include <unotools/compatibility.hxx> +#include <unotools/fltrcfg.hxx> +#include <unotools/lingucfg.hxx> +#include <unotools/moduleoptions.hxx> +#include <unotools/searchopt.hxx> +#include <unotools/useroptions.hxx> +#include <unotools/viewoptions.hxx> +#include <vcl/settings.hxx> + +#include <officecfg/Setup.hxx> +#include <com/sun/star/ui/XAcceleratorConfiguration.hpp> +#include <svtools/acceleratorexecute.hxx> + +using namespace css; +using namespace vcl; +using namespace desktop; +using namespace utl; +using namespace bridge; +using namespace uno; +using namespace lang; + +using LanguageToolCfg = officecfg::Office::Linguistic::GrammarChecking::LanguageTool; + +static LibLibreOffice_Impl *gImpl = nullptr; +static bool lok_preinit_2_called = false; +static std::weak_ptr< LibreOfficeKitClass > gOfficeClass; +static std::weak_ptr< LibreOfficeKitDocumentClass > gDocumentClass; + +static void SetLastExceptionMsg(const OUString& s = OUString()) +{ + SAL_WARN_IF(!s.isEmpty(), "lok", "lok exception '" + s + "'"); + if (gImpl) + gImpl->maLastExceptionMsg = s; +} + +namespace { + +struct ExtensionMap +{ + std::string_view extn; + OUString filterName; +}; + +class TraceEventDumper : public AutoTimer +{ + static const int dumpTimeoutMS = 5000; + +public: + TraceEventDumper() : AutoTimer( "Trace Event dumper" ) + { + SetTimeout(dumpTimeoutMS); + Start(); + } + + virtual void Invoke() override + { + flushRecordings(); + } + + static void flushRecordings() + { + const css::uno::Sequence<OUString> aEvents = + comphelper::TraceEvent::getRecordingAndClear(); + OStringBuffer aOutput; + for (const auto &s : aEvents) + { + aOutput.append(OUStringToOString(s, RTL_TEXTENCODING_UTF8) + + "\n"); + } + if (aOutput.getLength() > 0) + { + OString aChunk = aOutput.makeStringAndClear(); + if (gImpl && gImpl->mpCallback) + gImpl->mpCallback(LOK_CALLBACK_PROFILE_FRAME, aChunk.getStr(), gImpl->mpCallbackData); + } + } +}; + +TraceEventDumper *traceEventDumper = nullptr; + +constexpr ExtensionMap aWriterExtensionMap[] = +{ + { "doc", u"MS Word 97"_ustr }, + { "docm", u"MS Word 2007 XML VBA"_ustr }, + { "docx", u"MS Word 2007 XML"_ustr }, + { "fodt", u"OpenDocument Text Flat XML"_ustr }, + { "html", u"HTML (StarWriter)"_ustr }, + { "odt", u"writer8"_ustr }, + { "ott", u"writer8_template"_ustr }, + { "pdf", u"writer_pdf_Export"_ustr }, + { "epub", u"EPUB"_ustr }, + { "rtf", u"Rich Text Format"_ustr }, + { "txt", u"Text"_ustr }, + { "xhtml", u"XHTML Writer File"_ustr }, + { "png", u"writer_png_Export"_ustr }, + { "xml", u"writer_indexing_export"_ustr }, +}; + +constexpr ExtensionMap aCalcExtensionMap[] = +{ + { "csv", u"Text - txt - csv (StarCalc)"_ustr }, + { "fods", u"OpenDocument Spreadsheet Flat XML"_ustr }, + { "html", u"HTML (StarCalc)"_ustr }, + { "ods", u"calc8"_ustr }, + { "ots", u"calc8_template"_ustr }, + { "pdf", u"calc_pdf_Export"_ustr }, + { "xhtml", u"XHTML Calc File"_ustr }, + { "xls", u"MS Excel 97"_ustr }, + { "xlsm", u"Calc MS Excel 2007 VBA XML"_ustr }, + { "xlsx", u"Calc MS Excel 2007 XML"_ustr }, + { "png", u"calc_png_Export"_ustr }, +}; + +constexpr ExtensionMap aImpressExtensionMap[] = +{ + { "fodp", u"OpenDocument Presentation Flat XML"_ustr }, + { "html", u"impress_html_Export"_ustr }, + { "odg", u"impress8_draw"_ustr }, + { "odp", u"impress8"_ustr }, + { "otp", u"impress8_template"_ustr }, + { "pdf", u"impress_pdf_Export"_ustr }, + { "potm", u"Impress MS PowerPoint 2007 XML Template"_ustr }, + { "pot", u"MS PowerPoint 97 Vorlage"_ustr }, + { "pptm", u"Impress MS PowerPoint 2007 XML VBA"_ustr }, + { "pptx", u"Impress MS PowerPoint 2007 XML"_ustr }, + { "pps", u"MS PowerPoint 97 Autoplay"_ustr }, + { "ppt", u"MS PowerPoint 97"_ustr }, + { "svg", u"impress_svg_Export"_ustr }, + { "xhtml", u"XHTML Impress File"_ustr }, + { "png", u"impress_png_Export"_ustr }, +}; + +constexpr ExtensionMap aDrawExtensionMap[] = +{ + { "fodg", u"draw_ODG_FlatXML"_ustr }, + { "html", u"draw_html_Export"_ustr }, + { "odg", u"draw8"_ustr }, + { "pdf", u"draw_pdf_Export"_ustr }, + { "svg", u"draw_svg_Export"_ustr }, + { "xhtml", u"XHTML Draw File"_ustr }, + { "png", u"draw_png_Export"_ustr }, +}; + +OUString getUString(const char* pString) +{ + if (pString == nullptr) + return OUString(); + + return OStringToOUString(pString, RTL_TEXTENCODING_UTF8); +} + +// Tolerate embedded \0s etc. +char *convertOString(const OString &rStr) +{ + char* pMemory = static_cast<char*>(malloc(rStr.getLength() + 1)); + assert(pMemory); // don't tolerate failed allocations. + memcpy(pMemory, rStr.getStr(), rStr.getLength() + 1); + return pMemory; +} + +char *convertOUString(std::u16string_view aStr) +{ + return convertOString(OUStringToOString(aStr, RTL_TEXTENCODING_UTF8)); +} + +/// Try to convert a relative URL to an absolute one, unless it already looks like a URL. +OUString getAbsoluteURL(const char* pURL) +{ + OUString aURL(getUString(pURL)); + if (aURL.isEmpty()) + return aURL; + + // convert relative paths to absolute ones + OUString aWorkingDir; + osl_getProcessWorkingDir(&aWorkingDir.pData); + if (!aWorkingDir.endsWith("/")) + aWorkingDir += "/"; + + try + { + return rtl::Uri::convertRelToAbs(aWorkingDir, aURL); + } + catch (const rtl::MalformedUriException &) + { + } + + return OUString(); +} + +} // unnamed namespace + +std::vector<beans::PropertyValue> desktop::jsonToPropertyValuesVector(const char* pJSON) +{ + std::vector<beans::PropertyValue> aArguments; + if (pJSON && pJSON[0] != '\0') + { + aArguments = comphelper::JsonToPropertyValues(pJSON); + } + return aArguments; +} + +static void extractLinks(const uno::Reference< container::XNameAccess >& xLinks, bool subcontent, tools::JsonWriter& aJson) +{ + for (const OUString& aLink : xLinks->getElementNames()) + { + uno::Any aAny; + + try + { + aAny = xLinks->getByName( aLink ); + } + catch(const uno::Exception&) + { + // if the name of the target was invalid (like empty headings) + // no object can be provided + continue; + } + + uno::Reference< beans::XPropertySet > xTarget; + if( aAny >>= xTarget ) + { + try + { + // get name to display + aAny = xTarget->getPropertyValue(u"LinkDisplayName"_ustr); + OUString aStrDisplayname; + aAny >>= aStrDisplayname; + + if (subcontent) + { + aJson.put(aStrDisplayname, aLink); + } + else + { + uno::Reference<lang::XServiceInfo> xSI(xTarget, uno::UNO_QUERY_THROW); + if (xSI->supportsService(u"com.sun.star.document.LinkTarget"_ustr)) + { + aJson.put(aStrDisplayname, aLink); + continue; + } + else + { + auto aNode = aJson.startNode( + OUStringToOString(aStrDisplayname, RTL_TEXTENCODING_UTF8)); + + uno::Reference< document::XLinkTargetSupplier > xLTS( xTarget, uno::UNO_QUERY ); + if( xLTS.is() ) + extractLinks(xLTS->getLinks(), true, aJson); + } + } + } + catch(...) + { + SAL_WARN("lok", "extractLinks: Exception"); + } + } + } +} + +static void unoAnyToJson(tools::JsonWriter& rJson, std::string_view pNodeName, const uno::Any& anyItem) +{ + auto aNode = rJson.startNode(pNodeName); + OUString aType = anyItem.getValueTypeName(); + rJson.put("type", aType); + + if (aType == "string") + rJson.put("value", anyItem.get<OUString>()); + else if (aType == "unsigned long") + rJson.put("value", OString::number(anyItem.get<sal_uInt32>())); + else if (aType == "long") + rJson.put("value", OString::number(anyItem.get<sal_Int32>())); + else if (aType == "[]any") + { + uno::Sequence<uno::Any> aSeq; + if (anyItem >>= aSeq) + { + auto valueNode = rJson.startNode("value"); + + for (auto i = 0; i < aSeq.getLength(); ++i) + { + unoAnyToJson(rJson, OString::number(i), aSeq[i]); + } + } + } +} + +static int lcl_getViewId(std::string_view payload); + +namespace desktop { + +RectangleAndPart RectangleAndPart::Create(const OString& rPayload) +{ + RectangleAndPart aRet; + if (rPayload.startsWith("EMPTY")) // payload starts with "EMPTY" + { + aRet.m_aRectangle = tools::Rectangle(0, 0, SfxLokHelper::MaxTwips, SfxLokHelper::MaxTwips); + if (comphelper::LibreOfficeKit::isPartInInvalidation()) + { + int nSeparatorPos = rPayload.indexOf(',', 6); + bool bHasMode = nSeparatorPos > 0; + if (bHasMode) + { + aRet.m_nPart = o3tl::toInt32(rPayload.subView(6, nSeparatorPos - 6)); + assert(rPayload.getLength() > nSeparatorPos); + aRet.m_nMode = o3tl::toInt32(rPayload.subView(nSeparatorPos + 1)); + } + else + { + aRet.m_nPart = o3tl::toInt32(rPayload.subView(6)); + aRet.m_nMode = 0; + } + } + + return aRet; + } + + // Read '<left>, <top>, <width>, <height>[, <part>, <mode>]'. C++ streams are simpler but slower. + const char* pos = rPayload.getStr(); + const char* end = rPayload.getStr() + rPayload.getLength(); + tools::Long nLeft = rtl_str_toInt64_WithLength(pos, 10, end - pos); + while (pos < end && *pos != ',') + ++pos; + if (pos < end) + ++pos; + assert(pos < end); + tools::Long nTop = rtl_str_toInt64_WithLength(pos, 10, end - pos); + while (pos < end && *pos != ',') + ++pos; + if (pos < end) + ++pos; + assert(pos < end); + tools::Long nWidth = rtl_str_toInt64_WithLength(pos, 10, end - pos); + while (pos < end && *pos != ',') + ++pos; + if (pos < end) + ++pos; + assert(pos < end); + tools::Long nHeight = rtl_str_toInt64_WithLength(pos, 10, end - pos); + tools::Long nPart = INT_MIN; + tools::Long nMode = 0; + if (comphelper::LibreOfficeKit::isPartInInvalidation()) + { + while (pos < end && *pos != ',') + ++pos; + if (pos < end) + ++pos; + assert(pos < end); + nPart = rtl_str_toInt64_WithLength(pos, 10, end - pos); + + while (pos < end && *pos != ',') + ++pos; + if (pos < end) + { + ++pos; + assert(pos < end); + nMode = rtl_str_toInt64_WithLength(pos, 10, end - pos); + } + } + + aRet.m_aRectangle = SanitizedRectangle(nLeft, nTop, nWidth, nHeight); + aRet.m_nPart = nPart; + aRet.m_nMode = nMode; + return aRet; +} + +tools::Rectangle RectangleAndPart::SanitizedRectangle(tools::Long nLeft, tools::Long nTop, tools::Long nWidth, tools::Long nHeight) +{ + if (nWidth <= 0 || nHeight <= 0) + return tools::Rectangle(); + + // The top-left corner starts at (0, 0). + // Anything negative is invalid. + if (nLeft < 0) + { + nWidth += nLeft; + nLeft = 0; + } + + if (nTop < 0) + { + nHeight += nTop; + nTop = 0; + } + + if (nWidth > 0 && nHeight > 0) + return tools::Rectangle(nLeft, nTop, nLeft + nWidth, nTop + nHeight); + // Else set empty rect. + return tools::Rectangle(); +} + +tools::Rectangle RectangleAndPart::SanitizedRectangle(const tools::Rectangle& rect) +{ + return SanitizedRectangle(rect.Left(), rect.Top(), rect.getOpenWidth(), rect.getOpenHeight()); +} + +const OString& CallbackFlushHandler::CallbackData::getPayload() const +{ + if(PayloadString.isEmpty()) + { + // Do to-string conversion on demand, as many calls will get dropped without + // needing the string. + if(PayloadObject.which() == 1) + PayloadString = getRectangleAndPart().toString(); + } + return PayloadString; +} + +void CallbackFlushHandler::CallbackData::updateRectangleAndPart(const RectangleAndPart& rRectAndPart) +{ + PayloadObject = rRectAndPart; + PayloadString.clear(); // will be set on demand if needed +} + +const RectangleAndPart& CallbackFlushHandler::CallbackData::getRectangleAndPart() const +{ + // TODO: In case of unittests, they do not pass invalidations in binary but as text messages. + // LO core should preferably always pass binary for performance. + if(PayloadObject.which() != 1) + PayloadObject = RectangleAndPart::Create(PayloadString); + return boost::get<RectangleAndPart>(PayloadObject); +} + +boost::property_tree::ptree& CallbackFlushHandler::CallbackData::setJson(const std::string& payload) +{ + boost::property_tree::ptree aTree; + std::stringstream aStream(payload); + boost::property_tree::read_json(aStream, aTree); + + // Let boost normalize the payload so it always matches the cache. + setJson(aTree); + + // Return reference to the cached object. + return boost::get<boost::property_tree::ptree>(PayloadObject); +} + +void CallbackFlushHandler::CallbackData::setJson(const boost::property_tree::ptree& rTree) +{ + std::stringstream aJSONStream; + constexpr bool bPretty = false; // Don't waste time and bloat logs. + boost::property_tree::write_json(aJSONStream, rTree, bPretty); + PayloadString = OString(o3tl::trim(aJSONStream.str())); + + PayloadObject = rTree; +} + +const boost::property_tree::ptree& CallbackFlushHandler::CallbackData::getJson() const +{ + assert(PayloadObject.which() == 2); + return boost::get<boost::property_tree::ptree>(PayloadObject); +} + +int CallbackFlushHandler::CallbackData::getViewId() const +{ + if (isCached()) + { + assert(PayloadObject.which() == 3); + return boost::get<int>(PayloadObject); + } + return lcl_getViewId(getPayload()); +} + +bool CallbackFlushHandler::CallbackData::validate() const +{ + switch (PayloadObject.which()) + { + // Not cached. + case 0: + return true; + + // RectangleAndPart. + case 1: + return getRectangleAndPart().toString().getStr() == getPayload(); + + // Json. + case 2: + { + std::stringstream aJSONStream; + boost::property_tree::write_json(aJSONStream, getJson(), false); + const std::string aExpected = boost::trim_copy(aJSONStream.str()); + return getPayload() == std::string_view(aExpected); + } + + // View id. + case 3: + return getViewId() == lcl_getViewId( getPayload()); + + default: + assert(!"Unknown variant type; please add an entry to validate."); + } + + return false; +} + +} // namespace desktop + +static bool lcl_isViewCallbackType(const int type) +{ + switch (type) + { + case LOK_CALLBACK_CELL_VIEW_CURSOR: + case LOK_CALLBACK_GRAPHIC_VIEW_SELECTION: + case LOK_CALLBACK_INVALIDATE_VIEW_CURSOR: + case LOK_CALLBACK_TEXT_VIEW_SELECTION: + case LOK_CALLBACK_VIEW_CURSOR_VISIBLE: + return true; + + default: + return false; + } +} + +static bool isUpdatedType(int type) +{ + switch (type) + { + case LOK_CALLBACK_TEXT_SELECTION: + case LOK_CALLBACK_TEXT_SELECTION_START: + case LOK_CALLBACK_TEXT_SELECTION_END: + return true; + default: + return false; + } +} + +static bool isUpdatedTypePerViewId(int type) +{ + switch (type) + { + case LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR: + case LOK_CALLBACK_INVALIDATE_VIEW_CURSOR: + case LOK_CALLBACK_TEXT_VIEW_SELECTION: + return true; + default: + return false; + } +} + +static int lcl_getViewId(std::string_view payload) +{ + // this is a cheap way how to get the viewId from a JSON message; proper + // parsing is terribly expensive, and we just need the viewId here + size_t viewIdPos = payload.find("viewId"); + if (viewIdPos == std::string::npos) + return 0; + + size_t numberPos = payload.find(":", viewIdPos + 6); + if (numberPos == std::string::npos) + return 0; + + for (++numberPos; numberPos < payload.length(); ++numberPos) + { + if (payload[numberPos] == ',' || payload[numberPos] == '}' || (payload[numberPos] >= '0' && payload[numberPos] <= '9')) + break; + } + + if (numberPos < payload.length() && payload[numberPos] >= '0' && payload[numberPos] <= '9') + return o3tl::toInt32(payload.substr(numberPos)); + + return 0; +} + +namespace { + +std::string extractCertificate(const std::string & certificate) +{ + static constexpr std::string_view header("-----BEGIN CERTIFICATE-----"); + static constexpr std::string_view footer("-----END CERTIFICATE-----"); + + std::string result; + + size_t pos1 = certificate.find(header); + if (pos1 == std::string::npos) + return result; + + size_t pos2 = certificate.find(footer, pos1 + 1); + if (pos2 == std::string::npos) + return result; + + pos1 = pos1 + header.length(); + pos2 = pos2 - pos1; + + return certificate.substr(pos1, pos2); +} + +std::string extractPrivateKey(const std::string & privateKey) +{ + static constexpr std::string_view header("-----BEGIN PRIVATE KEY-----"); + static constexpr std::string_view footer("-----END PRIVATE KEY-----"); + + std::string result; + + size_t pos1 = privateKey.find(header); + if (pos1 == std::string::npos) + return result; + + size_t pos2 = privateKey.find(footer, pos1 + 1); + if (pos2 == std::string::npos) + return result; + + pos1 = pos1 + header.length(); + pos2 = pos2 - pos1; + + return privateKey.substr(pos1, pos2); +} + +OUString lcl_getCurrentDocumentMimeType(const LibLODocument_Impl* pDocument) +{ + SfxBaseModel* pBaseModel = dynamic_cast<SfxBaseModel*>(pDocument->mxComponent.get()); + if (!pBaseModel) + return ""; + + SfxObjectShell* pObjectShell = pBaseModel->GetObjectShell(); + if (!pObjectShell) + return ""; + + SfxMedium* pMedium = pObjectShell->GetMedium(); + if (!pMedium) + return ""; + + auto pFilter = pMedium->GetFilter(); + if (!pFilter) + return ""; + + return pFilter->GetMimeType(); +} + +// Gets an undo manager to enter and exit undo context. Needed by ToggleOrientation +css::uno::Reference< css::document::XUndoManager > getUndoManager( const css::uno::Reference< css::frame::XFrame >& rxFrame ) +{ + const css::uno::Reference< css::frame::XController >& xController = rxFrame->getController(); + if ( xController.is() ) + { + const css::uno::Reference< css::frame::XModel >& xModel = xController->getModel(); + if ( xModel.is() ) + { + const css::uno::Reference< css::document::XUndoManagerSupplier > xSuppUndo( xModel, css::uno::UNO_QUERY_THROW ); + return css::uno::Reference< css::document::XUndoManager >( xSuppUndo->getUndoManager(), css::uno::UNO_SET_THROW ); + } + } + + return css::uno::Reference< css::document::XUndoManager > (); +} + +// Adjusts page margins for Writer doc. Needed by ToggleOrientation +void ExecuteMarginLRChange( + const tools::Long nPageLeftMargin, + const tools::Long nPageRightMargin, + SvxLongLRSpaceItem* pPageLRMarginItem) +{ + pPageLRMarginItem->SetLeft( nPageLeftMargin ); + pPageLRMarginItem->SetRight( nPageRightMargin ); + SfxViewShell::Current()->GetDispatcher()->ExecuteList(SID_ATTR_PAGE_LRSPACE, + SfxCallMode::RECORD, { pPageLRMarginItem }); +} + +// Adjusts page margins for Writer doc. Needed by ToggleOrientation +void ExecuteMarginULChange( + const tools::Long nPageTopMargin, + const tools::Long nPageBottomMargin, + SvxLongULSpaceItem* pPageULMarginItem) +{ + pPageULMarginItem->SetUpper( nPageTopMargin ); + pPageULMarginItem->SetLower( nPageBottomMargin ); + SfxViewShell::Current()->GetDispatcher()->ExecuteList(SID_ATTR_PAGE_ULSPACE, + SfxCallMode::RECORD, { pPageULMarginItem }); +} + +// Main function which toggles page orientation of the Writer doc. Needed by ToggleOrientation +void ExecuteOrientationChange() +{ + SfxViewFrame* pViewFrm = SfxViewFrame::Current(); + if (!pViewFrm) + return; + + std::unique_ptr<SvxPageItem> pPageItem(new SvxPageItem(SID_ATTR_PAGE)); + + // 1mm in twips rounded + // This should be in sync with MINBODY in sw/source/uibase/sidebar/PageMarginControl.hxx + constexpr tools::Long MINBODY = o3tl::toTwips(1, o3tl::Length::mm); + + css::uno::Reference< css::document::XUndoManager > mxUndoManager( + getUndoManager( pViewFrm->GetFrame().GetFrameInterface() ) ); + + if ( mxUndoManager.is() ) + mxUndoManager->enterUndoContext( "" ); + + SfxPoolItemHolder aResult; + pViewFrm->GetBindings().GetDispatcher()->QueryState(SID_ATTR_PAGE_SIZE, aResult); + std::unique_ptr<SvxSizeItem> pPageSizeItem(static_cast<const SvxSizeItem*>(aResult.getItem())->Clone()); + + pViewFrm->GetBindings().GetDispatcher()->QueryState(SID_ATTR_PAGE_LRSPACE, aResult); + std::unique_ptr<SvxLongLRSpaceItem> pPageLRMarginItem(static_cast<const SvxLongLRSpaceItem*>(aResult.getItem())->Clone()); + + pViewFrm->GetBindings().GetDispatcher()->QueryState(SID_ATTR_PAGE_ULSPACE, aResult); + std::unique_ptr<SvxLongULSpaceItem> pPageULMarginItem(static_cast<const SvxLongULSpaceItem*>(aResult.getItem())->Clone()); + + { + bool bIsLandscape = false; + if ( pPageSizeItem->GetSize().Width() > pPageSizeItem->GetSize().Height()) + bIsLandscape = true; + + // toggle page orientation + pPageItem->SetLandscape(!bIsLandscape); + + + // swap the width and height of the page size + const tools::Long nRotatedWidth = pPageSizeItem->GetSize().Height(); + const tools::Long nRotatedHeight = pPageSizeItem->GetSize().Width(); + pPageSizeItem->SetSize(Size(nRotatedWidth, nRotatedHeight)); + + + // apply changed attributes + if (SfxViewShell::Current()) + { + SfxViewShell::Current()->GetDispatcher()->ExecuteList(SID_ATTR_PAGE_SIZE, + SfxCallMode::RECORD, { pPageSizeItem.get(), pPageItem.get() }); + } + } + + + // check, if margin values still fit to the changed page size. + // if not, adjust margin values + { + const tools::Long nML = pPageLRMarginItem->GetLeft(); + const tools::Long nMR = pPageLRMarginItem->GetRight(); + const tools::Long nTmpPW = nML + nMR + MINBODY; + + const tools::Long nPW = pPageSizeItem->GetSize().Width(); + + if ( nTmpPW > nPW ) + { + if ( nML <= nMR ) + { + ExecuteMarginLRChange( pPageLRMarginItem->GetLeft(), nMR - (nTmpPW - nPW ), pPageLRMarginItem.get() ); + } + else + { + ExecuteMarginLRChange( nML - (nTmpPW - nPW ), pPageLRMarginItem->GetRight(), pPageLRMarginItem.get() ); + } + } + + const tools::Long nMT = pPageULMarginItem->GetUpper(); + const tools::Long nMB = pPageULMarginItem->GetLower(); + const tools::Long nTmpPH = nMT + nMB + MINBODY; + + const tools::Long nPH = pPageSizeItem->GetSize().Height(); + + if ( nTmpPH > nPH ) + { + if ( nMT <= nMB ) + { + ExecuteMarginULChange( pPageULMarginItem->GetUpper(), nMB - ( nTmpPH - nPH ), pPageULMarginItem.get() ); + } + else + { + ExecuteMarginULChange( nMT - ( nTmpPH - nPH ), pPageULMarginItem->GetLower(), pPageULMarginItem.get() ); + } + } + } + + if ( mxUndoManager.is() ) + mxUndoManager->leaveUndoContext(); +} + +void setupSidebar(std::u16string_view sidebarDeckId = u"") +{ + SfxViewShell* pViewShell = SfxViewShell::Current(); + SfxViewFrame* pViewFrame = pViewShell ? &pViewShell->GetViewFrame() : nullptr; + if (pViewFrame) + { + if (!pViewFrame->GetChildWindow(SID_SIDEBAR)) + pViewFrame->SetChildWindow(SID_SIDEBAR, false /* create it */, true /* focus */); + + pViewFrame->ShowChildWindow(SID_SIDEBAR, true); + + // Force synchronous population of panels + SfxChildWindow *pChild = pViewFrame->GetChildWindow(SID_SIDEBAR); + if (!pChild) + return; + + auto pDockingWin = dynamic_cast<sfx2::sidebar::SidebarDockingWindow *>(pChild->GetWindow()); + if (!pDockingWin) + return; + + pViewFrame->ShowChildWindow( SID_SIDEBAR ); + + const rtl::Reference<sfx2::sidebar::SidebarController>& xController + = pDockingWin->GetOrCreateSidebarController(); + + xController->FadeIn(); + xController->RequestOpenDeck(); + + if (!sidebarDeckId.empty()) + { + xController->SwitchToDeck(sidebarDeckId); + } + else + { + xController->SwitchToDefaultDeck(); + } + + pDockingWin->SyncUpdate(); + } + else + SetLastExceptionMsg(u"No view shell or sidebar"_ustr); +} + +void hideSidebar() +{ + SfxViewShell* pViewShell = SfxViewShell::Current(); + SfxViewFrame* pViewFrame = pViewShell ? &pViewShell->GetViewFrame() : nullptr; + if (pViewFrame) + pViewFrame->SetChildWindow(SID_SIDEBAR, false , false ); + else + SetLastExceptionMsg(u"No view shell or sidebar"_ustr); +} + +} // end anonymous namespace + +// Could be anonymous in principle, but for the unit testing purposes, we +// declare it in init.hxx. +OUString desktop::extractParameter(OUString& rOptions, std::u16string_view rName) +{ + OUString aValue; + + OUString aNameEquals(OUString::Concat(rName) + "="); + OUString aCommaNameEquals(OUString::Concat(",") + rName + "="); + + int nIndex = -1; + if (rOptions.startsWith(aNameEquals)) + { + size_t nLen = aNameEquals.getLength(); + int nComma = rOptions.indexOf(",", nLen); + if (nComma >= 0) + { + aValue = rOptions.copy(nLen, nComma - nLen); + rOptions = rOptions.copy(nComma + 1); + } + else + { + aValue = rOptions.copy(nLen); + rOptions.clear(); + } + } + else if ((nIndex = rOptions.indexOf(aCommaNameEquals)) >= 0) + { + size_t nLen = aCommaNameEquals.getLength(); + int nComma = rOptions.indexOf(",", nIndex + nLen); + if (nComma >= 0) + { + aValue = rOptions.copy(nIndex + nLen, nComma - nIndex - nLen); + rOptions = OUString::Concat(rOptions.subView(0, nIndex)) + rOptions.subView(nComma); + } + else + { + aValue = rOptions.copy(nIndex + nLen); + rOptions = rOptions.copy(0, nIndex); + } + } + + return aValue; +} + +extern "C" +{ + +static void doc_destroy(LibreOfficeKitDocument* pThis); +static int doc_saveAs(LibreOfficeKitDocument* pThis, const char* pUrl, const char* pFormat, const char* pFilterOptions); +static int doc_getDocumentType(LibreOfficeKitDocument* pThis); +static int doc_getParts(LibreOfficeKitDocument* pThis); +static char* doc_getPartPageRectangles(LibreOfficeKitDocument* pThis); +static int doc_getPart(LibreOfficeKitDocument* pThis); +static void doc_setPart(LibreOfficeKitDocument* pThis, int nPart); +static void doc_selectPart(LibreOfficeKitDocument* pThis, int nPart, int nSelect); +static void doc_moveSelectedParts(LibreOfficeKitDocument* pThis, int nPosition, bool bDuplicate); +static char* doc_getPartName(LibreOfficeKitDocument* pThis, int nPart); +static void doc_setPartMode(LibreOfficeKitDocument* pThis, int nPartMode); +static int doc_getEditMode(LibreOfficeKitDocument* pThis); +static void doc_paintTile(LibreOfficeKitDocument* pThis, + unsigned char* pBuffer, + const int nCanvasWidth, const int nCanvasHeight, + const int nTilePosX, const int nTilePosY, + const int nTileWidth, const int nTileHeight); +static void doc_paintPartTile(LibreOfficeKitDocument* pThis, + unsigned char* pBuffer, + const int nPart, + const int nMode, + const int nCanvasWidth, const int nCanvasHeight, + const int nTilePosX, const int nTilePosY, + const int nTileWidth, const int nTileHeight); +static int doc_getTileMode(LibreOfficeKitDocument* pThis); +static void doc_getDocumentSize(LibreOfficeKitDocument* pThis, + long* pWidth, + long* pHeight); +static void doc_getDataArea(LibreOfficeKitDocument* pThis, + long nTab, + long* pCol, + long* pRow); +static void doc_initializeForRendering(LibreOfficeKitDocument* pThis, + const char* pArguments); + +static void doc_registerCallback(LibreOfficeKitDocument* pThis, + LibreOfficeKitCallback pCallback, + void* pData); +static void doc_postKeyEvent(LibreOfficeKitDocument* pThis, + int nType, + int nCharCode, + int nKeyCode); +static void doc_setBlockedCommandList(LibreOfficeKitDocument* pThis, + int nViewId, + const char* blockedCommandList); + +static void doc_postWindowExtTextInputEvent(LibreOfficeKitDocument* pThis, + unsigned nWindowId, + int nType, + const char* pText); +static void doc_removeTextContext(LibreOfficeKitDocument* pThis, + unsigned nLOKWindowId, + int nCharBefore, + int nCharAfter); +static void doc_sendDialogEvent(LibreOfficeKitDocument* pThis, + unsigned long long int nLOKWindowId, + const char* pArguments); +static void doc_postWindowKeyEvent(LibreOfficeKitDocument* pThis, + unsigned nLOKWindowId, + int nType, + int nCharCode, + int nKeyCode); +static void doc_postMouseEvent (LibreOfficeKitDocument* pThis, + int nType, + int nX, + int nY, + int nCount, + int nButtons, + int nModifier); +static void doc_postWindowMouseEvent (LibreOfficeKitDocument* pThis, + unsigned nLOKWindowId, + int nType, + int nX, + int nY, + int nCount, + int nButtons, + int nModifier); +static void doc_postWindowGestureEvent(LibreOfficeKitDocument* pThis, + unsigned nLOKWindowId, + const char* pType, + int nX, + int nY, + int nOffset); +static void doc_postUnoCommand(LibreOfficeKitDocument* pThis, + const char* pCommand, + const char* pArguments, + bool bNotifyWhenFinished); +static void doc_setWindowTextSelection(LibreOfficeKitDocument* pThis, + unsigned nLOKWindowId, + bool swap, + int nX, + int nY); +static void doc_setTextSelection (LibreOfficeKitDocument* pThis, + int nType, + int nX, + int nY); +static char* doc_getTextSelection(LibreOfficeKitDocument* pThis, + const char* pMimeType, + char** pUsedMimeType); +static int doc_getSelectionType(LibreOfficeKitDocument* pThis); +static int doc_getSelectionTypeAndText(LibreOfficeKitDocument* pThis, + const char* pMimeType, + char** pText, + char** pUsedMimeType); +static int doc_getClipboard (LibreOfficeKitDocument* pThis, + const char **pMimeTypes, + size_t *pOutCount, + char ***pOutMimeTypes, + size_t **pOutSizes, + char ***pOutStreams); +static int doc_setClipboard (LibreOfficeKitDocument* pThis, + const size_t nInCount, + const char **pInMimeTypes, + const size_t *pInSizes, + const char **pInStreams); +static bool doc_paste(LibreOfficeKitDocument* pThis, + const char* pMimeType, + const char* pData, + size_t nSize); +static void doc_setGraphicSelection (LibreOfficeKitDocument* pThis, + int nType, + int nX, + int nY); +static void doc_resetSelection (LibreOfficeKitDocument* pThis); +static char* doc_getCommandValues(LibreOfficeKitDocument* pThis, const char* pCommand); +static void doc_setClientZoom(LibreOfficeKitDocument* pThis, + int nTilePixelWidth, + int nTilePixelHeight, + int nTileTwipWidth, + int nTileTwipHeight); +static void doc_setClientVisibleArea(LibreOfficeKitDocument* pThis, int nX, int nY, int nWidth, int nHeight); +static void doc_setOutlineState(LibreOfficeKitDocument* pThis, bool bColumn, int nLevel, int nIndex, bool bHidden); +static int doc_createView(LibreOfficeKitDocument* pThis); +static int doc_createViewWithOptions(LibreOfficeKitDocument* pThis, const char* pOptions); +static void doc_destroyView(LibreOfficeKitDocument* pThis, int nId); +static void doc_setView(LibreOfficeKitDocument* pThis, int nId); +static int doc_getView(LibreOfficeKitDocument* pThis); +static int doc_getViewsCount(LibreOfficeKitDocument* pThis); +static bool doc_getViewIds(LibreOfficeKitDocument* pThis, int* pArray, size_t nSize); +static void doc_setViewLanguage(LibreOfficeKitDocument* pThis, int nId, const char* language); +static unsigned char* doc_renderFontOrientation(LibreOfficeKitDocument* pThis, + const char *pFontName, + const char *pChar, + int* pFontWidth, + int* pFontHeight, + int pOrientation); +static unsigned char* doc_renderFont(LibreOfficeKitDocument* pThis, + const char *pFontName, + const char *pChar, + int* pFontWidth, + int* pFontHeight); +static char* doc_getPartHash(LibreOfficeKitDocument* pThis, int nPart); + +static void doc_paintWindow(LibreOfficeKitDocument* pThis, unsigned nLOKWindowId, unsigned char* pBuffer, + const int nX, const int nY, + const int nWidth, const int nHeight); + +static void doc_paintWindowDPI(LibreOfficeKitDocument* pThis, unsigned nLOKWindowId, unsigned char* pBuffer, + const int nX, const int nY, + const int nWidth, const int nHeight, + const double fDPIScale); + +static void doc_paintWindowForView(LibreOfficeKitDocument* pThis, unsigned nLOKWindowId, unsigned char* pBuffer, + const int nX, const int nY, + const int nWidth, const int nHeight, + const double fDPIScale, int viewId); + +static void doc_postWindow(LibreOfficeKitDocument* pThis, unsigned + nLOKWindowId, int nAction, const char* pData); + +static char* doc_getPartInfo(LibreOfficeKitDocument* pThis, int nPart); + +static bool doc_insertCertificate(LibreOfficeKitDocument* pThis, + const unsigned char* pCertificateBinary, + const int nCertificateBinarySize, + const unsigned char* pPrivateKeyBinary, + const int nPrivateKeyBinarySize); + +static bool doc_addCertificate(LibreOfficeKitDocument* pThis, + const unsigned char* pCertificateBinary, + const int nCertificateBinarySize); + +static int doc_getSignatureState(LibreOfficeKitDocument* pThis); + +static size_t doc_renderShapeSelection(LibreOfficeKitDocument* pThis, char** pOutput); + +static void doc_resizeWindow(LibreOfficeKitDocument* pThis, unsigned nLOKWindowId, + const int nWidth, const int nHeight); + +static void doc_completeFunction(LibreOfficeKitDocument* pThis, const char*); + + +static void doc_sendFormFieldEvent(LibreOfficeKitDocument* pThis, + const char* pArguments); + +static bool doc_renderSearchResult(LibreOfficeKitDocument* pThis, + const char* pSearchResult, unsigned char** pBitmapBuffer, + int* pWidth, int* pHeight, size_t* pByteSize); + +static void doc_sendContentControlEvent(LibreOfficeKitDocument* pThis, const char* pArguments); + +static void doc_setViewTimezone(LibreOfficeKitDocument* pThis, int nId, const char* timezone); + +static void doc_setAccessibilityState(LibreOfficeKitDocument* pThis, int nId, bool bEnabled); + +static char* doc_getA11yFocusedParagraph(LibreOfficeKitDocument* pThis); + +static int doc_getA11yCaretPosition(LibreOfficeKitDocument* pThis); +} // extern "C" + +namespace { +ITiledRenderable* getTiledRenderable(LibreOfficeKitDocument* pThis) +{ + LibLODocument_Impl* pDocument = static_cast<LibLODocument_Impl*>(pThis); + return dynamic_cast<ITiledRenderable*>(pDocument->mxComponent.get()); +} + +#ifndef IOS + +/* + * Unfortunately clipboard creation using UNO is insanely baroque. + * we also need to ensure that this works for the first view which + * has no clear 'createView' called for it (unfortunately). + */ +rtl::Reference<LOKClipboard> forceSetClipboardForCurrentView(LibreOfficeKitDocument *pThis) +{ + ITiledRenderable* pDoc = getTiledRenderable(pThis); + rtl::Reference<LOKClipboard> xClip(LOKClipboardFactory::getClipboardForCurView()); + + SAL_INFO("lok", "Set to clipboard for view " << xClip.get()); + // FIXME: using a hammer here - should not be necessary if all tests used createView. + pDoc->setClipboard(uno::Reference<datatransfer::clipboard::XClipboard>(xClip->getXI(), UNO_QUERY)); + + return xClip; +} + +#endif + +const vcl::Font* FindFont(std::u16string_view rFontName) +{ + SfxObjectShell* pDocSh = SfxObjectShell::Current(); + if (!pDocSh) + return nullptr; + const SvxFontListItem* pFonts + = static_cast<const SvxFontListItem*>(pDocSh->GetItem(SID_ATTR_CHAR_FONTLIST)); + const FontList* pList = pFonts ? pFonts->GetFontList() : nullptr; + if (pList && !rFontName.empty()) + if (sal_Handle hMetric = pList->GetFirstFontMetric(rFontName)) + return &FontList::GetFontMetric(hMetric); + return nullptr; +} + +vcl::Font FindFont_FallbackToDefault(std::u16string_view rFontName) +{ + if (auto pFound = FindFont(rFontName)) + return *pFound; + + return OutputDevice::GetDefaultFont(DefaultFontType::SANS_UNICODE, LANGUAGE_NONE, + GetDefaultFontFlags::NONE); +} + +int getDocumentType (LibreOfficeKitDocument* pThis) +{ + SetLastExceptionMsg(); + + LibLODocument_Impl* pDocument = static_cast<LibLODocument_Impl*>(pThis); + + try + { + uno::Reference<lang::XServiceInfo> xDocument(pDocument->mxComponent, uno::UNO_QUERY_THROW); + + if (xDocument->supportsService(u"com.sun.star.sheet.SpreadsheetDocument"_ustr)) + { + return LOK_DOCTYPE_SPREADSHEET; + } + else if (xDocument->supportsService(u"com.sun.star.presentation.PresentationDocument"_ustr)) + { + return LOK_DOCTYPE_PRESENTATION; + } + else if (xDocument->supportsService(u"com.sun.star.drawing.DrawingDocument"_ustr)) + { + return LOK_DOCTYPE_DRAWING; + } + else if (xDocument->supportsService(u"com.sun.star.text.TextDocument"_ustr) || xDocument->supportsService(u"com.sun.star.text.WebDocument"_ustr)) + { + return LOK_DOCTYPE_TEXT; + } + else + { + SetLastExceptionMsg(u"unknown document type"_ustr); + } + } + catch (const uno::Exception& exception) + { + SetLastExceptionMsg("exception: " + exception.Message); + } + return LOK_DOCTYPE_OTHER; +} + +} // anonymous namespace + +LibLODocument_Impl::LibLODocument_Impl(uno::Reference <css::lang::XComponent> xComponent, int nDocumentId) + : mxComponent(std::move(xComponent)) + , mnDocumentId(nDocumentId) +{ + assert(nDocumentId != -1 && "Cannot set mnDocumentId to -1"); + + m_pDocumentClass = gDocumentClass.lock(); + if (!m_pDocumentClass) + { + m_pDocumentClass = std::make_shared<LibreOfficeKitDocumentClass>(); + + m_pDocumentClass->nSize = sizeof(LibreOfficeKitDocumentClass); + + m_pDocumentClass->destroy = doc_destroy; + m_pDocumentClass->saveAs = doc_saveAs; + m_pDocumentClass->getDocumentType = doc_getDocumentType; + m_pDocumentClass->getParts = doc_getParts; + m_pDocumentClass->getPartPageRectangles = doc_getPartPageRectangles; + m_pDocumentClass->getPart = doc_getPart; + m_pDocumentClass->setPart = doc_setPart; + m_pDocumentClass->selectPart = doc_selectPart; + m_pDocumentClass->moveSelectedParts = doc_moveSelectedParts; + m_pDocumentClass->getPartName = doc_getPartName; + m_pDocumentClass->setPartMode = doc_setPartMode; + m_pDocumentClass->getEditMode = doc_getEditMode; + m_pDocumentClass->paintTile = doc_paintTile; + m_pDocumentClass->paintPartTile = doc_paintPartTile; + m_pDocumentClass->getTileMode = doc_getTileMode; + m_pDocumentClass->getDocumentSize = doc_getDocumentSize; + m_pDocumentClass->getDataArea = doc_getDataArea; + m_pDocumentClass->initializeForRendering = doc_initializeForRendering; + m_pDocumentClass->registerCallback = doc_registerCallback; + m_pDocumentClass->postKeyEvent = doc_postKeyEvent; + m_pDocumentClass->postWindowExtTextInputEvent = doc_postWindowExtTextInputEvent; + m_pDocumentClass->removeTextContext = doc_removeTextContext; + m_pDocumentClass->postWindowKeyEvent = doc_postWindowKeyEvent; + m_pDocumentClass->postMouseEvent = doc_postMouseEvent; + m_pDocumentClass->postWindowMouseEvent = doc_postWindowMouseEvent; + m_pDocumentClass->sendDialogEvent = doc_sendDialogEvent; + m_pDocumentClass->postUnoCommand = doc_postUnoCommand; + m_pDocumentClass->setTextSelection = doc_setTextSelection; + m_pDocumentClass->setWindowTextSelection = doc_setWindowTextSelection; + m_pDocumentClass->getTextSelection = doc_getTextSelection; + m_pDocumentClass->getSelectionType = doc_getSelectionType; + m_pDocumentClass->getSelectionTypeAndText = doc_getSelectionTypeAndText; + m_pDocumentClass->getClipboard = doc_getClipboard; + m_pDocumentClass->setClipboard = doc_setClipboard; + m_pDocumentClass->paste = doc_paste; + m_pDocumentClass->setGraphicSelection = doc_setGraphicSelection; + m_pDocumentClass->resetSelection = doc_resetSelection; + m_pDocumentClass->getCommandValues = doc_getCommandValues; + m_pDocumentClass->setClientZoom = doc_setClientZoom; + m_pDocumentClass->setClientVisibleArea = doc_setClientVisibleArea; + m_pDocumentClass->setOutlineState = doc_setOutlineState; + + m_pDocumentClass->createView = doc_createView; + m_pDocumentClass->destroyView = doc_destroyView; + m_pDocumentClass->setView = doc_setView; + m_pDocumentClass->getView = doc_getView; + m_pDocumentClass->getViewsCount = doc_getViewsCount; + m_pDocumentClass->getViewIds = doc_getViewIds; + + m_pDocumentClass->renderFont = doc_renderFont; + m_pDocumentClass->renderFontOrientation = doc_renderFontOrientation; + m_pDocumentClass->getPartHash = doc_getPartHash; + + m_pDocumentClass->paintWindow = doc_paintWindow; + m_pDocumentClass->paintWindowDPI = doc_paintWindowDPI; + m_pDocumentClass->paintWindowForView = doc_paintWindowForView; + m_pDocumentClass->postWindow = doc_postWindow; + m_pDocumentClass->resizeWindow = doc_resizeWindow; + + m_pDocumentClass->setViewLanguage = doc_setViewLanguage; + + m_pDocumentClass->getPartInfo = doc_getPartInfo; + + m_pDocumentClass->insertCertificate = doc_insertCertificate; + m_pDocumentClass->addCertificate = doc_addCertificate; + m_pDocumentClass->getSignatureState = doc_getSignatureState; + + m_pDocumentClass->renderShapeSelection = doc_renderShapeSelection; + m_pDocumentClass->postWindowGestureEvent = doc_postWindowGestureEvent; + + m_pDocumentClass->createViewWithOptions = doc_createViewWithOptions; + m_pDocumentClass->completeFunction = doc_completeFunction; + + m_pDocumentClass->sendFormFieldEvent = doc_sendFormFieldEvent; + m_pDocumentClass->renderSearchResult = doc_renderSearchResult; + + m_pDocumentClass->setBlockedCommandList = doc_setBlockedCommandList; + + m_pDocumentClass->sendContentControlEvent = doc_sendContentControlEvent; + + m_pDocumentClass->setViewTimezone = doc_setViewTimezone; + + m_pDocumentClass->setAccessibilityState = doc_setAccessibilityState; + + m_pDocumentClass->getA11yFocusedParagraph = doc_getA11yFocusedParagraph; + m_pDocumentClass->getA11yCaretPosition = doc_getA11yCaretPosition; + + gDocumentClass = m_pDocumentClass; + } + pClass = m_pDocumentClass.get(); + +#ifndef IOS + forceSetClipboardForCurrentView(this); +#endif +} + +LibLODocument_Impl::~LibLODocument_Impl() +{ + try + { + mxComponent->dispose(); + } + catch (const css::lang::DisposedException&) + { + TOOLS_WARN_EXCEPTION("lok", "failed to dispose document"); + } +} + +static OUString getGenerator() +{ + OUString sGenerator( + Translate::ExpandVariables(u"%PRODUCTNAME %PRODUCTVERSION%PRODUCTEXTENSION (%1)"_ustr)); + OUString os(u"$_OS"_ustr); + ::rtl::Bootstrap::expandMacros(os); + return sGenerator.replaceFirst("%1", os); +} + +extern "C" { + +CallbackFlushHandler::TimeoutIdle::TimeoutIdle( CallbackFlushHandler* handler ) + : Timer( "lokit timer callback" ) + , mHandler( handler ) +{ + // A second timer with higher priority, it'll ensure we flush in reasonable time if we get too busy + // to get POST_PAINT priority processing. Otherwise it could take a long time to flush. + SetPriority(TaskPriority::DEFAULT); + SetTimeout( 100 ); // 100 ms +} + +void CallbackFlushHandler::TimeoutIdle::Invoke() +{ + mHandler->Invoke(); +} + +// One of these is created per view to handle events cf. doc_registerCallback +CallbackFlushHandler::CallbackFlushHandler(LibreOfficeKitDocument* pDocument, LibreOfficeKitCallback pCallback, void* pData) + : Idle( "lokit idle callback" ), + m_pDocument(pDocument), + m_pCallback(pCallback), + m_pData(pData), + m_nDisableCallbacks(0), + m_TimeoutIdle( this ) +{ + SetPriority(TaskPriority::POST_PAINT); + + // Add the states that are safe to skip duplicates on, even when + // not consequent (i.e. do no emit them if unchanged from last). + m_states.emplace(LOK_CALLBACK_TEXT_SELECTION, "NIL"_ostr); + m_states.emplace(LOK_CALLBACK_GRAPHIC_SELECTION, "NIL"_ostr); + m_states.emplace(LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR, "NIL"_ostr); + m_states.emplace(LOK_CALLBACK_STATE_CHANGED, "NIL"_ostr); + m_states.emplace(LOK_CALLBACK_MOUSE_POINTER, "NIL"_ostr); + m_states.emplace(LOK_CALLBACK_CELL_CURSOR, "NIL"_ostr); + m_states.emplace(LOK_CALLBACK_CELL_FORMULA, "NIL"_ostr); + m_states.emplace(LOK_CALLBACK_CELL_ADDRESS, "NIL"_ostr); + m_states.emplace(LOK_CALLBACK_CURSOR_VISIBLE, "NIL"_ostr); + m_states.emplace(LOK_CALLBACK_SET_PART, "NIL"_ostr); + m_states.emplace(LOK_CALLBACK_TABLE_SELECTED, "NIL"_ostr); + m_states.emplace(LOK_CALLBACK_TAB_STOP_LIST, "NIL"_ostr); + m_states.emplace(LOK_CALLBACK_RULER_UPDATE, "NIL"_ostr); + m_states.emplace(LOK_CALLBACK_STATUS_INDICATOR_SET_VALUE, "NIL"_ostr); +} + +CallbackFlushHandler::~CallbackFlushHandler() +{ + Stop(); +} + +CallbackFlushHandler::queue_type2::iterator CallbackFlushHandler::toQueue2(CallbackFlushHandler::queue_type1::iterator pos) +{ + int delta = std::distance(m_queue1.begin(), pos); + return m_queue2.begin() + delta; +} + +CallbackFlushHandler::queue_type2::reverse_iterator CallbackFlushHandler::toQueue2(CallbackFlushHandler::queue_type1::reverse_iterator pos) +{ + int delta = std::distance(m_queue1.rbegin(), pos); + return m_queue2.rbegin() + delta; +} + +void CallbackFlushHandler::setUpdatedType( int nType, bool value ) +{ + assert(isUpdatedType(nType)); + if( m_updatedTypes.size() <= o3tl::make_unsigned( nType )) + m_updatedTypes.resize( nType + 1 ); // new are default-constructed, i.e. false + m_updatedTypes[ nType ] = value; + if(value) + startTimer(); +} + +void CallbackFlushHandler::resetUpdatedType( int nType ) +{ + setUpdatedType( nType, false ); +} + +void CallbackFlushHandler::setUpdatedTypePerViewId( int nType, int nViewId, int nSourceViewId, bool value ) +{ + assert(isUpdatedTypePerViewId(nType)); + std::vector<PerViewIdData>& types = m_updatedTypesPerViewId[ nViewId ]; + if( types.size() <= o3tl::make_unsigned( nType )) + types.resize( nType + 1 ); // new are default-constructed, i.e. 'set' is false + types[ nType ] = PerViewIdData{ value, nSourceViewId }; + if(value) + startTimer(); +} + +void CallbackFlushHandler::resetUpdatedTypePerViewId( int nType, int nViewId ) +{ + assert(isUpdatedTypePerViewId(nType)); + bool allViewIds = false; + // Handle specially messages that do not have viewId for backwards compatibility. + if( nType == LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR && !comphelper::LibreOfficeKit::isViewIdForVisCursorInvalidation()) + allViewIds = true; + if( !allViewIds ) + { + setUpdatedTypePerViewId( nType, nViewId, -1, false ); + return; + } + for( auto& it : m_updatedTypesPerViewId ) + { + std::vector<PerViewIdData>& types = it.second; + if( types.size() >= o3tl::make_unsigned( nType )) + types[ nType ].set = false; + } +} + +void CallbackFlushHandler::libreOfficeKitViewCallback(int nType, const OString& pPayload) +{ + CallbackData callbackData(pPayload); + queue(nType, callbackData); +} + +void CallbackFlushHandler::libreOfficeKitViewCallbackWithViewId(int nType, const OString& pPayload, int nViewId) +{ + CallbackData callbackData(pPayload, nViewId); + queue(nType, callbackData); +} + +void CallbackFlushHandler::libreOfficeKitViewInvalidateTilesCallback(const tools::Rectangle* pRect, int nPart, int nMode) +{ + CallbackData callbackData(pRect, nPart, nMode); + queue(LOK_CALLBACK_INVALIDATE_TILES, callbackData); +} + +void CallbackFlushHandler::libreOfficeKitViewUpdatedCallback(int nType) +{ + assert(isUpdatedType( nType )); + std::unique_lock<std::recursive_mutex> lock(m_mutex); + SAL_INFO("lok", "Updated: [" << nType << "]"); + setUpdatedType(nType, true); +} + +void CallbackFlushHandler::libreOfficeKitViewUpdatedCallbackPerViewId(int nType, int nViewId, int nSourceViewId) +{ + assert(isUpdatedTypePerViewId( nType )); + std::unique_lock<std::recursive_mutex> lock(m_mutex); + SAL_INFO("lok", "Updated: [" << nType << "]"); + setUpdatedTypePerViewId(nType, nViewId, nSourceViewId, true); +} + +void CallbackFlushHandler::dumpState(rtl::OStringBuffer &rState) +{ + // NB. no locking + rState.append("\nView:\t"); + rState.append(static_cast<sal_Int32>(m_viewId)); + rState.append("\n\tDisableCallbacks:\t"); + rState.append(static_cast<sal_Int32>(m_nDisableCallbacks)); + rState.append("\n\tStates:\n"); + for (const auto &i : m_states) + { + rState.append("\n\t\t"); + rState.append(static_cast<sal_Int32>(i.first)); + rState.append("\t"); + rState.append(i.second); + } +} + +void CallbackFlushHandler::libreOfficeKitViewAddPendingInvalidateTiles() +{ + // Invoke() will call flushPendingLOKInvalidateTiles(), so just make sure the timer is active. + startTimer(); +} + +void CallbackFlushHandler::queue(const int type, const OString& data) +{ + CallbackData callbackData(data); + queue(type, callbackData); +} + +void CallbackFlushHandler::queue(const int type, CallbackData& aCallbackData) +{ + comphelper::ProfileZone aZone("CallbackFlushHandler::queue"); + + SAL_INFO("lok", "Queue: [" << type << "]: [" << aCallbackData.getPayload() << "] on " << m_queue1.size() << " entries."); + + bool bIsChartActive = false; + bool bIsComment = false; + if (type == LOK_CALLBACK_GRAPHIC_SELECTION) + { + LokChartHelper aChartHelper(SfxViewShell::Current()); + bIsChartActive = aChartHelper.GetWindow() != nullptr; + } + else if (type == LOK_CALLBACK_COMMENT) + { + bIsComment = true; + } + + if (callbacksDisabled() && !bIsChartActive && !bIsComment) + { + // We drop notifications when this is set, except for important ones. + // When we issue a complex command (such as .uno:InsertAnnotation) + // there will be multiple notifications. On the first invalidation + // we will start painting, but other events will get fired + // while the complex command in question executes. + // We don't want to suppress everything here on the wrong assumption + // that no new events are fired during painting. + if (type != LOK_CALLBACK_STATE_CHANGED && + type != LOK_CALLBACK_INVALIDATE_TILES && + type != LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR && + type != LOK_CALLBACK_CURSOR_VISIBLE && + type != LOK_CALLBACK_VIEW_CURSOR_VISIBLE && + type != LOK_CALLBACK_TEXT_SELECTION && + type != LOK_CALLBACK_TEXT_SELECTION_START && + type != LOK_CALLBACK_TEXT_SELECTION_END && + type != LOK_CALLBACK_MEDIA_SHAPE && + type != LOK_CALLBACK_REFERENCE_MARKS) + { + SAL_INFO("lok", "Skipping while painting [" << type << "]: [" << aCallbackData.getPayload() << "]."); + return; + } + + // In Writer we drop all notifications during painting. + if (doc_getDocumentType(m_pDocument) == LOK_DOCTYPE_TEXT) + return; + } + + // Suppress invalid payloads. + if (type == LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR && + aCallbackData.getPayload().indexOf(", 0, 0, ") != -1 && + aCallbackData.getPayload().indexOf("\"hyperlink\":\"\"") == -1 && + aCallbackData.getPayload().indexOf("\"hyperlink\": {}") == -1) + { + // The cursor position is often the relative coordinates of the widget + // issuing it, instead of the absolute one that we expect. + // This is temporary however, and, once the control is created and initialized + // correctly, it eventually emits the correct absolute coordinates. + SAL_INFO("lok", "Skipping invalid event [" << type << "]: [" << aCallbackData.getPayload() << "]."); + return; + } + + std::unique_lock<std::recursive_mutex> lock(m_mutex); + + // Update types should be received via the updated callbacks for performance, + // getting them as normal callbacks is technically not wrong, but probably should be avoided. + // Reset the updated flag if we get a normal message. + if(isUpdatedType(type)) + { + SAL_INFO("lok", "Received event with updated type [" << type << "] as normal callback"); + resetUpdatedType(type); + } + if(isUpdatedTypePerViewId(type)) + { + SAL_INFO("lok", "Received event with updated type [" << type << "] as normal callback"); + resetUpdatedTypePerViewId(type, aCallbackData.getViewId()); + } + + // drop duplicate callbacks for the listed types + switch (type) + { + case LOK_CALLBACK_TEXT_SELECTION_START: + case LOK_CALLBACK_TEXT_SELECTION_END: + case LOK_CALLBACK_TEXT_SELECTION: + case LOK_CALLBACK_GRAPHIC_SELECTION: + case LOK_CALLBACK_GRAPHIC_VIEW_SELECTION: + case LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR: + case LOK_CALLBACK_INVALIDATE_VIEW_CURSOR: + case LOK_CALLBACK_STATE_CHANGED: + case LOK_CALLBACK_MOUSE_POINTER: + case LOK_CALLBACK_CELL_CURSOR: + case LOK_CALLBACK_CELL_VIEW_CURSOR: + case LOK_CALLBACK_CELL_FORMULA: + case LOK_CALLBACK_CELL_ADDRESS: + case LOK_CALLBACK_CELL_SELECTION_AREA: + case LOK_CALLBACK_CURSOR_VISIBLE: + case LOK_CALLBACK_VIEW_CURSOR_VISIBLE: + case LOK_CALLBACK_SET_PART: + case LOK_CALLBACK_TEXT_VIEW_SELECTION: + case LOK_CALLBACK_INVALIDATE_HEADER: + case LOK_CALLBACK_WINDOW: + case LOK_CALLBACK_CALC_FUNCTION_LIST: + case LOK_CALLBACK_INVALIDATE_SHEET_GEOMETRY: + case LOK_CALLBACK_REFERENCE_MARKS: + case LOK_CALLBACK_CELL_AUTO_FILL_AREA: + case LOK_CALLBACK_A11Y_FOCUS_CHANGED: + case LOK_CALLBACK_A11Y_CARET_CHANGED: + case LOK_CALLBACK_A11Y_TEXT_SELECTION_CHANGED: + case LOK_CALLBACK_A11Y_FOCUSED_CELL_CHANGED: + case LOK_CALLBACK_COLOR_PALETTES: + case LOK_CALLBACK_A11Y_EDITING_IN_SELECTION_STATE: + case LOK_CALLBACK_A11Y_SELECTION_CHANGED: + { + const auto& pos = std::find(m_queue1.rbegin(), m_queue1.rend(), type); + auto pos2 = toQueue2(pos); + if (pos != m_queue1.rend() && pos2->getPayload() == aCallbackData.getPayload()) + { + SAL_INFO("lok", "Skipping queue duplicate [" << type << + "]: [" << aCallbackData.getPayload() << "]."); + return; + } + } + break; + } + + if (type == LOK_CALLBACK_TEXT_SELECTION && aCallbackData.isEmpty()) + { + const auto& posStart = std::find(m_queue1.rbegin(), m_queue1.rend(), LOK_CALLBACK_TEXT_SELECTION_START); + auto posStart2 = toQueue2(posStart); + if (posStart != m_queue1.rend()) + posStart2->clear(); + + const auto& posEnd = std::find(m_queue1.rbegin(), m_queue1.rend(), LOK_CALLBACK_TEXT_SELECTION_END); + auto posEnd2 = toQueue2(posEnd); + if (posEnd != m_queue1.rend()) + posEnd2->clear(); + } + + // When payload is empty discards any previous state. + if (aCallbackData.isEmpty()) + { + switch (type) + { + case LOK_CALLBACK_TEXT_SELECTION_START: + case LOK_CALLBACK_TEXT_SELECTION_END: + case LOK_CALLBACK_TEXT_SELECTION: + case LOK_CALLBACK_GRAPHIC_SELECTION: + case LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR: + case LOK_CALLBACK_INVALIDATE_TILES: + if (removeAll(type)) + SAL_INFO("lok", "Removed dups of [" << type << "]: [" << aCallbackData.getPayload() << "]."); + break; + } + } + else + { + switch (type) + { + // These are safe to use the latest state and ignore previous + // ones (if any) since the last overrides previous ones. + case LOK_CALLBACK_TEXT_SELECTION_START: + case LOK_CALLBACK_TEXT_SELECTION_END: + case LOK_CALLBACK_TEXT_SELECTION: + case LOK_CALLBACK_MOUSE_POINTER: + case LOK_CALLBACK_CELL_CURSOR: + case LOK_CALLBACK_CELL_FORMULA: + case LOK_CALLBACK_CELL_ADDRESS: + case LOK_CALLBACK_CURSOR_VISIBLE: + case LOK_CALLBACK_SET_PART: + case LOK_CALLBACK_STATUS_INDICATOR_SET_VALUE: + case LOK_CALLBACK_RULER_UPDATE: + case LOK_CALLBACK_A11Y_FOCUS_CHANGED: + case LOK_CALLBACK_A11Y_CARET_CHANGED: + case LOK_CALLBACK_A11Y_TEXT_SELECTION_CHANGED: + case LOK_CALLBACK_A11Y_FOCUSED_CELL_CHANGED: + case LOK_CALLBACK_COLOR_PALETTES: + { + if (removeAll(type)) + SAL_INFO("lok", "Removed dups of [" << type << "]: [" << aCallbackData.getPayload() << "]."); + } + break; + + // These are safe to use the latest state and ignore previous + // ones (if any) since the last overrides previous ones, + // but only if the view is the same. + case LOK_CALLBACK_CELL_VIEW_CURSOR: + case LOK_CALLBACK_GRAPHIC_VIEW_SELECTION: + case LOK_CALLBACK_INVALIDATE_VIEW_CURSOR: + case LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR: + case LOK_CALLBACK_TEXT_VIEW_SELECTION: + case LOK_CALLBACK_VIEW_CURSOR_VISIBLE: + case LOK_CALLBACK_CALC_FUNCTION_LIST: + case LOK_CALLBACK_FORM_FIELD_BUTTON: + { + // deleting the duplicate of visible cursor message can cause hyperlink popup not to show up on second/or more click on the same place. + // If the hyperlink is not empty we can bypass that to show the popup + const bool hyperLinkException = type == LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR && + aCallbackData.getPayload().indexOf("\"hyperlink\":\"\"") == -1 && + aCallbackData.getPayload().indexOf("\"hyperlink\": {}") == -1; + if(!hyperLinkException) + { + const int nViewId = aCallbackData.getViewId(); + removeAll(type, [nViewId] (const CallbackData& elemData) { + return (nViewId == elemData.getViewId()); + } + ); + } + } + break; + + case LOK_CALLBACK_INVALIDATE_TILES: + if (processInvalidateTilesEvent(type, aCallbackData)) + return; + break; + + // State changes with same name override previous ones with a different value. + // Ex. ".uno:PageStatus=Slide 20 of 83" overwrites any previous PageStatus. + case LOK_CALLBACK_STATE_CHANGED: + { + // Compare the state name=value and overwrite earlier entries with same name. + const auto pos = aCallbackData.getPayload().indexOf('='); + if (pos != -1) + { + const std::string_view name = aCallbackData.getPayload().subView(0, pos + 1); + // This is needed because otherwise it creates some problems when + // a save occurs while a cell is still edited in Calc. + if (name != ".uno:ModifiedStatus=") + { + removeAll(type, [&name] (const CallbackData& elemData) { + return elemData.getPayload().startsWith(name); + } + ); + } + } + } + break; + + case LOK_CALLBACK_WINDOW: + if (processWindowEvent(type, aCallbackData)) + return; + break; + + case LOK_CALLBACK_GRAPHIC_SELECTION: + { + // remove only selection ranges and 'EMPTY' messages + // always send 'INPLACE' and 'INPLACE EXIT' messages + removeAll(type, [] (const CallbackData& elemData) + { return (elemData.getPayload().indexOf("INPLACE") == -1); }); + } + break; + } + } + + // Validate that the cached data and the payload string are identical. + assert(aCallbackData.validate() && "Cached callback payload object and string mismatch!"); + m_queue1.emplace_back(type); + m_queue2.emplace_back(aCallbackData); + SAL_INFO("lok", "Queued #" << (m_queue1.size() - 1) << + " [" << type << "]: [" << aCallbackData.getPayload() << "] to have " << m_queue1.size() << " entries."); + +#ifdef DBG_UTIL + { + // Dump the queue state and validate cached data. + int i = 1; + std::ostringstream oss; + if (m_queue1.empty()) + oss << "Empty"; + else + oss << m_queue1.size() << " items\n"; + auto it1 = m_queue1.begin(); + auto it2 = m_queue2.begin(); + for (; it1 != m_queue1.end(); ++it1, ++it2) + oss << i++ << ": [" << *it1 << "] [" << it2->getPayload() << "].\n"; + SAL_INFO("lok", "Current Queue: " << oss.str()); + assert( + std::all_of( + m_queue2.begin(), m_queue2.end(), + [](const CallbackData& c) { return c.validate(); })); + } +#endif + + lock.unlock(); + startTimer(); +} + +bool CallbackFlushHandler::processInvalidateTilesEvent(int type, CallbackData& aCallbackData) +{ + RectangleAndPart rcNew = aCallbackData.getRectangleAndPart(); + if (rcNew.isEmpty()) + { + SAL_INFO("lok", "Skipping invalid event [" << type << "]: [" << aCallbackData.getPayload() << "]."); + return true; + } + + // If we have to invalidate all tiles, we can skip any new tile invalidation. + // Find the last INVALIDATE_TILES entry, if any to see if it's invalidate-all. + const auto& pos + = std::find(m_queue1.rbegin(), m_queue1.rend(), LOK_CALLBACK_INVALIDATE_TILES); + if (pos != m_queue1.rend()) + { + auto pos2 = toQueue2(pos); + const RectangleAndPart& rcOld = pos2->getRectangleAndPart(); + if (rcOld.isInfinite() && (rcOld.m_nPart == -1 || rcOld.m_nPart == rcNew.m_nPart) && + (rcOld.m_nMode == rcNew.m_nMode)) + { + SAL_INFO("lok", "Skipping queue [" << type << "]: [" << aCallbackData.getPayload() + << "] since all tiles need to be invalidated."); + return true; + } + + if ((rcOld.m_nPart == -1 || rcOld.m_nPart == rcNew.m_nPart) && (rcOld.m_nMode == rcNew.m_nMode)) + { + // If fully overlapping. + if (rcOld.m_aRectangle.Contains(rcNew.m_aRectangle)) + { + SAL_INFO("lok", "Skipping queue [" << type << "]: [" << aCallbackData.getPayload() + << "] since overlaps existing all-parts."); + return true; + } + } + } + + if (rcNew.isInfinite()) + { + SAL_INFO("lok", "Have Empty [" << type << "]: [" << aCallbackData.getPayload() + << "] so removing all with part " << rcNew.m_nPart << "."); + removeAll(LOK_CALLBACK_INVALIDATE_TILES, [&rcNew](const CallbackData& elemData) { + // Remove exiting if new is all-encompassing, or if of the same part. + return ((rcNew.m_nPart == -1 || rcNew.m_nPart == elemData.getRectangleAndPart().m_nPart) + && (rcNew.m_nMode == elemData.getRectangleAndPart().m_nMode)); + }); + } + else + { + const auto rcOrig = rcNew; + + SAL_INFO("lok", "Have [" << type << "]: [" << aCallbackData.getPayload() << "] so merging overlapping."); + removeAll(LOK_CALLBACK_INVALIDATE_TILES,[&rcNew](const CallbackData& elemData) { + const RectangleAndPart& rcOld = elemData.getRectangleAndPart(); + if (rcNew.m_nPart != -1 && rcOld.m_nPart != -1 && + (rcOld.m_nPart != rcNew.m_nPart || rcOld.m_nMode != rcNew.m_nMode)) + { + SAL_INFO("lok", "Nothing to merge between new: " + << rcNew.toString() << ", and old: " << rcOld.toString()); + return false; + } + + if (rcNew.m_nPart == -1) + { + // Don't merge unless fully overlapped. + SAL_INFO("lok", "New " << rcNew.toString() << " has " << rcOld.toString() + << "?"); + if (rcNew.m_aRectangle.Contains(rcOld.m_aRectangle) && rcOld.m_nMode == rcNew.m_nMode) + { + SAL_INFO("lok", "New " << rcNew.toString() << " engulfs old " + << rcOld.toString() << "."); + return true; + } + } + else if (rcOld.m_nPart == -1) + { + // Don't merge unless fully overlapped. + SAL_INFO("lok", "Old " << rcOld.toString() << " has " << rcNew.toString() + << "?"); + if (rcOld.m_aRectangle.Contains(rcNew.m_aRectangle) && rcOld.m_nMode == rcNew.m_nMode) + { + SAL_INFO("lok", "New " << rcNew.toString() << " engulfs old " + << rcOld.toString() << "."); + return true; + } + } + else + { + const tools::Rectangle rcOverlap + = rcNew.m_aRectangle.GetIntersection(rcOld.m_aRectangle); + const bool bOverlap = !rcOverlap.IsEmpty() && rcOld.m_nMode == rcNew.m_nMode; + SAL_INFO("lok", "Merging " << rcNew.toString() << " & " << rcOld.toString() + << " => " << rcOverlap.toString() + << " Overlap: " << bOverlap); + if (bOverlap) + { + rcNew.m_aRectangle.Union(rcOld.m_aRectangle); + SAL_INFO("lok", "Merged: " << rcNew.toString()); + return true; + } + } + + // Keep others. + return false; + }); + + if (rcNew.m_aRectangle != rcOrig.m_aRectangle) + { + SAL_INFO("lok", "Replacing: " << rcOrig.toString() << " by " << rcNew.toString()); + if (rcNew.m_aRectangle.GetWidth() < rcOrig.m_aRectangle.GetWidth() + || rcNew.m_aRectangle.GetHeight() < rcOrig.m_aRectangle.GetHeight()) + { + SAL_WARN("lok", "Error: merged rect smaller."); + } + } + } + + aCallbackData.updateRectangleAndPart(rcNew); + // Queue this one. + return false; +} + +bool CallbackFlushHandler::processWindowEvent(int type, CallbackData& aCallbackData) +{ + const OString& payload = aCallbackData.getPayload(); + + boost::property_tree::ptree& aTree = aCallbackData.setJson(std::string(payload)); + const unsigned nLOKWindowId = aTree.get<unsigned>("id", 0); + const std::string aAction = aTree.get<std::string>("action", ""); + if (aAction == "invalidate") + { + std::string aRectStr = aTree.get<std::string>("rectangle", ""); + // no 'rectangle' field => invalidate all of the window => + // remove all previous window part invalidations + if (aRectStr.empty()) + { + removeAll(LOK_CALLBACK_WINDOW,[&nLOKWindowId](const CallbackData& elemData) { + const boost::property_tree::ptree& aOldTree = elemData.getJson(); + if (nLOKWindowId == aOldTree.get<unsigned>("id", 0) + && aOldTree.get<std::string>("action", "") == "invalidate") + { + return true; + } + return false; + }); + } + else + { + // if we have to invalidate all of the window, ignore + // any part invalidation message + bool invAllExist = false; + auto it1 = m_queue1.rbegin(); + auto it2 = m_queue2.rbegin(); + for (;it1 != m_queue1.rend(); ++it1, ++it2) + { + if (*it1 != LOK_CALLBACK_WINDOW) + continue; + const boost::property_tree::ptree& aOldTree = it2->getJson(); + if (nLOKWindowId == aOldTree.get<unsigned>("id", 0) + && aOldTree.get<std::string>("action", "") == "invalidate" + && aOldTree.get<std::string>("rectangle", "").empty()) + { + invAllExist = true; + break; + } + } + + // we found a invalidate-all window callback + if (invAllExist) + { + SAL_INFO("lok.dialog", "Skipping queue [" + << type << "]: [" << payload + << "] since whole window needs to be invalidated."); + return true; + } + + std::istringstream aRectStream(aRectStr); + tools::Long nLeft, nTop, nWidth, nHeight; + char nComma; + aRectStream >> nLeft >> nComma >> nTop >> nComma >> nWidth >> nComma >> nHeight; + tools::Rectangle aNewRect(nLeft, nTop, nLeft + nWidth, nTop + nHeight); + bool currentIsRedundant = false; + removeAll(LOK_CALLBACK_WINDOW, [&aNewRect, &nLOKWindowId, + ¤tIsRedundant](const CallbackData& elemData) { + const boost::property_tree::ptree& aOldTree = elemData.getJson(); + if (aOldTree.get<std::string>("action", "") == "invalidate") + { + // Not possible that we encounter an empty rectangle here; we already handled this case above. + std::istringstream aOldRectStream(aOldTree.get<std::string>("rectangle", "")); + tools::Long nOldLeft, nOldTop, nOldWidth, nOldHeight; + char nOldComma; + aOldRectStream >> nOldLeft >> nOldComma >> nOldTop >> nOldComma >> nOldWidth + >> nOldComma >> nOldHeight; + const tools::Rectangle aOldRect = tools::Rectangle( + nOldLeft, nOldTop, nOldLeft + nOldWidth, nOldTop + nOldHeight); + + if (nLOKWindowId == aOldTree.get<unsigned>("id", 0)) + { + if (aNewRect == aOldRect) + { + SAL_INFO("lok.dialog", "Duplicate rect [" << aNewRect.toString() + << "]. Skipping new."); + // We have a rectangle in the queue already that makes the current Callback useless. + currentIsRedundant = true; + return false; + } + // new one engulfs the old one? + else if (aNewRect.Contains(aOldRect)) + { + SAL_INFO("lok.dialog", + "New rect [" << aNewRect.toString() << "] engulfs old [" + << aOldRect.toString() << "]. Replacing old."); + return true; + } + // old one engulfs the new one? + else if (aOldRect.Contains(aNewRect)) + { + SAL_INFO("lok.dialog", + "Old rect [" << aOldRect.toString() << "] engulfs new [" + << aNewRect.toString() << "]. Skipping new."); + // We have a rectangle in the queue already that makes the current Callback useless. + currentIsRedundant = true; + return false; + } + else + { + // Overlapping rects. + const tools::Rectangle aPreMergeRect = aNewRect; + aNewRect.Union(aOldRect); + SAL_INFO("lok.dialog", "Merging rects [" + << aPreMergeRect.toString() << "] & [" + << aOldRect.toString() << "] = [" + << aNewRect.toString() + << "]. Replacing old."); + return true; + } + } + } + + // keep rest + return false; + }); + + // Do not enqueue if redundant. + if (currentIsRedundant) + return true; + + aTree.put("rectangle", aNewRect.toString().getStr()); + aCallbackData.setJson(aTree); + assert(aCallbackData.validate() && "Validation after setJson failed!"); + } + } + else if (aAction == "created") + { + // Remove all previous actions on same dialog, if we are creating it anew. + removeAll(LOK_CALLBACK_WINDOW,[&nLOKWindowId](const CallbackData& elemData) { + const boost::property_tree::ptree& aOldTree = elemData.getJson(); + if (nLOKWindowId == aOldTree.get<unsigned>("id", 0)) + return true; + return false; + }); + + VclPtr<Window> pWindow = vcl::Window::FindLOKWindow(nLOKWindowId); + if (!pWindow) + { + SetLastExceptionMsg(u"Document doesn't support dialog rendering, or window not found."_ustr); + return false; + } + +#ifndef IOS + auto xClip = forceSetClipboardForCurrentView(m_pDocument); + + uno::Reference<datatransfer::clipboard::XClipboard> xClipboard(xClip); + pWindow->SetClipboard(xClipboard); +#endif + } + else if (aAction == "size_changed") + { + // A size change is practically re-creation of the window. + // But at a minimum it's a full invalidation. + removeAll(LOK_CALLBACK_WINDOW, [&nLOKWindowId](const CallbackData& elemData) { + const boost::property_tree::ptree& aOldTree = elemData.getJson(); + if (nLOKWindowId == aOldTree.get<unsigned>("id", 0)) + { + const std::string aOldAction = aOldTree.get<std::string>("action", ""); + if (aOldAction == "invalidate") + return true; + } + return false; + }); + } + + // Queue this one. + return false; +} + +void CallbackFlushHandler::enqueueUpdatedTypes() +{ + if( m_updatedTypes.empty() && m_updatedTypesPerViewId.empty()) + return; + assert(m_viewId >= 0); + SfxViewShell* viewShell = SfxViewShell::GetFirst( false, + [this](const SfxViewShell* shell) { return shell->GetViewShellId().get() == m_viewId; } ); + assert(viewShell != nullptr); + + // First move data to local structures, so that callbacks don't possibly modify it. + std::vector<bool> updatedTypes; + std::swap(updatedTypes, m_updatedTypes); + boost::container::flat_map<int, std::vector<PerViewIdData>> updatedTypesPerViewId; + std::swap(updatedTypesPerViewId, m_updatedTypesPerViewId); + + // Some types must always precede other types, for example + // LOK_CALLBACK_TEXT_SELECTION_START and LOK_CALLBACK_TEXT_SELECTION_END + // must always precede LOK_CALLBACK_TEXT_SELECTION if present. + // Only these types should be present (see isUpdatedType()) and should be processed in this order. + static const int orderedUpdatedTypes[] = { + LOK_CALLBACK_TEXT_SELECTION_START, LOK_CALLBACK_TEXT_SELECTION_END, LOK_CALLBACK_TEXT_SELECTION }; + // Only these types should be present (see isUpdatedTypePerViewId()) and (as of now) + // the order doesn't matter. + static const int orderedUpdatedTypesPerViewId[] = { + LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR, + LOK_CALLBACK_INVALIDATE_VIEW_CURSOR, + LOK_CALLBACK_TEXT_VIEW_SELECTION }; + + for( int type : orderedUpdatedTypes ) + { + if(o3tl::make_unsigned( type ) < updatedTypes.size() && updatedTypes[ type ]) + { + enqueueUpdatedType( type, viewShell, m_viewId ); + } + } + for( const auto& it : updatedTypesPerViewId ) + { + int viewId = it.first; + const std::vector<PerViewIdData>& types = it.second; + for( int type : orderedUpdatedTypesPerViewId ) + { + if(o3tl::make_unsigned( type ) < types.size() && types[ type ].set) + { + SfxViewShell* sourceViewShell = viewShell; + const int sourceViewId = types[ type ].sourceViewId; + if( sourceViewId != m_viewId ) + { + assert(sourceViewId >= 0); + sourceViewShell = SfxViewShell::GetFirst( false, + [sourceViewId](const SfxViewShell* shell) { return shell->GetViewShellId().get() == sourceViewId; } ); + } + if(sourceViewShell == nullptr) + { + SAL_INFO("lok", "View #" << sourceViewId << " no longer found for updated event [" << type << "]"); + continue; // View removed, probably cleaning up. + } + enqueueUpdatedType( type, sourceViewShell, viewId ); + } + } + } +} + +void CallbackFlushHandler::enqueueUpdatedType( int type, const SfxViewShell* viewShell, int viewId ) +{ + if (type == LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR) + { + if (const SfxViewShell* viewShell2 = LokStarMathHelper(viewShell).GetSmViewShell()) + viewShell = viewShell2; + } + std::optional<OString> payload = viewShell->getLOKPayload( type, viewId ); + if(!payload) + return; // No actual payload to send. + CallbackData callbackData(*payload, viewId); + m_queue1.emplace_back(type); + m_queue2.emplace_back(callbackData); + SAL_INFO("lok", "Queued updated [" << type << "]: [" << callbackData.getPayload() + << "] to have " << m_queue1.size() << " entries."); +} + +void CallbackFlushHandler::Invoke() +{ + comphelper::ProfileZone aZone("CallbackFlushHandler::Invoke"); + + if (!m_pCallback) + return; + + // Get any pending invalidate tile events. This will call our callbacks, + // so it must be done before taking the mutex. + assert(m_viewId >= 0); + if(SfxViewShell* viewShell = SfxViewShell::GetFirst( false, + [this](const SfxViewShell* shell) { return shell->GetViewShellId().get() == m_viewId; } )) + { + viewShell->flushPendingLOKInvalidateTiles(); + } + + std::unique_lock<std::recursive_mutex> lock(m_mutex); + + // Append messages for updated types, fetch them only now. + enqueueUpdatedTypes(); + + SAL_INFO("lok", "Flushing " << m_queue1.size() << " elements."); + auto it1 = m_queue1.begin(); + auto it2 = m_queue2.begin(); + for (; it1 != m_queue1.end(); ++it1, ++it2) + { + const int type = *it1; + const auto& payload = it2->getPayload(); + const int viewId = lcl_isViewCallbackType(type) ? it2->getViewId() : -1; + + SAL_INFO("lok", "processing event: [" << type << ',' << viewId << "]: [" << payload << "]."); + + // common code-path for events on this view: + if (viewId == -1) + { + sal_Int32 idx; + // key-value pairs + if (type == LOK_CALLBACK_STATE_CHANGED && + (idx = payload.indexOf('=')) != -1) + { + OString key = payload.copy(0, idx); + OString value = payload.copy(idx+1); + const auto stateIt = m_lastStateChange.find(key); + if (stateIt != m_lastStateChange.end()) + { + // If the value didn't change, it's safe to ignore. + if (stateIt->second == value) + { + SAL_INFO("lok", "Skipping new state duplicate: [" << type << "]: [" << payload << "]."); + continue; + } + SAL_INFO("lok", "Replacing a state element [" << type << "]: [" << payload << "]."); + stateIt->second = value; + } + else + { + SAL_INFO("lok", "Inserted a new state element: [" << type << "]: [" << payload << "]"); + m_lastStateChange.emplace(key, value); + } + } + else + { + const auto stateIt = m_states.find(type); + if (stateIt != m_states.end()) + { + // If the state didn't change, it's safe to ignore. + if (stateIt->second == payload) + { + SAL_INFO("lok", "Skipping duplicate [" << type << "]: [" << payload << "]."); + continue; + } + stateIt->second = payload; + } + } + } + else // less common path for events relating to other views + { + const auto statesIt = m_viewStates.find(viewId); + if (statesIt != m_viewStates.end()) + { + auto& states = statesIt->second; + const auto stateIt = states.find(type); + if (stateIt != states.end()) + { + // If the state didn't change, it's safe to ignore. + if (stateIt->second == payload) + { + SAL_INFO("lok", "Skipping view duplicate [" << type << ',' << viewId << "]: [" << payload << "]."); + continue; + } + + SAL_INFO("lok", "Replacing an element in view states [" << type << ',' << viewId << "]: [" << payload << "]."); + stateIt->second = payload; + } + else + { + SAL_INFO("lok", "Inserted a new element in view states: [" << type << ',' << viewId << "]: [" << payload << "]"); + states.emplace(type, payload); + + } + } + } + + m_pCallback(type, payload.getStr(), m_pData); + } + + m_queue1.clear(); + m_queue2.clear(); + Stop(); + m_TimeoutIdle.Stop(); +} + +void CallbackFlushHandler::startTimer() +{ + if (!IsActive()) + Start(); + if (!m_TimeoutIdle.IsActive()) + m_TimeoutIdle.Start(); +} + +bool CallbackFlushHandler::removeAll(int type) +{ + bool bErased = false; + auto it1 = m_queue1.begin(); + for(;;) + { + it1 = std::find(it1, m_queue1.end(), type); + if(it1 == m_queue1.end()) + break; + m_queue2.erase(toQueue2(it1)); + it1 = m_queue1.erase(it1); + bErased = true; + } + return bErased; +} + +bool CallbackFlushHandler::removeAll(int type, const std::function<bool (const CallbackData&)>& rTestFunc) +{ + bool bErased = false; + auto it1 = m_queue1.begin(); + for(;;) + { + it1 = std::find(it1, m_queue1.end(), type); + if(it1 == m_queue1.end()) + break; + auto it2 = toQueue2(it1); + if (rTestFunc(*it2)) + { + m_queue2.erase(it2); + it1 = m_queue1.erase(it1); + bErased = true; + } + else + ++it1; + } + return bErased; +} + +void CallbackFlushHandler::addViewStates(int viewId) +{ + const auto& result = m_viewStates.emplace(viewId, decltype(m_viewStates)::mapped_type()); + if (!result.second && result.first != m_viewStates.end()) + { + result.first->second.clear(); + } +} + +void CallbackFlushHandler::removeViewStates(int viewId) +{ + m_viewStates.erase(viewId); +} + + +static void doc_destroy(LibreOfficeKitDocument *pThis) +{ + comphelper::ProfileZone aZone("doc_destroy"); + + SolarMutexGuard aGuard; + +#ifndef IOS + LOKClipboardFactory::releaseClipboardForView(-1); +#endif + + LibLODocument_Impl *pDocument = static_cast<LibLODocument_Impl*>(pThis); + delete pDocument; +} + +static void lo_destroy (LibreOfficeKit* pThis); +static int lo_initialize (LibreOfficeKit* pThis, const char* pInstallPath, const char* pUserProfilePath); +static LibreOfficeKitDocument* lo_documentLoad (LibreOfficeKit* pThis, const char* pURL); +static char * lo_getError (LibreOfficeKit* pThis); +static void lo_freeError (char* pFree); +static LibreOfficeKitDocument* lo_documentLoadWithOptions (LibreOfficeKit* pThis, + const char* pURL, + const char* pOptions); +static void lo_registerCallback (LibreOfficeKit* pThis, + LibreOfficeKitCallback pCallback, + void* pData); +static char* lo_getFilterTypes(LibreOfficeKit* pThis); +static void lo_setOptionalFeatures(LibreOfficeKit* pThis, unsigned long long features); +static void lo_setDocumentPassword(LibreOfficeKit* pThis, + const char* pURL, + const char* pPassword); +static char* lo_getVersionInfo(LibreOfficeKit* pThis); +static int lo_runMacro (LibreOfficeKit* pThis, const char* pURL); + +static bool lo_signDocument(LibreOfficeKit* pThis, + const char* pUrl, + const unsigned char* pCertificateBinary, + const int nCertificateBinarySize, + const unsigned char* pPrivateKeyBinary, + const int nPrivateKeyBinarySize); + +static char* lo_extractRequest(LibreOfficeKit* pThis, + const char* pFilePath); + +static void lo_trimMemory(LibreOfficeKit* pThis, int nTarget); + +static void* +lo_startURP(LibreOfficeKit* pThis, void* pReceiveURPFromLOContext, void* pSendURPToLOContext, + int (*fnReceiveURPFromLO)(void* pContext, const signed char* pBuffer, int nLen), + int (*fnSendURPToLO)(void* pContext, signed char* pBuffer, int nLen)); + +static void lo_stopURP(LibreOfficeKit* pThis, void* pSendURPToLOContext); + +static void lo_runLoop(LibreOfficeKit* pThis, + LibreOfficeKitPollCallback pPollCallback, + LibreOfficeKitWakeCallback pWakeCallback, + void* pData); + +static void lo_sendDialogEvent(LibreOfficeKit* pThis, + unsigned long long int nLOKWindowId, + const char* pArguments); + +static void lo_setOption(LibreOfficeKit* pThis, const char* pOption, const char* pValue); + +static void lo_dumpState(LibreOfficeKit* pThis, const char* pOptions, char** pState); + +LibLibreOffice_Impl::LibLibreOffice_Impl() + : m_pOfficeClass( gOfficeClass.lock() ) + , maThread(nullptr) + , mpCallback(nullptr) + , mpCallbackData(nullptr) + , mOptionalFeatures(0) +{ + if(!m_pOfficeClass) { + m_pOfficeClass = std::make_shared<LibreOfficeKitClass>(); + m_pOfficeClass->nSize = sizeof(LibreOfficeKitClass); + + m_pOfficeClass->destroy = lo_destroy; + m_pOfficeClass->documentLoad = lo_documentLoad; + m_pOfficeClass->getError = lo_getError; + m_pOfficeClass->freeError = lo_freeError; + m_pOfficeClass->documentLoadWithOptions = lo_documentLoadWithOptions; + m_pOfficeClass->registerCallback = lo_registerCallback; + m_pOfficeClass->getFilterTypes = lo_getFilterTypes; + m_pOfficeClass->setOptionalFeatures = lo_setOptionalFeatures; + m_pOfficeClass->setDocumentPassword = lo_setDocumentPassword; + m_pOfficeClass->getVersionInfo = lo_getVersionInfo; + m_pOfficeClass->runMacro = lo_runMacro; + m_pOfficeClass->signDocument = lo_signDocument; + m_pOfficeClass->runLoop = lo_runLoop; + m_pOfficeClass->sendDialogEvent = lo_sendDialogEvent; + m_pOfficeClass->setOption = lo_setOption; + m_pOfficeClass->dumpState = lo_dumpState; + m_pOfficeClass->extractRequest = lo_extractRequest; + m_pOfficeClass->trimMemory = lo_trimMemory; + m_pOfficeClass->startURP = lo_startURP; + m_pOfficeClass->stopURP = lo_stopURP; + + gOfficeClass = m_pOfficeClass; + } + + pClass = m_pOfficeClass.get(); +} + +LibLibreOffice_Impl::~LibLibreOffice_Impl() +{ +} + +namespace +{ + +void setLanguageAndLocale(OUString const & aLangISO) +{ + SvtSysLocaleOptions aLocalOptions; + aLocalOptions.SetLocaleConfigString(aLangISO); + aLocalOptions.SetUILocaleConfigString(aLangISO); + aLocalOptions.Commit(); +} + +void setFormatSpecificFilterData(std::u16string_view sFormat, comphelper::SequenceAsHashMap & rFilterDataMap) +{ + if (sFormat == u"pdf") + { + // always export bookmarks, which is needed for annotations + rFilterDataMap[u"ExportBookmarks"_ustr] <<= true; + } +} + +} // anonymous namespace + +// Wonder global state ... +static uno::Reference<css::uno::XComponentContext> xContext; +static uno::Reference<css::lang::XMultiServiceFactory> xSFactory; +static uno::Reference<css::lang::XMultiComponentFactory> xFactory; + +static LibreOfficeKitDocument* lo_documentLoad(LibreOfficeKit* pThis, const char* pURL) +{ + return lo_documentLoadWithOptions(pThis, pURL, nullptr); +} + +static LibreOfficeKitDocument* lo_documentLoadWithOptions(LibreOfficeKit* pThis, const char* pURL, const char* pOptions) +{ + comphelper::ProfileZone aZone("lo_documentLoadWithOptions"); + + SolarMutexGuard aGuard; + + static int nDocumentIdCounter = 0; + + LibLibreOffice_Impl* pLib = static_cast<LibLibreOffice_Impl*>(pThis); + pLib->maLastExceptionMsg.clear(); + + const OUString aURL(getAbsoluteURL(pURL)); + if (aURL.isEmpty()) + { + pLib->maLastExceptionMsg = u"Filename to load was not provided."_ustr; + SAL_INFO("lok", "URL for load is empty"); + return nullptr; + } + + pLib->maLastExceptionMsg.clear(); + + if (!xContext.is()) + { + pLib->maLastExceptionMsg = u"ComponentContext is not available"_ustr; + SAL_INFO("lok", "ComponentContext is not available"); + return nullptr; + } + + uno::Reference<frame::XDesktop2> xComponentLoader = frame::Desktop::create(xContext); + + if (!xComponentLoader.is()) + { + pLib->maLastExceptionMsg = u"ComponentLoader is not available"_ustr; + SAL_INFO("lok", "ComponentLoader is not available"); + return nullptr; + } + + try + { + // 'Language=...' is an option that LOK consumes by itself, and does + // not pass it as a parameter to the filter + OUString aOptions = getUString(pOptions); + const OUString aLanguage = extractParameter(aOptions, u"Language"); + + if (!aLanguage.isEmpty() && LanguageTag::isValidBcp47(aLanguage, nullptr)) + { + static bool isLoading = true; + if (isLoading) + { + // Capture the language used to load the document. + SfxLokHelper::setLoadLanguage(aLanguage); + isLoading = false; + } + + SfxLokHelper::setDefaultLanguage(aLanguage); + // Set the LOK language tag, used for dialog tunneling. + comphelper::LibreOfficeKit::setLanguageTag(LanguageTag(aLanguage)); + comphelper::LibreOfficeKit::setLocale(LanguageTag(aLanguage)); + + SAL_INFO("lok", "Set document language to " << aLanguage); + // use with care - it sets it for the entire core, not just the + // document + setLanguageAndLocale(aLanguage); + // Need to reset the static initialized values + SvNumberFormatter::resetTheCurrencyTable(); + } + + // Set the timezone, if not empty. + const OUString aTimezone = extractParameter(aOptions, u"Timezone"); + if (!aTimezone.isEmpty()) + { + SfxLokHelper::setDefaultTimezone(true, aTimezone); + } + else + { + // Default to the TZ envar, if set. + const char* tz = ::getenv("TZ"); + if (tz) + { + SfxLokHelper::setDefaultTimezone(true, + OStringToOUString(tz, RTL_TEXTENCODING_UTF8)); + } + else + { + SfxLokHelper::setDefaultTimezone(false, OUString()); + } + } + + const OUString aDeviceFormFactor = extractParameter(aOptions, u"DeviceFormFactor"); + SfxLokHelper::setDeviceFormFactor(aDeviceFormFactor); + + const OUString aBatch = extractParameter(aOptions, u"Batch"); + if (!aBatch.isEmpty()) + { + Application::SetDialogCancelMode(DialogCancelMode::LOKSilent); + } + + const OUString sFilterOptions = aOptions; + + rtl::Reference<LOKInteractionHandler> const pInteraction( + new LOKInteractionHandler("load"_ostr, pLib)); + auto const pair(pLib->mInteractionMap.insert(std::make_pair(aURL.toUtf8(), pInteraction))); + comphelper::ScopeGuard const g([&] () { + if (pair.second) + { + pLib->mInteractionMap.erase(aURL.toUtf8()); + } + }); + uno::Reference<task::XInteractionHandler2> const xInteraction(pInteraction); + + int nMacroSecurityLevel = 1; + const OUString aMacroSecurityLevel = extractParameter(aOptions, u"MacroSecurityLevel"); + if (!aMacroSecurityLevel.isEmpty()) + { + double nNumber; + sal_uInt32 nFormat = 1; + SvNumberFormatter aFormatter(::comphelper::getProcessComponentContext(), LANGUAGE_ENGLISH_US); + if (aFormatter.IsNumberFormat(aMacroSecurityLevel, nFormat, nNumber)) + nMacroSecurityLevel = static_cast<int>(nNumber); + } + SvtSecurityOptions::SetMacroSecurityLevel(nMacroSecurityLevel); + +#if defined(ANDROID) && HAVE_FEATURE_ANDROID_LOK + sal_Int16 nMacroExecMode = document::MacroExecMode::USE_CONFIG; +#else + const OUString aEnableMacrosExecution = extractParameter(aOptions, u"EnableMacrosExecution"); + sal_Int16 nMacroExecMode = aEnableMacrosExecution == "true" ? document::MacroExecMode::USE_CONFIG : + document::MacroExecMode::NEVER_EXECUTE; +#endif + + // set AsTemplate explicitly false to be able to load template files + // as regular files, otherwise we cannot save them; it will try + // to bring saveas dialog which cannot work with LOK case + uno::Sequence<css::beans::PropertyValue> aFilterOptions{ + comphelper::makePropertyValue(u"FilterOptions"_ustr, sFilterOptions), + comphelper::makePropertyValue(u"InteractionHandler"_ustr, xInteraction), + comphelper::makePropertyValue(u"MacroExecutionMode"_ustr, nMacroExecMode), + comphelper::makePropertyValue(u"AsTemplate"_ustr, false), + comphelper::makePropertyValue(u"Silent"_ustr, !aBatch.isEmpty()) + }; + + /* TODO + sal_Int16 nUpdateDoc = document::UpdateDocMode::ACCORDING_TO_CONFIG; + aFilterOptions[3].Name = "UpdateDocMode"; + aFilterOptions[3].Value <<= nUpdateDoc; + */ + + OutputDevice::StartTrackingFontMappingUse(); + + const int nThisDocumentId = nDocumentIdCounter++; + SfxViewShell::SetCurrentDocId(ViewShellDocId(nThisDocumentId)); + uno::Reference<lang::XComponent> xComponent = xComponentLoader->loadComponentFromURL( + aURL, u"_blank"_ustr, 0, + aFilterOptions); + + assert(!xComponent.is() || pair.second); // concurrent loading of same URL ought to fail + + if (!xComponent.is()) + { + pLib->maLastExceptionMsg = u"loadComponentFromURL returned an empty reference"_ustr; + SAL_INFO("lok", "Document can't be loaded - " << pLib->maLastExceptionMsg); + return nullptr; + } + + LibLODocument_Impl* pDocument = new LibLODocument_Impl(xComponent, nThisDocumentId); + + // After loading the document, its initial view is the "current" view. + if (pLib->mpCallback) + { + int nState = doc_getSignatureState(pDocument); + pLib->mpCallback(LOK_CALLBACK_SIGNATURE_STATUS, OString::number(nState).getStr(), pLib->mpCallbackData); + } + + auto aFontMappingUseData = OutputDevice::FinishTrackingFontMappingUse(); + + if (aFontMappingUseData.size() > 0) + { + SAL_INFO("lok.fontsubst", "================ Original substitutions:"); + for (const auto &i : aFontMappingUseData) + { + SAL_INFO("lok.fontsubst", i.mOriginalFont); + for (const auto &j : i.mUsedFonts) + SAL_INFO("lok.fontsubst", " " << j); + } + } + + // Filter out font substitutions that actually aren't any substitutions, like "Liberation + // Serif" -> "Liberation Serif/Regular". If even one of the "substitutions" of a font is to + // the same font, don't count that as a missing font. + + std::erase_if + (aFontMappingUseData, + [](OutputDevice::FontMappingUseItem x) + { + // If the original font had an empty style and one of its + // replacement fonts has the same family name, we assume the font is + // present. The root problem here is that the code that collects + // font substitutions tends to get just empty styles for the font + // that is being substituted, as vcl::Font::GetStyleName() tends to + // return an empty string. (Italicness is instead indicated by what + // vcl::Font::GetItalic() returns and boldness by what + // vcl::Font::GetWeight() returns.) + + if (x.mOriginalFont.indexOf('/') == -1) + for (const auto &j : x.mUsedFonts) + if (j == x.mOriginalFont || + j.startsWith(Concat2View(x.mOriginalFont + "/"))) + return true; + + return false; + }); + + // Filter out substitutions where a proprietary font has been substituted by a + // metric-compatible one. Obviously this is just a heuristic and implemented only for some + // well-known cases. + + std::erase_if + (aFontMappingUseData, + [](OutputDevice::FontMappingUseItem x) + { + // Again, handle only cases where the original font does not include + // a style. Unclear whether there ever will be a style part included + // in the mOriginalFont. + + if (x.mOriginalFont.indexOf('/') == -1) + for (const auto &j : x.mUsedFonts) + if ((x.mOriginalFont == "Arial" && + j.startsWith("Liberation Sans/")) || + (x.mOriginalFont == "Times New Roman" && + j.startsWith("Liberation Serif/")) || + (x.mOriginalFont == "Courier New" && + j.startsWith("Liberation Mono/")) || + (x.mOriginalFont == "Arial Narrow" && + j.startsWith("Liberation Sans Narrow/")) || + (x.mOriginalFont == "Cambria" && + j.startsWith("Caladea/")) || + (x.mOriginalFont == "Calibri" && + j.startsWith("Carlito/")) || + (x.mOriginalFont == "Palatino Linotype" && + j.startsWith("P052/")) || + // Perhaps a risky heuristic? If some glyphs from Symbol + // have been mapped to ones in OpenSymbol, don't warn + // that Symbol is missing. + (x.mOriginalFont == "Symbol" && + j.startsWith("OpenSymbol/"))) + { + return true; + } + + return false; + }); + + if (aFontMappingUseData.size() > 0) + { + SAL_INFO("lok.fontsubst", "================ Pruned substitutions:"); + for (const auto &i : aFontMappingUseData) + { + SAL_INFO("lok.fontsubst", i.mOriginalFont); + for (const auto &j : i.mUsedFonts) + SAL_INFO("lok.fontsubst", " " << j); + } + } + + for (std::size_t i = 0; i < aFontMappingUseData.size(); ++i) + { + pDocument->maFontsMissing.insert(aFontMappingUseData[i].mOriginalFont); + } + + return pDocument; + } + catch (const uno::Exception& exception) + { + pLib->maLastExceptionMsg = exception.Message; + TOOLS_INFO_EXCEPTION("lok", "Document can't be loaded"); + } + + return nullptr; +} + +static int lo_runMacro(LibreOfficeKit* pThis, const char *pURL) +{ + comphelper::ProfileZone aZone("lo_runMacro"); + + SolarMutexGuard aGuard; + + LibLibreOffice_Impl* pLib = static_cast<LibLibreOffice_Impl*>(pThis); + pLib->maLastExceptionMsg.clear(); + + OUString sURL( pURL, strlen(pURL), RTL_TEXTENCODING_UTF8 ); + if (sURL.isEmpty()) + { + pLib->maLastExceptionMsg = u"Macro to run was not provided."_ustr; + SAL_INFO("lok", "Macro URL is empty"); + return false; + } + + if (!sURL.startsWith("macro://")) + { + pLib->maLastExceptionMsg = u"This doesn't look like macro URL"_ustr; + SAL_INFO("lok", "Macro URL is invalid"); + return false; + } + + pLib->maLastExceptionMsg.clear(); + + if (!xContext.is()) + { + pLib->maLastExceptionMsg = u"ComponentContext is not available"_ustr; + SAL_INFO("lok", "ComponentContext is not available"); + return false; + } + + util::URL aURL; + aURL.Complete = sURL; + + uno::Reference < util::XURLTransformer > xParser( util::URLTransformer::create( xContext ) ); + + if( xParser.is() ) + xParser->parseStrict( aURL ); + + uno::Reference<frame::XDesktop2> xComponentLoader = frame::Desktop::create(xContext); + + if (!xComponentLoader.is()) + { + pLib->maLastExceptionMsg = u"ComponentLoader is not available"_ustr; + SAL_INFO("lok", "ComponentLoader is not available"); + return false; + } + + xFactory = xContext->getServiceManager(); + + if (!xFactory) + return false; + + uno::Reference<frame::XDispatchProvider> xDP; + xSFactory.set(xFactory, uno::UNO_QUERY_THROW); + xDP.set( xSFactory->createInstance(u"com.sun.star.comp.sfx2.SfxMacroLoader"_ustr), uno::UNO_QUERY ); + uno::Reference<frame::XDispatch> xD = xDP->queryDispatch( aURL, OUString(), 0); + + if (!xD.is()) + { + pLib->maLastExceptionMsg = u"Macro loader is not available"_ustr; + SAL_INFO("lok", "Macro loader is not available"); + return false; + } + + uno::Reference < frame::XSynchronousDispatch > xSyncDisp( xD, uno::UNO_QUERY_THROW ); + uno::Sequence<css::beans::PropertyValue> aEmpty; + css::beans::PropertyValue aErr; + uno::Any aRet = xSyncDisp->dispatchWithReturnValue( aURL, aEmpty ); + aRet >>= aErr; + + if (aErr.Name == "ErrorCode") + { + sal_uInt32 nErrCode = 0; // ERRCODE_NONE + aErr.Value >>= nErrCode; + + pLib->maLastExceptionMsg = "An error occurred running macro (error code: " + OUString::number( nErrCode ) + ")"; + SAL_INFO("lok", "Macro execution terminated with error code " << nErrCode); + + return false; + } + + return true; +} + +static bool lo_signDocument(LibreOfficeKit* /*pThis*/, + const char* pURL, + const unsigned char* pCertificateBinary, + const int nCertificateBinarySize, + const unsigned char* pPrivateKeyBinary, + const int nPrivateKeyBinarySize) +{ + comphelper::ProfileZone aZone("lo_signDocument"); + + OUString aURL(getAbsoluteURL(pURL)); + if (aURL.isEmpty()) + return false; + + if (!xContext.is()) + return false; + + uno::Sequence<sal_Int8> aCertificateSequence; + + std::string aCertificateString(reinterpret_cast<const char*>(pCertificateBinary), nCertificateBinarySize); + std::string aCertificateBase64String = extractCertificate(aCertificateString); + if (!aCertificateBase64String.empty()) + { + OUString aBase64OUString = OUString::createFromAscii(aCertificateBase64String); + comphelper::Base64::decode(aCertificateSequence, aBase64OUString); + } + else + { + aCertificateSequence.realloc(nCertificateBinarySize); + std::copy(pCertificateBinary, pCertificateBinary + nCertificateBinarySize, aCertificateSequence.getArray()); + } + + uno::Sequence<sal_Int8> aPrivateKeySequence; + std::string aPrivateKeyString(reinterpret_cast<const char*>(pPrivateKeyBinary), nPrivateKeyBinarySize); + std::string aPrivateKeyBase64String = extractPrivateKey(aPrivateKeyString); + if (!aPrivateKeyBase64String.empty()) + { + OUString aBase64OUString = OUString::createFromAscii(aPrivateKeyBase64String); + comphelper::Base64::decode(aPrivateKeySequence, aBase64OUString); + } + else + { + aPrivateKeySequence.realloc(nPrivateKeyBinarySize); + std::copy(pPrivateKeyBinary, pPrivateKeyBinary + nPrivateKeyBinarySize, aPrivateKeySequence.getArray()); + } + + uno::Reference<xml::crypto::XSEInitializer> xSEInitializer = xml::crypto::SEInitializer::create(xContext); + uno::Reference<xml::crypto::XXMLSecurityContext> xSecurityContext = xSEInitializer->createSecurityContext(OUString()); + if (!xSecurityContext.is()) + return false; + + uno::Reference<xml::crypto::XSecurityEnvironment> xSecurityEnvironment = xSecurityContext->getSecurityEnvironment(); + uno::Reference<xml::crypto::XCertificateCreator> xCertificateCreator(xSecurityEnvironment, uno::UNO_QUERY); + + if (!xCertificateCreator.is()) + return false; + + uno::Reference<security::XCertificate> xCertificate = xCertificateCreator->createDERCertificateWithPrivateKey(aCertificateSequence, aPrivateKeySequence); + + if (!xCertificate.is()) + return false; + + sfx2::DocumentSigner aDocumentSigner(aURL); + if (!aDocumentSigner.signDocument(xCertificate)) + return false; + + return true; +} + + +static char* lo_extractRequest(LibreOfficeKit* /*pThis*/, const char* pFilePath) +{ + uno::Reference<frame::XDesktop2> xComponentLoader = frame::Desktop::create(xContext); + uno::Reference< css::lang::XComponent > xComp; + OUString aURL(getAbsoluteURL(pFilePath)); + if (!aURL.isEmpty()) + { + if (xComponentLoader.is()) + { + try + { + uno::Sequence<css::beans::PropertyValue> aFilterOptions(comphelper::InitPropertySequence( + { + {u"Hidden"_ustr, css::uno::Any(true)}, + {u"ReadOnly"_ustr, css::uno::Any(true)} + })); + xComp = xComponentLoader->loadComponentFromURL( aURL, u"_blank"_ustr, 0, aFilterOptions ); + } + catch ( const lang::IllegalArgumentException& ex ) + { + SAL_WARN("lok", "lo_extractRequest: IllegalArgumentException: " << ex.Message); + } + catch (...) + { + SAL_WARN("lok", "lo_extractRequest: Exception on loadComponentFromURL, url= " << aURL); + } + + if (xComp.is()) + { + uno::Reference< document::XLinkTargetSupplier > xLTS( xComp, uno::UNO_QUERY ); + + if( xLTS.is() ) + { + tools::JsonWriter aJson; + { + auto aNode = aJson.startNode("Targets"); + extractLinks(xLTS->getLinks(), false, aJson); + } + return convertOString(aJson.finishAndGetAsOString()); + } + xComp->dispose(); + } + } + } + return strdup("{ }"); +} + +static void lo_trimMemory(LibreOfficeKit* /* pThis */, int nTarget) +{ + vcl::lok::trimMemory(nTarget); + + if (nTarget > 2000) + { + SolarMutexGuard aGuard; + + // Flush all buffered VOC primitives from the pages. + SfxViewShell* pViewShell = SfxViewShell::Current(); + if (pViewShell) + { + const SdrView* pView = pViewShell->GetDrawView(); + if (pView) + { + SdrPageView* pPageView = pView->GetSdrPageView(); + if (pPageView) + { + SdrPage* pCurPage = pPageView->GetPage(); + if (pCurPage) + { + SdrModel& sdrModel = pCurPage->getSdrModelFromSdrPage(); + for (sal_uInt16 i = 0; i < sdrModel.GetPageCount(); ++i) + { + SdrPage* pPage = sdrModel.GetPage(i); + if (pPage) + pPage->GetViewContact().flushViewObjectContacts(); + } + } + } + } + } + } + + if (nTarget > 1000) + { +#ifdef HAVE_MALLOC_TRIM + malloc_trim(0); +#endif + } +} + +namespace +{ +class FunctionBasedURPInstanceProvider + : public ::cppu::WeakImplHelper<css::bridge::XInstanceProvider> +{ +private: + css::uno::Reference<css::uno::XComponentContext> m_rContext; + +public: + FunctionBasedURPInstanceProvider( + const css::uno::Reference<css::uno::XComponentContext>& rxContext); + + // XInstanceProvider + virtual css::uno::Reference<css::uno::XInterface> + SAL_CALL getInstance(const OUString& aName) override; +}; + +// InstanceProvider +FunctionBasedURPInstanceProvider::FunctionBasedURPInstanceProvider( + const Reference<XComponentContext>& rxContext) + : m_rContext(rxContext) +{ +} + +Reference<XInterface> FunctionBasedURPInstanceProvider::getInstance(const OUString& aName) +{ + Reference<XInterface> rInstance; + + if (aName == "StarOffice.ServiceManager") + { + rInstance.set(m_rContext->getServiceManager()); + } + else if (aName == "StarOffice.ComponentContext") + { + rInstance = m_rContext; + } + else if (aName == "StarOffice.NamingService") + { + Reference<XNamingService> rNamingService( + m_rContext->getServiceManager()->createInstanceWithContext( + u"com.sun.star.uno.NamingService"_ustr, m_rContext), + UNO_QUERY); + if (rNamingService.is()) + { + rNamingService->registerObject(u"StarOffice.ServiceManager"_ustr, + m_rContext->getServiceManager()); + rNamingService->registerObject(u"StarOffice.ComponentContext"_ustr, m_rContext); + rInstance = rNamingService; + } + } + return rInstance; +} + +class FunctionBasedURPConnection : public cppu::WeakImplHelper<css::connection::XConnection> +{ +public: + explicit FunctionBasedURPConnection(void*, int (*)(void* pContext, const signed char* pBuffer, int nLen), + void*, int (*)(void* pContext, signed char* pBuffer, int nLen)); + ~FunctionBasedURPConnection(); + + // These overridden member functions use "read" and "write" from the point of view of LO, + // i.e. the opposite to how startURP() uses them. + virtual sal_Int32 SAL_CALL read(Sequence<sal_Int8>& rReadBytes, + sal_Int32 nBytesToRead) override; + virtual void SAL_CALL write(const Sequence<sal_Int8>& aData) override; + virtual void SAL_CALL flush() override; + virtual void SAL_CALL close() override; + virtual OUString SAL_CALL getDescription() override; + void setBridge(Reference<XBridge>); + void* getContext(); + inline static int g_connectionCount = 0; + +private: + void* m_pRecieveFromLOContext; + void* m_pSendURPToLOContext; + int (*m_fnReceiveURPFromLO)(void* pContext, const signed char* pBuffer, int nLen); + int (*m_fnSendURPToLO)(void* pContext, signed char* pBuffer, int nLen); + Reference<XBridge> m_URPBridge; +}; + +FunctionBasedURPConnection::FunctionBasedURPConnection( + void* pRecieveFromLOContext, + int (*fnReceiveURPFromLO)(void* pContext, const signed char* pBuffer, int nLen), + void* pSendURPToLOContext, + int (*fnSendURPToLO)(void* pContext, signed char* pBuffer, int nLen)) + : m_pRecieveFromLOContext(pRecieveFromLOContext) + , m_pSendURPToLOContext(pSendURPToLOContext) + , m_fnReceiveURPFromLO(fnReceiveURPFromLO) + , m_fnSendURPToLO(fnSendURPToLO) +{ + g_connectionCount++; +} + +FunctionBasedURPConnection::~FunctionBasedURPConnection() +{ + Reference<XComponent> xComp(m_URPBridge, UNO_QUERY_THROW); + xComp->dispose(); // TODO: check this doesn't deadlock +} + +void* FunctionBasedURPConnection::getContext() { return this; } + +sal_Int32 FunctionBasedURPConnection::read(Sequence<sal_Int8>& rReadBytes, sal_Int32 nBytesToRead) +{ + if (nBytesToRead < 0) + return 0; + + if (rReadBytes.getLength() != nBytesToRead) + rReadBytes.realloc(nBytesToRead); + + // As with osl::StreamPipe, we must always read nBytesToRead... + return m_fnSendURPToLO(m_pSendURPToLOContext, rReadBytes.getArray(), nBytesToRead); +} + +void FunctionBasedURPConnection::write(const Sequence<sal_Int8>& rData) +{ + m_fnReceiveURPFromLO(m_pRecieveFromLOContext, rData.getConstArray(), rData.getLength()); +} + +void FunctionBasedURPConnection::flush() {} + +void FunctionBasedURPConnection::close() +{ + SAL_INFO("lok.urp", "Requested to close FunctionBasedURPConnection"); +} + +OUString FunctionBasedURPConnection::getDescription() { return ""; } + +void FunctionBasedURPConnection::setBridge(Reference<XBridge> xBridge) { m_URPBridge = xBridge; } +} + +static void* +lo_startURP(LibreOfficeKit* /* pThis */, void* pRecieveFromLOContext, void* pSendToLOContext, + int (*fnReceiveURPFromLO)(void* pContext, const signed char* pBuffer, int nLen), + int (*fnSendURPToLO)(void* pContext, signed char* pBuffer, int nLen)) +{ + // Here we will roughly do what desktop LO does when one passes a command-line switch like + // --accept=socket,port=nnnn;urp;StarOffice.ServiceManager. Except that no listening socket will + // be created. The communication to the URP will be through the nReceiveURPFromLO and nSendURPToLO + // functions. + + rtl::Reference<FunctionBasedURPConnection> connection( + new FunctionBasedURPConnection(pRecieveFromLOContext, fnReceiveURPFromLO, + pSendToLOContext, fnSendURPToLO)); + + Reference<XBridgeFactory> xBridgeFactory = css::bridge::BridgeFactory::create(xContext); + + Reference<XInstanceProvider> xInstanceProvider(new FunctionBasedURPInstanceProvider(xContext)); + + Reference<XBridge> xBridge(xBridgeFactory->createBridge( + "functionurp" + OUString::number(FunctionBasedURPConnection::g_connectionCount), u"urp"_ustr, + connection, xInstanceProvider)); + + connection->setBridge(std::move(xBridge)); + + return connection->getContext(); +} + +/** + * Stop a function based URP connection that you started with lo_startURP above + * + * @param pSendToLOContext a pointer to the context returned by lo_startURP */ +static void lo_stopURP(LibreOfficeKit* /* pThis */, + void* pFunctionBasedURPConnection/* FunctionBasedURPConnection* */) +{ + static_cast<FunctionBasedURPConnection*>(pFunctionBasedURPConnection)->close(); +} + +static void lo_registerCallback (LibreOfficeKit* pThis, + LibreOfficeKitCallback pCallback, + void* pData) +{ + SolarMutexGuard aGuard; + + Application* pApp = GetpApp(); + assert(pApp); + + LibLibreOffice_Impl* pLib = static_cast<LibLibreOffice_Impl*>(pThis); + pLib->maLastExceptionMsg.clear(); + + pApp->m_pCallback = pLib->mpCallback = pCallback; + pApp->m_pCallbackData = pLib->mpCallbackData = pData; +} + +static int doc_saveAs(LibreOfficeKitDocument* pThis, const char* sUrl, const char* pFormat, const char* pFilterOptions) +{ + comphelper::ProfileZone aZone("doc_saveAs"); + + SolarMutexGuard aGuard; + SetLastExceptionMsg(); + + LibLODocument_Impl* pDocument = static_cast<LibLODocument_Impl*>(pThis); + + OUString sFormat = getUString(pFormat); + OUString aURL(getAbsoluteURL(sUrl)); + + uno::Reference<frame::XStorable> xStorable(pDocument->mxComponent, uno::UNO_QUERY_THROW); + + if (aURL.isEmpty()) + { + SetLastExceptionMsg(u"Filename to save to was not provided."_ustr); + SAL_INFO("lok", "URL for save is empty"); + return false; + } + + try + { + std::span<const ExtensionMap> pMap; + + switch (doc_getDocumentType(pThis)) + { + case LOK_DOCTYPE_SPREADSHEET: + pMap = aCalcExtensionMap; + break; + case LOK_DOCTYPE_PRESENTATION: + pMap = aImpressExtensionMap; + break; + case LOK_DOCTYPE_DRAWING: + pMap = aDrawExtensionMap; + break; + case LOK_DOCTYPE_TEXT: + pMap = aWriterExtensionMap; + break; + case LOK_DOCTYPE_OTHER: + default: + SAL_INFO("lok", "Can't save document - unsupported document type."); + return false; + } + + if (pFormat == nullptr) + { + // sniff from the extension + sal_Int32 idx = aURL.lastIndexOf("."); + if( idx > 0 ) + { + sFormat = aURL.copy( idx + 1 ); + } + else + { + SetLastExceptionMsg("input URL '" + aURL + "' lacks a suffix"); + return false; + } + } + + OUString aFilterName; + for (const auto& item : pMap) + { + if (sFormat.equalsIgnoreAsciiCaseAscii(item.extn)) + { + aFilterName = item.filterName; + break; + } + } + if (aFilterName.isEmpty()) + { + SetLastExceptionMsg(u"no output filter found for provided suffix"_ustr); + return false; + } + + OUString aFilterOptions = getUString(pFilterOptions); + + // Check if watermark for pdf is passed by filteroptions... + // It is not a real filter option so it must be filtered out. + OUString watermarkText; + std::u16string_view sFullSheetPreview; + int aIndex = -1; + if ((aIndex = aFilterOptions.indexOf(",Watermark=")) >= 0) + { + int bIndex = aFilterOptions.indexOf("WATERMARKEND"); + watermarkText = aFilterOptions.subView(aIndex+11, bIndex-(aIndex+11)); + aFilterOptions = OUString::Concat(aFilterOptions.subView(0, aIndex)) + aFilterOptions.subView(bIndex+12); + } + + if ((aIndex = aFilterOptions.indexOf(",FullSheetPreview=")) >= 0) + { + int bIndex = aFilterOptions.indexOf("FULLSHEETPREVEND"); + sFullSheetPreview = aFilterOptions.subView(aIndex+18, bIndex-(aIndex+18)); + aFilterOptions = OUString::Concat(aFilterOptions.subView(0, aIndex)) + aFilterOptions.subView(bIndex+16); + } + + bool bFullSheetPreview = sFullSheetPreview == u"true"; + + OUString filePassword; + if ((aIndex = aFilterOptions.indexOf(",Password=")) >= 0) + { + int bIndex = aFilterOptions.indexOf("PASSWORDEND"); + filePassword = aFilterOptions.subView(aIndex + 10, bIndex - (aIndex + 10)); + aFilterOptions = OUString::Concat(aFilterOptions.subView(0, aIndex)) + + aFilterOptions.subView(bIndex + 11); + } + OUString filePasswordToModify; + if ((aIndex = aFilterOptions.indexOf(",PasswordToModify=")) >= 0) + { + int bIndex = aFilterOptions.indexOf("PASSWORDTOMODIFYEND"); + filePassword = aFilterOptions.subView(aIndex + 18, bIndex - (aIndex + 18)); + aFilterOptions = OUString::Concat(aFilterOptions.subView(0, aIndex)) + + aFilterOptions.subView(bIndex + 19); + } + + // Select a pdf version if specified a valid one. If not specified then ignore. + // If invalid then fail. + sal_Int32 pdfVer = 0; + if ((aIndex = aFilterOptions.indexOf(",PDFVer=")) >= 0) + { + int bIndex = aFilterOptions.indexOf("PDFVEREND"); + std::u16string_view sPdfVer = aFilterOptions.subView(aIndex+8, bIndex-(aIndex+8)); + aFilterOptions = OUString::Concat(aFilterOptions.subView(0, aIndex)) + aFilterOptions.subView(bIndex+9); + + if (o3tl::equalsIgnoreAsciiCase(sPdfVer, u"PDF/A-1b")) + pdfVer = 1; + else if (o3tl::equalsIgnoreAsciiCase(sPdfVer, u"PDF/A-2b")) + pdfVer = 2; + else if (o3tl::equalsIgnoreAsciiCase(sPdfVer, u"PDF/A-3b")) + pdfVer = 3; + else if (o3tl::equalsIgnoreAsciiCase(sPdfVer, u"PDF-1.5")) + pdfVer = 15; + else if (o3tl::equalsIgnoreAsciiCase(sPdfVer, u"PDF-1.6")) + pdfVer = 16; + else + { + SetLastExceptionMsg(u"wrong PDF version"_ustr); + return false; + } + } + + // 'TakeOwnership' == this is a 'real' SaveAs (that is, the document + // gets a new name). When this is not provided, the meaning of + // saveAs() is more like save-a-copy, which allows saving to any + // random format like PDF or PNG. + // It is not a real filter option, so we have to filter it out. + const uno::Sequence<OUString> aOptionSeq = comphelper::string::convertCommaSeparated(aFilterOptions); + std::vector<OUString> aFilteredOptionVec; + bool bTakeOwnership = false; + MediaDescriptor aSaveMediaDescriptor; + for (const auto& rOption : aOptionSeq) + { + if (rOption == "TakeOwnership") + bTakeOwnership = true; + else if (rOption == "NoFileSync") + aSaveMediaDescriptor[u"NoFileSync"_ustr] <<= true; + else + aFilteredOptionVec.push_back(rOption); + } + + aSaveMediaDescriptor[u"Overwrite"_ustr] <<= true; + aSaveMediaDescriptor[u"FilterName"_ustr] <<= aFilterName; + + auto aFilteredOptionSeq = comphelper::containerToSequence<OUString>(aFilteredOptionVec); + aFilterOptions = comphelper::string::convertCommaSeparated(aFilteredOptionSeq); + aSaveMediaDescriptor[MediaDescriptor::PROP_FILTEROPTIONS] <<= aFilterOptions; + + comphelper::SequenceAsHashMap aFilterDataMap; + + // If filter options is JSON string, then make sure aFilterDataMap stays empty, otherwise we + // would ignore the filter options. + if (!aFilterOptions.startsWith("{")) + { + setFormatSpecificFilterData(sFormat, aFilterDataMap); + } + + if (!watermarkText.isEmpty()) + aFilterDataMap[u"TiledWatermark"_ustr] <<= watermarkText; + + if (bFullSheetPreview) + aFilterDataMap[u"SinglePageSheets"_ustr] <<= true; + + if (pdfVer) + aFilterDataMap[u"SelectPdfVersion"_ustr] <<= pdfVer; + + if (!aFilterDataMap.empty()) + { + aSaveMediaDescriptor[u"FilterData"_ustr] <<= aFilterDataMap.getAsConstPropertyValueList(); + } + if (!filePassword.isEmpty()) + aSaveMediaDescriptor[u"Password"_ustr] <<= filePassword; + if (!filePasswordToModify.isEmpty()) + aSaveMediaDescriptor[u"PasswordToModify"_ustr] <<= filePasswordToModify; + + // add interaction handler too + if (gImpl) + { + // gImpl does not have to exist when running from a unit test + rtl::Reference<LOKInteractionHandler> const pInteraction( + new LOKInteractionHandler("saveas"_ostr, gImpl, pDocument)); + uno::Reference<task::XInteractionHandler2> const xInteraction(pInteraction); + + aSaveMediaDescriptor[MediaDescriptor::PROP_INTERACTIONHANDLER] <<= xInteraction; + } + + + if (bTakeOwnership) + xStorable->storeAsURL(aURL, aSaveMediaDescriptor.getAsConstPropertyValueList()); + else + xStorable->storeToURL(aURL, aSaveMediaDescriptor.getAsConstPropertyValueList()); + + return true; + } + catch (const uno::Exception& exception) + { + SetLastExceptionMsg("exception: " + exception.Message); + } + return false; +} + +/** + * Initialize UNO commands, in the sense that from now on, the LOK client gets updates for status + * changes of these commands. This is necessary, because (unlike in the desktop case) there are no + * toolbars hosting widgets these UNO commands, so no such status updates would be sent to the + * headless LOK clients out of the box. + */ +static void doc_iniUnoCommands () +{ + SolarMutexGuard aGuard; + SetLastExceptionMsg(); + + static constexpr OUString sUnoCommands[] = + { + u".uno:AlignLeft"_ustr, + u".uno:AlignHorizontalCenter"_ustr, + u".uno:AlignRight"_ustr, + u".uno:BackColor"_ustr, + u".uno:BackgroundColor"_ustr, + u".uno:TableCellBackgroundColor"_ustr, + u".uno:Bold"_ustr, + u".uno:CenterPara"_ustr, + u".uno:CharBackColor"_ustr, + u".uno:CharBackgroundExt"_ustr, + u".uno:CharFontName"_ustr, + u".uno:Color"_ustr, + u".uno:ControlCodes"_ustr, + u".uno:DecrementIndent"_ustr, + u".uno:DefaultBullet"_ustr, + u".uno:DefaultNumbering"_ustr, + u".uno:FontColor"_ustr, + u".uno:FontHeight"_ustr, + u".uno:IncrementIndent"_ustr, + u".uno:Italic"_ustr, + u".uno:JustifyPara"_ustr, + u".uno:JumpToMark"_ustr, + u".uno:OutlineFont"_ustr, + u".uno:LeftPara"_ustr, + u".uno:LanguageStatus"_ustr, + u".uno:RightPara"_ustr, + u".uno:Shadowed"_ustr, + u".uno:SubScript"_ustr, + u".uno:SuperScript"_ustr, + u".uno:Strikeout"_ustr, + u".uno:StyleApply"_ustr, + u".uno:Underline"_ustr, + u".uno:ModifiedStatus"_ustr, + u".uno:Undo"_ustr, + u".uno:Redo"_ustr, + u".uno:InsertPage"_ustr, + u".uno:DeletePage"_ustr, + u".uno:DuplicatePage"_ustr, + u".uno:InsertSlide"_ustr, + u".uno:DeleteSlide"_ustr, + u".uno:DuplicateSlide"_ustr, + u".uno:ChangeTheme"_ustr, + u".uno:Cut"_ustr, + u".uno:Copy"_ustr, + u".uno:Paste"_ustr, + u".uno:SelectAll"_ustr, + u".uno:ReplyComment"_ustr, + u".uno:ResolveComment"_ustr, + u".uno:ResolveCommentThread"_ustr, + u".uno:InsertRowsBefore"_ustr, + u".uno:InsertRowsAfter"_ustr, + u".uno:InsertColumnsBefore"_ustr, + u".uno:InsertColumnsAfter"_ustr, + u".uno:DeleteRows"_ustr, + u".uno:DeleteColumns"_ustr, + u".uno:DeleteTable"_ustr, + u".uno:SelectTable"_ustr, + u".uno:EntireRow"_ustr, + u".uno:EntireColumn"_ustr, + u".uno:EntireCell"_ustr, + u".uno:AssignLayout"_ustr, + u".uno:StatusDocPos"_ustr, + u".uno:RowColSelCount"_ustr, + u".uno:StatusPageStyle"_ustr, + u".uno:InsertMode"_ustr, + u".uno:SpellOnline"_ustr, + u".uno:StatusSelectionMode"_ustr, + u".uno:StateTableCell"_ustr, + u".uno:StatusBarFunc"_ustr, + u".uno:StatePageNumber"_ustr, + u".uno:StateWordCount"_ustr, + u".uno:SelectionMode"_ustr, + u".uno:PageStatus"_ustr, + u".uno:LayoutStatus"_ustr, + u".uno:Scale"_ustr, + u".uno:Context"_ustr, + u".uno:WrapText"_ustr, + u".uno:ToggleMergeCells"_ustr, + u".uno:NumberFormatCurrency"_ustr, + u".uno:NumberFormatPercent"_ustr, + u".uno:NumberFormatDecimal"_ustr, + u".uno:NumberFormatIncDecimals"_ustr, + u".uno:NumberFormatDecDecimals"_ustr, + u".uno:NumberFormatDate"_ustr, + u".uno:EditHeaderAndFooter"_ustr, + u".uno:FrameLineColor"_ustr, + u".uno:SortAscending"_ustr, + u".uno:SortDescending"_ustr, + u".uno:TrackChanges"_ustr, + u".uno:ShowTrackedChanges"_ustr, + u".uno:NextTrackedChange"_ustr, + u".uno:PreviousTrackedChange"_ustr, + u".uno:AcceptAllTrackedChanges"_ustr, + u".uno:RejectAllTrackedChanges"_ustr, + u".uno:TableDialog"_ustr, + u".uno:FormatCellDialog"_ustr, + u".uno:FontDialog"_ustr, + u".uno:ParagraphDialog"_ustr, + u".uno:OutlineBullet"_ustr, + u".uno:InsertIndexesEntry"_ustr, + u".uno:DocumentRepair"_ustr, + u".uno:TransformDialog"_ustr, + u".uno:InsertPageHeader"_ustr, + u".uno:InsertPageFooter"_ustr, + u".uno:OnlineAutoFormat"_ustr, + u".uno:InsertObjectChart"_ustr, + u".uno:InsertSection"_ustr, + u".uno:InsertAnnotation"_ustr, + u".uno:DeleteAnnotation"_ustr, + u".uno:InsertPagebreak"_ustr, + u".uno:InsertColumnBreak"_ustr, + u".uno:HyperlinkDialog"_ustr, + u".uno:InsertSymbol"_ustr, + u".uno:EditRegion"_ustr, + u".uno:ThesaurusDialog"_ustr, + u".uno:FormatArea"_ustr, + u".uno:FormatLine"_ustr, + u".uno:FormatColumns"_ustr, + u".uno:Watermark"_ustr, + u".uno:ResetAttributes"_ustr, + u".uno:Orientation"_ustr, + u".uno:ObjectAlignLeft"_ustr, + u".uno:ObjectAlignRight"_ustr, + u".uno:AlignCenter"_ustr, + u".uno:TransformPosX"_ustr, + u".uno:TransformPosY"_ustr, + u".uno:TransformWidth"_ustr, + u".uno:TransformHeight"_ustr, + u".uno:ObjectBackOne"_ustr, + u".uno:SendToBack"_ustr, + u".uno:ObjectForwardOne"_ustr, + u".uno:BringToFront"_ustr, + u".uno:WrapRight"_ustr, + u".uno:WrapThrough"_ustr, + u".uno:WrapLeft"_ustr, + u".uno:WrapIdeal"_ustr, + u".uno:WrapOn"_ustr, + u".uno:WrapOff"_ustr, + u".uno:UpdateCurIndex"_ustr, + u".uno:InsertCaptionDialog"_ustr, + u".uno:FormatGroup"_ustr, + u".uno:SplitTable"_ustr, + u".uno:SplitCell"_ustr, + u".uno:MergeCells"_ustr, + u".uno:DeleteNote"_ustr, + u".uno:AcceptChanges"_ustr, + u".uno:FormatPaintbrush"_ustr, + u".uno:SetDefault"_ustr, + u".uno:ParaLeftToRight"_ustr, + u".uno:ParaRightToLeft"_ustr, + u".uno:ParaspaceIncrease"_ustr, + u".uno:ParaspaceDecrease"_ustr, + u".uno:AcceptTrackedChange"_ustr, + u".uno:RejectTrackedChange"_ustr, + u".uno:ShowResolvedAnnotations"_ustr, + u".uno:InsertBreak"_ustr, + u".uno:InsertEndnote"_ustr, + u".uno:InsertFootnote"_ustr, + u".uno:InsertReferenceField"_ustr, + u".uno:InsertBookmark"_ustr, + u".uno:InsertAuthoritiesEntry"_ustr, + u".uno:InsertMultiIndex"_ustr, + u".uno:InsertField"_ustr, + u".uno:PageNumberWizard"_ustr, + u".uno:InsertPageNumberField"_ustr, + u".uno:InsertPageCountField"_ustr, + u".uno:InsertDateField"_ustr, + u".uno:InsertTitleField"_ustr, + u".uno:InsertFieldCtrl"_ustr, + u".uno:CharmapControl"_ustr, + u".uno:EnterGroup"_ustr, + u".uno:LeaveGroup"_ustr, + u".uno:AlignUp"_ustr, + u".uno:AlignMiddle"_ustr, + u".uno:AlignDown"_ustr, + u".uno:TraceChangeMode"_ustr, + u".uno:Combine"_ustr, + u".uno:Merge"_ustr, + u".uno:Dismantle"_ustr, + u".uno:Substract"_ustr, + u".uno:DistributeSelection"_ustr, + u".uno:Intersect"_ustr, + u".uno:BorderInner"_ustr, + u".uno:BorderOuter"_ustr, + u".uno:FreezePanes"_ustr, + u".uno:FreezePanesColumn"_ustr, + u".uno:FreezePanesRow"_ustr, + u".uno:Sidebar"_ustr, + u".uno:SheetRightToLeft"_ustr, + u".uno:RunMacro"_ustr, + u".uno:SpacePara1"_ustr, + u".uno:SpacePara15"_ustr, + u".uno:SpacePara2"_ustr, + u".uno:InsertSparkline"_ustr, + u".uno:DeleteSparkline"_ustr, + u".uno:DeleteSparklineGroup"_ustr, + u".uno:EditSparklineGroup"_ustr, + u".uno:EditSparkline"_ustr, + u".uno:GroupSparklines"_ustr, + u".uno:UngroupSparklines"_ustr, + u".uno:FormatSparklineMenu"_ustr, + u".uno:DataDataPilotRun"_ustr, + u".uno:RecalcPivotTable"_ustr, + u".uno:DeletePivotTable"_ustr, + u".uno:Protect"_ustr, + u".uno:UnsetCellsReadOnly"_ustr, + u".uno:ContentControlProperties"_ustr, + u".uno:InsertCheckboxContentControl"_ustr, + u".uno:InsertContentControl"_ustr, + u".uno:InsertDateContentControl"_ustr, + u".uno:InsertDropdownContentControl"_ustr, + u".uno:InsertPlainTextContentControl"_ustr, + u".uno:InsertPictureContentControl"_ustr, + u".uno:DataFilterAutoFilter"_ustr, + u".uno:CellProtection"_ustr, + }; + + util::URL aCommandURL; + SfxViewShell* pViewShell = SfxViewShell::Current(); + SfxViewFrame* pViewFrame = pViewShell ? &pViewShell->GetViewFrame() : nullptr; + + // check if Frame-Controller were created. + if (!pViewFrame) + { + SAL_WARN("lok", "iniUnoCommands: No Frame-Controller created."); + return; + } + + if (!xContext.is()) + xContext = comphelper::getProcessComponentContext(); + if (!xContext.is()) + { + SAL_WARN("lok", "iniUnoCommands: Component context is not available"); + return; + } + +#if !defined IOS && !defined ANDROID && !defined __EMSCRIPTEN__ + uno::Reference<xml::crypto::XSEInitializer> xSEInitializer = xml::crypto::SEInitializer::create(xContext); + if (!xSEInitializer.is()) + { + SAL_WARN("lok", "iniUnoCommands: XSEInitializer is not available"); + return; + } + + uno::Reference<xml::crypto::XXMLSecurityContext> xSecurityContext = + xSEInitializer->createSecurityContext(OUString()); + if (!xSecurityContext.is()) + { + SAL_WARN("lok", "iniUnoCommands: failed to create security context"); + } +#endif + + SfxSlotPool& rSlotPool = SfxSlotPool::GetSlotPool(pViewFrame); + uno::Reference<util::XURLTransformer> xParser(util::URLTransformer::create(xContext)); + + for (const auto & sUnoCommand : sUnoCommands) + { + aCommandURL.Complete = sUnoCommand; + xParser->parseStrict(aCommandURL); + + // when null, this command is not supported by the given component + // (like eg. Calc does not have ".uno:DefaultBullet" etc.) + if (const SfxSlot* pSlot = rSlotPool.GetUnoSlot(aCommandURL.Path)) + { + // Initialize slot to dispatch .uno: Command. + pViewFrame->GetBindings().GetDispatch(pSlot, aCommandURL, false); + } + } +} + +static int doc_getDocumentType (LibreOfficeKitDocument* pThis) +{ + comphelper::ProfileZone aZone("doc_getDocumentType"); + + SolarMutexGuard aGuard; + return getDocumentType(pThis); +} + +static int doc_getParts (LibreOfficeKitDocument* pThis) +{ + comphelper::ProfileZone aZone("doc_getParts"); + + SolarMutexGuard aGuard; + + ITiledRenderable* pDoc = getTiledRenderable(pThis); + if (!pDoc) + { + SetLastExceptionMsg(u"Document doesn't support tiled rendering"_ustr); + return 0; + } + + return pDoc->getParts(); +} + +static int doc_getPart (LibreOfficeKitDocument* pThis) +{ + comphelper::ProfileZone aZone("doc_getPart"); + + SolarMutexGuard aGuard; + SetLastExceptionMsg(); + + ITiledRenderable* pDoc = getTiledRenderable(pThis); + if (!pDoc) + { + SetLastExceptionMsg(u"Document doesn't support tiled rendering"_ustr); + return 0; + } + + return pDoc->getPart(); +} + +static void doc_setPartImpl(LibreOfficeKitDocument* pThis, int nPart, bool bAllowChangeFocus = true) +{ + comphelper::ProfileZone aZone("doc_setPart"); + + SolarMutexGuard aGuard; + SetLastExceptionMsg(); + + ITiledRenderable* pDoc = getTiledRenderable(pThis); + if (!pDoc) + { + SetLastExceptionMsg(u"Document doesn't support tiled rendering"_ustr); + return; + } + + pDoc->setPart( nPart, bAllowChangeFocus ); +} + +static void doc_setPart(LibreOfficeKitDocument* pThis, int nPart) +{ + doc_setPartImpl(pThis, nPart, true); +} + +static char* doc_getPartInfo(LibreOfficeKitDocument* pThis, int nPart) +{ + comphelper::ProfileZone aZone("doc_getPartInfo"); + + SolarMutexGuard aGuard; + ITiledRenderable* pDoc = getTiledRenderable(pThis); + if (!pDoc) + { + SetLastExceptionMsg(u"Document doesn't support tiled rendering"_ustr); + return nullptr; + } + + return convertOUString(pDoc->getPartInfo(nPart)); +} + +static void doc_selectPart(LibreOfficeKitDocument* pThis, int nPart, int nSelect) +{ + SolarMutexGuard aGuard; + SetLastExceptionMsg(); + + ITiledRenderable* pDoc = getTiledRenderable(pThis); + if (!pDoc) + { + SetLastExceptionMsg(u"Document doesn't support tiled rendering"_ustr); + return; + } + + pDoc->selectPart( nPart, nSelect ); +} + +static void doc_moveSelectedParts(LibreOfficeKitDocument* pThis, int nPosition, bool bDuplicate) +{ + SolarMutexGuard aGuard; + SetLastExceptionMsg(); + + ITiledRenderable* pDoc = getTiledRenderable(pThis); + if (!pDoc) + { + SetLastExceptionMsg(u"Document doesn't support tiled rendering"_ustr); + return; + } + + pDoc->moveSelectedParts(nPosition, bDuplicate); +} + +static char* doc_getPartPageRectangles(LibreOfficeKitDocument* pThis) +{ + comphelper::ProfileZone aZone("doc_getPartPageRectangles"); + + SolarMutexGuard aGuard; + SetLastExceptionMsg(); + + ITiledRenderable* pDoc = getTiledRenderable(pThis); + if (!pDoc) + { + SetLastExceptionMsg(u"Document doesn't support tiled rendering"_ustr); + return nullptr; + } + + return convertOUString(pDoc->getPartPageRectangles()); +} + +static char* doc_getA11yFocusedParagraph(LibreOfficeKitDocument* pThis) +{ + SolarMutexGuard aGuard; + SetLastExceptionMsg(); + + ITiledRenderable* pDoc = getTiledRenderable(pThis); + if (!pDoc) + { + SetLastExceptionMsg(u"Document doesn't support tiled rendering"_ustr); + return nullptr; + } + + if (SfxViewShell* pViewShell = SfxViewShell::Current()) + { + return convertOUString(pViewShell->getA11yFocusedParagraph()); + + } + return nullptr; +} + +static int doc_getA11yCaretPosition(LibreOfficeKitDocument* pThis) +{ + SolarMutexGuard aGuard; + SetLastExceptionMsg(); + + ITiledRenderable* pDoc = getTiledRenderable(pThis); + if (!pDoc) + { + SetLastExceptionMsg(u"Document doesn't support tiled rendering"_ustr); + return -1; + } + if (SfxViewShell* pViewShell = SfxViewShell::Current()) + { + return pViewShell->getA11yCaretPosition(); + + } + return -1; + +} + +static char* doc_getPartName(LibreOfficeKitDocument* pThis, int nPart) +{ + comphelper::ProfileZone aZone("doc_getPartName"); + + SolarMutexGuard aGuard; + SetLastExceptionMsg(); + + ITiledRenderable* pDoc = getTiledRenderable(pThis); + if (!pDoc) + { + SetLastExceptionMsg(u"Document doesn't support tiled rendering"_ustr); + return nullptr; + } + + return convertOUString(pDoc->getPartName(nPart)); +} + +static char* doc_getPartHash(LibreOfficeKitDocument* pThis, int nPart) +{ + comphelper::ProfileZone aZone("doc_getPartHash"); + + SolarMutexGuard aGuard; + SetLastExceptionMsg(); + + ITiledRenderable* pDoc = getTiledRenderable(pThis); + if (!pDoc) + { + SetLastExceptionMsg(u"Document doesn't support tiled rendering"_ustr); + return nullptr; + } + + return convertOUString(pDoc->getPartHash(nPart)); +} + +static void doc_setPartMode(LibreOfficeKitDocument* pThis, + int nPartMode) +{ + comphelper::ProfileZone aZone("doc_setPartMode"); + + SolarMutexGuard aGuard; + SetLastExceptionMsg(); + + ITiledRenderable* pDoc = getTiledRenderable(pThis); + if (!pDoc) + { + SetLastExceptionMsg(u"Document doesn't support tiled rendering"_ustr); + return; + } + + + int nCurrentPart = pDoc->getPart(); + + pDoc->setPartMode(nPartMode); + + // We need to make sure the internal state is updated, just changing the mode + // might not update the relevant shells (i.e. impress will keep rendering the + // previous mode unless we do this). + // TODO: we might want to do this within the relevant components rather than + // here, but that's also dependent on how we implement embedded object + // rendering I guess? + // TODO: we could be clever and e.g. set to 0 when we change to/from + // embedded object mode, and not when changing between slide/notes/combined + // modes? + if ( nCurrentPart < pDoc->getParts() ) + { + pDoc->setPart( nCurrentPart ); + } + else + { + pDoc->setPart( 0 ); + } +} + +static int doc_getEditMode(LibreOfficeKitDocument* pThis) +{ + comphelper::ProfileZone aZone("doc_getEditMode"); + + SolarMutexGuard aGuard; + SetLastExceptionMsg(); + + ITiledRenderable* pDoc = getTiledRenderable(pThis); + if (!pDoc) + { + SetLastExceptionMsg(u"Document doesn't support tiled rendering"_ustr); + return 0; + } + + return pDoc->getEditMode(); +} + +static void doc_paintTile(LibreOfficeKitDocument* pThis, + unsigned char* pBuffer, + const int nCanvasWidth, const int nCanvasHeight, + const int nTilePosX, const int nTilePosY, + const int nTileWidth, const int nTileHeight) +{ + comphelper::ProfileZone aZone("doc_paintTile"); + + SolarMutexGuard aGuard; + SetLastExceptionMsg(); + + SAL_INFO( "lok.tiledrendering", "paintTile: painting [" << nTileWidth << "x" << nTileHeight << + "]@(" << nTilePosX << ", " << nTilePosY << ") to [" << + nCanvasWidth << "x" << nCanvasHeight << "]px" ); + + ITiledRenderable* pDoc = getTiledRenderable(pThis); + if (!pDoc) + { + SetLastExceptionMsg(u"Document doesn't support tiled rendering"_ustr); + return; + } + +#if defined(UNX) && !defined(MACOSX) || defined(_WIN32) + + // Painting of zoomed or HiDPI spreadsheets is special, we actually draw everything at 100%, + // and only set cairo's (or CoreGraphic's, in the iOS case) scale factor accordingly, so that + // everything is painted bigger or smaller. This is different to what Calc's internal scaling + // would do - because that one is trying to fit the lines between cells to integer multiples of + // pixels. + comphelper::ScopeGuard dpiScaleGuard([]() { comphelper::LibreOfficeKit::setDPIScale(1.0); }); + +#if defined(IOS) + double fDPIScale = 1.0; + + // Onine uses the LOK_TILEMODE_RGBA by default so flip the normal flags + // to kCGImageAlphaPremultipliedLast | kCGImageByteOrder32Big + CGContextRef pCGContext = CGBitmapContextCreate(pBuffer, nCanvasWidth, nCanvasHeight, 8, + nCanvasWidth * 4, CGColorSpaceCreateDeviceRGB(), + kCGImageAlphaPremultipliedLast | kCGImageByteOrder32Big); + + CGContextTranslateCTM(pCGContext, 0, nCanvasHeight); + CGContextScaleCTM(pCGContext, fDPIScale, -fDPIScale); + + SAL_INFO( "lok.tiledrendering", "doc_paintTile: painting [" << nTileWidth << "x" << nTileHeight << + "]@(" << nTilePosX << ", " << nTilePosY << ") to [" << + nCanvasWidth << "x" << nCanvasHeight << "]px" ); + + Size aCanvasSize(nCanvasWidth, nCanvasHeight); + + SystemGraphicsData aData; + aData.rCGContext = reinterpret_cast<CGContextRef>(pCGContext); + + ScopedVclPtrInstance<VirtualDevice> pDevice(aData, Size(1, 1), DeviceFormat::WITHOUT_ALPHA); + pDevice->SetBackground(Wallpaper(COL_TRANSPARENT)); + pDevice->SetOutputSizePixel(aCanvasSize); + pDoc->paintTile(*pDevice, aCanvasSize.Width(), aCanvasSize.Height(), + nTilePosX, nTilePosY, nTileWidth, nTileHeight); + + CGContextRelease(pCGContext); +#else + ScopedVclPtrInstance< VirtualDevice > pDevice(DeviceFormat::WITHOUT_ALPHA); + + // Set background to transparent by default. + pDevice->SetBackground(Wallpaper(COL_TRANSPARENT)); + + pDevice->SetOutputSizePixelScaleOffsetAndLOKBuffer( + Size(nCanvasWidth, nCanvasHeight), Fraction(1.0), Point(), + pBuffer); + + pDoc->paintTile(*pDevice, nCanvasWidth, nCanvasHeight, + nTilePosX, nTilePosY, nTileWidth, nTileHeight); + + static bool bDebug = getenv("LOK_DEBUG_TILES") != nullptr; + if (bDebug) + { + // Draw a small red rectangle in the top left corner so that it's easy to see where a new tile begins. + tools::Rectangle aRect(0, 0, 5, 5); + aRect = pDevice->PixelToLogic(aRect); + pDevice->Push(PushFlags::FILLCOLOR | PushFlags::LINECOLOR); + pDevice->SetFillColor(COL_LIGHTRED); + pDevice->SetLineColor(); + pDevice->DrawRect(aRect); + pDevice->Pop(); + } + +#ifdef _WIN32 + // pBuffer was not used there + pDevice->EnableMapMode(false); + BitmapEx aBmpEx = pDevice->GetBitmapEx({ 0, 0 }, { nCanvasWidth, nCanvasHeight }); + Bitmap aBmp = aBmpEx.GetBitmap(); + AlphaMask aAlpha = aBmpEx.GetAlphaMask(); + BitmapScopedReadAccess sraBmp(aBmp); + BitmapScopedReadAccess sraAlpha(aAlpha); + + assert(sraBmp->Height() == nCanvasHeight); + assert(sraBmp->Width() == nCanvasWidth); + assert(!sraAlpha || sraBmp->Height() == sraAlpha->Height()); + assert(!sraAlpha || sraBmp->Width() == sraAlpha->Width()); + auto p = pBuffer; + for (tools::Long y = 0; y < sraBmp->Height(); ++y) + { + Scanline dataBmp = sraBmp->GetScanline(y); + Scanline dataAlpha = sraAlpha ? sraAlpha->GetScanline(y) : nullptr; + for (tools::Long x = 0; x < sraBmp->Width(); ++x) + { + BitmapColor color = sraBmp->GetPixelFromData(dataBmp, x); + sal_uInt8 alpha = dataAlpha ? sraAlpha->GetPixelFromData(dataAlpha, x).GetBlue() : 255; + *p++ = color.GetBlue(); + *p++ = color.GetGreen(); + *p++ = color.GetRed(); + *p++ = alpha; + } + } +#endif +#endif + +#else + (void) pBuffer; +#endif +} + +static void doc_paintPartTile(LibreOfficeKitDocument* pThis, + unsigned char* pBuffer, + const int nPart, + const int nMode, + const int nCanvasWidth, const int nCanvasHeight, + const int nTilePosX, const int nTilePosY, + const int nTileWidth, const int nTileHeight) +{ + comphelper::ProfileZone aZone("doc_paintPartTile"); + + SolarMutexGuard aGuard; + SetLastExceptionMsg(); + + SAL_INFO( "lok.tiledrendering", "paintPartTile: painting @ " << nPart << " : " << nMode << " [" + << nTileWidth << "x" << nTileHeight << "]@(" + << nTilePosX << ", " << nTilePosY << ") to [" + << nCanvasWidth << "x" << nCanvasHeight << "]px" ); + + LibLODocument_Impl* pDocument = static_cast<LibLODocument_Impl*>(pThis); + int nOrigViewId = doc_getView(pThis); + + ITiledRenderable* pDoc = getTiledRenderable(pThis); + if (!pDoc) + { + SetLastExceptionMsg(u"Document doesn't support tiled rendering"_ustr); + return; + } + + if (nOrigViewId < 0) + { + // tile painting always needs a SfxViewShell::Current(), but actually + // it does not really matter which one - all of them should paint the + // same thing. It's important to get a view for the correct document, + // though. + // doc_getViewsCount() returns the count of views for the document in the current view. + int viewCount = doc_getViewsCount(pThis); + if (viewCount == 0) + return; + + std::vector<int> viewIds(viewCount); + doc_getViewIds(pThis, viewIds.data(), viewCount); + + nOrigViewId = viewIds[0]; + doc_setView(pThis, nOrigViewId); + } + + // Disable callbacks while we are painting. + if (nOrigViewId >= 0) + { + const auto handlerIt = pDocument->mpCallbackFlushHandlers.find(nOrigViewId); + if (handlerIt != pDocument->mpCallbackFlushHandlers.end()) + handlerIt->second->disableCallbacks(); + } + + try + { + // Text documents have a single coordinate system; don't change part. + int nOrigPart = 0; + const int aType = doc_getDocumentType(pThis); + const bool isText = (aType == LOK_DOCTYPE_TEXT); + const bool isCalc = (aType == LOK_DOCTYPE_SPREADSHEET); + int nOrigEditMode = 0; + bool bPaintTextEdit = true; + int nViewId = nOrigViewId; + int nLastNonEditorView = -1; + int nViewMatchingMode = -1; + SfxViewShell* pCurrentViewShell = SfxViewShell::Current(); + + if (!isText) + { + // Check if just switching to another view is enough, that has + // less side-effects. + if (nPart != doc_getPart(pThis) || nMode != pDoc->getEditMode()) + { + SfxViewShell* pViewShell = SfxViewShell::GetFirst(); + while (pViewShell) + { + bool bIsInEdit = pViewShell->GetDrawView() && + pViewShell->GetDrawView()->GetTextEditOutliner(); + + OString sCurrentViewRenderState = pDoc->getViewRenderState(pCurrentViewShell); + OString sNewRenderState = pDoc->getViewRenderState(pViewShell); + + if (sCurrentViewRenderState == sNewRenderState && !bIsInEdit) + nLastNonEditorView = pViewShell->GetViewShellId().get(); + + if (pViewShell->getPart() == nPart && + pViewShell->getEditMode() == nMode && + sCurrentViewRenderState == sNewRenderState && + !bIsInEdit) + { + nViewId = pViewShell->GetViewShellId().get(); + nViewMatchingMode = nViewId; + nLastNonEditorView = nViewId; + doc_setView(pThis, nViewId); + break; + } + else if (pViewShell->getEditMode() == nMode && sCurrentViewRenderState == sNewRenderState && !bIsInEdit) + { + nViewMatchingMode = pViewShell->GetViewShellId().get(); + } + + pViewShell = SfxViewShell::GetNext(*pViewShell); + } + } + + // if not found view with correct part + // - at least avoid rendering active textbox, This is for Impress. + // - prefer view with the same mode + if (nViewMatchingMode >= 0 && nViewMatchingMode != nViewId) + { + nViewId = nViewMatchingMode; + doc_setView(pThis, nViewId); + } + else if (!isCalc && nLastNonEditorView >= 0 && nLastNonEditorView != nViewId && + pCurrentViewShell && pCurrentViewShell->GetDrawView() && + pCurrentViewShell->GetDrawView()->GetTextEditOutliner()) + { + nViewId = nLastNonEditorView; + doc_setView(pThis, nViewId); + } + + // Disable callbacks while we are painting - after setting the view + if (nViewId != nOrigViewId && nViewId >= 0) + { + const auto handlerIt = pDocument->mpCallbackFlushHandlers.find(nViewId); + if (handlerIt != pDocument->mpCallbackFlushHandlers.end()) + handlerIt->second->disableCallbacks(); + } + + nOrigPart = doc_getPart(pThis); + if (nPart != nOrigPart) + { + doc_setPartImpl(pThis, nPart, false); + } + + nOrigEditMode = pDoc->getEditMode(); + if (nOrigEditMode != nMode) + { + SfxLokHelper::setEditMode(nMode, pDoc); + } + + bPaintTextEdit = (nPart == nOrigPart && nMode == nOrigEditMode); + pDoc->setPaintTextEdit(bPaintTextEdit); + } + + doc_paintTile(pThis, pBuffer, nCanvasWidth, nCanvasHeight, nTilePosX, nTilePosY, nTileWidth, nTileHeight); + + if (!isText) + { + pDoc->setPaintTextEdit(true); + + if (nMode != nOrigEditMode) + { + SfxLokHelper::setEditMode(nOrigEditMode, pDoc); + } + + if (nPart != nOrigPart) + { + doc_setPartImpl(pThis, nOrigPart, false); + } + + if (nViewId != nOrigViewId) + { + if (nViewId >= 0) + { + const auto handlerIt = pDocument->mpCallbackFlushHandlers.find(nViewId); + if (handlerIt != pDocument->mpCallbackFlushHandlers.end()) + handlerIt->second->enableCallbacks(); + } + + doc_setView(pThis, nOrigViewId); + } + } + } + catch (const std::exception&) + { + // Nothing to do but restore the PartTilePainting flag. + } + + if (nOrigViewId >= 0) + { + const auto handlerIt = pDocument->mpCallbackFlushHandlers.find(nOrigViewId); + if (handlerIt != pDocument->mpCallbackFlushHandlers.end()) + handlerIt->second->enableCallbacks(); + } +} + +static int doc_getTileMode(SAL_UNUSED_PARAMETER LibreOfficeKitDocument* /*pThis*/) +{ + SetLastExceptionMsg(); +#if ENABLE_CAIRO_RGBA || defined IOS + return LOK_TILEMODE_RGBA; +#else + return LOK_TILEMODE_BGRA; +#endif +} + +static void doc_getDocumentSize(LibreOfficeKitDocument* pThis, + long* pWidth, + long* pHeight) +{ + comphelper::ProfileZone aZone("doc_getDocumentSize"); + + SolarMutexGuard aGuard; + SetLastExceptionMsg(); + + ITiledRenderable* pDoc = getTiledRenderable(pThis); + if (pDoc) + { + Size aDocumentSize = pDoc->getDocumentSize(); + *pWidth = aDocumentSize.Width(); + *pHeight = aDocumentSize.Height(); + } + else + { + SetLastExceptionMsg(u"Document doesn't support tiled rendering"_ustr); + } +} + +static void doc_getDataArea(LibreOfficeKitDocument* pThis, + long nTab, + long* pCol, + long* pRow) +{ + comphelper::ProfileZone aZone("doc_getDataArea"); + + SolarMutexGuard aGuard; + SetLastExceptionMsg(); + + ITiledRenderable* pDoc = getTiledRenderable(pThis); + if (pDoc) + { + Size aDocumentSize = pDoc->getDataArea(nTab); + *pCol = aDocumentSize.Width(); + *pRow = aDocumentSize.Height(); + } + else + { + SetLastExceptionMsg(u"Document doesn't support tiled rendering"_ustr); + } +} + +static void doc_initializeForRendering(LibreOfficeKitDocument* pThis, + const char* pArguments) +{ + comphelper::ProfileZone aZone("doc_initializeForRendering"); + + SolarMutexGuard aGuard; + SetLastExceptionMsg(); + + ITiledRenderable* pDoc = getTiledRenderable(pThis); + if (pDoc) + { + doc_iniUnoCommands(); + pDoc->initializeForTiledRendering( + comphelper::containerToSequence(jsonToPropertyValuesVector(pArguments))); + } +} + +static void doc_registerCallback(LibreOfficeKitDocument* pThis, + LibreOfficeKitCallback pCallback, + void* pData) +{ + SolarMutexGuard aGuard; + SetLastExceptionMsg(); + + LibLODocument_Impl* pDocument = static_cast<LibLODocument_Impl*>(pThis); + + const int nView = SfxLokHelper::getView(); + if (nView < 0) + return; + + const size_t nId = nView; + if (pCallback != nullptr) + { + for (auto& pair : pDocument->mpCallbackFlushHandlers) + { + if (pair.first == nId) + continue; + + pair.second->addViewStates(nView); + } + } + else + { + for (auto& pair : pDocument->mpCallbackFlushHandlers) + { + if (pair.first == nId) + continue; + + pair.second->removeViewStates(nView); + } + } + + pDocument->mpCallbackFlushHandlers[nView] = std::make_shared<CallbackFlushHandler>(pThis, pCallback, pData); + + if (pCallback != nullptr) + { + for (const auto& pair : pDocument->mpCallbackFlushHandlers) + { + if (pair.first == nId) + continue; + + pDocument->mpCallbackFlushHandlers[nView]->addViewStates(pair.first); + } + + if (SfxViewShell* pViewShell = SfxViewShell::Current()) + { + pDocument->mpCallbackFlushHandlers[nView]->setViewId(pViewShell->GetViewShellId().get()); + pViewShell->setLibreOfficeKitViewCallback(pDocument->mpCallbackFlushHandlers[nView].get()); + } + + if (pDocument->maFontsMissing.size() != 0) + { + OString sPayload = "{ \"fontsmissing\": [ "_ostr; + bool bFirst = true; + for (const auto &f : pDocument->maFontsMissing) + { + if (bFirst) + bFirst = false; + else + sPayload += ", "; + sPayload += "\"" + f.toUtf8() + "\""; + } + sPayload += " ] }"; + pCallback(LOK_CALLBACK_FONTS_MISSING, sPayload.getStr(), pData); + pDocument->maFontsMissing.clear(); + } + } + else + { + if (SfxViewShell* pViewShell = SfxViewShell::Current()) + { + pViewShell->setLibreOfficeKitViewCallback(nullptr); + pDocument->mpCallbackFlushHandlers[nView]->setViewId(-1); + } + } +} + +/// Returns the JSON representation of all the comments in the document +static char* getPostIts(LibreOfficeKitDocument* pThis) +{ + SetLastExceptionMsg(); + ITiledRenderable* pDoc = getTiledRenderable(pThis); + if (!pDoc) + { + SetLastExceptionMsg(u"Document doesn't support tiled rendering"_ustr); + return nullptr; + } + tools::JsonWriter aJsonWriter; + pDoc->getPostIts(aJsonWriter); + return convertOString(aJsonWriter.finishAndGetAsOString()); +} + +/// Returns the JSON representation of the positions of all the comments in the document +static char* getPostItsPos(LibreOfficeKitDocument* pThis) +{ + SetLastExceptionMsg(); + ITiledRenderable* pDoc = getTiledRenderable(pThis); + if (!pDoc) + { + SetLastExceptionMsg(u"Document doesn't support tiled rendering"_ustr); + return nullptr; + } + tools::JsonWriter aJsonWriter; + pDoc->getPostItsPos(aJsonWriter); + return convertOString(aJsonWriter.finishAndGetAsOString()); +} + +static char* getRulerState(LibreOfficeKitDocument* pThis) +{ + SetLastExceptionMsg(); + ITiledRenderable* pDoc = getTiledRenderable(pThis); + if (!pDoc) + { + SetLastExceptionMsg(u"Document doesn't support tiled rendering"_ustr); + return nullptr; + } + tools::JsonWriter aJsonWriter; + pDoc->getRulerState(aJsonWriter); + return convertOString(aJsonWriter.finishAndGetAsOString()); +} + +static void doc_postKeyEvent(LibreOfficeKitDocument* pThis, int nType, int nCharCode, int nKeyCode) +{ + comphelper::ProfileZone aZone("doc_postKeyEvent"); + + SolarMutexGuard aGuard; + SetLastExceptionMsg(); + + ITiledRenderable* pDoc = getTiledRenderable(pThis); + if (!pDoc) + { + SetLastExceptionMsg(u"Document doesn't support tiled rendering"_ustr); + return; + } + + try + { + pDoc->postKeyEvent(nType, nCharCode, nKeyCode); + } + catch (const uno::Exception& exception) + { + SetLastExceptionMsg(exception.Message); + SAL_INFO("lok", "Failed to postKeyEvent " << exception.Message); + } +} + +static void doc_setBlockedCommandList(LibreOfficeKitDocument* /*pThis*/, int nViewId, const char* blockedCommandList) +{ + SolarMutexGuard aGuard; + SfxLokHelper::setBlockedCommandList(nViewId, blockedCommandList); +} + +static void doc_postWindowExtTextInputEvent(LibreOfficeKitDocument* pThis, unsigned nWindowId, int nType, const char* pText) +{ + comphelper::ProfileZone aZone("doc_postWindowExtTextInputEvent"); + + SolarMutexGuard aGuard; + VclPtr<vcl::Window> pWindow; + if (nWindowId == 0) + { + ITiledRenderable* pDoc = getTiledRenderable(pThis); + if (!pDoc) + { + SetLastExceptionMsg(u"Document doesn't support tiled rendering"_ustr); + return; + } + pWindow = pDoc->getDocWindow(); + } + else + { + pWindow = vcl::Window::FindLOKWindow(nWindowId); + } + + if (!pWindow) + { + SetLastExceptionMsg("No window found for window id: " + OUString::number(nWindowId)); + return; + } + + SfxLokHelper::postExtTextEventAsync(pWindow, nType, OUString::fromUtf8(std::string_view(pText, strlen(pText)))); +} + +static void doc_removeTextContext(LibreOfficeKitDocument* pThis, unsigned nLOKWindowId, int nCharBefore, int nCharAfter) +{ + SolarMutexGuard aGuard; + VclPtr<vcl::Window> pWindow; + if (nLOKWindowId == 0) + { + ITiledRenderable* pDoc = getTiledRenderable(pThis); + if (!pDoc) + { + SetLastExceptionMsg(u"Document doesn't support tiled rendering"_ustr); + return; + } + pWindow = pDoc->getDocWindow(); + } + else + { + pWindow = vcl::Window::FindLOKWindow(nLOKWindowId); + } + + if (!pWindow) + { + SetLastExceptionMsg("No window found for window id: " + OUString::number(nLOKWindowId)); + return; + } + + // Annoyingly - backspace and delete are handled in the apps via an accelerator + // which are PostMessage'd by SfxViewShell::ExecKey_Impl so to stay in the same + // order we do this synchronously here, unless we're in a dialog. + if (nCharBefore > 0) + { + // backspace + if (nLOKWindowId == 0) + { + KeyEvent aEvt(8, KEY_BACKSPACE); + for (int i = 0; i < nCharBefore; ++i) + pWindow->KeyInput(aEvt); + } + else + SfxLokHelper::postKeyEventAsync(pWindow, LOK_KEYEVENT_KEYINPUT, 8, KEY_BACKSPACE, nCharBefore - 1); + } + + if (nCharAfter > 0) + { + // delete (forward) + if (nLOKWindowId == 0) + { + KeyEvent aEvt(46, KEY_DELETE); + for (int i = 0; i < nCharAfter; ++i) + pWindow->KeyInput(aEvt); + } + else + SfxLokHelper::postKeyEventAsync(pWindow, LOK_KEYEVENT_KEYINPUT, 46, KEY_DELETE, nCharAfter - 1); + } +} + +static void doc_postWindowKeyEvent(LibreOfficeKitDocument* /*pThis*/, unsigned nLOKWindowId, int nType, int nCharCode, int nKeyCode) +{ + comphelper::ProfileZone aZone("doc_postWindowKeyEvent"); + + SolarMutexGuard aGuard; + SetLastExceptionMsg(); + + VclPtr<Window> pWindow = vcl::Window::FindLOKWindow(nLOKWindowId); + if (!pWindow) + { + SetLastExceptionMsg(u"Document doesn't support dialog rendering, or window not found."_ustr); + return; + } + + KeyEvent aEvent(nCharCode, nKeyCode, 0); + + switch (nType) + { + case LOK_KEYEVENT_KEYINPUT: + Application::PostKeyEvent(VclEventId::WindowKeyInput, pWindow, &aEvent); + break; + case LOK_KEYEVENT_KEYUP: + Application::PostKeyEvent(VclEventId::WindowKeyUp, pWindow, &aEvent); + break; + default: + assert(false); + break; + } +} + +static size_t doc_renderShapeSelection(LibreOfficeKitDocument* pThis, char** pOutput) +{ + comphelper::ProfileZone aZone("doc_renderShapeSelection"); + + SolarMutexGuard aGuard; + SetLastExceptionMsg(); + + LokChartHelper aChartHelper(SfxViewShell::Current()); + + if (aChartHelper.GetWindow()) + return 0; + + try + { + LibLODocument_Impl* pDocument = static_cast<LibLODocument_Impl*>(pThis); + + uno::Reference<frame::XStorable> xStorable(pDocument->mxComponent, uno::UNO_QUERY_THROW); + + SvMemoryStream aOutStream; + uno::Reference<io::XOutputStream> xOut = new utl::OOutputStreamWrapper(aOutStream); + + utl::MediaDescriptor aMediaDescriptor; + switch (doc_getDocumentType(pThis)) + { + case LOK_DOCTYPE_PRESENTATION: + aMediaDescriptor[u"FilterName"_ustr] <<= u"impress_svg_Export"_ustr; + break; + case LOK_DOCTYPE_DRAWING: + aMediaDescriptor[u"FilterName"_ustr] <<= u"draw_svg_Export"_ustr; + break; + case LOK_DOCTYPE_TEXT: + aMediaDescriptor[u"FilterName"_ustr] <<= u"writer_svg_Export"_ustr; + break; + case LOK_DOCTYPE_SPREADSHEET: + aMediaDescriptor[u"FilterName"_ustr] <<= u"calc_svg_Export"_ustr; + break; + default: + SAL_WARN("lok", "Failed to render shape selection: Document type is not supported"); + } + aMediaDescriptor[u"SelectionOnly"_ustr] <<= true; + aMediaDescriptor[u"OutputStream"_ustr] <<= xOut; + aMediaDescriptor[u"IsPreview"_ustr] <<= true; // will down-scale graphics + + xStorable->storeToURL(u"private:stream"_ustr, aMediaDescriptor.getAsConstPropertyValueList()); + + if (pOutput) + { + const size_t nOutputSize = aOutStream.GetEndOfData(); + *pOutput = static_cast<char*>(malloc(nOutputSize)); + if (*pOutput) + { + std::memcpy(*pOutput, aOutStream.GetData(), nOutputSize); + return nOutputSize; + } + } + } + catch (const uno::Exception& exception) + { + css::uno::Any exAny( cppu::getCaughtException() ); + SetLastExceptionMsg(exception.Message); + SAL_WARN("lok", "Failed to render shape selection: " << exceptionToString(exAny)); + } + + return 0; +} + +namespace { + +/** Class to react on finishing of a dispatched command. + + This will call a LOK_COMMAND_FINISHED callback when postUnoCommand was + called with the parameter requesting the notification. + + @see LibreOfficeKitCallbackType::LOK_CALLBACK_UNO_COMMAND_RESULT. +*/ +class DispatchResultListener : public cppu::WeakImplHelper<css::frame::XDispatchResultListener> +{ + const OString maCommand; ///< Command for which this is the result. + const std::shared_ptr<CallbackFlushHandler> mpCallback; ///< Callback to call. + const std::chrono::steady_clock::time_point mSaveTime; //< The time we started saving. + const bool mbWasModified; //< Whether or not the document was modified before saving. + +public: + DispatchResultListener(const char* pCommand, std::shared_ptr<CallbackFlushHandler> pCallback) + : maCommand(pCommand) + , mpCallback(std::move(pCallback)) + , mSaveTime(std::chrono::steady_clock::now()) + , mbWasModified(SfxObjectShell::Current()->IsModified()) + { + assert(mpCallback); + } + + virtual void SAL_CALL dispatchFinished(const css::frame::DispatchResultEvent& rEvent) override + { + tools::JsonWriter aJson; + aJson.put("commandName", maCommand); + + if (rEvent.State != frame::DispatchResultState::DONTKNOW) + { + bool bSuccess = (rEvent.State == frame::DispatchResultState::SUCCESS); + aJson.put("success", bSuccess); + } + + unoAnyToJson(aJson, "result", rEvent.Result); + aJson.put("wasModified", mbWasModified); + aJson.put("startUnixTimeMics", + std::chrono::time_point_cast<std::chrono::microseconds>(mSaveTime) + .time_since_epoch() + .count()); + aJson.put("saveDurationMics", std::chrono::duration_cast<std::chrono::microseconds>( + std::chrono::steady_clock::now() - mSaveTime) + .count()); + mpCallback->queue(LOK_CALLBACK_UNO_COMMAND_RESULT, aJson.finishAndGetAsOString()); + } + + virtual void SAL_CALL disposing(const css::lang::EventObject&) override {} +}; + +} // anonymous namespace + + +static void lcl_sendDialogEvent(unsigned long long int nWindowId, const char* pArguments) +{ + SolarMutexGuard aGuard; + + StringMap aMap(jsdialog::jsonToStringMap(pArguments)); + + if (aMap.find(u"id"_ustr) == aMap.end()) + return; + + sal_uInt64 nCurrentShellId = reinterpret_cast<sal_uInt64>(SfxViewShell::Current()); + + try + { + OUString sControlId = aMap[u"id"_ustr]; + OUString sWindowId = OUString::number(nWindowId); + OUString sCurrentShellId = OUString::number(nCurrentShellId); + + // special values for window id + if (nWindowId == static_cast<unsigned long long int>(-1)) + sWindowId = sCurrentShellId + "sidebar"; + if (nWindowId == static_cast<unsigned long long int>(-2)) + sWindowId = sCurrentShellId + "notebookbar"; + if (nWindowId == static_cast<unsigned long long int>(-3)) + sWindowId = sCurrentShellId + "formulabar"; + + // dialogs send own id but notebookbar and sidebar controls are remembered by SfxViewShell id + if (jsdialog::ExecuteAction(sWindowId, sControlId, aMap)) + return; + + if (jsdialog::ExecuteAction(sCurrentShellId + "sidebar", sControlId, aMap)) + return; + if (jsdialog::ExecuteAction(sCurrentShellId + "notebookbar", sControlId, aMap)) + return; + if (jsdialog::ExecuteAction(sCurrentShellId + "formulabar", sControlId, aMap)) + return; + // this is needed for dialogs shown before document is loaded: MacroWarning dialog, etc... + // these dialogs are created with WindowId "0" + if (!SfxViewShell::Current() && jsdialog::ExecuteAction(u"0"_ustr, sControlId, aMap)) + return; + + // force resend - used in mobile-wizard + jsdialog::SendFullUpdate(sCurrentShellId + "sidebar", u"Panel"_ustr); + + } catch(...) {} +} + + +static void doc_sendDialogEvent(LibreOfficeKitDocument* /*pThis*/, unsigned long long int nWindowId, const char* pArguments) +{ + lcl_sendDialogEvent(nWindowId, pArguments); +} + +static void lo_sendDialogEvent(LibreOfficeKit* /*pThis*/, unsigned long long int nWindowId, const char* pArguments) +{ + lcl_sendDialogEvent(nWindowId, pArguments); +} + +static void lo_setOption(LibreOfficeKit* /*pThis*/, const char *pOption, const char* pValue) +{ + static char* pCurrentSalLogOverride = nullptr; + + if (strcmp(pOption, "traceeventrecording") == 0) + { + if (strcmp(pValue, "start") == 0) + { + comphelper::TraceEvent::setBufferSizeAndCallback(100, TraceEventDumper::flushRecordings); + comphelper::TraceEvent::startRecording(); + if (traceEventDumper == nullptr) + traceEventDumper = new TraceEventDumper(); + } + else if (strcmp(pValue, "stop") == 0) + comphelper::TraceEvent::stopRecording(); + } + else if (strcmp(pOption, "sallogoverride") == 0) + { + if (pCurrentSalLogOverride != nullptr) + free(pCurrentSalLogOverride); + if (pValue == nullptr) + pCurrentSalLogOverride = nullptr; + else + pCurrentSalLogOverride = strdup(pValue); + + if (pCurrentSalLogOverride == nullptr || pCurrentSalLogOverride[0] == '\0') + sal_detail_set_log_selector(nullptr); + else + sal_detail_set_log_selector(pCurrentSalLogOverride); + } +#ifdef LINUX + else if (strcmp(pOption, "addfont") == 0) + { + if (memcmp(pValue, "file://", 7) == 0) + pValue += 7; + + int fd = open(pValue, O_RDONLY); + if (fd == -1) + { + std::cerr << "Could not open font file '" << pValue << "': " << strerror(errno) << std::endl; + return; + } + + OUString sMagicFileName = "file:///:FD:/" + OUString::number(fd); + + OutputDevice *pDevice = Application::GetDefaultDevice(); + OutputDevice::ImplClearAllFontData(false); + pDevice->AddTempDevFont(sMagicFileName, ""); + OutputDevice::ImplRefreshAllFontData(false); + } +#endif +} + +static void lo_dumpState (LibreOfficeKit* pThis, const char* /* pOptions */, char** pState) +{ + if (!pState) + return; + + // NB. no SolarMutexGuard since this may be caused in some extremis / deadlock + SetLastExceptionMsg(); + + *pState = nullptr; + OStringBuffer aState(4096*256); + + LibLibreOffice_Impl* pLib = static_cast<LibLibreOffice_Impl*>(pThis); + + pLib->dumpState(aState); + + *pState = convertOString(aState.makeStringAndClear()); +} + +void LibLibreOffice_Impl::dumpState(rtl::OStringBuffer &rState) +{ + rState.append("LibreOfficeKit state:" + "\n\tLastExceptionMsg:\t"); + rState.append(rtl::OUStringToOString(maLastExceptionMsg, RTL_TEXTENCODING_UTF8)); + rState.append("\n\tUnipoll:\t"); + rState.append(vcl::lok::isUnipoll() ? "yes" : "no: events on thread"); + rState.append("\n\tOptionalFeatures:\t0x"); + rState.append(static_cast<sal_Int64>(mOptionalFeatures), 16); + rState.append("\n\tCallbackData:\t0x"); + rState.append(reinterpret_cast<sal_Int64>(mpCallback), 16); + // TODO: dump mInteractionMap + SfxLokHelper::dumpState(rState); + vcl::lok::dumpState(rState); +} + +static void doc_postUnoCommand(LibreOfficeKitDocument* pThis, const char* pCommand, const char* pArguments, bool bNotifyWhenFinished) +{ + comphelper::ProfileZone aZone("doc_postUnoCommand"); + + SolarMutexGuard aGuard; + SetLastExceptionMsg(); + + SfxObjectShell* pDocSh = SfxObjectShell::Current(); + OUString aCommand(pCommand, strlen(pCommand), RTL_TEXTENCODING_UTF8); + LibLODocument_Impl* pDocument = static_cast<LibLODocument_Impl*>(pThis); + + std::vector<beans::PropertyValue> aPropertyValuesVector(jsonToPropertyValuesVector(pArguments)); + + if (!vcl::lok::isUnipoll()) + { + beans::PropertyValue aSynchronMode; + aSynchronMode.Name = u"SynchronMode"_ustr; + aSynchronMode.Value <<= false; + aPropertyValuesVector.push_back(aSynchronMode); + } + + int nView = SfxLokHelper::getView(); + if (nView < 0) + return; + + if (gImpl && aCommand == ".uno:ToggleOrientation") + { + ExecuteOrientationChange(); + return; + } + + // handle potential interaction + if (gImpl && aCommand == ".uno:Save") + { + // Check if saving a PDF file + OUString aMimeType = lcl_getCurrentDocumentMimeType(pDocument); + if (pDocSh && pDocSh->IsModified() && aMimeType == "application/pdf") + { + // If we have a PDF file (for saving annotations for example), we need + // to run save-as to the same file as the opened document. Plain save + // doesn't work as the PDF is not a "native" format. + uno::Reference<frame::XStorable> xStorable(pDocument->mxComponent, uno::UNO_QUERY_THROW); + OUString aURL = xStorable->getLocation(); + OString aURLUtf8 = OUStringToOString(aURL, RTL_TEXTENCODING_UTF8); + bool bResult = doc_saveAs(pThis, aURLUtf8.getStr(), "pdf", nullptr); + + // Send the result of save + tools::JsonWriter aJson; + aJson.put("commandName", pCommand); + aJson.put("success", bResult); + pDocument->mpCallbackFlushHandlers[nView]->queue(LOK_CALLBACK_UNO_COMMAND_RESULT, aJson.finishAndGetAsOString()); + return; + } + + + rtl::Reference<LOKInteractionHandler> const pInteraction( + new LOKInteractionHandler("save"_ostr, gImpl, pDocument)); + uno::Reference<task::XInteractionHandler2> const xInteraction(pInteraction); + + beans::PropertyValue aValue; + aValue.Name = u"InteractionHandler"_ustr; + aValue.Value <<= xInteraction; + aPropertyValuesVector.push_back(aValue); + + bool bDontSaveIfUnmodified = false; + std::erase_if(aPropertyValuesVector, + [&bDontSaveIfUnmodified](const beans::PropertyValue& aItem){ + if (aItem.Name == "DontSaveIfUnmodified") + { + bDontSaveIfUnmodified = aItem.Value.get<bool>(); + return true; + } + return false; + }); + + // skip saving and tell the result via UNO_COMMAND_RESULT + if (bDontSaveIfUnmodified && (!pDocSh || !pDocSh->IsModified())) + { + tools::JsonWriter aJson; + aJson.put("commandName", pCommand); + aJson.put("success", false); + // Add the reason for not saving + { + auto resultNode = aJson.startNode("result"); + aJson.put("type", "string"); + aJson.put("value", "unmodified"); + } + pDocument->mpCallbackFlushHandlers[nView]->queue(LOK_CALLBACK_UNO_COMMAND_RESULT, aJson.finishAndGetAsOString()); + return; + } + } + else if (gImpl && aCommand == ".uno:TransformDialog") + { + bool bNeedConversion = false; + SfxViewShell* pViewShell = SfxViewShell::Current(); + LokChartHelper aChartHelper(pViewShell); + + if (aChartHelper.GetWindow() ) + { + bNeedConversion = true; + } + else if (const SdrView* pView = pViewShell->GetDrawView()) + { + if (OutputDevice* pOutputDevice = pView->GetFirstOutputDevice()) + { + bNeedConversion = (pOutputDevice->GetMapMode().GetMapUnit() == MapUnit::Map100thMM); + } + } + + if (bNeedConversion) + { + sal_Int32 value; + for (beans::PropertyValue& rPropValue: aPropertyValuesVector) + { + if (rPropValue.Name == "TransformPosX" + || rPropValue.Name == "TransformPosY" + || rPropValue.Name == "TransformWidth" + || rPropValue.Name == "TransformHeight" + || rPropValue.Name == "TransformRotationX" + || rPropValue.Name == "TransformRotationY") + { + rPropValue.Value >>= value; + value = o3tl::convert(value, o3tl::Length::twip, o3tl::Length::mm100); + rPropValue.Value <<= value; + } + } + } + + if (aChartHelper.GetWindow() && aPropertyValuesVector.size() > 0) + { + if (aPropertyValuesVector[0].Name != "Action") + { + tools::Rectangle aChartBB = aChartHelper.GetChartBoundingBox(); + + int nLeft = o3tl::convert(aChartBB.Left(), o3tl::Length::twip, o3tl::Length::mm100); + int nTop = o3tl::convert(aChartBB.Top(), o3tl::Length::twip, o3tl::Length::mm100); + + for (beans::PropertyValue& rPropValue: aPropertyValuesVector) + { + if (rPropValue.Name == "TransformPosX" || rPropValue.Name == "TransformRotationX") + { + auto const value = *o3tl::doAccess<sal_Int32>(rPropValue.Value); + rPropValue.Value <<= value - nLeft; + } + else if (rPropValue.Name == "TransformPosY" || rPropValue.Name == "TransformRotationY") + { + auto const value = *o3tl::doAccess<sal_Int32>(rPropValue.Value); + rPropValue.Value <<= value - nTop; + } + } + } + util::URL aCommandURL; + aCommandURL.Path = u"LOKTransform"_ustr; + css::uno::Reference<css::frame::XDispatch>& aChartDispatcher = aChartHelper.GetXDispatcher(); + aChartDispatcher->dispatch(aCommandURL, comphelper::containerToSequence(aPropertyValuesVector)); + return; + } + } + else if (gImpl && aCommand == ".uno:LOKSidebarWriterPage") + { + setupSidebar(u"WriterPageDeck"); + return; + } + else if (gImpl && aCommand == ".uno:SidebarShow") + { + setupSidebar(); + return; + } + else if (gImpl && aCommand == ".uno:SidebarHide") + { + hideSidebar(); + return; + } + + bool bResult = false; + LokChartHelper aChartHelper(SfxViewShell::Current()); + + if (aChartHelper.GetWindow() && aCommand != ".uno:Save" ) + { + util::URL aCommandURL; + aCommandURL.Path = aCommand.copy(5); + css::uno::Reference<css::frame::XDispatch>& aChartDispatcher = aChartHelper.GetXDispatcher(); + aChartDispatcher->dispatch(aCommandURL, comphelper::containerToSequence(aPropertyValuesVector)); + return; + } + if (LokStarMathHelper aMathHelper(SfxViewShell::Current()); + aMathHelper.GetGraphicWindow() && aCommand != ".uno:Save") + { + aMathHelper.Dispatch(aCommand, comphelper::containerToSequence(aPropertyValuesVector)); + return; + } + if (bNotifyWhenFinished && pDocument->mpCallbackFlushHandlers.count(nView)) + { + bResult = comphelper::dispatchCommand(aCommand, comphelper::containerToSequence(aPropertyValuesVector), + new DispatchResultListener(pCommand, pDocument->mpCallbackFlushHandlers[nView])); + } + else + bResult = comphelper::dispatchCommand(aCommand, comphelper::containerToSequence(aPropertyValuesVector)); + + if (!bResult) + { + SetLastExceptionMsg("Failed to dispatch " + aCommand); + } +} + +static void doc_postMouseEvent(LibreOfficeKitDocument* pThis, int nType, int nX, int nY, int nCount, int nButtons, int nModifier) +{ + comphelper::ProfileZone aZone("doc_postMouseEvent"); + + SolarMutexGuard aGuard; + SetLastExceptionMsg(); + + ITiledRenderable* pDoc = getTiledRenderable(pThis); + if (!pDoc) + { + SetLastExceptionMsg(u"Document doesn't support tiled rendering"_ustr); + return; + } + try + { + pDoc->postMouseEvent(nType, nX, nY, nCount, nButtons, nModifier); + } + catch (const uno::Exception& exception) + { + SetLastExceptionMsg(exception.Message); + SAL_INFO("lok", "Failed to postMouseEvent " << exception.Message); + } +} + +static void doc_postWindowMouseEvent(LibreOfficeKitDocument* /*pThis*/, unsigned nLOKWindowId, int nType, int nX, int nY, int nCount, int nButtons, int nModifier) +{ + comphelper::ProfileZone aZone("doc_postWindowMouseEvent"); + + SolarMutexGuard aGuard; + SetLastExceptionMsg(); + + VclPtr<Window> pWindow = vcl::Window::FindLOKWindow(nLOKWindowId); + if (!pWindow) + { + SetLastExceptionMsg(u"Document doesn't support dialog rendering, or window not found."_ustr); + return; + } + + const Point aPos(nX, nY); + + MouseEvent aEvent(aPos, nCount, MouseEventModifiers::SIMPLECLICK, nButtons, nModifier); + + vcl::EnableDialogInput(pWindow); + + switch (nType) + { + case LOK_MOUSEEVENT_MOUSEBUTTONDOWN: + Application::PostMouseEvent(VclEventId::WindowMouseButtonDown, pWindow, &aEvent); + break; + case LOK_MOUSEEVENT_MOUSEBUTTONUP: + Application::PostMouseEvent(VclEventId::WindowMouseButtonUp, pWindow, &aEvent); + break; + case LOK_MOUSEEVENT_MOUSEMOVE: + Application::PostMouseEvent(VclEventId::WindowMouseMove, pWindow, &aEvent); + break; + default: + assert(false); + break; + } +} + +static void doc_postWindowGestureEvent(LibreOfficeKitDocument* /*pThis*/, unsigned nLOKWindowId, const char* pType, int nX, int nY, int nOffset) +{ + comphelper::ProfileZone aZone("doc_postWindowGestureEvent"); + + SolarMutexGuard aGuard; + SetLastExceptionMsg(); + + VclPtr<Window> pWindow = vcl::Window::FindLOKWindow(nLOKWindowId); + if (!pWindow) + { + SetLastExceptionMsg(u"Document doesn't support dialog rendering, or window not found."_ustr); + return; + } + + OString aType(pType); + GestureEventPanType eEventType = GestureEventPanType::Update; + + if (aType == "panBegin") + eEventType = GestureEventPanType::Begin; + else if (aType == "panEnd") + eEventType = GestureEventPanType::End; + + GestureEventPan aEvent { + sal_Int32(nX), + sal_Int32(nY), + eEventType, + sal_Int32(nOffset), + PanningOrientation::Vertical, + }; + + vcl::EnableDialogInput(pWindow); + + Application::PostGestureEvent(VclEventId::WindowGestureEvent, pWindow, &aEvent); +} + +static void doc_setTextSelection(LibreOfficeKitDocument* pThis, int nType, int nX, int nY) +{ + comphelper::ProfileZone aZone("doc_setTextSelection"); + + SolarMutexGuard aGuard; + SetLastExceptionMsg(); + + ITiledRenderable* pDoc = getTiledRenderable(pThis); + if (!pDoc) + { + SetLastExceptionMsg(u"Document doesn't support tiled rendering"_ustr); + return; + } + + pDoc->setTextSelection(nType, nX, nY); +} + +static void doc_setWindowTextSelection(LibreOfficeKitDocument* /*pThis*/, unsigned nLOKWindowId, bool swap, int nX, int nY) +{ + comphelper::ProfileZone aZone("doc_setWindowTextSelection"); + + SolarMutexGuard aGuard; + SetLastExceptionMsg(); + + VclPtr<Window> pWindow = vcl::Window::FindLOKWindow(nLOKWindowId); + if (!pWindow) + { + SetLastExceptionMsg(u"Document doesn't support dialog rendering, or window not found."_ustr); + return; + } + + + Size aOffset(pWindow->GetOutOffXPixel(), pWindow->GetOutOffYPixel()); + Point aCursorPos(nX, nY); + aCursorPos.Move(aOffset); + sal_uInt16 nModifier = swap ? KEY_MOD1 + KEY_MOD2 : KEY_SHIFT; + + MouseEvent aCursorEvent(aCursorPos, 1, MouseEventModifiers::SIMPLECLICK, 0, nModifier); + Application::PostMouseEvent(VclEventId::WindowMouseButtonDown, pWindow, &aCursorEvent); + Application::PostMouseEvent(VclEventId::WindowMouseButtonUp, pWindow, &aCursorEvent); +} + +static bool getFromTransferable( + const css::uno::Reference<css::datatransfer::XTransferable> &xTransferable, + const OString &aInMimeType, OString &aRet); + +static bool encodeImageAsHTML( + const css::uno::Reference<css::datatransfer::XTransferable> &xTransferable, + const OString &aMimeType, OString &aRet) +{ + if (!getFromTransferable(xTransferable, aMimeType, aRet)) + return false; + + // Encode in base64. + auto aSeq = Sequence<sal_Int8>(reinterpret_cast<const sal_Int8*>(aRet.getStr()), + aRet.getLength()); + OStringBuffer aBase64Data; + comphelper::Base64::encode(aBase64Data, aSeq); + + // Embed in HTML. + aRet = "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\">\n" + "<html><head>" + "<meta http-equiv=\"content-type\" content=\"text/html; charset=utf-8\"/><meta " + "name=\"generator\" content=\"" + + getGenerator().toUtf8() + + "\"/>" + "</head><body><img src=\"data:" + aMimeType + ";base64," + + aBase64Data + "\"/></body></html>"; + + return true; +} + +static bool encodeTextAsHTML( + const css::uno::Reference<css::datatransfer::XTransferable> &xTransferable, + const OString &aMimeType, OString &aRet) +{ + if (!getFromTransferable(xTransferable, aMimeType, aRet)) + return false; + + // Embed in HTML - FIXME: needs some escaping. + aRet = "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\">\n" + "<html><head>" + "<meta http-equiv=\"content-type\" content=\"text/html; charset=utf-8\"/><meta " + "name=\"generator\" content=\"" + + getGenerator().toUtf8() + + "\"/></head><body><pre>" + aRet + "</pre></body></html>"; + + return true; +} + +static bool getFromTransferable( + const css::uno::Reference<css::datatransfer::XTransferable> &xTransferable, + const OString &aInMimeType, OString &aRet) +{ + OString aMimeType(aInMimeType); + + // Take care of UTF-8 text here. + bool bConvert = false; + sal_Int32 nIndex = 0; + if (o3tl::getToken(aMimeType, 0, ';', nIndex) == "text/plain") + { + if (o3tl::getToken(aMimeType, 0, ';', nIndex) == "charset=utf-8") + { + aMimeType = "text/plain;charset=utf-16"_ostr; + bConvert = true; + } + } + + datatransfer::DataFlavor aFlavor; + aFlavor.MimeType = OUString::fromUtf8(aMimeType); + if (aMimeType == "text/plain;charset=utf-16") + aFlavor.DataType = cppu::UnoType<OUString>::get(); + else + aFlavor.DataType = cppu::UnoType< uno::Sequence<sal_Int8> >::get(); + + if (!xTransferable->isDataFlavorSupported(aFlavor)) + { + // Try harder for HTML it is our copy/paste meta-file format + if (aInMimeType == "text/html") + { + // Desperate measures - convert text to HTML instead. + if (encodeTextAsHTML(xTransferable, "text/plain;charset=utf-8"_ostr, aRet)) + return true; + // If html is not supported, might be a graphic-selection, + if (encodeImageAsHTML(xTransferable, "image/png"_ostr, aRet)) + return true; + } + + SetLastExceptionMsg("Flavor " + aFlavor.MimeType + " is not supported"); + return false; + } + + uno::Any aAny; + try + { + aAny = xTransferable->getTransferData(aFlavor); + } + catch (const css::datatransfer::UnsupportedFlavorException& e) + { + SetLastExceptionMsg("Unsupported flavor " + aFlavor.MimeType + " exception " + e.Message); + return false; + } + catch (const css::uno::Exception& e) + { + SetLastExceptionMsg("Exception getting " + aFlavor.MimeType + " exception " + e.Message); + return false; + } + + if (aFlavor.DataType == cppu::UnoType<OUString>::get()) + { + OUString aString; + aAny >>= aString; + if (bConvert) + aRet = OUStringToOString(aString, RTL_TEXTENCODING_UTF8); + else + aRet = OString(reinterpret_cast<const char *>(aString.getStr()), aString.getLength() * sizeof(sal_Unicode)); + } + else + { + uno::Sequence<sal_Int8> aSequence; + aAny >>= aSequence; + aRet = OString(reinterpret_cast<const char*>(aSequence.getConstArray()), aSequence.getLength()); + } + + return true; +} + +static char* doc_getTextSelection(LibreOfficeKitDocument* pThis, const char* pMimeType, char** pUsedMimeType) +{ + comphelper::ProfileZone aZone("doc_getTextSelection"); + + SolarMutexGuard aGuard; + SetLastExceptionMsg(); + + ITiledRenderable* pDoc = getTiledRenderable(pThis); + if (!pDoc) + { + SetLastExceptionMsg(u"Document doesn't support tiled rendering"_ustr); + return nullptr; + } + + css::uno::Reference<css::datatransfer::XTransferable> xTransferable = pDoc->getSelection(); + if (!xTransferable) + { + SetLastExceptionMsg(u"No selection available"_ustr); + return nullptr; + } + + OString aType + = pMimeType && pMimeType[0] != '\0' ? OString(pMimeType) : "text/plain;charset=utf-8"_ostr; + + OString aRet; + bool bSuccess = getFromTransferable(xTransferable, aType, aRet); + if (!bSuccess) + return nullptr; + + if (pUsedMimeType) // legacy + { + if (pMimeType) + *pUsedMimeType = strdup(pMimeType); + else + *pUsedMimeType = nullptr; + } + + return convertOString(aRet); +} + +static int doc_getSelectionType(LibreOfficeKitDocument* pThis) +{ + comphelper::ProfileZone aZone("doc_getSelectionType"); + + SolarMutexGuard aGuard; + SetLastExceptionMsg(); + + ITiledRenderable* pDoc = getTiledRenderable(pThis); + if (!pDoc) + { + SetLastExceptionMsg(u"Document doesn't support tiled rendering"_ustr); + return LOK_SELTYPE_NONE; + } + + css::uno::Reference<css::datatransfer::XTransferable> xTransferable = pDoc->getSelection(); + if (!xTransferable) + { + SetLastExceptionMsg(u"No selection available"_ustr); + return LOK_SELTYPE_NONE; + } + + css::uno::Reference<css::datatransfer::XTransferable2> xTransferable2(xTransferable, css::uno::UNO_QUERY); + if (xTransferable2.is() && xTransferable2->isComplex()) + return LOK_SELTYPE_COMPLEX; + + OString aRet; + bool bSuccess = getFromTransferable(xTransferable, "text/plain;charset=utf-8"_ostr, aRet); + if (!bSuccess) + return LOK_SELTYPE_NONE; + + if (aRet.getLength() > 10000) + return LOK_SELTYPE_COMPLEX; + + return !aRet.isEmpty() ? LOK_SELTYPE_TEXT : LOK_SELTYPE_NONE; +} + +static int doc_getSelectionTypeAndText(LibreOfficeKitDocument* pThis, const char* pMimeType, char** pText, char** pUsedMimeType) +{ + // The purpose of this function is to avoid double call to pDoc->getSelection(), + // which may be expensive. + comphelper::ProfileZone aZone("doc_getSelectionTypeAndText"); + + SolarMutexGuard aGuard; + SetLastExceptionMsg(); + + ITiledRenderable* pDoc = getTiledRenderable(pThis); + if (!pDoc) + { + SetLastExceptionMsg(u"Document doesn't support tiled rendering"_ustr); + return LOK_SELTYPE_NONE; + } + + css::uno::Reference<css::datatransfer::XTransferable> xTransferable = pDoc->getSelection(); + if (!xTransferable) + { + SetLastExceptionMsg(u"No selection available"_ustr); + return LOK_SELTYPE_NONE; + } + + css::uno::Reference<css::datatransfer::XTransferable2> xTransferable2(xTransferable, css::uno::UNO_QUERY); + if (xTransferable2.is() && xTransferable2->isComplex()) + return LOK_SELTYPE_COMPLEX; + + OString aType + = pMimeType && pMimeType[0] != '\0' ? OString(pMimeType) : "text/plain;charset=utf-8"_ostr; + + OString aRet; + bool bSuccess = getFromTransferable(xTransferable, aType, aRet); + if (!bSuccess) + return LOK_SELTYPE_NONE; + + if (aRet.getLength() > 10000) + return LOK_SELTYPE_COMPLEX; + + if (aRet.isEmpty()) + return LOK_SELTYPE_NONE; + + if (pText) + *pText = convertOString(aRet); + + if (pUsedMimeType) // legacy + { + if (pMimeType) + *pUsedMimeType = strdup(pMimeType); + else + *pUsedMimeType = nullptr; + } + + return LOK_SELTYPE_TEXT; +} + +static int doc_getClipboard(LibreOfficeKitDocument* pThis, + const char **pMimeTypes, + size_t *pOutCount, + char ***pOutMimeTypes, + size_t **pOutSizes, + char ***pOutStreams) +{ +#ifdef IOS + (void) pThis; + (void) pMimeTypes; + (void) pOutCount; + (void) pOutMimeTypes; + (void) pOutSizes; + (void) pOutStreams; + + assert(!"doc_getClipboard should not be called on iOS"); + + return 0; +#else + comphelper::ProfileZone aZone("doc_getClipboard"); + + SolarMutexGuard aGuard; + SetLastExceptionMsg(); + + assert (pOutCount); + assert (pOutMimeTypes); + assert (pOutSizes); + assert (pOutStreams); + + *pOutCount = 0; + *pOutMimeTypes = nullptr; + *pOutSizes = nullptr; + *pOutStreams = nullptr; + + ITiledRenderable* pDoc = getTiledRenderable(pThis); + if (!pDoc) + { + SetLastExceptionMsg(u"Document doesn't support tiled rendering"_ustr); + return 0; + } + + rtl::Reference<LOKClipboard> xClip(LOKClipboardFactory::getClipboardForCurView()); + + css::uno::Reference<css::datatransfer::XTransferable> xTransferable = xClip->getContents(); + SAL_INFO("lok", "Got from clip: " << xClip.get() << " transferable: " << xTransferable); + if (!xTransferable) + { + SetLastExceptionMsg(u"No clipboard content available"_ustr); + return 0; + } + + std::vector<OString> aMimeTypes; + if (!pMimeTypes) // everything + { + const uno::Sequence< css::datatransfer::DataFlavor > flavors = xTransferable->getTransferDataFlavors(); + if (!flavors.getLength()) + { + SetLastExceptionMsg(u"Flavourless selection"_ustr); + return 0; + } + for (const auto &it : flavors) + aMimeTypes.push_back(OUStringToOString(it.MimeType, RTL_TEXTENCODING_UTF8)); + } + else + { + for (size_t i = 0; pMimeTypes[i]; ++i) + aMimeTypes.push_back(OString(pMimeTypes[i])); + } + + *pOutCount = aMimeTypes.size(); + *pOutSizes = static_cast<size_t *>(malloc(*pOutCount * sizeof(size_t))); + *pOutMimeTypes = static_cast<char **>(malloc(*pOutCount * sizeof(char *))); + *pOutStreams = static_cast<char **>(malloc(*pOutCount * sizeof(char *))); + for (size_t i = 0; i < aMimeTypes.size(); ++i) + { + if (aMimeTypes[i] == "text/plain;charset=utf-16") + (*pOutMimeTypes)[i] = strdup("text/plain;charset=utf-8"); + else + (*pOutMimeTypes)[i] = convertOString(aMimeTypes[i]); + + OString aRet; + bool bSuccess = getFromTransferable(xTransferable, (*pOutMimeTypes)[i], aRet); + if (!bSuccess || aRet.getLength() < 1) + { + (*pOutSizes)[i] = 0; + (*pOutStreams)[i] = nullptr; + } + else + { + (*pOutSizes)[i] = aRet.getLength(); + (*pOutStreams)[i] = convertOString(aRet); + } + } + + return 1; +#endif +} + +static int doc_setClipboard(LibreOfficeKitDocument* pThis, + const size_t nInCount, + const char **pInMimeTypes, + const size_t *pInSizes, + const char **pInStreams) +{ +#ifdef IOS + (void) pThis; + (void) nInCount; + (void) pInMimeTypes; + (void) pInSizes; + (void) pInStreams; +#else + comphelper::ProfileZone aZone("doc_setClipboard"); + + SolarMutexGuard aGuard; + SetLastExceptionMsg(); + + ITiledRenderable* pDoc = getTiledRenderable(pThis); + if (!pDoc) + { + SetLastExceptionMsg(u"Document doesn't support tiled rendering"_ustr); + return false; + } + + uno::Reference<datatransfer::XTransferable> xTransferable(new LOKTransferable(nInCount, pInMimeTypes, pInSizes, pInStreams)); + + auto xClip = forceSetClipboardForCurrentView(pThis); + xClip->setContents(xTransferable, uno::Reference<datatransfer::clipboard::XClipboardOwner>()); + + SAL_INFO("lok", "Set clip: " << xClip.get() << " to: " << xTransferable); + + if (!pDoc->isMimeTypeSupported()) + { + SetLastExceptionMsg(u"Document doesn't support this mime type"_ustr); + return false; + } +#endif + return true; +} + +static bool doc_paste(LibreOfficeKitDocument* pThis, const char* pMimeType, const char* pData, size_t nSize) +{ + comphelper::ProfileZone aZone("doc_paste"); + + SolarMutexGuard aGuard; + + const char *pInMimeTypes[1]; + const char *pInStreams[1]; + size_t pInSizes[1]; + pInMimeTypes[0] = pMimeType; + pInSizes[0] = nSize; + pInStreams[0] = pData; + + if (!doc_setClipboard(pThis, 1, pInMimeTypes, pInSizes, pInStreams)) + return false; + + uno::Sequence<beans::PropertyValue> aPropertyValues(comphelper::InitPropertySequence( + { + {"AnchorType", uno::Any(static_cast<sal_uInt16>(css::text::TextContentAnchorType_AS_CHARACTER))}, + {"IgnoreComments", uno::Any(true)}, + })); + if (!comphelper::dispatchCommand(u".uno:Paste"_ustr, aPropertyValues)) + { + SetLastExceptionMsg(u"Failed to dispatch the .uno: command"_ustr); + return false; + } + + return true; +} + +static void doc_setGraphicSelection(LibreOfficeKitDocument* pThis, int nType, int nX, int nY) +{ + comphelper::ProfileZone aZone("doc_setGraphicSelection"); + + SolarMutexGuard aGuard; + SetLastExceptionMsg(); + + ITiledRenderable* pDoc = getTiledRenderable(pThis); + if (!pDoc) + { + SetLastExceptionMsg(u"Document doesn't support tiled rendering"_ustr); + return; + } + + pDoc->setGraphicSelection(nType, nX, nY); +} + +static void doc_resetSelection(LibreOfficeKitDocument* pThis) +{ + comphelper::ProfileZone aZone("doc_resetSelection"); + + SolarMutexGuard aGuard; + SetLastExceptionMsg(); + + ITiledRenderable* pDoc = getTiledRenderable(pThis); + if (!pDoc) + { + SetLastExceptionMsg(u"Document doesn't support tiled rendering"_ustr); + return; + } + + pDoc->resetSelection(); +} + +static char* getDocReadOnly(LibreOfficeKitDocument* pThis) +{ + LibLODocument_Impl* pDocument = static_cast<LibLODocument_Impl*>(pThis); + if (!pDocument) + return nullptr; + + SfxBaseModel* pBaseModel = dynamic_cast<SfxBaseModel*>(pDocument->mxComponent.get()); + if (!pBaseModel) + return nullptr; + + SfxObjectShell* pObjectShell = pBaseModel->GetObjectShell(); + if (!pObjectShell) + return nullptr; + + boost::property_tree::ptree aTree; + aTree.put("commandName", ".uno:ReadOnly"); + aTree.put("success", pObjectShell->IsLoadReadonly()); + + std::stringstream aStream; + boost::property_tree::write_json(aStream, aTree); + char* pJson = static_cast<char*>(malloc(aStream.str().size() + 1)); + if (!pJson) + return nullptr; + + strcpy(pJson, aStream.str().c_str()); + pJson[aStream.str().size()] = '\0'; + return pJson; +} + +static void addLocale(boost::property_tree::ptree& rValues, css::lang::Locale const & rLocale) +{ + boost::property_tree::ptree aChild; + const LanguageTag aLanguageTag( rLocale ); + OUString sLanguage = SvtLanguageTable::GetLanguageString(aLanguageTag.getLanguageType()); + if (sLanguage.endsWith("}")) + return; + + sLanguage += ";" + aLanguageTag.getBcp47(false); + aChild.put("", sLanguage.toUtf8()); + rValues.push_back(std::make_pair("", aChild)); +} + +static char* getLanguages(const char* pCommand) +{ + css::uno::Sequence< css::lang::Locale > aLocales; + css::uno::Sequence< css::lang::Locale > aGrammarLocales; + + if (xContext.is()) + { + // SpellChecker + css::uno::Reference<css::linguistic2::XLinguServiceManager2> xLangSrv = css::linguistic2::LinguServiceManager::create(xContext); + if (xLangSrv.is()) + { + css::uno::Reference<css::linguistic2::XSpellChecker> xSpell = xLangSrv->getSpellChecker(); + if (xSpell.is()) + aLocales = xSpell->getLocales(); + } + + // LanguageTool + if (LanguageToolCfg::IsEnabled::get()) + { + uno::Reference< linguistic2::XProofreader > xGC( + xContext->getServiceManager()->createInstanceWithContext(u"org.openoffice.lingu.LanguageToolGrammarChecker"_ustr, xContext), + uno::UNO_QUERY_THROW ); + uno::Reference< linguistic2::XSupportedLocales > xSuppLoc( xGC, uno::UNO_QUERY_THROW ); + aGrammarLocales = xSuppLoc->getLocales(); + } + } + + boost::property_tree::ptree aTree; + aTree.put("commandName", pCommand); + boost::property_tree::ptree aValues; + for ( css::lang::Locale const & rLocale : std::as_const(aLocales) ) + addLocale(aValues, rLocale); + for ( css::lang::Locale const & rLocale : std::as_const(aGrammarLocales) ) + addLocale(aValues, rLocale); + aTree.add_child("commandValues", aValues); + std::stringstream aStream; + boost::property_tree::write_json(aStream, aTree); + char* pJson = static_cast<char*>(malloc(aStream.str().size() + 1)); + assert(pJson); // Don't handle OOM conditions + strcpy(pJson, aStream.str().c_str()); + pJson[aStream.str().size()] = '\0'; + return pJson; +} + +static char* getFonts (const char* pCommand) +{ + SfxObjectShell* pDocSh = SfxObjectShell::Current(); + if (!pDocSh) + return nullptr; + const SvxFontListItem* pFonts = static_cast<const SvxFontListItem*>( + pDocSh->GetItem(SID_ATTR_CHAR_FONTLIST)); + const FontList* pList = pFonts ? pFonts->GetFontList() : nullptr; + + boost::property_tree::ptree aTree; + aTree.put("commandName", pCommand); + boost::property_tree::ptree aValues; + if ( pList ) + { + sal_uInt16 nFontCount = pList->GetFontNameCount(); + for (sal_uInt16 i = 0; i < nFontCount; ++i) + { + boost::property_tree::ptree aChildren; + const FontMetric& rFontMetric = pList->GetFontName(i); + const int* pAry = FontList::GetStdSizeAry(); + sal_uInt16 nSizeCount = 0; + while (pAry[nSizeCount]) + { + boost::property_tree::ptree aChild; + aChild.put("", static_cast<float>(pAry[nSizeCount]) / 10); + aChildren.push_back(std::make_pair("", aChild)); + nSizeCount++; + } + aValues.add_child(rFontMetric.GetFamilyName().toUtf8().getStr(), aChildren); + } + } + aTree.add_child("commandValues", aValues); + std::stringstream aStream; + boost::property_tree::write_json(aStream, aTree); + char* pJson = static_cast<char*>(malloc(aStream.str().size() + 1)); + assert(pJson); // Don't handle OOM conditions + strcpy(pJson, aStream.str().c_str()); + pJson[aStream.str().size()] = '\0'; + return pJson; +} + +static char* getFontSubset (std::string_view aFontName) +{ + OUString aFoundFont(::rtl::Uri::decode(OStringToOUString(aFontName, RTL_TEXTENCODING_UTF8), rtl_UriDecodeStrict, RTL_TEXTENCODING_UTF8)); + + boost::property_tree::ptree aTree; + aTree.put("commandName", ".uno:FontSubset"); + boost::property_tree::ptree aValues; + + if (const vcl::Font* pFont = FindFont(aFoundFont)) + { + FontCharMapRef xFontCharMap (new FontCharMap()); + auto aDevice(VclPtr<VirtualDevice>::Create(DeviceFormat::WITHOUT_ALPHA)); + + aDevice->SetFont(*pFont); + aDevice->GetFontCharMap(xFontCharMap); + SubsetMap aSubMap(xFontCharMap); + + for (auto const& subset : aSubMap.GetSubsetMap()) + { + boost::property_tree::ptree aChild; + aChild.put("", static_cast<int>(ublock_getCode(subset.GetRangeMin()))); + aValues.push_back(std::make_pair("", aChild)); + } + } + + aTree.add_child("commandValues", aValues); + std::stringstream aStream; + boost::property_tree::write_json(aStream, aTree); + char* pJson = static_cast<char*>(malloc(aStream.str().size() + 1)); + assert(pJson); // Don't handle OOM conditions + strcpy(pJson, aStream.str().c_str()); + pJson[aStream.str().size()] = '\0'; + return pJson; +} + +static char* getStyles(LibreOfficeKitDocument* pThis, const char* pCommand) +{ + LibLODocument_Impl* pDocument = static_cast<LibLODocument_Impl*>(pThis); + + boost::property_tree::ptree aTree; + aTree.put("commandName", pCommand); + uno::Reference<css::style::XStyleFamiliesSupplier> xStyleFamiliesSupplier(pDocument->mxComponent, uno::UNO_QUERY); + const uno::Reference<container::XNameAccess> xStyleFamilies = xStyleFamiliesSupplier->getStyleFamilies(); + const uno::Sequence<OUString> aStyleFamilies = xStyleFamilies->getElementNames(); + + static constexpr OUString aWriterStyles[] = + { + u"Text body"_ustr, + u"Quotations"_ustr, + u"Title"_ustr, + u"Subtitle"_ustr, + u"Heading 1"_ustr, + u"Heading 2"_ustr, + u"Heading 3"_ustr, + }; + + // We need to keep a list of the default style names + // in order to filter these out later when processing + // the full list of styles. + std::set<OUString> aDefaultStyleNames; + + boost::property_tree::ptree aValues; + for (OUString const & sStyleFam : aStyleFamilies) + { + boost::property_tree::ptree aChildren; + uno::Reference<container::XNameAccess> xStyleFamily(xStyleFamilies->getByName(sStyleFam), uno::UNO_QUERY); + + // Writer provides a huge number of styles, we have a list of 7 "default" styles which + // should be shown in the normal dropdown, which we should add to the start of the list + // to simplify their selection. + if (sStyleFam == "ParagraphStyles" + && doc_getDocumentType(pThis) == LOK_DOCTYPE_TEXT) + { + for (const OUString& rStyle: aWriterStyles) + { + aDefaultStyleNames.insert( rStyle ); + + boost::property_tree::ptree aChild; + aChild.put("", rStyle.toUtf8()); + aChildren.push_back(std::make_pair("", aChild)); + } + } + + const uno::Sequence<OUString> aStyles = xStyleFamily->getElementNames(); + for (const OUString& rStyle: aStyles ) + { + // Filter out the default styles - they are already at the top + // of the list + if (aDefaultStyleNames.find(rStyle) == aDefaultStyleNames.end() || + (sStyleFam != "ParagraphStyles" || doc_getDocumentType(pThis) != LOK_DOCTYPE_TEXT) ) + { + boost::property_tree::ptree aChild; + aChild.put("", rStyle.toUtf8()); + aChildren.push_back(std::make_pair("", aChild)); + } + } + aValues.add_child(sStyleFam.toUtf8().getStr(), aChildren); + } + + // Header & Footer Styles + { + boost::property_tree::ptree aChild; + boost::property_tree::ptree aChildren; + static constexpr OUString sPageStyles(u"PageStyles"_ustr); + uno::Reference<beans::XPropertySet> xProperty; + uno::Reference<container::XNameContainer> xContainer; + + if (xStyleFamilies->hasByName(sPageStyles) && (xStyleFamilies->getByName(sPageStyles) >>= xContainer)) + { + const uno::Sequence<OUString> aSeqNames = xContainer->getElementNames(); + for (OUString const & sName : aSeqNames) + { + bool bIsPhysical; + xProperty.set(xContainer->getByName(sName), uno::UNO_QUERY); + if (xProperty.is() && (xProperty->getPropertyValue(u"IsPhysical"_ustr) >>= bIsPhysical) && bIsPhysical) + { + OUString displayName; + xProperty->getPropertyValue(u"DisplayName"_ustr) >>= displayName; + aChild.put("", displayName.toUtf8()); + aChildren.push_back(std::make_pair("", aChild)); + } + } + aValues.add_child("HeaderFooter", aChildren); + } + } + + { + boost::property_tree::ptree aCommandList; + + { + boost::property_tree::ptree aChild; + + OUString sClearFormat = SvxResId(RID_SVXSTR_CLEARFORM); + + boost::property_tree::ptree aName; + aName.put("", sClearFormat.toUtf8()); + aChild.push_back(std::make_pair("text", aName)); + + boost::property_tree::ptree aCommand; + aCommand.put("", ".uno:ResetAttributes"); + aChild.push_back(std::make_pair("id", aCommand)); + + aCommandList.push_back(std::make_pair("", aChild)); + } + + aValues.add_child("Commands", aCommandList); + } + + aTree.add_child("commandValues", aValues); + std::stringstream aStream; + boost::property_tree::write_json(aStream, aTree); + char* pJson = static_cast<char*>(malloc(aStream.str().size() + 1)); + assert(pJson); // Don't handle OOM conditions + strcpy(pJson, aStream.str().c_str()); + pJson[aStream.str().size()] = '\0'; + return pJson; +} + +namespace { + +enum class UndoOrRedo +{ + UNDO, + REDO +}; + +} + +/// Returns the JSON representation of either an undo or a redo stack. +static char* getUndoOrRedo(LibreOfficeKitDocument* pThis, UndoOrRedo eCommand) +{ + LibLODocument_Impl* pDocument = static_cast<LibLODocument_Impl*>(pThis); + + auto pBaseModel = dynamic_cast<SfxBaseModel*>(pDocument->mxComponent.get()); + if (!pBaseModel) + return nullptr; + + SfxObjectShell* pObjectShell = pBaseModel->GetObjectShell(); + if (!pObjectShell) + return nullptr; + + SfxUndoManager* pUndoManager = pObjectShell->GetUndoManager(); + if (!pUndoManager) + return nullptr; + + OUString aString; + if (eCommand == UndoOrRedo::UNDO) + aString = pUndoManager->GetUndoActionsInfo(); + else + aString = pUndoManager->GetRedoActionsInfo(); + char* pJson = convertOUString(aString); + return pJson; +} + +/// Returns the JSON representation of the redline stack. +static char* getTrackedChanges(LibreOfficeKitDocument* pThis) +{ + LibLODocument_Impl* pDocument = static_cast<LibLODocument_Impl*>(pThis); + + uno::Reference<document::XRedlinesSupplier> xRedlinesSupplier(pDocument->mxComponent, uno::UNO_QUERY); + tools::JsonWriter aJson; + // We want positions of the track changes also which is not possible from + // UNO. Enable positioning information for text documents only for now, so + // construct the tracked changes JSON from inside the sw/, not here using UNO + if (doc_getDocumentType(pThis) != LOK_DOCTYPE_TEXT && xRedlinesSupplier.is()) + { + auto redlinesNode = aJson.startArray("redlines"); + uno::Reference<container::XEnumeration> xRedlines = xRedlinesSupplier->getRedlines()->createEnumeration(); + for (size_t nIndex = 0; xRedlines->hasMoreElements(); ++nIndex) + { + uno::Reference<beans::XPropertySet> xRedline(xRedlines->nextElement(), uno::UNO_QUERY); + auto redlineNode = aJson.startStruct(); + aJson.put("index", static_cast<sal_Int32>(nIndex)); + + OUString sAuthor; + xRedline->getPropertyValue(u"RedlineAuthor"_ustr) >>= sAuthor; + aJson.put("author", sAuthor); + + OUString sType; + xRedline->getPropertyValue(u"RedlineType"_ustr) >>= sType; + aJson.put("type", sType); + + OUString sComment; + xRedline->getPropertyValue(u"RedlineComment"_ustr) >>= sComment; + aJson.put("comment", sComment); + + OUString sDescription; + xRedline->getPropertyValue(u"RedlineDescription"_ustr) >>= sDescription; + aJson.put("description", sDescription); + + util::DateTime aDateTime; + xRedline->getPropertyValue(u"RedlineDateTime"_ustr) >>= aDateTime; + OUString sDateTime = utl::toISO8601(aDateTime); + aJson.put("dateTime", sDateTime); + } + } + else + { + ITiledRenderable* pDoc = getTiledRenderable(pThis); + if (!pDoc) + { + SetLastExceptionMsg(u"Document doesn't support tiled rendering"_ustr); + return nullptr; + } + pDoc->getTrackedChanges(aJson); + } + + return convertOString(aJson.finishAndGetAsOString()); +} + + +/// Returns the JSON representation of the redline author table. +static char* getTrackedChangeAuthors(LibreOfficeKitDocument* pThis) +{ + ITiledRenderable* pDoc = getTiledRenderable(pThis); + if (!pDoc) + { + SetLastExceptionMsg(u"Document doesn't support tiled rendering"_ustr); + return nullptr; + } + tools::JsonWriter aJsonWriter; + pDoc->getTrackedChangeAuthors(aJsonWriter); + return convertOString(aJsonWriter.finishAndGetAsOString()); +} + +static char* doc_getCommandValues(LibreOfficeKitDocument* pThis, const char* pCommand) +{ + comphelper::ProfileZone aZone("doc_getCommandValues"); + + SolarMutexGuard aGuard; + SetLastExceptionMsg(); + + const std::string_view aCommand(pCommand); + static constexpr std::string_view aViewRowColumnHeaders(".uno:ViewRowColumnHeaders"); + static constexpr std::string_view aSheetGeometryData(".uno:SheetGeometryData"); + static constexpr std::string_view aFontSubset(".uno:FontSubset&name="); + + ITiledRenderable* pDoc = getTiledRenderable(pThis); + if (!pDoc) + { + SetLastExceptionMsg(u"Document doesn't support tiled rendering"_ustr); + return nullptr; + } + + if (aCommand == ".uno:ReadOnly") + { + return getDocReadOnly(pThis); + } + else if (aCommand == ".uno:LanguageStatus") + { + return getLanguages(pCommand); + } + else if (aCommand == ".uno:CharFontName") + { + return getFonts(pCommand); + } + else if (aCommand == ".uno:StyleApply") + { + return getStyles(pThis, pCommand); + } + else if (aCommand == ".uno:Undo") + { + return getUndoOrRedo(pThis, UndoOrRedo::UNDO); + } + else if (aCommand == ".uno:Redo") + { + return getUndoOrRedo(pThis, UndoOrRedo::REDO); + } + else if (aCommand == ".uno:AcceptTrackedChanges") + { + return getTrackedChanges(pThis); + } + else if (aCommand == ".uno:TrackedChangeAuthors") + { + return getTrackedChangeAuthors(pThis); + } + else if (aCommand == ".uno:ViewAnnotations") + { + return getPostIts(pThis); + } + else if (aCommand == ".uno:ViewAnnotationsPosition") + { + return getPostItsPos(pThis); + } + else if (aCommand == ".uno:RulerState") + { + return getRulerState(pThis); + } + else if (aCommand.starts_with(aViewRowColumnHeaders)) + { + tools::Rectangle aRectangle; + if (aCommand.size() > aViewRowColumnHeaders.size()) + { + // Command has parameters. + int nX = 0; + int nY = 0; + int nWidth = 0; + int nHeight = 0; + std::string_view aArguments = aCommand.substr(aViewRowColumnHeaders.size() + 1); + sal_Int32 nParamIndex = 0; + do + { + std::string_view aParamToken = o3tl::getToken(aArguments, 0, '&', nParamIndex); + sal_Int32 nIndex = 0; + std::string_view aKey; + std::string_view aValue; + do + { + std::string_view aToken = o3tl::getToken(aParamToken, 0, '=', nIndex); + if (aKey.empty()) + aKey = aToken; + else + aValue = aToken; + } + while (nIndex >= 0); + if (aKey == "x") + nX = o3tl::toInt32(aValue); + else if (aKey == "y") + nY = o3tl::toInt32(aValue); + else if (aKey == "width") + nWidth = o3tl::toInt32(aValue); + else if (aKey == "height") + nHeight = o3tl::toInt32(aValue); + } + while (nParamIndex >= 0); + + aRectangle = tools::Rectangle(nX, nY, nX + nWidth, nY + nHeight); + } + + tools::JsonWriter aJsonWriter; + pDoc->getRowColumnHeaders(aRectangle, aJsonWriter); + return convertOString(aJsonWriter.finishAndGetAsOString()); + } + else if (aCommand.starts_with(aSheetGeometryData)) + { + bool bColumns = true; + bool bRows = true; + bool bSizes = true; + bool bHidden = true; + bool bFiltered = true; + bool bGroups = true; + if (aCommand.size() > aSheetGeometryData.size()) + { + bColumns = bRows = bSizes = bHidden = bFiltered = bGroups = false; + + std::string_view aArguments = aCommand.substr(aSheetGeometryData.size() + 1); + sal_Int32 nParamIndex = 0; + do + { + std::string_view aParamToken = o3tl::getToken(aArguments, 0, '&', nParamIndex); + sal_Int32 nIndex = 0; + std::string_view aKey; + std::string_view aValue; + do + { + std::string_view aToken = o3tl::getToken(aParamToken, 0, '=', nIndex); + if (aKey.empty()) + aKey = aToken; + else + aValue = aToken; + + } while (nIndex >= 0); + + bool bEnableFlag = aValue.empty() || + o3tl::equalsIgnoreAsciiCase(aValue, "true") || o3tl::toInt32(aValue) > 0; + if (!bEnableFlag) + continue; + + if (aKey == "columns") + bColumns = true; + else if (aKey == "rows") + bRows = true; + else if (aKey == "sizes") + bSizes = true; + else if (aKey == "hidden") + bHidden = true; + else if (aKey == "filtered") + bFiltered = true; + else if (aKey == "groups") + bGroups = true; + + } while (nParamIndex >= 0); + } + + OString aGeomDataStr + = pDoc->getSheetGeometryData(bColumns, bRows, bSizes, bHidden, bFiltered, bGroups); + + if (aGeomDataStr.isEmpty()) + return nullptr; + + return convertOString(aGeomDataStr); + } + else if (aCommand.starts_with(".uno:CellCursor")) + { + // Ignore command's deprecated parameters. + tools::JsonWriter aJsonWriter; + pDoc->getCellCursor(aJsonWriter); + return convertOString(aJsonWriter.finishAndGetAsOString()); + } + else if (aCommand.starts_with(aFontSubset)) + { + return getFontSubset(aCommand.substr(aFontSubset.size())); + } + else if (pDoc->supportsCommand(INetURLObject(OUString::fromUtf8(aCommand)).GetURLPath())) + { + tools::JsonWriter aJsonWriter; + pDoc->getCommandValues(aJsonWriter, aCommand); + return convertOString(aJsonWriter.finishAndGetAsOString()); + } + else + { + SetLastExceptionMsg(u"Unknown command, no values returned"_ustr); + return nullptr; + } +} + +static void doc_setClientZoom(LibreOfficeKitDocument* pThis, int nTilePixelWidth, int nTilePixelHeight, + int nTileTwipWidth, int nTileTwipHeight) +{ + comphelper::ProfileZone aZone("doc_setClientZoom"); + + SolarMutexGuard aGuard; + SetLastExceptionMsg(); + + ITiledRenderable* pDoc = getTiledRenderable(pThis); + if (!pDoc) + { + SetLastExceptionMsg(u"Document doesn't support tiled rendering"_ustr); + return; + } + + pDoc->setClientZoom(nTilePixelWidth, nTilePixelHeight, nTileTwipWidth, nTileTwipHeight); +} + +static void doc_setClientVisibleArea(LibreOfficeKitDocument* pThis, int nX, int nY, int nWidth, int nHeight) +{ + comphelper::ProfileZone aZone("doc_setClientVisibleArea"); + + SolarMutexGuard aGuard; + SetLastExceptionMsg(); + + ITiledRenderable* pDoc = getTiledRenderable(pThis); + if (!pDoc) + { + SetLastExceptionMsg(u"Document doesn't support tiled rendering"_ustr); + return; + } + + tools::Rectangle aRectangle(Point(nX, nY), Size(nWidth, nHeight)); + pDoc->setClientVisibleArea(aRectangle); +} + +static void doc_setOutlineState(LibreOfficeKitDocument* pThis, bool bColumn, int nLevel, int nIndex, bool bHidden) +{ + comphelper::ProfileZone aZone("doc_setOutlineState"); + + SolarMutexGuard aGuard; + SetLastExceptionMsg(); + + ITiledRenderable* pDoc = getTiledRenderable(pThis); + if (!pDoc) + { + SetLastExceptionMsg(u"Document doesn't support tiled rendering"_ustr); + return; + } + + pDoc->setOutlineState(bColumn, nLevel, nIndex, bHidden); +} + +static int doc_createViewWithOptions(LibreOfficeKitDocument* pThis, + const char* pOptions) +{ + comphelper::ProfileZone aZone("doc_createView"); + + SolarMutexGuard aGuard; + SetLastExceptionMsg(); + + OUString aOptions = getUString(pOptions); + const OUString aLanguage = extractParameter(aOptions, u"Language"); + + if (!aLanguage.isEmpty()) + { + // Set the LOK language tag, used for dialog tunneling. + comphelper::LibreOfficeKit::setLanguageTag(LanguageTag(aLanguage)); + comphelper::LibreOfficeKit::setLocale(LanguageTag(aLanguage)); + } + + const OUString aDeviceFormFactor = extractParameter(aOptions, u"DeviceFormFactor"); + SfxLokHelper::setDeviceFormFactor(aDeviceFormFactor); + + LibLODocument_Impl* pDocument = static_cast<LibLODocument_Impl*>(pThis); + const int nId = SfxLokHelper::createView(pDocument->mnDocumentId); + + vcl::lok::numberOfViewsChanged(SfxLokHelper::getViewsCount(pDocument->mnDocumentId)); + +#ifdef IOS + (void) pThis; +#else + forceSetClipboardForCurrentView(pThis); +#endif + + return nId; +} + +static int doc_createView(LibreOfficeKitDocument* pThis) +{ + return doc_createViewWithOptions(pThis, nullptr); // No options. +} + +static void doc_destroyView(SAL_UNUSED_PARAMETER LibreOfficeKitDocument* pThis, int nId) +{ + comphelper::ProfileZone aZone("doc_destroyView"); + + SolarMutexGuard aGuard; + SetLastExceptionMsg(); + +#ifndef IOS + LOKClipboardFactory::releaseClipboardForView(nId); +#endif + + SfxLokHelper::destroyView(nId); + + LibLODocument_Impl* pDocument = static_cast<LibLODocument_Impl*>(pThis); + vcl::lok::numberOfViewsChanged(SfxLokHelper::getViewsCount(pDocument->mnDocumentId)); +} + +static void doc_setView(SAL_UNUSED_PARAMETER LibreOfficeKitDocument* /*pThis*/, int nId) +{ + comphelper::ProfileZone aZone("doc_setView"); + + SolarMutexGuard aGuard; + SetLastExceptionMsg(); + + SfxLokHelper::setView(nId); +} + +static int doc_getView(SAL_UNUSED_PARAMETER LibreOfficeKitDocument* /*pThis*/) +{ + comphelper::ProfileZone aZone("doc_getView"); + + SolarMutexGuard aGuard; + SetLastExceptionMsg(); + + return SfxLokHelper::getView(); +} + +static int doc_getViewsCount(SAL_UNUSED_PARAMETER LibreOfficeKitDocument* pThis) +{ + comphelper::ProfileZone aZone("doc_getViewsCount"); + + SolarMutexGuard aGuard; + SetLastExceptionMsg(); + + LibLODocument_Impl* pDocument = static_cast<LibLODocument_Impl*>(pThis); + return SfxLokHelper::getViewsCount(pDocument->mnDocumentId); +} + +static bool doc_getViewIds(SAL_UNUSED_PARAMETER LibreOfficeKitDocument* pThis, int* pArray, size_t nSize) +{ + comphelper::ProfileZone aZone("doc_getViewsIds"); + + SolarMutexGuard aGuard; + SetLastExceptionMsg(); + + LibLODocument_Impl* pDocument = static_cast<LibLODocument_Impl*>(pThis); + return SfxLokHelper::getViewIds(pDocument->mnDocumentId, pArray, nSize); +} + +static void doc_setViewLanguage(SAL_UNUSED_PARAMETER LibreOfficeKitDocument* /*pThis*/, int nId, const char* language) +{ + comphelper::ProfileZone aZone("doc_setViewLanguage"); + + SolarMutexGuard aGuard; + SetLastExceptionMsg(); + + OUString sLanguage = OStringToOUString(language, RTL_TEXTENCODING_UTF8); + SfxLokHelper::setViewLanguage(nId, sLanguage); + SfxLokHelper::setViewLocale(nId, sLanguage); +} + +unsigned char* doc_renderFont(LibreOfficeKitDocument* pThis, + const char* pFontName, + const char* pChar, + int* pFontWidth, + int* pFontHeight) +{ + return doc_renderFontOrientation(pThis, pFontName, pChar, pFontWidth, pFontHeight, 0); +} + +unsigned char* doc_renderFontOrientation(SAL_UNUSED_PARAMETER LibreOfficeKitDocument* /*pThis*/, + const char* pFontName, + const char* pChar, + int* pFontWidth, + int* pFontHeight, + int pOrientation) +{ + comphelper::ProfileZone aZone("doc_renderFont"); + + SolarMutexGuard aGuard; + SetLastExceptionMsg(); + + const int nDefaultFontSize = 25; + + auto aFont = FindFont_FallbackToDefault(OStringToOUString(pFontName, RTL_TEXTENCODING_UTF8)); + + OUString aText(OStringToOUString(pChar, RTL_TEXTENCODING_UTF8)); + if (aText.isEmpty()) + aText = aFont.GetFamilyName(); + + auto aDevice(VclPtr<VirtualDevice>::Create(DeviceFormat::WITHOUT_ALPHA)); + ::tools::Rectangle aRect; + aFont.SetFontSize(Size(0, nDefaultFontSize)); + aFont.SetOrientation(Degree10(pOrientation)); + aDevice->SetFont(aFont); + aDevice->GetTextBoundRect(aRect, aText); + if (aRect.IsEmpty()) + return nullptr; + + int nFontWidth = aRect.Right() + 1; + int nFontHeight = aRect.Bottom() + 1; + + if (nFontWidth <= 0 || nFontHeight <= 0) + return nullptr; + + if (*pFontWidth > 0 && *pFontHeight > 0) + { + double fScaleX = *pFontWidth / static_cast<double>(nFontWidth) / 1.5; + double fScaleY = *pFontHeight / static_cast<double>(nFontHeight) / 1.5; + + double fScale = std::min(fScaleX, fScaleY); + + if (fScale >= 1.0) + { + int nFontSize = fScale * nDefaultFontSize; + aFont.SetFontSize(Size(0, nFontSize)); + aDevice->SetFont(aFont); + } + + aRect = tools::Rectangle(0, 0, *pFontWidth, *pFontHeight); + + nFontWidth = *pFontWidth; + nFontHeight = *pFontHeight; + + } + + unsigned char* pBuffer = static_cast<unsigned char*>(malloc(4 * nFontWidth * nFontHeight)); + if (!pBuffer) + return nullptr; + + memset(pBuffer, 0, nFontWidth * nFontHeight * 4); + aDevice->SetBackground(Wallpaper(COL_TRANSPARENT)); + aDevice->SetOutputSizePixelScaleOffsetAndLOKBuffer( + Size(nFontWidth, nFontHeight), Fraction(1.0), Point(), + pBuffer); + + if (*pFontWidth > 0 && *pFontHeight > 0) + { + DrawTextFlags const nStyle = + DrawTextFlags::Center + | DrawTextFlags::VCenter + | DrawTextFlags::MultiLine + | DrawTextFlags::WordBreak;// | DrawTextFlags::WordBreakHyphenation ; + + aDevice->DrawText(aRect, aText, nStyle); + } + else + { + *pFontWidth = nFontWidth; + *pFontHeight = nFontHeight; + + aDevice->DrawText(Point(0,0), aText); + } + + + return pBuffer; +} + + +static void doc_paintWindow(LibreOfficeKitDocument* pThis, unsigned nLOKWindowId, + unsigned char* pBuffer, + const int nX, const int nY, + const int nWidth, const int nHeight) +{ + doc_paintWindowDPI(pThis, nLOKWindowId, pBuffer, nX, nY, nWidth, nHeight, 1.0); +} + +static void doc_paintWindowDPI(LibreOfficeKitDocument* pThis, unsigned nLOKWindowId, + unsigned char* pBuffer, + const int nX, const int nY, + const int nWidth, const int nHeight, + const double fDPIScale) +{ + doc_paintWindowForView(pThis, nLOKWindowId, pBuffer, nX, nY, nWidth, nHeight, fDPIScale, -1); +} + +static void doc_paintWindowForView(LibreOfficeKitDocument* pThis, unsigned nLOKWindowId, + unsigned char* pBuffer, const int nX, const int nY, + const int nWidth, const int nHeight, + const double fDPIScale, int viewId) +{ + comphelper::ProfileZone aZone("doc_paintWindowDPI"); + + SolarMutexGuard aGuard; + SetLastExceptionMsg(); + + VclPtr<Window> pWindow = vcl::Window::FindLOKWindow(nLOKWindowId); + if (!pWindow) + { + SetLastExceptionMsg(u"Document doesn't support dialog rendering, or window not found."_ustr); + return; + } + + // Used to avoid work in setView if set. + comphelper::LibreOfficeKit::setDialogPainting(true); + + if (viewId >= 0) + doc_setView(pThis, viewId); + + // Setup cairo (or CoreGraphics, in the iOS case) to draw with the changed DPI scale (and return + // back to 1.0 when the painting finishes) + comphelper::ScopeGuard dpiScaleGuard([]() { comphelper::LibreOfficeKit::setDPIScale(1.0); }); + comphelper::LibreOfficeKit::setDPIScale(fDPIScale); + +#if defined(IOS) + // Onine uses the LOK_TILEMODE_RGBA by default so flip the normal flags + // to kCGImageAlphaNoneSkipLast | kCGImageByteOrder32Big + CGContextRef cgc = CGBitmapContextCreate(pBuffer, nWidth, nHeight, 8, nWidth*4, CGColorSpaceCreateDeviceRGB(), kCGImageAlphaNoneSkipLast | kCGImageByteOrder32Big); + + CGContextTranslateCTM(cgc, 0, nHeight); + CGContextScaleCTM(cgc, fDPIScale, -fDPIScale); + + SystemGraphicsData aData; + aData.rCGContext = cgc; + + ScopedVclPtrInstance<VirtualDevice> pDevice(aData, Size(1, 1), DeviceFormat::WITHOUT_ALPHA); + pDevice->SetBackground(Wallpaper(COL_TRANSPARENT)); + + pDevice->SetOutputSizePixel(Size(nWidth, nHeight)); + + MapMode aMapMode(pDevice->GetMapMode()); + aMapMode.SetOrigin(Point(-(nX / fDPIScale), -(nY / fDPIScale))); + pDevice->SetMapMode(aMapMode); + + pWindow->PaintToDevice(pDevice.get(), Point(0, 0)); + + CGContextRelease(cgc); + +#else + + ScopedVclPtrInstance<VirtualDevice> pDevice(DeviceFormat::WITHOUT_ALPHA); + pDevice->SetBackground(Wallpaper(COL_TRANSPARENT)); + + pDevice->SetOutputSizePixelScaleOffsetAndLOKBuffer(Size(nWidth, nHeight), Fraction(1.0), Point(), pBuffer); + + MapMode aMapMode(pDevice->GetMapMode()); + aMapMode.SetOrigin(Point(-(nX / fDPIScale), -(nY / fDPIScale))); + pDevice->SetMapMode(aMapMode); + + pWindow->PaintToDevice(pDevice.get(), Point(0, 0)); +#endif + + comphelper::LibreOfficeKit::setDialogPainting(false); +} + +static void doc_postWindow(LibreOfficeKitDocument* /*pThis*/, unsigned nLOKWindowId, int nAction, const char* pData) +{ + comphelper::ProfileZone aZone("doc_postWindow"); + + SolarMutexGuard aGuard; + SetLastExceptionMsg(); + + VclPtr<Window> pWindow = vcl::Window::FindLOKWindow(nLOKWindowId); + if (!pWindow) + { + SetLastExceptionMsg(u"Document doesn't support dialog rendering, or window not found."_ustr); + return; + } + + if (nAction == LOK_WINDOW_CLOSE) + { + vcl::CloseTopLevel(pWindow); + } + else if (nAction == LOK_WINDOW_PASTE) + { +#ifndef IOS + OUString aMimeType; + css::uno::Sequence<sal_Int8> aData; + std::vector<beans::PropertyValue> aArgs(jsonToPropertyValuesVector(pData)); + { + aArgs.size() == 2 && + aArgs[0].Name == "MimeType" && (aArgs[0].Value >>= aMimeType) && + aArgs[1].Name == "Data" && (aArgs[1].Value >>= aData); + } + + if (!aMimeType.isEmpty() && aData.hasElements()) + { + uno::Reference<datatransfer::XTransferable> xTransferable(new LOKTransferable(aMimeType, aData)); + uno::Reference<datatransfer::clipboard::XClipboard> xClipboard(new LOKClipboard); + xClipboard->setContents(xTransferable, uno::Reference<datatransfer::clipboard::XClipboardOwner>()); + pWindow->SetClipboard(xClipboard); + + KeyEvent aEvent(0, KEY_PASTE, 0); + Application::PostKeyEvent(VclEventId::WindowKeyInput, pWindow, &aEvent); + } + else + SetLastExceptionMsg(u"Window command 'paste': wrong parameters."_ustr); +#else + (void) pData; + assert(!"doc_postWindow() with LOK_WINDOW_PASTE should not be called on iOS"); +#endif + } +} + +// CERTIFICATE AND DOCUMENT SIGNING +static bool doc_insertCertificate(LibreOfficeKitDocument* pThis, + const unsigned char* pCertificateBinary, const int nCertificateBinarySize, + const unsigned char* pPrivateKeyBinary, const int nPrivateKeySize) +{ + comphelper::ProfileZone aZone("doc_insertCertificate"); + + if (!xContext.is()) + return false; + + LibLODocument_Impl* pDocument = static_cast<LibLODocument_Impl*>(pThis); + + if (!pDocument->mxComponent.is()) + return false; + + SfxBaseModel* pBaseModel = dynamic_cast<SfxBaseModel*>(pDocument->mxComponent.get()); + if (!pBaseModel) + return false; + + SfxObjectShell* pObjectShell = pBaseModel->GetObjectShell(); + + if (!pObjectShell) + return false; + + uno::Reference<xml::crypto::XSEInitializer> xSEInitializer = xml::crypto::SEInitializer::create(xContext); + uno::Reference<xml::crypto::XXMLSecurityContext> xSecurityContext = xSEInitializer->createSecurityContext(OUString()); + if (!xSecurityContext.is()) + return false; + + uno::Reference<xml::crypto::XSecurityEnvironment> xSecurityEnvironment = xSecurityContext->getSecurityEnvironment(); + uno::Reference<xml::crypto::XCertificateCreator> xCertificateCreator(xSecurityEnvironment, uno::UNO_QUERY); + + if (!xCertificateCreator.is()) + return false; + + uno::Sequence<sal_Int8> aCertificateSequence; + + std::string aCertificateString(reinterpret_cast<const char*>(pCertificateBinary), nCertificateBinarySize); + std::string aCertificateBase64String = extractCertificate(aCertificateString); + if (!aCertificateBase64String.empty()) + { + OUString aBase64OUString = OUString::createFromAscii(aCertificateBase64String); + comphelper::Base64::decode(aCertificateSequence, aBase64OUString); + } + else + { + aCertificateSequence.realloc(nCertificateBinarySize); + std::copy(pCertificateBinary, pCertificateBinary + nCertificateBinarySize, aCertificateSequence.getArray()); + } + + uno::Sequence<sal_Int8> aPrivateKeySequence; + std::string aPrivateKeyString(reinterpret_cast<const char*>(pPrivateKeyBinary), nPrivateKeySize); + std::string aPrivateKeyBase64String = extractPrivateKey(aPrivateKeyString); + if (!aPrivateKeyBase64String.empty()) + { + OUString aBase64OUString = OUString::createFromAscii(aPrivateKeyBase64String); + comphelper::Base64::decode(aPrivateKeySequence, aBase64OUString); + } + else + { + aPrivateKeySequence.realloc(nPrivateKeySize); + std::copy(pPrivateKeyBinary, pPrivateKeyBinary + nPrivateKeySize, aPrivateKeySequence.getArray()); + } + + uno::Reference<security::XCertificate> xCertificate = xCertificateCreator->createDERCertificateWithPrivateKey(aCertificateSequence, aPrivateKeySequence); + + if (!xCertificate.is()) + return false; + + SolarMutexGuard aGuard; + + return pObjectShell->SignDocumentContentUsingCertificate(xCertificate); +} + +static bool doc_addCertificate(LibreOfficeKitDocument* pThis, + const unsigned char* pCertificateBinary, const int nCertificateBinarySize) +{ + comphelper::ProfileZone aZone("doc_addCertificate"); + + if (!xContext.is()) + return false; + + LibLODocument_Impl* pDocument = static_cast<LibLODocument_Impl*>(pThis); + + if (!pDocument->mxComponent.is()) + return false; + + SfxBaseModel* pBaseModel = dynamic_cast<SfxBaseModel*>(pDocument->mxComponent.get()); + if (!pBaseModel) + return false; + + SfxObjectShell* pObjectShell = pBaseModel->GetObjectShell(); + + if (!pObjectShell) + return false; + + uno::Reference<xml::crypto::XSEInitializer> xSEInitializer = xml::crypto::SEInitializer::create(xContext); + uno::Reference<xml::crypto::XXMLSecurityContext> xSecurityContext = xSEInitializer->createSecurityContext(OUString()); + if (!xSecurityContext.is()) + return false; + + uno::Reference<xml::crypto::XSecurityEnvironment> xSecurityEnvironment = xSecurityContext->getSecurityEnvironment(); + uno::Reference<xml::crypto::XCertificateCreator> xCertificateCreator(xSecurityEnvironment, uno::UNO_QUERY); + + if (!xCertificateCreator.is()) + return false; + + uno::Sequence<sal_Int8> aCertificateSequence; + + std::string aCertificateString(reinterpret_cast<const char*>(pCertificateBinary), nCertificateBinarySize); + std::string aCertificateBase64String = extractCertificate(aCertificateString); + if (!aCertificateBase64String.empty()) + { + OUString aBase64OUString = OUString::createFromAscii(aCertificateBase64String); + comphelper::Base64::decode(aCertificateSequence, aBase64OUString); + } + else + { + aCertificateSequence.realloc(nCertificateBinarySize); + std::copy(pCertificateBinary, pCertificateBinary + nCertificateBinarySize, aCertificateSequence.getArray()); + } + + uno::Reference<security::XCertificate> xCertificate = xCertificateCreator->addDERCertificateToTheDatabase(aCertificateSequence, u"TCu,Cu,Tu"_ustr); + + if (!xCertificate.is()) + return false; + + SAL_INFO("lok", "Certificate Added = IssuerName: " << xCertificate->getIssuerName() << " SubjectName: " << xCertificate->getSubjectName()); + + return true; +} + +static int doc_getSignatureState(LibreOfficeKitDocument* pThis) +{ + comphelper::ProfileZone aZone("doc_getSignatureState"); + + LibLODocument_Impl* pDocument = static_cast<LibLODocument_Impl*>(pThis); + + if (!pDocument->mxComponent.is()) + return int(SignatureState::UNKNOWN); + + SfxBaseModel* pBaseModel = dynamic_cast<SfxBaseModel*>(pDocument->mxComponent.get()); + if (!pBaseModel) + return int(SignatureState::UNKNOWN); + + SfxObjectShell* pObjectShell = pBaseModel->GetObjectShell(); + if (!pObjectShell) + return int(SignatureState::UNKNOWN); + + SolarMutexGuard aGuard; + + pObjectShell->RecheckSignature(false); + + return int(pObjectShell->GetDocumentSignatureState()); +} + +static void doc_resizeWindow(LibreOfficeKitDocument* /*pThis*/, unsigned nLOKWindowId, + const int nWidth, const int nHeight) +{ + SolarMutexGuard aGuard; + SetLastExceptionMsg(); + + VclPtr<Window> pWindow = vcl::Window::FindLOKWindow(nLOKWindowId); + if (!pWindow) + { + SetLastExceptionMsg(u"Document doesn't support dialog resizing, or window not found."_ustr); + return; + } + + pWindow->SetSizePixel(Size(nWidth, nHeight)); +} + +static void doc_completeFunction(LibreOfficeKitDocument* pThis, const char* pFunctionName) +{ + SolarMutexGuard aGuard; + SetLastExceptionMsg(); + + ITiledRenderable* pDoc = getTiledRenderable(pThis); + if (!pDoc) + { + SetLastExceptionMsg(u"Document doesn't support tiled rendering"_ustr); + return; + } + + pDoc->completeFunction(OUString::fromUtf8(pFunctionName)); +} + + +static void doc_sendFormFieldEvent(LibreOfficeKitDocument* pThis, const char* pArguments) +{ + SolarMutexGuard aGuard; + + // Supported in Writer only + if (doc_getDocumentType(pThis) != LOK_DOCTYPE_TEXT) + return; + + StringMap aMap(jsdialog::jsonToStringMap(pArguments)); + ITiledRenderable* pDoc = getTiledRenderable(pThis); + if (!pDoc) + { + SetLastExceptionMsg(u"Document doesn't support tiled rendering!"_ustr); + return; + } + + // Sanity check + if (aMap.find(u"type"_ustr) == aMap.end() || aMap.find(u"cmd"_ustr) == aMap.end()) + { + SetLastExceptionMsg(u"Wrong arguments for sendFormFieldEvent!"_ustr); + return; + } + + pDoc->executeFromFieldEvent(aMap); +} + +static bool doc_renderSearchResult(LibreOfficeKitDocument* pThis, + const char* pSearchResult, unsigned char** pBitmapBuffer, + int* pWidth, int* pHeight, size_t* pByteSize) +{ + if (doc_getDocumentType(pThis) != LOK_DOCTYPE_TEXT) + return false; + + if (pBitmapBuffer == nullptr) + return false; + + if (!pSearchResult || pSearchResult[0] == '\0') + return false; + + ITiledRenderable* pDoc = getTiledRenderable(pThis); + if (!pDoc) + { + SetLastExceptionMsg(u"Document doesn't support tiled rendering"_ustr); + return false; + } + + auto aRectangleVector = pDoc->getSearchResultRectangles(pSearchResult); + + // combine into a rectangle union + basegfx::B2DRange aRangeUnion; + for (basegfx::B2DRange const & rRange : aRectangleVector) + { + aRangeUnion.expand(rRange); + } + + int aPixelWidth = o3tl::convert(aRangeUnion.getWidth(), o3tl::Length::twip, o3tl::Length::px); + int aPixelHeight = o3tl::convert(aRangeUnion.getHeight(), o3tl::Length::twip, o3tl::Length::px); + + size_t nByteSize = aPixelWidth * aPixelHeight * 4; + + *pWidth = aPixelWidth; + *pHeight = aPixelHeight; + *pByteSize = nByteSize; + + auto* pBuffer = static_cast<unsigned char*>(std::malloc(nByteSize)); + + doc_paintTile(pThis, pBuffer, + aPixelWidth, aPixelHeight, + aRangeUnion.getMinX(), aRangeUnion.getMinY(), + aRangeUnion.getWidth(), aRangeUnion.getHeight()); + + *pBitmapBuffer = pBuffer; + + return true; +} + +static void doc_sendContentControlEvent(LibreOfficeKitDocument* pThis, const char* pArguments) +{ + SolarMutexGuard aGuard; + + // Supported in Writer only + if (doc_getDocumentType(pThis) != LOK_DOCTYPE_TEXT) + { + return; + } + + StringMap aMap(jsdialog::jsonToStringMap(pArguments)); + ITiledRenderable* pDoc = getTiledRenderable(pThis); + if (!pDoc) + { + SetLastExceptionMsg(u"Document doesn't support tiled rendering"_ustr); + return; + } + + // Sanity check + if (aMap.find(u"type"_ustr) == aMap.end()) + { + SetLastExceptionMsg(u"Missing 'type' argument for sendContentControlEvent"_ustr); + return; + } + + pDoc->executeContentControlEvent(aMap); +} + +static void doc_setViewTimezone(SAL_UNUSED_PARAMETER LibreOfficeKitDocument* /*pThis*/, int nId, + const char* pTimezone) +{ + comphelper::ProfileZone aZone("doc_setViewTimezone"); + + SolarMutexGuard aGuard; + SetLastExceptionMsg(); + + // Leave the default if we get a null timezone. + if (pTimezone) + { + OUString sTimezone = OStringToOUString(pTimezone, RTL_TEXTENCODING_UTF8); + SfxLokHelper::setViewTimezone(nId, true, sTimezone); + } +} + +static void doc_setAccessibilityState(SAL_UNUSED_PARAMETER LibreOfficeKitDocument* pThis, int nId, bool nEnabled) +{ + SolarMutexGuard aGuard; + + int nDocType = getDocumentType(pThis); + if (!(nDocType == LOK_DOCTYPE_TEXT || nDocType == LOK_DOCTYPE_PRESENTATION || nDocType == LOK_DOCTYPE_SPREADSHEET)) + return; + + SfxLokHelper::setAccessibilityState(nId, nEnabled); +} + +static char* lo_getError (LibreOfficeKit *pThis) +{ + comphelper::ProfileZone aZone("lo_getError"); + + SolarMutexGuard aGuard; + + LibLibreOffice_Impl* pLib = static_cast<LibLibreOffice_Impl*>(pThis); + return convertOUString(pLib->maLastExceptionMsg); +} + +static void lo_freeError(char* pFree) +{ + free(pFree); +} + +static char* lo_getFilterTypes(LibreOfficeKit* pThis) +{ + SolarMutexGuard aGuard; + SetLastExceptionMsg(); + + LibLibreOffice_Impl* pImpl = static_cast<LibLibreOffice_Impl*>(pThis); + + if (!xSFactory.is()) + xSFactory = comphelper::getProcessServiceFactory(); + + if (!xSFactory.is()) + { + pImpl->maLastExceptionMsg = u"Service factory is not available"_ustr; + return nullptr; + } + + uno::Reference<container::XNameAccess> xTypeDetection(xSFactory->createInstance(u"com.sun.star.document.TypeDetection"_ustr), uno::UNO_QUERY); + const uno::Sequence<OUString> aTypes = xTypeDetection->getElementNames(); + tools::JsonWriter aJson; + for (const OUString& rType : aTypes) + { + uno::Sequence<beans::PropertyValue> aValues; + if (xTypeDetection->getByName(rType) >>= aValues) + { + auto it = std::find_if(std::cbegin(aValues), std::cend(aValues), [](const beans::PropertyValue& rValue) { return rValue.Name == "MediaType"; }); + OUString aValue; + if (it != std::cend(aValues) && (it->Value >>= aValue) && !aValue.isEmpty()) + { + auto typeNode = aJson.startNode(rType.toUtf8()); + aJson.put("MediaType", aValue.toUtf8()); + } + } + } + + return convertOString(aJson.finishAndGetAsOString()); +} + +static void lo_setOptionalFeatures(LibreOfficeKit* pThis, unsigned long long const features) +{ + comphelper::ProfileZone aZone("lo_setOptionalFeatures"); + + SolarMutexGuard aGuard; + SetLastExceptionMsg(); + + LibLibreOffice_Impl *const pLib = static_cast<LibLibreOffice_Impl*>(pThis); + pLib->mOptionalFeatures = features; + if (features & LOK_FEATURE_PART_IN_INVALIDATION_CALLBACK) + comphelper::LibreOfficeKit::setPartInInvalidation(true); + if (features & LOK_FEATURE_NO_TILED_ANNOTATIONS) + comphelper::LibreOfficeKit::setTiledAnnotations(false); + if (features & LOK_FEATURE_RANGE_HEADERS) + comphelper::LibreOfficeKit::setRangeHeaders(true); + if (features & LOK_FEATURE_VIEWID_IN_VISCURSOR_INVALIDATION_CALLBACK) + comphelper::LibreOfficeKit::setViewIdForVisCursorInvalidation(true); +} + +static void lo_setDocumentPassword(LibreOfficeKit* pThis, + const char* pURL, const char* pPassword) +{ + comphelper::ProfileZone aZone("lo_setDocumentPassword"); + + SolarMutexGuard aGuard; + SetLastExceptionMsg(); + + assert(pThis); + assert(pURL); + LibLibreOffice_Impl *const pLib = static_cast<LibLibreOffice_Impl*>(pThis); + assert(pLib->mInteractionMap.find(OString(pURL)) != pLib->mInteractionMap.end()); + pLib->mInteractionMap.find(OString(pURL))->second->SetPassword(pPassword); +} + +static char* lo_getVersionInfo(SAL_UNUSED_PARAMETER LibreOfficeKit* /*pThis*/) +{ + SetLastExceptionMsg(); + return convertOUString(ReplaceStringHookProc( + u"{ " + "\"ProductName\": \"%PRODUCTNAME\", " + "\"ProductVersion\": \"%PRODUCTVERSION\", " + "\"ProductExtension\": \"%PRODUCTEXTENSION\", " + "\"BuildId\": \"%BUILDID\"" +#if BUILDCONFIG_RECORDED + ", \"BuildConfig\": \"" BUILDCONFIG "\"" +#endif + " }"_ustr)); +} + +static void aBasicErrorFunc(const OUString& rError, const OUString& rAction) +{ + OString aBuffer = "Unexpected dialog: " + + OUStringToOString(rAction, RTL_TEXTENCODING_ASCII_US) + + " Error: " + + OUStringToOString(rError, RTL_TEXTENCODING_ASCII_US); + + fprintf(stderr, "Unexpected basic error dialog '%s'\n", aBuffer.getStr()); +} + +static bool initialize_uno(const OUString& aAppProgramURL) +{ +#ifdef IOS + // For iOS we already hardcode the inifile as "rc" in the .app directory. + rtl::Bootstrap::setIniFilename(aAppProgramURL + "/" SAL_CONFIGFILE("fundamental")); + xContext = cppu::defaultBootstrap_InitialComponentContext(aAppProgramURL + "/rc"); +#elif defined MACOSX + rtl::Bootstrap::setIniFilename(aAppProgramURL + "/../Resources/" SAL_CONFIGFILE("soffice")); + xContext = cppu::defaultBootstrap_InitialComponentContext(); +#else + rtl::Bootstrap::setIniFilename(aAppProgramURL + "/" SAL_CONFIGFILE("soffice")); + xContext = cppu::defaultBootstrap_InitialComponentContext(); +#endif + + if (!xContext.is()) + { + SetLastExceptionMsg(u"XComponentContext could not be created"_ustr); + SAL_INFO("lok", "XComponentContext could not be created"); + return false; + } + + xFactory = xContext->getServiceManager(); + if (!xFactory.is()) + { + SetLastExceptionMsg(u"XMultiComponentFactory could not be created"_ustr); + SAL_INFO("lok", "XMultiComponentFactory could not be created"); + return false; + } + + xSFactory.set(xFactory, uno::UNO_QUERY_THROW); + comphelper::setProcessServiceFactory(xSFactory); + + SAL_INFO("lok", "Uno initialized - " << xContext.is()); + + // set UserInstallation to user profile dir in test/user-template +// rtl::Bootstrap aDefaultVars; +// aDefaultVars.set(OUString("UserInstallation"), aAppProgramURL + "../registry" ); + // configmgr setup ? + + return true; +} + +// pre-unipoll version. +static void lo_startmain(void*) +{ + osl_setThreadName("lo_startmain"); + + if (comphelper::SolarMutex::get()) + Application::GetSolarMutex().tryToAcquire(); + + Application::UpdateMainThread(); + + soffice_main(); + + Application::ReleaseSolarMutex(); +} + +// unipoll version. +static void lo_runLoop(LibreOfficeKit* /*pThis*/, + LibreOfficeKitPollCallback pPollCallback, + LibreOfficeKitWakeCallback pWakeCallback, + void* pData) +{ +#if defined(IOS) || defined(ANDROID) || defined(__EMSCRIPTEN__) + Application::GetSolarMutex().acquire(); +#endif + + { + SolarMutexGuard aGuard; + + vcl::lok::registerPollCallbacks(pPollCallback, pWakeCallback, pData); + Application::UpdateMainThread(); + soffice_main(); + } +#if defined(IOS) || defined(ANDROID) || defined(__EMSCRIPTEN__) + vcl::lok::unregisterPollCallbacks(); + Application::ReleaseSolarMutex(); +#endif +} + +static bool bInitialized = false; + +static void lo_status_indicator_callback(void *data, comphelper::LibreOfficeKit::statusIndicatorCallbackType type, int percent, const char* pText) +{ + LibLibreOffice_Impl* pLib = static_cast<LibLibreOffice_Impl*>(data); + + if (!pLib->mpCallback) + return; + + switch (type) + { + case comphelper::LibreOfficeKit::statusIndicatorCallbackType::Start: + pLib->mpCallback(LOK_CALLBACK_STATUS_INDICATOR_START, pText, pLib->mpCallbackData); + break; + case comphelper::LibreOfficeKit::statusIndicatorCallbackType::SetValue: + pLib->mpCallback(LOK_CALLBACK_STATUS_INDICATOR_SET_VALUE, + OUString(OUString::number(percent)).toUtf8().getStr(), pLib->mpCallbackData); + break; + case comphelper::LibreOfficeKit::statusIndicatorCallbackType::Finish: + pLib->mpCallback(LOK_CALLBACK_STATUS_INDICATOR_FINISH, nullptr, pLib->mpCallbackData); + break; + } +} + +/// Used by preloadData (LibreOfficeKit) for providing different shortcuts for different languages. +static void preLoadShortCutAccelerators() +{ + std::unordered_map<OUString, css::uno::Reference<com::sun::star::ui::XAcceleratorConfiguration>>& acceleratorConfs = SfxLokHelper::getAcceleratorConfs(); + css::uno::Sequence<OUString> installedLocales(officecfg::Setup::Office::InstalledLocales::get()->getElementNames()); + OUString actualLang = officecfg::Setup::L10N::ooLocale::get(); + + for (sal_Int32 i = 0; i < installedLocales.getLength(); i++) + { + // Set the UI language to current one, before creating the accelerator. + std::shared_ptr<comphelper::ConfigurationChanges> batch(comphelper::ConfigurationChanges::create()); + officecfg::Setup::L10N::ooLocale::set(installedLocales[i], batch); + batch->commit(); + + // Supported module names: Writer, Calc, Draw, Impress + static constexpr OUString supportedModuleNames[] = { + u"com.sun.star.text.TextDocument"_ustr, + u"com.sun.star.sheet.SpreadsheetDocument"_ustr, + u"com.sun.star.drawing.DrawingDocument"_ustr, + u"com.sun.star.presentation.PresentationDocument"_ustr, + }; + // Create the accelerators. + for (const OUString& supportedModuleName : supportedModuleNames) + { + OUString key = supportedModuleName + installedLocales[i]; + acceleratorConfs[key] = svt::AcceleratorExecute::lok_createNewAcceleratorConfiguration(::comphelper::getProcessComponentContext(), supportedModuleName); + } + } + + // Set the UI language back to default one. + std::shared_ptr<comphelper::ConfigurationChanges> batch(comphelper::ConfigurationChanges::create()); + officecfg::Setup::L10N::ooLocale::set(actualLang, batch); + batch->commit(); +} + +void setLanguageToolConfig(); + +/// Used only by LibreOfficeKit when used by Online to pre-initialize +static void preloadData() +{ + comphelper::ProfileZone aZone("preload data"); + + // Create user profile in the temp directory for loading the dictionaries + OUString sUserPath; + rtl::Bootstrap::get(u"UserInstallation"_ustr, sUserPath); + utl::TempFileNamed aTempDir(nullptr, true); + aTempDir.EnableKillingFile(); + rtl::Bootstrap::set(u"UserInstallation"_ustr, aTempDir.GetURL()); + + // Register the bundled extensions + desktop::Desktop::SynchronizeExtensionRepositories(true); + bool bAbort = desktop::Desktop::CheckExtensionDependencies(); + if(bAbort) + std::cerr << "CheckExtensionDependencies failed" << std::endl; + + // setup LanguageTool config before spell checking init + setLanguageToolConfig(); + + // preload all available dictionaries + linguistic2::DictionaryList::create(comphelper::getProcessComponentContext()); + css::uno::Reference<css::linguistic2::XLinguServiceManager> xLngSvcMgr = + css::linguistic2::LinguServiceManager::create(comphelper::getProcessComponentContext()); + css::uno::Reference<linguistic2::XSpellChecker> xSpellChecker(xLngSvcMgr->getSpellChecker()); + + std::cerr << "Preloading dictionaries: "; + css::uno::Reference<linguistic2::XSupportedLocales> xSpellLocales(xSpellChecker, css::uno::UNO_QUERY_THROW); + uno::Sequence< css::lang::Locale > aLocales = xSpellLocales->getLocales(); + for (auto &it : std::as_const(aLocales)) + { + std::cerr << LanguageTag::convertToBcp47(it) << " "; + css::beans::PropertyValues aNone; + xSpellChecker->isValid(u"forcefed"_ustr, it, aNone); + } + std::cerr << "\n"; + + // Hack to load and cache the module liblocaledata_others.so which is not loaded normally + // (when loading dictionaries of just non-Asian locales). Creating a XCalendar4 of one Asian locale + // will cheaply load this missing "others" locale library. Appending an Asian locale in + // LOK_ALLOWLIST_LANGUAGES env-var also works but at the cost of loading that dictionary. + css::uno::Reference< css::i18n::XCalendar4 > xCal = css::i18n::LocaleCalendar2::create(comphelper::getProcessComponentContext()); + css::lang::Locale aAsianLocale = { u"hi"_ustr, u"IN"_ustr, {} }; + xCal->loadDefaultCalendar(aAsianLocale); + + // preload all available thesauri + css::uno::Reference<linguistic2::XThesaurus> xThesaurus(xLngSvcMgr->getThesaurus()); + css::uno::Reference<linguistic2::XSupportedLocales> xThesLocales(xSpellChecker, css::uno::UNO_QUERY_THROW); + aLocales = xThesLocales->getLocales(); + std::cerr << "Preloading thesauri: "; + for (auto &it : std::as_const(aLocales)) + { + std::cerr << LanguageTag::convertToBcp47(it) << " "; + css::beans::PropertyValues aNone; + xThesaurus->queryMeanings(u"forcefed"_ustr, it, aNone); + } + std::cerr << "\n"; + + css::uno::Reference< css::ui::XAcceleratorConfiguration > xGlobalCfg = css::ui::GlobalAcceleratorConfiguration::create( + comphelper::getProcessComponentContext()); + xGlobalCfg->getAllKeyEvents(); + + std::cerr << "Preload icons\n"; + ImageTree &images = ImageTree::get(); + images.getImageUrl(u"forcefed.png"_ustr, u"style"_ustr, u"FO_oo"_ustr); + + std::cerr << "Preload short cut accelerators\n"; + preLoadShortCutAccelerators(); + + std::cerr << "Preload languages\n"; + + // force load language singleton + SvtLanguageTable::HasLanguageType(LANGUAGE_SYSTEM); + (void)LanguageTag::isValidBcp47(u"foo"_ustr, nullptr); + + std::cerr << "Preload fonts\n"; + + // Initialize fonts. + css::uno::Reference<css::linguistic2::XLinguServiceManager2> xLangSrv = css::linguistic2::LinguServiceManager::create(xContext); + if (xLangSrv.is()) + { + css::uno::Reference<css::linguistic2::XSpellChecker> xSpell = xLangSrv->getSpellChecker(); + if (xSpell.is()) + aLocales = xSpell->getLocales(); + } + + for (const auto& aLocale : std::as_const(aLocales)) + { + //TODO: Add more types and cache more aggressively. For now this initializes the fontcache. + using namespace ::com::sun::star::i18n::ScriptType; + LanguageType nLang; + nLang = MsLangId::resolveSystemLanguageByScriptType(LanguageTag::convertToLanguageType(aLocale, false), LATIN); + OutputDevice::GetDefaultFont(DefaultFontType::LATIN_SPREADSHEET, nLang, GetDefaultFontFlags::OnlyOne); + nLang = MsLangId::resolveSystemLanguageByScriptType(LanguageTag::convertToLanguageType(aLocale, false), ASIAN); + OutputDevice::GetDefaultFont(DefaultFontType::CJK_SPREADSHEET, nLang, GetDefaultFontFlags::OnlyOne); + nLang = MsLangId::resolveSystemLanguageByScriptType(LanguageTag::convertToLanguageType(aLocale, false), COMPLEX); + OutputDevice::GetDefaultFont(DefaultFontType::CTL_SPREADSHEET, nLang, GetDefaultFontFlags::OnlyOne); + } + + std::cerr << "Preload config\n"; +#if defined __GNUC__ || defined __clang__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-variable" +#endif + static SvtOptionsDialogOptions aDialogOptions; + static SvtCTLOptions aSvtCTLOptions; + static SvtAccessibilityOptions aSvtAccessibilityOptions; + static svtools::ColorConfig aColorConfig; + static SvtMiscOptions aSvtMiscOptions; + static SvtSlideSorterBarOptions aSvtSlideSorterBarOptions; + static SvtCommandOptions aSvtCommandOptions; + static SvtCompatibilityOptions aSvtCompatibilityOptions; + static SvtFilterOptions aSvtFilterOptions; + static SvtLinguConfig aSvtLinguConfig; + static SvtModuleOptions aSvtModuleOptions; + static SvtPathOptions aSvtPathOptions; + static SvtSearchOptions aSvtSearchOptions; + static SvtSysLocaleOptions aSvtSysLocaleOptions; + static SvtUserOptions aSvtUserOptions; + //static SvtViewOptions aSvtViewOptions; + static MouseSettings aMouseSettings; + static StyleSettings aStyleSettings; + static MiscSettings aMiscSettings; + static HelpSettings aHelpSettings; + static AllSettings aAllSettings; +#if defined __GNUC__ || defined __clang__ +#pragma GCC diagnostic pop +#endif + + // Set user profile's path back to the original one + rtl::Bootstrap::set(u"UserInstallation"_ustr, sUserPath); +} + +namespace { + +static void activateNotebookbar(std::u16string_view rApp) +{ + OUString aPath = OUString::Concat("org.openoffice.Office.UI.ToolbarMode/Applications/") + rApp; + + const utl::OConfigurationTreeRoot aAppNode(xContext, aPath, true); + + if (aAppNode.isValid()) + { + static constexpr OUString sNoteBookbarName(u"notebookbar_online.ui"_ustr); + aAppNode.setNodeValue(u"Active"_ustr, Any(sNoteBookbarName)); + + const utl::OConfigurationNode aImplsNode = aAppNode.openNode(u"Modes"_ustr); + const Sequence<OUString> aModeNodeNames( aImplsNode.getNodeNames() ); + + for (const auto& rModeNodeName : aModeNodeNames) + { + const utl::OConfigurationNode aImplNode(aImplsNode.openNode(rModeNodeName)); + if (!aImplNode.isValid()) + continue; + + OUString aCommandArg = comphelper::getString(aImplNode.getNodeValue(u"CommandArg"_ustr)); + if (aCommandArg == "notebookbar.ui") + aImplNode.setNodeValue(u"CommandArg"_ustr, Any(sNoteBookbarName)); + } + + aAppNode.commit(); + } +} + +void setHelpRootURL() +{ + const char* pHelpRootURL = ::getenv("LOK_HELP_URL"); + if (pHelpRootURL) + { + OUString aHelpRootURL = OStringToOUString(pHelpRootURL, RTL_TEXTENCODING_UTF8); + try + { + std::shared_ptr<comphelper::ConfigurationChanges> batch(comphelper::ConfigurationChanges::create()); + officecfg::Office::Common::Help::HelpRootURL::set(aHelpRootURL, batch); + batch->commit(); + } + catch (uno::Exception const& rException) + { + SAL_WARN("lok", "Failed to set the help root URL: " << rException.Message); + } + } +} + +void setCertificateDir() +{ + const char* pEnvVarString = ::getenv("LO_CERTIFICATE_DATABASE_PATH"); + if (pEnvVarString) + { + OUString aCertificateDatabasePath = OStringToOUString(pEnvVarString, RTL_TEXTENCODING_UTF8); + try + { + std::shared_ptr<comphelper::ConfigurationChanges> pBatch(comphelper::ConfigurationChanges::create()); + officecfg::Office::Common::Security::Scripting::CertDir::set(aCertificateDatabasePath, pBatch); + officecfg::Office::Common::Security::Scripting::ManualCertDir::set(aCertificateDatabasePath, pBatch); + pBatch->commit(); + } + catch (uno::Exception const& rException) + { + SAL_WARN("lok", "Failed to set the NSS certificate database directory: " << rException.Message); + } + } +} + +void setDeeplConfig() +{ + const char* pAPIUrlString = ::getenv("DEEPL_API_URL"); + const char* pAuthKeyString = ::getenv("DEEPL_AUTH_KEY"); + if (pAPIUrlString && pAuthKeyString) + { + OUString aAPIUrl = OStringToOUString(pAPIUrlString, RTL_TEXTENCODING_UTF8); + OUString aAuthKey = OStringToOUString(pAuthKeyString, RTL_TEXTENCODING_UTF8); + try + { + std::shared_ptr<comphelper::ConfigurationChanges> batch(comphelper::ConfigurationChanges::create()); + officecfg::Office::Linguistic::Translation::Deepl::ApiURL::set(aAPIUrl, batch); + officecfg::Office::Linguistic::Translation::Deepl::AuthKey::set(aAuthKey, batch); + batch->commit(); + } + catch(uno::Exception const& rException) + { + SAL_WARN("lok", "Failed to set Deepl API settings: " << rException.Message); + } + } +} + +void setLanguageToolConfig() +{ + const char* pEnabled = ::getenv("LANGUAGETOOL_ENABLED"); + const char* pBaseUrlString = ::getenv("LANGUAGETOOL_BASEURL"); + + if (pEnabled && pBaseUrlString) + { + const char* pUsername = ::getenv("LANGUAGETOOL_USERNAME"); + const char* pApikey = ::getenv("LANGUAGETOOL_APIKEY"); + const char* pSSLVerification = ::getenv("LANGUAGETOOL_SSL_VERIFICATION"); + const char* pRestProtocol = ::getenv("LANGUAGETOOL_RESTPROTOCOL"); + + OUString aEnabled = OStringToOUString(pEnabled, RTL_TEXTENCODING_UTF8); + if (aEnabled != "true") + return; + OUString aBaseUrl = OStringToOUString(pBaseUrlString, RTL_TEXTENCODING_UTF8); + try + { + using LanguageToolCfg = officecfg::Office::Linguistic::GrammarChecking::LanguageTool; + auto batch(comphelper::ConfigurationChanges::create()); + + LanguageToolCfg::BaseURL::set(aBaseUrl, batch); + LanguageToolCfg::IsEnabled::set(true, batch); + if (pSSLVerification) + { + OUString aSSLVerification = OStringToOUString(pSSLVerification, RTL_TEXTENCODING_UTF8); + LanguageToolCfg::SSLCertVerify::set(aSSLVerification == "true", batch); + } + if (pRestProtocol) + { + OUString aRestProtocol = OStringToOUString(pRestProtocol, RTL_TEXTENCODING_UTF8); + LanguageToolCfg::RestProtocol::set(aRestProtocol, batch); + } + if (pUsername && pApikey) + { + OUString aUsername = OStringToOUString(pUsername, RTL_TEXTENCODING_UTF8); + OUString aApiKey = OStringToOUString(pApikey, RTL_TEXTENCODING_UTF8); + LanguageToolCfg::Username::set(aUsername, batch); + LanguageToolCfg::ApiKey::set(aApiKey, batch); + } + batch->commit(); + + css::uno::Reference<css::linguistic2::XLinguServiceManager2> xLangSrv = + css::linguistic2::LinguServiceManager::create(xContext); + if (xLangSrv.is()) + { + css::uno::Reference<css::linguistic2::XSpellChecker> xSpell = xLangSrv->getSpellChecker(); + if (xSpell.is()) + { + Sequence<OUString> aEmpty; + Sequence<css::lang::Locale> aLocales = xSpell->getLocales(); + + uno::Reference<linguistic2::XProofreader> xGC( + xContext->getServiceManager()->createInstanceWithContext(u"org.openoffice.lingu.LanguageToolGrammarChecker"_ustr, xContext), + uno::UNO_QUERY_THROW); + uno::Reference<linguistic2::XSupportedLocales> xSuppLoc(xGC, uno::UNO_QUERY_THROW); + + for (int itLocale = 0; itLocale < aLocales.getLength(); itLocale++) + { + // turn off spell checker if LanguageTool supports the locale already + if (xSuppLoc->hasLocale(aLocales[itLocale])) + xLangSrv->setConfiguredServices( + SN_SPELLCHECKER, aLocales[itLocale], aEmpty); + } + } + } + } + catch(uno::Exception const& rException) + { + SAL_WARN("lok", "Failed to set LanguageTool API settings: " << rException.Message); + } + } +} + +} + +static int lo_initialize(LibreOfficeKit* pThis, const char* pAppPath, const char* pUserProfileUrl) +{ + enum { + PRE_INIT, // setup shared data in master process + SECOND_INIT, // complete init. after fork + FULL_INIT // do a standard complete init. + } eStage; + + // Did we do a pre-initialize + static bool bPreInited = false; + static bool bUnipoll = false; + static bool bProfileZones = false; + static bool bNotebookbar = false; + + { // cf. string lifetime for preinit + std::vector<OUString> aOpts; + + // ':' delimited options - avoiding ABI change for new parameters + const char *pOptions = getenv("SAL_LOK_OPTIONS"); + if (pOptions) + aOpts = comphelper::string::split(OUString(pOptions, strlen(pOptions), RTL_TEXTENCODING_UTF8), ':'); + for (const auto &it : aOpts) + { + if (it == "unipoll") + bUnipoll = true; + else if (it == "profile_events") + bProfileZones = true; + else if (it == "sc_no_grid_bg") + comphelper::LibreOfficeKit::setCompatFlag( + comphelper::LibreOfficeKit::Compat::scNoGridBackground); + else if (it == "sc_print_twips_msgs") + comphelper::LibreOfficeKit::setCompatFlag( + comphelper::LibreOfficeKit::Compat::scPrintTwipsMsgs); + else if (it == "notebookbar") + bNotebookbar = true; + } + } + + // What stage are we at ? + if (pThis == nullptr) + { + eStage = PRE_INIT; + if (lok_preinit_2_called) + { + SAL_INFO("lok", "Create libreoffice object"); + gImpl = new LibLibreOffice_Impl(); + } + } + else if (bPreInited) + eStage = SECOND_INIT; + else + eStage = FULL_INIT; + + LibLibreOffice_Impl* pLib = static_cast<LibLibreOffice_Impl*>(pThis); + + if (bInitialized) + return 1; + + // Turn profile zones on early + if (bProfileZones && eStage == SECOND_INIT) + { + comphelper::TraceEvent::startRecording(); + traceEventDumper = new TraceEventDumper(); + } + + comphelper::ProfileZone aZone("lok-init"); + + if (eStage == PRE_INIT) + { + rtl_alloc_preInit(true); + + // Set the default timezone to the TZ envar, if set. + const char* tz = ::getenv("TZ"); + SfxLokHelper::setDefaultTimezone(!!tz, tz ? OStringToOUString(tz, RTL_TEXTENCODING_UTF8) + : OUString()); + } + + if (eStage != SECOND_INIT) + comphelper::LibreOfficeKit::setActive(); + + if (eStage != PRE_INIT) + comphelper::LibreOfficeKit::setStatusIndicatorCallback(lo_status_indicator_callback, pLib); + + if (pUserProfileUrl && eStage != PRE_INIT) + { + OUString url( + pUserProfileUrl, strlen(pUserProfileUrl), RTL_TEXTENCODING_UTF8); + OUString path; + if (url.startsWithIgnoreAsciiCase("vnd.sun.star.pathname:", &path)) + { + OUString url2; + osl::FileBase::RC e = osl::FileBase::getFileURLFromSystemPath( + path, url2); + if (e == osl::FileBase::E_None) + url = url2; + else + SAL_WARN("lok", "resolving <" << url << "> failed with " << +e); + } + rtl::Bootstrap::set(u"UserInstallation"_ustr, url); + if (eStage == SECOND_INIT) + utl::Bootstrap::reloadData(); + } + + OUString aAppPath; + if (pAppPath) + { + aAppPath = OUString(pAppPath, strlen(pAppPath), RTL_TEXTENCODING_UTF8); + } + else + { +#if defined ANDROID || defined EMSCRIPTEN + aAppPath = OUString::fromUtf8(lo_get_app_data_dir()) + "/program"; +#else + // Fun conversion dance back and forth between URLs and system paths... + OUString aAppURL; + ::osl::Module::getUrlFromAddress( reinterpret_cast< oslGenericFunction >(lo_initialize), + aAppURL); + osl::FileBase::getSystemPathFromFileURL( aAppURL, aAppPath ); +#endif + +#ifdef IOS + // The above gives something like + // "/private/var/containers/Bundle/Application/953AA851-CC15-4C60-A2CB-C2C6F24E6F71/Foo.app/Foo", + // and we want to drop the final component (the binary name). + sal_Int32 lastSlash = aAppPath.lastIndexOf('/'); + assert(lastSlash > 0); + aAppPath = aAppPath.copy(0, lastSlash); +#endif + } + + OUString aAppURL; + if (osl::FileBase::getFileURLFromSystemPath(aAppPath, aAppURL) != osl::FileBase::E_None) + return 0; + +#ifdef IOS + // A LibreOffice-using iOS app should have the ICU data file in the app bundle. Initialize ICU + // to use that. + NSString *bundlePath = [[NSBundle mainBundle] bundlePath]; + + int fd = open([[bundlePath stringByAppendingPathComponent:@"ICU.dat"] UTF8String], O_RDONLY); + if (fd == -1) + NSLog(@"Could not open ICU data file %s", [[bundlePath stringByAppendingPathComponent:@"ICU.dat"] UTF8String]); + else + { + struct stat st; + if (fstat(fd, &st) == -1) + NSLog(@"fstat on ICU data file failed: %s", strerror(errno)); + else + { + void *icudata = mmap(0, (size_t) st.st_size, PROT_READ, MAP_FILE|MAP_PRIVATE, fd, 0); + if (icudata == MAP_FAILED) + NSLog(@"mmap failed: %s", strerror(errno)); + else + { + UErrorCode icuStatus = U_ZERO_ERROR; + udata_setCommonData(icudata, &icuStatus); + if (U_FAILURE(icuStatus)) + NSLog(@"udata_setCommonData failed"); + else + { + // Quick test that ICU works... + UConverter *cnv = ucnv_open("iso-8859-3", &icuStatus); + if (U_SUCCESS(icuStatus)) + ucnv_close(cnv); + else + NSLog(@"ucnv_open() failed: %s", u_errorName(icuStatus)); + } + } + } + close(fd); + } +#endif + + try + { + if (eStage != SECOND_INIT) + { + SAL_INFO("lok", "Attempting to initialize UNO"); + + if (!initialize_uno(aAppURL)) + return false; + + // Force headless -- this is only for bitmap rendering. + rtl::Bootstrap::set(u"SAL_USE_VCLPLUGIN"_ustr, u"svp"_ustr); + + // We specifically need to make sure we have the "headless" + // command arg set (various code specifically checks via + // CommandLineArgs): + desktop::Desktop::GetCommandLineArgs().setHeadless(); + +#ifdef IOS + if (InitVCL() && [NSThread isMainThread]) + { + static bool bFirstTime = true; + if (bFirstTime) + { + Application::GetSolarMutex().release(); + bFirstTime = false; + } + } + SfxApplication::GetOrCreate(); +#endif + +#if HAVE_FEATURE_ANDROID_LOK + // Register the bundled extensions - so that the dictionaries work + desktop::Desktop::SynchronizeExtensionRepositories(false); + bool bFailed = desktop::Desktop::CheckExtensionDependencies(); + if (bFailed) + SAL_INFO("lok", "CheckExtensionDependencies failed"); +#endif + + if (eStage == PRE_INIT) + { + { + comphelper::ProfileZone aInit("Init vcl"); + std::cerr << "Init vcl\n"; + InitVCL(); + } + + // pre-load all component libraries. + if (!xContext.is()) + throw css::uno::DeploymentException(u"preInit: XComponentContext is not created"_ustr); + + css::uno::Reference< css::uno::XInterface > xService; + xContext->getValueByName(u"/singletons/com.sun.star.lang.theServiceManager"_ustr) >>= xService; + if (!xService.is()) + throw css::uno::DeploymentException(u"preInit: XMultiComponentFactory is not created"_ustr); + + css::uno::Reference<css::lang::XInitialization> aService( + xService, css::uno::UNO_QUERY_THROW); + + // pre-requisites: + // In order to load implementations and invoke + // component factory it is required: + // 1) defaultBootstrap_InitialComponentContext() + // 2) comphelper::setProcessServiceFactory(xSFactory); + // 3) InitVCL() + { + comphelper::ProfileZone aInit("preload"); + aService->initialize({css::uno::Any(u"preload"_ustr)}); + } + { // Force load some modules + comphelper::ProfileZone aInit("preload modules"); + VclBuilderPreload(); + VclAbstractDialogFactory::Create(); + } + + preloadData(); + + // Release Solar Mutex, lo_startmain thread should acquire it. + Application::ReleaseSolarMutex(); + } + + setLanguageAndLocale(u"en-US"_ustr); + } + + if (eStage != PRE_INIT) + { + SAL_INFO("lok", "Re-initialize temp paths"); + SvtPathOptions aOptions; + OUString aNewTemp; + osl::FileBase::getTempDirURL(aNewTemp); + aOptions.SetTempPath(aNewTemp); + desktop::Desktop::CreateTemporaryDirectory(); + + // The RequestHandler is specifically set to be ready when all the other + // init in Desktop::Main (run from soffice_main) is done. We can enable + // the RequestHandler here (without starting any IPC thread; + // shortcutting the invocation in Desktop::Main that would start the IPC + // thread), and can then use it to wait until we're definitely ready to + // continue. + + SAL_INFO("lok", "Enabling RequestHandler"); + RequestHandler::Enable(false); + SAL_INFO("lok", "Starting soffice_main"); + RequestHandler::SetReady(false); + if (!bUnipoll) + { + // Start the main thread only in non-unipoll mode (i.e. multithreaded). + pLib->maThread = osl_createThread(lo_startmain, nullptr); + SAL_INFO("lok", "Waiting for RequestHandler"); + RequestHandler::WaitForReady(); + SAL_INFO("lok", "RequestHandler ready -- continuing"); + } + else + InitVCL(); + } + + if (eStage != SECOND_INIT) + ErrorRegistry::RegisterDisplay(aBasicErrorFunc); + + SAL_INFO("lok", "LOK Initialized"); + if (eStage == PRE_INIT) + bPreInited = true; + else + bInitialized = true; + } + catch (css::uno::Exception& exception) + { + fprintf(stderr, "Bootstrapping exception '%s'\n", + OUStringToOString(exception.Message, RTL_TEXTENCODING_UTF8).getStr()); + } + + if (eStage == PRE_INIT) + { + comphelper::ThreadPool::getSharedOptimalPool().shutdown(); + } + +// Turn off quick editing on iOS, Android and Emscripten +#if defined IOS || defined ANDROID || defined __EMSCRIPTEN__ + if (officecfg::Office::Impress::Misc::TextObject::QuickEditing::get()) + { + std::shared_ptr<comphelper::ConfigurationChanges> batch(comphelper::ConfigurationChanges::create()); + officecfg::Office::Impress::Misc::TextObject::QuickEditing::set(false, batch); + batch->commit(); + } +#endif + + + setHelpRootURL(); + setCertificateDir(); + setDeeplConfig(); + setLanguageToolConfig(); + + if (bNotebookbar) + { + std::shared_ptr<comphelper::ConfigurationChanges> batch(comphelper::ConfigurationChanges::create()); + officecfg::Office::UI::ToolbarMode::ActiveWriter::set(u"notebookbar_online.ui"_ustr, batch); + officecfg::Office::UI::ToolbarMode::ActiveCalc::set(u"notebookbar_online.ui"_ustr, batch); + officecfg::Office::UI::ToolbarMode::ActiveImpress::set(u"notebookbar_online.ui"_ustr, batch); + officecfg::Office::UI::ToolbarMode::ActiveDraw::set(u"notebookbar_online.ui"_ustr, batch); + batch->commit(); + + activateNotebookbar(u"Writer"); + activateNotebookbar(u"Calc"); + activateNotebookbar(u"Impress"); + activateNotebookbar(u"Draw"); + } + + // staticize all strings. + if (eStage == PRE_INIT) + rtl_alloc_preInit(false); + + return bInitialized; +} + +SAL_JNI_EXPORT +LibreOfficeKit *libreofficekit_hook_2(const char* install_path, const char* user_profile_url) +{ + static bool alreadyCalled = false; + + if ((!lok_preinit_2_called && !gImpl) || (lok_preinit_2_called && !alreadyCalled)) + { + alreadyCalled = true; + + if (!lok_preinit_2_called) + { + SAL_INFO("lok", "Create libreoffice object"); + gImpl = new LibLibreOffice_Impl(); + } + + if (!lo_initialize(gImpl, install_path, user_profile_url)) + { + lo_destroy(gImpl); + } + } + return static_cast<LibreOfficeKit*>(gImpl); +} + +SAL_JNI_EXPORT +LibreOfficeKit *libreofficekit_hook(const char* install_path) +{ + return libreofficekit_hook_2(install_path, nullptr); +} + +SAL_JNI_EXPORT +int lok_preinit(const char* install_path, const char* user_profile_url) +{ + return lo_initialize(nullptr, install_path, user_profile_url); +} + +SAL_JNI_EXPORT +int lok_preinit_2(const char* install_path, const char* user_profile_url, LibreOfficeKit** kit) +{ + lok_preinit_2_called = true; + int result = lo_initialize(nullptr, install_path, user_profile_url); + if (kit != nullptr) + *kit = gImpl; + return result; +} + +static void lo_destroy(LibreOfficeKit* pThis) +{ + SolarMutexClearableGuard aGuard; + + LibLibreOffice_Impl* pLib = static_cast<LibLibreOffice_Impl*>(pThis); + gImpl = nullptr; + + SAL_INFO("lok", "LO Destroy"); + + comphelper::LibreOfficeKit::setStatusIndicatorCallback(nullptr, nullptr); + uno::Reference <frame::XDesktop2> xDesktop = frame::Desktop::create ( ::comphelper::getProcessComponentContext() ); + // FIXME: the terminate() call here is a no-op because it detects + // that LibreOfficeKit::isActive() and then returns early! + bool bSuccess = xDesktop.is() && xDesktop->terminate(); + + if (!bSuccess) + { + bSuccess = GetpApp() && GetpApp()->QueryExit(); + } + + if (!bSuccess) + { + Application::Quit(); + } + + aGuard.clear(); + + osl_joinWithThread(pLib->maThread); + osl_destroyThread(pLib->maThread); + + delete pLib; + bInitialized = false; + SAL_INFO("lok", "LO Destroy Done"); +} + +} // extern "C" + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/lib/lokandroid.cxx b/desktop/source/lib/lokandroid.cxx new file mode 100644 index 0000000000..e800c82b07 --- /dev/null +++ b/desktop/source/lib/lokandroid.cxx @@ -0,0 +1,422 @@ +/* -*- 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 <unistd.h> +#include <jni.h> + +#include <sal/types.h> +#include <vcl/event.hxx> +#include <android/log.h> + +#include <osl/detail/android-bootstrap.h> + +#include <LibreOfficeKit/LibreOfficeKit.h> + +/* LibreOfficeKit */ + +namespace +{ + +jfieldID getHandleField(JNIEnv* pEnv, jobject aObject) +{ + jclass clazz = pEnv->GetObjectClass(aObject); + return pEnv->GetFieldID(clazz, "handle", "Ljava/nio/ByteBuffer;"); +} + +template <typename T> +T* getHandle(JNIEnv* pEnv, jobject aObject) +{ + jobject aHandle = pEnv->GetObjectField(aObject, getHandleField(pEnv, aObject)); + return reinterpret_cast<T*>(pEnv->GetDirectBufferAddress(aHandle)); +} + +const char* copyJavaString(JNIEnv* pEnv, jstring aJavaString) +{ + const char* pTemp = pEnv->GetStringUTFChars(aJavaString, NULL); + const char* pClone = strdup(pTemp); + pEnv->ReleaseStringUTFChars(aJavaString, pTemp); + + return pClone; +} + +} // anonymous namespace + +extern "C" SAL_JNI_EXPORT jstring JNICALL Java_org_libreoffice_kit_Office_getError + (JNIEnv* pEnv, jobject aObject) +{ + LibreOfficeKit* pLibreOfficeKit = getHandle<LibreOfficeKit>(pEnv, aObject); + char* pError = pLibreOfficeKit->pClass->getError(pLibreOfficeKit); + return pEnv->NewStringUTF(pError); +} + +extern "C" SAL_JNI_EXPORT void JNICALL Java_org_libreoffice_kit_Office_destroy + (JNIEnv* pEnv, jobject aObject) +{ + LibreOfficeKit* pLibreOfficeKit = getHandle<LibreOfficeKit>(pEnv, aObject); + pLibreOfficeKit->pClass->destroy(pLibreOfficeKit); +} + +extern "C" SAL_JNI_EXPORT void JNICALL Java_org_libreoffice_kit_Office_destroyAndExit(JNIEnv* pEnv, jobject aObject) +{ + LibreOfficeKit* pLibreOfficeKit = getHandle<LibreOfficeKit>(pEnv, aObject); + pLibreOfficeKit->pClass->destroy(pLibreOfficeKit); + // Stopgap fix: _exit() to force the OS to restart the LO activity. + // Better than to hang. + _exit(0); +} + +namespace +{ + +struct CallbackData +{ + jmethodID aJavaCallbackMethod; + jclass aClass; + jobject aObject; +}; + +static CallbackData gCallbackData; +static CallbackData gCallbackDataLOKit; + +/** + * Handle retrieved callback + */ +void messageCallback(int nType, const char* pPayload, void* pData) +{ + CallbackData* pCallbackData = (CallbackData*) pData; + + JavaVM* pJavaVM = lo_get_javavm(); + JNIEnv* pEnv; + bool bIsAttached = false; + + int status = pJavaVM->GetEnv((void **) &pEnv, JNI_VERSION_1_6); + + if(status < 0) + { + status = pJavaVM->AttachCurrentThread(&pEnv, NULL); + if(status < 0) + { + return; + } + bIsAttached = true; + } + + jstring sPayload = pEnv->NewStringUTF(pPayload); + + jvalue aParameter[2]; + aParameter[0].i = nType; + aParameter[1].l = sPayload; + + pEnv->CallVoidMethodA(pCallbackData->aObject, pCallbackData->aJavaCallbackMethod, aParameter); + + pEnv->DeleteLocalRef(sPayload); + + if (bIsAttached) + { + pJavaVM->DetachCurrentThread(); + } +} + +} // anonymous namespace + +extern "C" SAL_JNI_EXPORT jobject JNICALL Java_org_libreoffice_kit_Office_documentLoadNative + (JNIEnv* pEnv, jobject aObject, jstring documentPath) +{ + const char* aCloneDocumentPath = copyJavaString(pEnv, documentPath); + LibreOfficeKit* pLibreOfficeKit = getHandle<LibreOfficeKit>(pEnv, aObject); + + LibreOfficeKitDocument* pDocument = pLibreOfficeKit->pClass->documentLoad(pLibreOfficeKit, aCloneDocumentPath); + + if (pDocument == NULL) + return NULL; + + jobject aHandle = pEnv->NewDirectByteBuffer((void*) pDocument, sizeof(LibreOfficeKitDocument)); + + return aHandle; +} + +extern "C" SAL_JNI_EXPORT void JNICALL Java_org_libreoffice_kit_Office_setDocumentPassword + (JNIEnv* pEnv, jobject aObject, jstring sUrl, jstring sPassword) +{ + LibreOfficeKit* pLibreOfficeKit = getHandle<LibreOfficeKit>(pEnv, aObject); + + char const* pUrl = copyJavaString(pEnv, sUrl); + if (sPassword == NULL) { + pLibreOfficeKit->pClass->setDocumentPassword(pLibreOfficeKit, pUrl, nullptr); + } else { + char const* pPassword = copyJavaString(pEnv, sPassword); + pLibreOfficeKit->pClass->setDocumentPassword(pLibreOfficeKit, pUrl, pPassword); + } +} + +extern "C" SAL_JNI_EXPORT void JNICALL Java_org_libreoffice_kit_Office_setOptionalFeatures + (JNIEnv* pEnv, jobject aObject, jlong options) +{ + LibreOfficeKit* pLibreOfficeKit = getHandle<LibreOfficeKit>(pEnv, aObject); + + unsigned long long pOptions = (unsigned long long)options; + + pLibreOfficeKit->pClass->setOptionalFeatures(pLibreOfficeKit, pOptions); +} + +/** Implementation of org.libreoffice.kit.Office.bindMessageCallback method */ +extern "C" SAL_JNI_EXPORT void JNICALL Java_org_libreoffice_kit_Office_bindMessageCallback + (JNIEnv* pEnv, jobject aObject) +{ + LibreOfficeKit* pLibreOfficeKit = getHandle<LibreOfficeKit>(pEnv, aObject); + + gCallbackDataLOKit.aObject = (jobject) pEnv->NewGlobalRef(aObject); + jclass aClass = pEnv->GetObjectClass(aObject); + gCallbackDataLOKit.aClass = (jclass) pEnv->NewGlobalRef(aClass); + + gCallbackDataLOKit.aJavaCallbackMethod = pEnv->GetMethodID(aClass, "messageRetrievedLOKit", "(ILjava/lang/String;)V"); + + pLibreOfficeKit->pClass->registerCallback(pLibreOfficeKit, messageCallback, (void*) &gCallbackDataLOKit); +} + +/* Document */ + +/** Implementation of org.libreoffice.kit.Document.bindMessageCallback method */ +extern "C" SAL_JNI_EXPORT void JNICALL Java_org_libreoffice_kit_Document_bindMessageCallback + (JNIEnv* pEnv, jobject aObject) +{ + LibreOfficeKitDocument* pDocument = getHandle<LibreOfficeKitDocument>(pEnv, aObject); + + gCallbackData.aObject = (jobject) pEnv->NewGlobalRef(aObject); + jclass aClass = pEnv->GetObjectClass(aObject); + gCallbackData.aClass = (jclass) pEnv->NewGlobalRef(aClass); + + gCallbackData.aJavaCallbackMethod = pEnv->GetMethodID(aClass, "messageRetrieved", "(ILjava/lang/String;)V"); + + pDocument->pClass->registerCallback(pDocument, messageCallback, (void*) &gCallbackData); +} + +extern "C" SAL_JNI_EXPORT void JNICALL Java_org_libreoffice_kit_Document_destroy + (JNIEnv* pEnv, jobject aObject) +{ + LibreOfficeKitDocument* pDocument = getHandle<LibreOfficeKitDocument>(pEnv, aObject); + pDocument->pClass->destroy(pDocument); +} + +extern "C" SAL_JNI_EXPORT void JNICALL Java_org_libreoffice_kit_Document_setPart + (JNIEnv* pEnv, jobject aObject, jint aPart) +{ + LibreOfficeKitDocument* pDocument = getHandle<LibreOfficeKitDocument>(pEnv, aObject); + pDocument->pClass->setPart(pDocument, aPart); +} + +extern "C" SAL_JNI_EXPORT jint JNICALL Java_org_libreoffice_kit_Document_getPart + (JNIEnv* pEnv, jobject aObject) +{ + LibreOfficeKitDocument* pDocument = getHandle<LibreOfficeKitDocument>(pEnv, aObject); + return (jint) pDocument->pClass->getPart(pDocument); +} + +extern "C" SAL_JNI_EXPORT jstring JNICALL Java_org_libreoffice_kit_Document_getPartPageRectangles + (JNIEnv* pEnv, jobject aObject) +{ + LibreOfficeKitDocument* pDocument = getHandle<LibreOfficeKitDocument>(pEnv, aObject); + char* pRectangles = pDocument->pClass->getPartPageRectangles(pDocument); + return pEnv->NewStringUTF(pRectangles); +} + +extern "C" SAL_JNI_EXPORT jint JNICALL Java_org_libreoffice_kit_Document_getParts + (JNIEnv* pEnv, jobject aObject) +{ + LibreOfficeKitDocument* pDocument = getHandle<LibreOfficeKitDocument>(pEnv, aObject); + return (jint) pDocument->pClass->getParts(pDocument); +} + +extern "C" SAL_JNI_EXPORT jstring JNICALL Java_org_libreoffice_kit_Document_getPartName + (JNIEnv* pEnv, jobject aObject, jint nPartIndex) +{ + LibreOfficeKitDocument* pDocument = getHandle<LibreOfficeKitDocument>(pEnv, aObject); + char* pPartName = pDocument->pClass->getPartName(pDocument, nPartIndex); + return pEnv->NewStringUTF(pPartName); +} + +extern "C" SAL_JNI_EXPORT void JNICALL Java_org_libreoffice_kit_Document_setPartMode + (JNIEnv* pEnv, jobject aObject, jint nPartMode) +{ + LibreOfficeKitDocument* pDocument = getHandle<LibreOfficeKitDocument>(pEnv, aObject); + + pDocument->pClass->setPartMode(pDocument, nPartMode); +} + +extern "C" SAL_JNI_EXPORT jint JNICALL Java_org_libreoffice_kit_Document_getDocumentTypeNative + (JNIEnv* pEnv, jobject aObject) +{ + LibreOfficeKitDocument* pDocument = getHandle<LibreOfficeKitDocument>(pEnv, aObject); + return (jint) pDocument->pClass->getDocumentType(pDocument); +} + +extern "C" SAL_JNI_EXPORT void JNICALL Java_org_libreoffice_kit_Document_paintTileNative + (JNIEnv* pEnv, jobject aObject, jobject aByteBuffer, + jint nCanvasWidth, jint nCanvasHeight, jint nTilePosX, jint nTilePosY, + jint nTileWidth, jint nTileHeight) +{ + LibreOfficeKitDocument* pDocument = getHandle<LibreOfficeKitDocument>(pEnv, aObject); + + unsigned char* buffer = (unsigned char*) pEnv->GetDirectBufferAddress(aByteBuffer); + pDocument->pClass->paintTile(pDocument, buffer, nCanvasWidth, nCanvasHeight, nTilePosX, nTilePosY, nTileWidth, nTileHeight); +} + +extern "C" SAL_JNI_EXPORT jlong JNICALL Java_org_libreoffice_kit_Document_getDocumentHeight + (JNIEnv* pEnv, jobject aObject) +{ + LibreOfficeKitDocument* pDocument = getHandle<LibreOfficeKitDocument>(pEnv, aObject); + long nWidth; + long nHeight; + pDocument->pClass->getDocumentSize(pDocument, &nWidth, &nHeight); + return nHeight; +} + +extern "C" SAL_JNI_EXPORT jlong JNICALL Java_org_libreoffice_kit_Document_getDocumentWidth + (JNIEnv* pEnv, jobject aObject) +{ + LibreOfficeKitDocument* pDocument = getHandle<LibreOfficeKitDocument>(pEnv, aObject); + long nWidth; + long nHeight; + pDocument->pClass->getDocumentSize(pDocument, &nWidth, &nHeight); + return nWidth; +} + +extern "C" SAL_JNI_EXPORT void JNICALL Java_org_libreoffice_kit_Document_initializeForRendering + (JNIEnv* pEnv, jobject aObject) +{ + LibreOfficeKitDocument* pDocument = getHandle<LibreOfficeKitDocument>(pEnv, aObject); + pDocument->pClass->initializeForRendering(pDocument, NULL); +} + +extern "C" SAL_JNI_EXPORT jint JNICALL Java_org_libreoffice_kit_Document_saveAs + (JNIEnv* pEnv, jobject aObject, jstring sUrl, jstring sFormat, jstring sOptions) +{ + LibreOfficeKitDocument* pDocument = getHandle<LibreOfficeKitDocument>(pEnv, aObject); + + const char* pUrl = pEnv->GetStringUTFChars(sUrl, NULL); + const char* pFormat = pEnv->GetStringUTFChars(sFormat, NULL); + const char* pOptions = pEnv->GetStringUTFChars(sOptions, NULL); + + int result = pDocument->pClass->saveAs(pDocument, pUrl, pFormat, pOptions); + + pEnv->ReleaseStringUTFChars(sUrl, pUrl); + pEnv->ReleaseStringUTFChars(sFormat, pFormat); + pEnv->ReleaseStringUTFChars(sOptions, pOptions); + + return result; +} + +extern "C" SAL_JNI_EXPORT void JNICALL Java_org_libreoffice_kit_Document_postKeyEvent + (JNIEnv* pEnv, jobject aObject, jint nType, jint nCharCode, jint nKeyCode) +{ + LibreOfficeKitDocument* pDocument = getHandle<LibreOfficeKitDocument>(pEnv, aObject); + pDocument->pClass->postKeyEvent(pDocument, nType, nCharCode, nKeyCode); +} + +extern "C" SAL_JNI_EXPORT void JNICALL Java_org_libreoffice_kit_Document_postMouseEvent + (JNIEnv* pEnv, jobject aObject, jint type, jint x, jint y, jint count, jint button, jint modifier) +{ + LibreOfficeKitDocument* pDocument = getHandle<LibreOfficeKitDocument>(pEnv, aObject); + pDocument->pClass->postMouseEvent(pDocument, type, x, y, count, button, modifier); +} + +extern "C" SAL_JNI_EXPORT void JNICALL Java_org_libreoffice_kit_Document_postUnoCommand + (JNIEnv* pEnv, jobject aObject, jstring command, jstring arguments, jboolean bNotifyWhenFinished) +{ + LibreOfficeKitDocument* pDocument = getHandle<LibreOfficeKitDocument>(pEnv, aObject); + + const char* pCommand = pEnv->GetStringUTFChars(command, NULL); + const char* pArguments = nullptr; + if (arguments != NULL) + pArguments = pEnv->GetStringUTFChars(arguments, NULL); + + pDocument->pClass->postUnoCommand(pDocument, pCommand, pArguments, bNotifyWhenFinished); + + pEnv->ReleaseStringUTFChars(command, pCommand); + if (arguments != NULL) + pEnv->ReleaseStringUTFChars(arguments, pArguments); +} + +extern "C" SAL_JNI_EXPORT void JNICALL Java_org_libreoffice_kit_Document_setTextSelection + (JNIEnv* pEnv, jobject aObject, jint type, jint x, jint y) +{ + LibreOfficeKitDocument* pDocument = getHandle<LibreOfficeKitDocument>(pEnv, aObject); + pDocument->pClass->setTextSelection(pDocument, type, x, y); +} + +extern "C" SAL_JNI_EXPORT jstring JNICALL Java_org_libreoffice_kit_Document_getTextSelection + (JNIEnv* pEnv, jobject aObject, jstring mimeType) +{ + LibreOfficeKitDocument* pDocument = getHandle<LibreOfficeKitDocument>(pEnv, aObject); + + const char* pMimeType = pEnv->GetStringUTFChars(mimeType, NULL); + + char* pUsedMimeType = 0; + LibreOfficeKitDocumentClass* pcls = pDocument->pClass; + char* pSelection = pcls->getTextSelection(pDocument, pMimeType, &pUsedMimeType); + free(pUsedMimeType); + + pEnv->ReleaseStringUTFChars(mimeType, pMimeType); + + return pEnv->NewStringUTF(pSelection); +} + +extern "C" SAL_JNI_EXPORT jboolean JNICALL Java_org_libreoffice_kit_Document_paste + (JNIEnv* pEnv, jobject aObject, jstring mimeType, jstring data) +{ + LibreOfficeKitDocument* pDocument = getHandle<LibreOfficeKitDocument>(pEnv, aObject); + + const char* pMimeType = pEnv->GetStringUTFChars(mimeType, NULL); + const char* pData = pEnv->GetStringUTFChars(data, NULL); + const size_t nSize = pEnv->GetStringLength(data); + + LibreOfficeKitDocumentClass* pcls = pDocument->pClass; + bool result = pcls->paste(pDocument, pMimeType, pData, nSize); + pEnv->ReleaseStringUTFChars(mimeType, pMimeType); + pEnv->ReleaseStringUTFChars(data, pData); + + return result; +} + +extern "C" SAL_JNI_EXPORT void JNICALL Java_org_libreoffice_kit_Document_setGraphicSelection + (JNIEnv* pEnv, jobject aObject, jint type, jint x, jint y) +{ + LibreOfficeKitDocument* pDocument = getHandle<LibreOfficeKitDocument>(pEnv, aObject); + pDocument->pClass->setGraphicSelection(pDocument, type, x, y); +} + +extern "C" SAL_JNI_EXPORT void JNICALL Java_org_libreoffice_kit_Document_resetSelection + (JNIEnv* pEnv, jobject aObject) +{ + LibreOfficeKitDocument* pDocument = getHandle<LibreOfficeKitDocument>(pEnv, aObject); + pDocument->pClass->resetSelection(pDocument); +} + +extern "C" SAL_JNI_EXPORT jstring JNICALL Java_org_libreoffice_kit_Document_getCommandValues + (JNIEnv* pEnv, jobject aObject, jstring command) +{ + LibreOfficeKitDocument* pDocument = getHandle<LibreOfficeKitDocument>(pEnv, aObject); + + const char* pCommand = pEnv->GetStringUTFChars(command, NULL); + + char* pValue = pDocument->pClass->getCommandValues(pDocument, pCommand); + + pEnv->ReleaseStringUTFChars(command, pCommand); + + return pEnv->NewStringUTF(pValue); +} + +extern "C" SAL_JNI_EXPORT void JNICALL Java_org_libreoffice_kit_Document_setClientZoom + (JNIEnv* pEnv, jobject aObject, jint nTilePixelWidth, jint nTilePixelHeight, jint nTileTwipWidth, jint nTileTwipHeight) +{ + LibreOfficeKitDocument* pDocument = getHandle<LibreOfficeKitDocument>(pEnv, aObject); + pDocument->pClass->setClientZoom(pDocument, nTilePixelWidth, nTilePixelHeight, nTileTwipWidth, nTileTwipHeight); + +} +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/lib/lokclipboard.cxx b/desktop/source/lib/lokclipboard.cxx new file mode 100644 index 0000000000..f7d52ba466 --- /dev/null +++ b/desktop/source/lib/lokclipboard.cxx @@ -0,0 +1,243 @@ +/* -*- 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 "lokclipboard.hxx" +#include <unordered_map> +#include <vcl/lazydelete.hxx> +#include <vcl/svapp.hxx> +#include <sfx2/lokhelper.hxx> +#include <sal/log.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <com/sun/star/uno/XComponentContext.hpp> + +using namespace css; +using namespace css::uno; + +/* static */ osl::Mutex LOKClipboardFactory::gMutex; +static vcl::DeleteOnDeinit<std::unordered_map<int, rtl::Reference<LOKClipboard>>> gClipboards{}; + +rtl::Reference<LOKClipboard> LOKClipboardFactory::getClipboardForCurView() +{ + int nViewId = SfxLokHelper::getView(); // currently active. + + osl::MutexGuard aGuard(gMutex); + + auto it = gClipboards.get()->find(nViewId); + if (it != gClipboards.get()->end()) + { + SAL_INFO("lok", "Got clip: " << it->second.get() << " from " << nViewId); + return it->second; + } + rtl::Reference<LOKClipboard> xClip(new LOKClipboard()); + (*gClipboards.get())[nViewId] = xClip; + SAL_INFO("lok", "Created clip: " << xClip.get() << " for viewId " << nViewId); + return xClip; +} + +void LOKClipboardFactory::releaseClipboardForView(int nViewId) +{ + osl::MutexGuard aGuard(gMutex); + + if (nViewId < 0) // clear all + { + gClipboards.get()->clear(); + SAL_INFO("lok", "Released all clipboards on doc destroy\n"); + } + else if (gClipboards.get()) + { + auto it = gClipboards.get()->find(nViewId); + if (it != gClipboards.get()->end()) + { + SAL_INFO("lok", "Releasing clip: " << it->second.get() << " for destroyed " << nViewId); + gClipboards.get()->erase(it); + } + } +} + +uno::Reference<uno::XInterface> + SAL_CALL LOKClipboardFactory::createInstanceWithArguments(const Sequence<Any>& /* rArgs */) +{ + return { static_cast<cppu::OWeakObject*>(getClipboardForCurView().get()) }; +} + +LOKClipboard::LOKClipboard() + : cppu::WeakComponentImplHelper<css::datatransfer::clipboard::XSystemClipboard, + css::lang::XServiceInfo>(m_aMutex) +{ + // Encourage 'paste' menu items to always show up. + uno::Reference<datatransfer::XTransferable> xTransferable(new LOKTransferable()); + setContents(xTransferable, uno::Reference<datatransfer::clipboard::XClipboardOwner>()); +} + +Sequence<OUString> LOKClipboard::getSupportedServiceNames_static() +{ + Sequence<OUString> aRet{ "com.sun.star.datatransfer.clipboard.LokClipboard" }; + return aRet; +} + +OUString LOKClipboard::getImplementationName() { return "com.sun.star.datatransfer.LOKClipboard"; } + +Sequence<OUString> LOKClipboard::getSupportedServiceNames() +{ + return getSupportedServiceNames_static(); +} + +sal_Bool LOKClipboard::supportsService(const OUString& ServiceName) +{ + return cppu::supportsService(this, ServiceName); +} + +Reference<css::datatransfer::XTransferable> LOKClipboard::getContents() { return m_xTransferable; } + +void LOKClipboard::setContents( + const Reference<css::datatransfer::XTransferable>& xTrans, + const Reference<css::datatransfer::clipboard::XClipboardOwner>& xClipboardOwner) +{ + osl::ClearableMutexGuard aGuard(m_aMutex); + Reference<datatransfer::clipboard::XClipboardOwner> xOldOwner(m_aOwner); + Reference<datatransfer::XTransferable> xOldContents(m_xTransferable); + m_xTransferable = xTrans; + m_aOwner = xClipboardOwner; + + std::vector<Reference<datatransfer::clipboard::XClipboardListener>> aListeners(m_aListeners); + datatransfer::clipboard::ClipboardEvent aEv; + aEv.Contents = m_xTransferable; + SAL_INFO("lok", "Clip: " << this << " set contents to " << m_xTransferable); + + aGuard.clear(); + + if (xOldOwner.is() && xOldOwner != xClipboardOwner) + xOldOwner->lostOwnership(this, xOldContents); + for (auto const& listener : aListeners) + { + listener->changedContents(aEv); + } +} + +void LOKClipboard::addClipboardListener( + const Reference<datatransfer::clipboard::XClipboardListener>& listener) +{ + osl::ClearableMutexGuard aGuard(m_aMutex); + m_aListeners.push_back(listener); +} + +void LOKClipboard::removeClipboardListener( + const Reference<datatransfer::clipboard::XClipboardListener>& listener) +{ + osl::ClearableMutexGuard aGuard(m_aMutex); + std::erase(m_aListeners, listener); +} +LOKTransferable::LOKTransferable(const OUString& sMimeType, + const css::uno::Sequence<sal_Int8>& aSequence) +{ + m_aContent.reserve(1); + m_aFlavors = css::uno::Sequence<css::datatransfer::DataFlavor>(1); + initFlavourFromMime(m_aFlavors.getArray()[0], sMimeType); + + uno::Any aContent; + if (m_aFlavors[0].DataType == cppu::UnoType<OUString>::get()) + { + auto pText = reinterpret_cast<const char*>(aSequence.getConstArray()); + aContent <<= OUString(pText, aSequence.getLength(), RTL_TEXTENCODING_UTF8); + } + else + aContent <<= aSequence; + m_aContent.push_back(aContent); +} + +/// Use to ensure we have some dummy content on the clipboard to allow a 1st 'paste' +LOKTransferable::LOKTransferable() +{ + m_aContent.reserve(1); + m_aFlavors = css::uno::Sequence<css::datatransfer::DataFlavor>(1); + initFlavourFromMime(m_aFlavors.getArray()[0], "text/plain"); + uno::Any aContent; + aContent <<= OUString(); + m_aContent.push_back(aContent); +} + +// cf. sot/source/base/exchange.cxx for these two exceptional types. +void LOKTransferable::initFlavourFromMime(css::datatransfer::DataFlavor& rFlavor, + OUString aMimeType) +{ + if (aMimeType.startsWith("text/plain")) + { + aMimeType = "text/plain;charset=utf-16"; + rFlavor.DataType = cppu::UnoType<OUString>::get(); + } + else if (aMimeType == "application/x-libreoffice-tsvc") + rFlavor.DataType = cppu::UnoType<OUString>::get(); + else + rFlavor.DataType = cppu::UnoType<uno::Sequence<sal_Int8>>::get(); + rFlavor.MimeType = aMimeType; + rFlavor.HumanPresentableName = aMimeType; +} + +LOKTransferable::LOKTransferable(const size_t nInCount, const char** pInMimeTypes, + const size_t* pInSizes, const char** pInStreams) +{ + m_aContent.reserve(nInCount); + m_aFlavors = css::uno::Sequence<css::datatransfer::DataFlavor>(nInCount); + auto p_aFlavors = m_aFlavors.getArray(); + for (size_t i = 0; i < nInCount; ++i) + { + initFlavourFromMime(p_aFlavors[i], OUString::fromUtf8(pInMimeTypes[i])); + + uno::Any aContent; + if (m_aFlavors[i].DataType == cppu::UnoType<OUString>::get()) + aContent <<= OUString(pInStreams[i], pInSizes[i], RTL_TEXTENCODING_UTF8); + else + aContent <<= css::uno::Sequence<sal_Int8>( + reinterpret_cast<const sal_Int8*>(pInStreams[i]), pInSizes[i]); + m_aContent.push_back(aContent); + } +} + +uno::Any SAL_CALL LOKTransferable::getTransferData(const datatransfer::DataFlavor& rFlavor) +{ + assert(m_aContent.size() == static_cast<size_t>(m_aFlavors.getLength())); + for (size_t i = 0; i < m_aContent.size(); ++i) + { + if (m_aFlavors[i].MimeType == rFlavor.MimeType) + { + if (m_aFlavors[i].DataType != rFlavor.DataType) + SAL_WARN("lok", "Horror type mismatch!"); + return m_aContent[i]; + } + } + return {}; +} + +uno::Sequence<datatransfer::DataFlavor> SAL_CALL LOKTransferable::getTransferDataFlavors() +{ + return m_aFlavors; +} + +sal_Bool SAL_CALL LOKTransferable::isDataFlavorSupported(const datatransfer::DataFlavor& rFlavor) +{ + return std::find_if(std::cbegin(m_aFlavors), std::cend(m_aFlavors), + [&rFlavor](const datatransfer::DataFlavor& i) { + return i.MimeType == rFlavor.MimeType && i.DataType == rFlavor.DataType; + }) + != std::cend(m_aFlavors); +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +desktop_LOKClipboard_get_implementation(css::uno::XComponentContext*, + css::uno::Sequence<css::uno::Any> const& /*args*/) +{ + SolarMutexGuard aGuard; + + cppu::OWeakObject* pClipboard = LOKClipboardFactory::getClipboardForCurView().get(); + + pClipboard->acquire(); + return pClipboard; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/lib/lokclipboard.hxx b/desktop/source/lib/lokclipboard.hxx new file mode 100644 index 0000000000..699830756a --- /dev/null +++ b/desktop/source/lib/lokclipboard.hxx @@ -0,0 +1,114 @@ +/* -*- 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 <vector> + +#include <rtl/ref.hxx> +#include <cppuhelper/implbase.hxx> +#include <cppuhelper/compbase.hxx> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/lang/XSingleServiceFactory.hpp> +#include <com/sun/star/datatransfer/clipboard/XSystemClipboard.hpp> + +using namespace css::uno; + +/// A clipboard implementation for LibreOfficeKit. +class LOKClipboard final + : public cppu::WeakComponentImplHelper<css::datatransfer::clipboard::XSystemClipboard, + css::lang::XServiceInfo> +{ + osl::Mutex m_aMutex; + css::uno::Reference<css::datatransfer::XTransferable> m_xTransferable; + css::uno::Reference<css::datatransfer::clipboard::XClipboardOwner> m_aOwner; + std::vector<css::uno::Reference<css::datatransfer::clipboard::XClipboardListener>> m_aListeners; + +public: + LOKClipboard(); + + /// get an XInterface easily. + css::uno::Reference<css::uno::XInterface> getXI() + { + return { static_cast<cppu::OWeakObject*>(this) }; + } + + // XServiceInfo + OUString SAL_CALL getImplementationName() override; + sal_Bool SAL_CALL supportsService(const OUString& ServiceName) override; + Sequence<OUString> SAL_CALL getSupportedServiceNames() override; + static Sequence<OUString> getSupportedServiceNames_static(); + + // XClipboard + css::uno::Reference<css::datatransfer::XTransferable> SAL_CALL getContents() override; + void SAL_CALL setContents( + const css::uno::Reference<css::datatransfer::XTransferable>& xTransferable, + const css::uno::Reference<css::datatransfer::clipboard::XClipboardOwner>& xClipboardOwner) + override; + OUString SAL_CALL getName() override { return "CLIPBOARD"; } + + // XClipboardEx + sal_Int8 SAL_CALL getRenderingCapabilities() override { return 0; } + + // XClipboardNotifier + void SAL_CALL addClipboardListener( + const css::uno::Reference<css::datatransfer::clipboard::XClipboardListener>& listener) + override; + void SAL_CALL removeClipboardListener( + const css::uno::Reference<css::datatransfer::clipboard::XClipboardListener>& listener) + override; +}; + +/// Represents the contents of LOKClipboard. +class LOKTransferable : public cppu::WeakImplHelper<css::datatransfer::XTransferable> +{ + css::uno::Sequence<css::datatransfer::DataFlavor> m_aFlavors; + std::vector<css::uno::Any> m_aContent; + + static void initFlavourFromMime(css::datatransfer::DataFlavor& rFlavor, OUString aMimeType); + +public: + LOKTransferable(); + LOKTransferable(size_t nInCount, const char** pInMimeTypes, const size_t* pInSizes, + const char** pInStreams); + LOKTransferable(const OUString& sMimeType, const css::uno::Sequence<sal_Int8>& aSequence); + + css::uno::Any SAL_CALL getTransferData(const css::datatransfer::DataFlavor& rFlavor) override; + + css::uno::Sequence<css::datatransfer::DataFlavor> SAL_CALL getTransferDataFlavors() override; + + sal_Bool SAL_CALL isDataFlavorSupported(const css::datatransfer::DataFlavor& rFlavor) override; +}; + +/// Theoretically to hook into the (horrible) vcl dtranscomp.cxx code. +class LOKClipboardFactory : public ::cppu::WeakComponentImplHelper<css::lang::XSingleServiceFactory> +{ + static osl::Mutex gMutex; + +public: + LOKClipboardFactory() + : cppu::WeakComponentImplHelper<css::lang::XSingleServiceFactory>(gMutex) + { + } + + css::uno::Reference<css::uno::XInterface> SAL_CALL createInstance() override + { + return createInstanceWithArguments(css::uno::Sequence<css::uno::Any>()); + } + css::uno::Reference<css::uno::XInterface> SAL_CALL + createInstanceWithArguments(const css::uno::Sequence<css::uno::Any>& /* rArgs */) override; + + /// Fetch clipboard from the global pool. + static rtl::Reference<LOKClipboard> getClipboardForCurView(); + + /// Release a clipboard before its document dies, nViewId of -1 clears all. + static void releaseClipboardForView(int nViewId); +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/lib/lokinteractionhandler.cxx b/desktop/source/lib/lokinteractionhandler.cxx new file mode 100644 index 0000000000..a05091cedf --- /dev/null +++ b/desktop/source/lib/lokinteractionhandler.cxx @@ -0,0 +1,457 @@ +/* -*- 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 "lokinteractionhandler.hxx" + +#include <comphelper/processfactory.hxx> +#include <cppuhelper/supportsservice.hxx> + +#include <com/sun/star/document/BrokenPackageRequest.hpp> +#include <com/sun/star/beans/NamedValue.hpp> +#include <com/sun/star/task/XInteractionAbort.hpp> +#include <com/sun/star/task/XInteractionApprove.hpp> +#include <com/sun/star/task/XInteractionPassword2.hpp> +#include <com/sun/star/task/DocumentMacroConfirmationRequest.hpp> +#include <com/sun/star/task/InteractionHandler.hpp> +#include <com/sun/star/ucb/InteractiveNetworkConnectException.hpp> +#include <com/sun/star/ucb/InteractiveNetworkOffLineException.hpp> + +#include <com/sun/star/ucb/InteractiveIOException.hpp> +#include <com/sun/star/ucb/InteractiveNetworkReadException.hpp> +#include <com/sun/star/ucb/InteractiveNetworkResolveNameException.hpp> +#include <com/sun/star/ucb/InteractiveNetworkWriteException.hpp> + +#include <com/sun/star/task/DocumentPasswordRequest2.hpp> +#include <com/sun/star/task/DocumentMSPasswordRequest2.hpp> + +#include <com/sun/star/document/FilterOptionsRequest.hpp> + +#include "../../inc/lib/init.hxx" + +#include <LibreOfficeKit/LibreOfficeKitEnums.h> +#include <sfx2/lokhelper.hxx> +#include <sfx2/viewsh.hxx> +#include <utility> +#include <vcl/svapp.hxx> + +#include <tools/json_writer.hxx> + +using namespace com::sun::star; + +LOKInteractionHandler::LOKInteractionHandler( + OString command, + desktop::LibLibreOffice_Impl *const pLOKit, + desktop::LibLODocument_Impl *const pLOKDocument) + : m_pLOKit(pLOKit) + , m_pLOKDocument(pLOKDocument) + , m_command(std::move(command)) + , m_usePassword(false) +{ + assert(m_pLOKit); +} + +LOKInteractionHandler::~LOKInteractionHandler() +{ +} + +OUString SAL_CALL LOKInteractionHandler::getImplementationName() +{ + return "com.sun.star.comp.uui.LOKInteractionHandler"; +} + +sal_Bool SAL_CALL LOKInteractionHandler::supportsService(OUString const & rServiceName) +{ + return cppu::supportsService(this, rServiceName); +} + +uno::Sequence< OUString > SAL_CALL LOKInteractionHandler::getSupportedServiceNames() +{ + return { "com.sun.star.task.InteractionHandler", + // added to indicate support for configuration.backend.MergeRecoveryRequest + "com.sun.star.configuration.backend.InteractionHandler", + // for backwards compatibility + "com.sun.star.uui.InteractionHandler" }; +} + +void SAL_CALL LOKInteractionHandler::initialize(uno::Sequence<uno::Any> const & /*rArguments*/) +{ +} + +void SAL_CALL LOKInteractionHandler::handle( + uno::Reference<task::XInteractionRequest> const & xRequest) +{ + // just do the same thing in both cases + handleInteractionRequest(xRequest); +} + +void LOKInteractionHandler::postError(css::task::InteractionClassification classif, const char* kind, ErrCode code, const OUString &message) +{ + std::string classification = "error"; + switch (classif) + { + case task::InteractionClassification_ERROR: break; + case task::InteractionClassification_WARNING: classification = "warning"; break; + case task::InteractionClassification_INFO: classification = "info"; break; + case task::InteractionClassification_QUERY: classification = "query"; break; + default: assert(false); break; + } + + // create the JSON representation + tools::JsonWriter aJson; + aJson.put("classification", classification); + aJson.put("cmd", m_command.getStr()); + aJson.put("kind", kind); + aJson.put("code", static_cast<sal_uInt32>(code)); + aJson.put("message", message.toUtf8()); + + std::size_t nView = SfxViewShell::Current() ? SfxLokHelper::getView() : 0; + if (m_pLOKDocument && m_pLOKDocument->mpCallbackFlushHandlers.count(nView)) + m_pLOKDocument->mpCallbackFlushHandlers[nView]->queue(LOK_CALLBACK_ERROR, aJson.finishAndGetAsOString()); + else if (m_pLOKit->mpCallback) + m_pLOKit->mpCallback(LOK_CALLBACK_ERROR, aJson.finishAndGetAsOString().getStr(), m_pLOKit->mpCallbackData); +} + +namespace { + +/// Just approve the interaction. +void selectApproved(uno::Sequence<uno::Reference<task::XInteractionContinuation>> const &rContinuations) +{ + for (auto const & c : rContinuations) + { + uno::Reference<task::XInteractionApprove> xApprove(c, uno::UNO_QUERY); + if (xApprove.is()) + xApprove->select(); + } +} + +} + +bool LOKInteractionHandler::handleIOException(const css::uno::Sequence<css::uno::Reference<css::task::XInteractionContinuation>> &rContinuations, const css::uno::Any& rRequest) +{ + ucb::InteractiveIOException aIoException; + if (!(rRequest >>= aIoException)) + return false; + + static ErrCode const aErrorCode[int(ucb::IOErrorCode_WRONG_VERSION) + 1] = + { + ERRCODE_IO_ABORT, + ERRCODE_IO_ACCESSDENIED, + ERRCODE_IO_ALREADYEXISTS, + ERRCODE_IO_BADCRC, + ERRCODE_IO_CANTCREATE, + ERRCODE_IO_CANTREAD, + ERRCODE_IO_CANTSEEK, + ERRCODE_IO_CANTTELL, + ERRCODE_IO_CANTWRITE, + ERRCODE_IO_CURRENTDIR, + ERRCODE_IO_DEVICENOTREADY, + ERRCODE_IO_NOTSAMEDEVICE, + ERRCODE_IO_GENERAL, + ERRCODE_IO_INVALIDACCESS, + ERRCODE_IO_INVALIDCHAR, + ERRCODE_IO_INVALIDDEVICE, + ERRCODE_IO_INVALIDLENGTH, + ERRCODE_IO_INVALIDPARAMETER, + ERRCODE_IO_ISWILDCARD, + ERRCODE_IO_LOCKVIOLATION, + ERRCODE_IO_MISPLACEDCHAR, + ERRCODE_IO_NAMETOOLONG, + ERRCODE_IO_NOTEXISTS, + ERRCODE_IO_NOTEXISTSPATH, + ERRCODE_IO_NOTSUPPORTED, + ERRCODE_IO_NOTADIRECTORY, + ERRCODE_IO_NOTAFILE, + ERRCODE_IO_OUTOFSPACE, + ERRCODE_IO_TOOMANYOPENFILES, + ERRCODE_IO_OUTOFMEMORY, + ERRCODE_IO_PENDING, + ERRCODE_IO_RECURSIVE, + ERRCODE_IO_UNKNOWN, + ERRCODE_IO_WRITEPROTECTED, + ERRCODE_IO_WRONGFORMAT, + ERRCODE_IO_WRONGVERSION, + }; + + postError(aIoException.Classification, "io", aErrorCode[static_cast<int>(aIoException.Code)], ""); + selectApproved(rContinuations); + + return true; +} + +bool LOKInteractionHandler::handleNetworkException(const uno::Sequence<uno::Reference<task::XInteractionContinuation>> &rContinuations, const uno::Any &rRequest) +{ + ucb::InteractiveNetworkException aNetworkException; + if (!(rRequest >>= aNetworkException)) + return false; + + ErrCode nErrorCode; + OUString aMessage; + + ucb::InteractiveNetworkOffLineException aOffLineException; + ucb::InteractiveNetworkResolveNameException aResolveNameException; + ucb::InteractiveNetworkConnectException aConnectException; + ucb::InteractiveNetworkReadException aReadException; + ucb::InteractiveNetworkWriteException aWriteException; + if (rRequest >>= aOffLineException) + { + nErrorCode = ERRCODE_INET_OFFLINE; + } + else if (rRequest >>= aResolveNameException) + { + nErrorCode = ERRCODE_INET_NAME_RESOLVE; + aMessage = aResolveNameException.Server; + } + else if (rRequest >>= aConnectException) + { + nErrorCode = ERRCODE_INET_CONNECT; + aMessage = aConnectException.Server; + } + else if (rRequest >>= aReadException) + { + nErrorCode = ERRCODE_INET_READ; + aMessage = aReadException.Diagnostic; + } + else if (rRequest >>= aWriteException) + { + nErrorCode = ERRCODE_INET_WRITE; + aMessage = aWriteException.Diagnostic; + } + else + { + nErrorCode = ERRCODE_INET_GENERAL; + } + + postError(aNetworkException.Classification, "network", nErrorCode, aMessage); + selectApproved(rContinuations); + + return true; +} + +bool LOKInteractionHandler::handlePasswordRequest(const uno::Sequence<uno::Reference<task::XInteractionContinuation>> &rContinuations, const uno::Any &rRequest) +{ + bool bPasswordRequestFound = false; + bool bIsRequestPasswordToModify = false; + + OString sUrl; + + task::DocumentPasswordRequest passwordRequest; + if (rRequest >>= passwordRequest) + { + bIsRequestPasswordToModify = false; + sUrl = passwordRequest.Name.toUtf8(); + bPasswordRequestFound = true; + } + + task::DocumentPasswordRequest2 passwordRequest2; + if (rRequest >>= passwordRequest2) + { + bIsRequestPasswordToModify = passwordRequest2.IsRequestPasswordToModify; + sUrl = passwordRequest2.Name.toUtf8(); + bPasswordRequestFound = true; + } + + task::DocumentMSPasswordRequest2 passwordMSRequest; + if (rRequest >>= passwordMSRequest) + { + bIsRequestPasswordToModify = passwordMSRequest.IsRequestPasswordToModify; + sUrl = passwordMSRequest.Name.toUtf8(); + bPasswordRequestFound = true; + } + + if (!bPasswordRequestFound) + return false; + + if (m_pLOKit->mpCallback && + m_pLOKit->hasOptionalFeature(bIsRequestPasswordToModify ? LOK_FEATURE_DOCUMENT_PASSWORD_TO_MODIFY + : LOK_FEATURE_DOCUMENT_PASSWORD)) + { + // release SolarMutex, so the callback handler, which may run in another thread, + // can acquire it in 'lo_setDocumentPassword' + SolarMutexReleaser aReleaser; + m_pLOKit->mpCallback(bIsRequestPasswordToModify ? LOK_CALLBACK_DOCUMENT_PASSWORD_TO_MODIFY + : LOK_CALLBACK_DOCUMENT_PASSWORD, + sUrl.getStr(), + m_pLOKit->mpCallbackData); + + // block until SetPassword is called + m_havePassword.wait(); + m_havePassword.reset(); + } + + for (auto const & cont : rContinuations) + { + if (m_usePassword) + { + if (bIsRequestPasswordToModify) + { + uno::Reference<task::XInteractionPassword2> const xIPW2(cont, uno::UNO_QUERY); + xIPW2->setPasswordToModify(m_Password); + xIPW2->select(); + } + else + { + uno::Reference<task::XInteractionPassword> const xIPW(cont, uno::UNO_QUERY); + if (xIPW.is()) + { + xIPW->setPassword(m_Password); + xIPW->select(); + } + } + } + else + { + if (bIsRequestPasswordToModify) + { + uno::Reference<task::XInteractionPassword2> const xIPW2(cont, uno::UNO_QUERY); + xIPW2->setRecommendReadOnly(true); + xIPW2->select(); + } + else + { + uno::Reference<task::XInteractionAbort> const xAbort(cont, uno::UNO_QUERY); + if (xAbort.is()) + { + xAbort->select(); + } + } + } + } + return true; +} + +bool LOKInteractionHandler::handleMacroConfirmationRequest(const uno::Reference<task::XInteractionRequest>& xRequest) +{ + uno::Any const request(xRequest->getRequest()); + + task::DocumentMacroConfirmationRequest aConfirmRequest; + if (request >>= aConfirmRequest) + { + auto xInteraction(task::InteractionHandler::createWithParent(comphelper::getProcessComponentContext(), nullptr)); + + if (xInteraction.is()) + xInteraction->handleInteractionRequest(xRequest); + + return true; + } + return false; +} + +bool LOKInteractionHandler::handlePackageReparationRequest(const uno::Reference<task::XInteractionRequest>& xRequest) +{ + uno::Any const request(xRequest->getRequest()); + + document::BrokenPackageRequest aBrokenPackageRequest; + if (request >>= aBrokenPackageRequest) + { + auto xInteraction(task::InteractionHandler::createWithParent(comphelper::getProcessComponentContext(), nullptr)); + + if (xInteraction.is()) + xInteraction->handleInteractionRequest(xRequest); + + return true; + } + return false; +} + +bool LOKInteractionHandler::handleLoadReadOnlyRequest(const uno::Reference<task::XInteractionRequest>& xRequest) +{ + uno::Any const request(xRequest->getRequest()); + + OUString aFileName; + beans::NamedValue aLoadReadOnlyRequest; + if ((request >>= aLoadReadOnlyRequest) && + aLoadReadOnlyRequest.Name == "LoadReadOnlyRequest" && + (aLoadReadOnlyRequest.Value >>= aFileName)) + { + auto xInteraction(task::InteractionHandler::createWithParent(comphelper::getProcessComponentContext(), nullptr)); + + if (xInteraction.is()) + xInteraction->handleInteractionRequest(xRequest); + + return true; + } + return false; +} + +bool LOKInteractionHandler::handleFilterOptionsRequest(const uno::Reference<task::XInteractionRequest>& xRequest) +{ + document::FilterOptionsRequest aFilterOptionsRequest; + uno::Any const request(xRequest->getRequest()); + if (request >>= aFilterOptionsRequest) + { + uno::Reference< task::XInteractionHandler2 > xInteraction( + task::InteractionHandler::createWithParent( + ::comphelper::getProcessComponentContext(), nullptr)); + + if (xInteraction.is()) + xInteraction->handleInteractionRequest(xRequest); + + return true; + } + return false; +} + +sal_Bool SAL_CALL LOKInteractionHandler::handleInteractionRequest( + const uno::Reference<task::XInteractionRequest>& xRequest) +{ + uno::Sequence<uno::Reference<task::XInteractionContinuation>> const &rContinuations = xRequest->getContinuations(); + uno::Any const request(xRequest->getRequest()); + + if (handleIOException(rContinuations, request)) + return true; + + if (handleNetworkException(rContinuations, request)) + return true; + + if (handlePasswordRequest(rContinuations, request)) + return true; + + if (handleFilterOptionsRequest(xRequest)) + return true; + + if (handleMacroConfirmationRequest(xRequest)) + return true; + + if (handlePackageReparationRequest(xRequest)) + return true; + + if (handleLoadReadOnlyRequest(xRequest)) + return true; + + // TODO: perform more interactions 'for real' like the above + selectApproved(rContinuations); + + return true; +} + +void LOKInteractionHandler::SetPassword(char const*const pPassword) +{ + if (pPassword) + { + m_Password = OUString(pPassword, strlen(pPassword), RTL_TEXTENCODING_UTF8); + m_usePassword = true; + } + else + { + m_usePassword = false; + } + m_havePassword.set(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/lib/lokinteractionhandler.hxx b/desktop/source/lib/lokinteractionhandler.hxx new file mode 100644 index 0000000000..c3641db076 --- /dev/null +++ b/desktop/source/lib/lokinteractionhandler.hxx @@ -0,0 +1,107 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#pragma once + +#include <osl/conditn.hxx> +#include <cppuhelper/implbase.hxx> +#include <comphelper/errcode.hxx> + +#include <com/sun/star/lang/XInitialization.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/task/InteractionClassification.hpp> +#include <com/sun/star/task/XInteractionHandler2.hpp> + +namespace desktop { + struct LibLibreOffice_Impl; + struct LibLODocument_Impl; +} + +/** InteractionHandler is an interface that provides the user with various dialogs / error messages. + +We need an own implementation for the LibreOfficeKit so that we can route the +information easily via callbacks. + +TODO: the callbacks are not implemented yet, we just approve any interaction +that we get. +*/ +class LOKInteractionHandler: public cppu::WeakImplHelper<com::sun::star::lang::XServiceInfo, + com::sun::star::lang::XInitialization, + com::sun::star::task::XInteractionHandler2> +{ +private: + desktop::LibLibreOffice_Impl * m_pLOKit; + desktop::LibLODocument_Impl * m_pLOKDocument; + + /// Command for which we use this interaction handler (like "load", "save", "saveas", ...) + OString m_command; + + OUString m_Password; + bool m_usePassword; + osl::Condition m_havePassword; + + LOKInteractionHandler(const LOKInteractionHandler&) = delete; + LOKInteractionHandler& operator=(const LOKInteractionHandler&) = delete; + + /** Call the LOK_CALLBACK_ERROR on the LOK document (if available) or LOK lib. + + The error itself is a JSON message, like: + { + "classification": "error" | "warning" | "info" + "kind": "network" etc. + "code": 403 | 404 | ... + "message": freeform description + } + */ + void postError(css::task::InteractionClassification classif, const char* kind, ErrCode code, const OUString &message); + + bool handleIOException(const css::uno::Sequence<css::uno::Reference<css::task::XInteractionContinuation>> &rContinuations, const css::uno::Any& rRequest); + bool handleNetworkException(const css::uno::Sequence<css::uno::Reference<css::task::XInteractionContinuation>> &rContinuations, const css::uno::Any& rRequest); + bool handlePasswordRequest(const css::uno::Sequence<css::uno::Reference<css::task::XInteractionContinuation>> &rContinuations, const css::uno::Any& rRequest); + static bool handleMacroConfirmationRequest(const css::uno::Reference<css::task::XInteractionRequest>& xRequest); + + static bool handleFilterOptionsRequest(const ::com::sun::star::uno::Reference<::com::sun::star::task::XInteractionRequest>& Request); + static bool handlePackageReparationRequest(const css::uno::Reference<css::task::XInteractionRequest>& xRequest); + + static bool handleLoadReadOnlyRequest(const css::uno::Reference<css::task::XInteractionRequest>& xRequest); + +public: + void SetPassword(char const* pPassword); + + explicit LOKInteractionHandler( + OString command, + desktop::LibLibreOffice_Impl *, + desktop::LibLODocument_Impl *pLOKDocumt = nullptr); + + virtual ~LOKInteractionHandler() override; + + virtual OUString SAL_CALL getImplementationName() override; + + virtual sal_Bool SAL_CALL supportsService(OUString const & rServiceName) override; + + virtual com::sun::star::uno::Sequence<OUString> SAL_CALL getSupportedServiceNames() override; + + virtual void SAL_CALL initialize(com::sun::star::uno::Sequence<com::sun::star::uno::Any > const & rArguments) override; + + virtual void SAL_CALL handle(com::sun::star::uno::Reference<com::sun::star::task::XInteractionRequest> const & rRequest) override; + + virtual sal_Bool SAL_CALL handleInteractionRequest(const ::com::sun::star::uno::Reference<::com::sun::star::task::XInteractionRequest>& Request) override; +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/migration/migration.cxx b/desktop/source/migration/migration.cxx new file mode 100644 index 0000000000..b0728de61a --- /dev/null +++ b/desktop/source/migration/migration.cxx @@ -0,0 +1,1217 @@ +/* -*- 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 <algorithm> +#include <iterator> +#include <map> +#include <set> + +#include <migration.hxx> +#include "migration_impl.hxx" + +#include <sal/log.hxx> +#include <unotools/textsearch.hxx> +#include <comphelper/processfactory.hxx> +#include <comphelper/sequence.hxx> +#include <unotools/bootstrap.hxx> +#include <rtl/uri.hxx> +#include <i18nlangtag/lang.h> +#include <comphelper/diagnose_ex.hxx> +#include <tools/urlobj.hxx> +#include <officecfg/Office/UI.hxx> +#include <osl/file.hxx> +#include <osl/security.hxx> +#include <unotools/configmgr.hxx> + +#include <com/sun/star/configuration/Update.hpp> +#include <com/sun/star/configuration/theDefaultProvider.hpp> +#include <com/sun/star/container/XNameContainer.hpp> +#include <com/sun/star/task/XJob.hpp> +#include <com/sun/star/beans/NamedValue.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/util/XRefreshable.hpp> +#include <com/sun/star/util/XChangesBatch.hpp> +#include <com/sun/star/embed/ElementModes.hpp> +#include <com/sun/star/embed/FileSystemStorageFactory.hpp> +#include <com/sun/star/embed/XStorage.hpp> +#include <com/sun/star/ui/theModuleUIConfigurationManagerSupplier.hpp> +#include <com/sun/star/ui/UIConfigurationManager.hpp> +#include <com/sun/star/ui/XUIConfigurationPersistence.hpp> +#include <vcl/commandinfoprovider.hxx> + +using namespace osl; +using namespace com::sun::star::task; +using namespace com::sun::star::lang; +using namespace com::sun::star::beans; +using namespace com::sun::star::util; +using namespace com::sun::star::container; +using com::sun::star::uno::Exception; +using namespace com::sun::star; + + +namespace desktop +{ + +constexpr OUString ITEM_DESCRIPTOR_COMMANDURL = u"CommandURL"_ustr; +constexpr OUString ITEM_DESCRIPTOR_CONTAINER = u"ItemDescriptorContainer"_ustr; +constexpr OUString ITEM_DESCRIPTOR_LABEL = u"Label"_ustr; + +static OUString mapModuleShortNameToIdentifier(std::u16string_view sShortName) +{ + OUString sIdentifier; + + if ( sShortName == u"StartModule" ) + sIdentifier = "com.sun.star.frame.StartModule"; + + else if ( sShortName == u"swriter" ) + sIdentifier = "com.sun.star.text.TextDocument"; + + else if ( sShortName == u"scalc" ) + sIdentifier = "com.sun.star.sheet.SpreadsheetDocument"; + + else if ( sShortName == u"sdraw" ) + sIdentifier = "com.sun.star.drawing.DrawingDocument"; + + else if ( sShortName == u"simpress" ) + sIdentifier = "com.sun.star.presentation.PresentationDocument"; + + else if ( sShortName == u"smath" ) + sIdentifier = "com.sun.star.formula.FormulaProperties"; + + else if ( sShortName == u"schart" ) + sIdentifier = "com.sun.star.chart2.ChartDocument"; + + else if ( sShortName == u"BasicIDE" ) + sIdentifier = "com.sun.star.script.BasicIDE"; + + else if ( sShortName == u"dbapp" ) + sIdentifier = "com.sun.star.sdb.OfficeDatabaseDocument"; + + else if ( sShortName == u"sglobal" ) + sIdentifier = "com.sun.star.text.GlobalDocument"; + + else if ( sShortName == u"sweb" ) + sIdentifier = "com.sun.star.text.WebDocument"; + + else if ( sShortName == u"swxform" ) + sIdentifier = "com.sun.star.xforms.XMLFormDocument"; + + else if ( sShortName == u"sbibliography" ) + sIdentifier = "com.sun.star.frame.Bibliography"; + + return sIdentifier; +} + +bool MigrationImpl::alreadyMigrated() +{ + OUString aStr = m_aInfo.userdata + "/MIGRATED4"; + File aFile(aStr); + // create migration stamp, and/or check its existence + bool bRet = aFile.open (osl_File_OpenFlag_Write | osl_File_OpenFlag_Create | osl_File_OpenFlag_NoLock) == FileBase::E_EXIST; + SAL_INFO( "desktop.migration", "File '" << aStr << "' exists? " << bRet ); + return bRet; +} + +bool MigrationImpl::initializeMigration() +{ + bool bRet = false; + + if (!checkMigrationCompleted()) { + readAvailableMigrations(m_vMigrationsAvailable); + sal_Int32 nIndex = findPreferredMigrationProcess(m_vMigrationsAvailable); + // m_aInfo is now set to the preferred migration source + if ( nIndex >= 0 ) { + if (alreadyMigrated()) + return false; + m_vrMigrations = readMigrationSteps(m_vMigrationsAvailable[nIndex].name); + } + + bRet = !m_aInfo.userdata.isEmpty(); + } + + SAL_INFO( "desktop.migration", "Migration " << ( bRet ? "needed" : "not required" ) ); + + return bRet; +} + +void Migration::migrateSettingsIfNecessary() +{ + MigrationImpl aImpl; + + if (! aImpl.initializeMigration() ) + return; + + bool bResult = false; + try { + bResult = aImpl.doMigration(); + } catch (const Exception&) { + TOOLS_WARN_EXCEPTION( "desktop", "doMigration()"); + } + OSL_ENSURE(bResult, "Migration has not been successful"); +} + +MigrationImpl::MigrationImpl() +{ +} + +MigrationImpl::~MigrationImpl() +{ +} + +// The main entry point for migrating settings +bool MigrationImpl::doMigration() +{ + // compile file list for migration + m_vrFileList = compileFileList(); + + bool result = false; + try { + NewVersionUIInfo aNewVersionUIInfo; + std::vector< MigrationModuleInfo > vModulesInfo = detectUIChangesForAllModules(); + aNewVersionUIInfo.init(vModulesInfo); + + copyFiles(); + + static constexpr OUString sMenubarResourceURL(u"private:resource/menubar/menubar"_ustr); + static constexpr OUStringLiteral sToolbarResourcePre(u"private:resource/toolbar/"); + for (MigrationModuleInfo & i : vModulesInfo) { + OUString sModuleIdentifier = mapModuleShortNameToIdentifier(i.sModuleShortName); + if (sModuleIdentifier.isEmpty()) + continue; + + + OUString aOldCfgDataPath = m_aInfo.userdata + "/user/config/soffice.cfg/modules/" + i.sModuleShortName; + uno::Sequence< uno::Any > lArgs {uno::Any(aOldCfgDataPath), uno::Any(embed::ElementModes::READ)}; + + uno::Reference< uno::XComponentContext > xContext(comphelper::getProcessComponentContext()); + uno::Reference< lang::XSingleServiceFactory > xStorageFactory(embed::FileSystemStorageFactory::create(xContext)); + uno::Reference< embed::XStorage > xModules(xStorageFactory->createInstanceWithArguments(lArgs), uno::UNO_QUERY); + uno::Reference< ui::XUIConfigurationManager2 > xOldCfgManager = ui::UIConfigurationManager::create(xContext); + + if ( xModules.is() ) { + xOldCfgManager->setStorage( xModules ); + xOldCfgManager->reload(); + } + + uno::Reference< ui::XUIConfigurationManager > xCfgManager = aNewVersionUIInfo.getConfigManager(i.sModuleShortName); + + if (i.bHasMenubar) { + uno::Reference< container::XIndexContainer > xOldVersionMenuSettings(xOldCfgManager->getSettings(sMenubarResourceURL, true), uno::UNO_QUERY); + uno::Reference< container::XIndexContainer > xNewVersionMenuSettings = aNewVersionUIInfo.getNewMenubarSettings(i.sModuleShortName); + compareOldAndNewConfig(OUString(), xOldVersionMenuSettings, xNewVersionMenuSettings, sMenubarResourceURL); + mergeOldToNewVersion(xCfgManager, xNewVersionMenuSettings, sModuleIdentifier, sMenubarResourceURL); + } + + sal_Int32 nToolbars = i.m_vToolbars.size(); + if (nToolbars >0) { + for (sal_Int32 j=0; j<nToolbars; ++j) { + OUString sToolbarName = i.m_vToolbars[j]; + OUString sToolbarResourceURL = sToolbarResourcePre + sToolbarName; + + uno::Reference< container::XIndexContainer > xOldVersionToolbarSettings(xOldCfgManager->getSettings(sToolbarResourceURL, true), uno::UNO_QUERY); + uno::Reference< container::XIndexContainer > xNewVersionToolbarSettings = aNewVersionUIInfo.getNewToolbarSettings(i.sModuleShortName, sToolbarName); + compareOldAndNewConfig(OUString(), xOldVersionToolbarSettings, xNewVersionToolbarSettings, sToolbarResourceURL); + mergeOldToNewVersion(xCfgManager, xNewVersionToolbarSettings, sModuleIdentifier, sToolbarResourceURL); + } + } + + m_aOldVersionItemsHashMap.clear(); + } + + // execute the migration items from Setup.xcu + copyConfig(); + + // execute custom migration services from Setup.xcu + // and refresh the cache + runServices(); + uno::Reference< XRefreshable >( + configuration::theDefaultProvider::get(comphelper::getProcessComponentContext()), + uno::UNO_QUERY_THROW)->refresh(); + + result = true; + } catch (const css::uno::Exception &) { + TOOLS_WARN_EXCEPTION( + "desktop.migration", + "ignored Exception while migrating from version \"" << m_aInfo.productname + << "\" data \"" << m_aInfo.userdata << "\""); + } + + // prevent running the migration multiple times + setMigrationCompleted(); + return result; +} + +void MigrationImpl::setMigrationCompleted() +{ + try { + uno::Reference< XPropertySet > aPropertySet(getConfigAccess("org.openoffice.Setup/Office", true), uno::UNO_QUERY_THROW); + aPropertySet->setPropertyValue("MigrationCompleted", uno::Any(true)); + uno::Reference< XChangesBatch >(aPropertySet, uno::UNO_QUERY_THROW)->commitChanges(); + } catch (...) { + // fail silently + } +} + +bool MigrationImpl::checkMigrationCompleted() +{ + bool bMigrationCompleted = false; + try { + uno::Reference< XPropertySet > aPropertySet( + getConfigAccess("org.openoffice.Setup/Office"), uno::UNO_QUERY_THROW); + aPropertySet->getPropertyValue("MigrationCompleted") >>= bMigrationCompleted; + + if( !bMigrationCompleted && getenv("SAL_DISABLE_USERMIGRATION" ) ) { + // migration prevented - fake its success + setMigrationCompleted(); + bMigrationCompleted = true; + } + } catch (const Exception&) { + // just return false... + } + SAL_INFO( "desktop.migration", "Migration " << ( bMigrationCompleted ? "already completed" : "not done" ) ); + + return bMigrationCompleted; +} + +static void insertSorted(migrations_available& rAvailableMigrations, supported_migration const & aSupportedMigration) +{ + migrations_available::iterator pIter = std::find_if(rAvailableMigrations.begin(), rAvailableMigrations.end(), + [&aSupportedMigration](const supported_migration& rMigration) { return rMigration.nPriority < aSupportedMigration.nPriority; }); + if (pIter != rAvailableMigrations.end()) + rAvailableMigrations.insert(pIter, aSupportedMigration ); + else + rAvailableMigrations.push_back( aSupportedMigration ); +} + +void MigrationImpl::readAvailableMigrations(migrations_available& rAvailableMigrations) +{ + // get supported version names + uno::Reference< XNameAccess > aMigrationAccess(getConfigAccess("org.openoffice.Setup/Migration/SupportedVersions"), uno::UNO_SET_THROW); + const uno::Sequence< OUString > seqSupportedVersions = aMigrationAccess->getElementNames(); + + static constexpr OUStringLiteral aVersionIdentifiers( u"VersionIdentifiers" ); + static constexpr OUStringLiteral aPriorityIdentifier( u"Priority" ); + + for (OUString const & supportedVersion :seqSupportedVersions) { + sal_Int32 nPriority( 0 ); + uno::Sequence< OUString > seqVersions; + uno::Reference< XNameAccess > xMigrationData( aMigrationAccess->getByName(supportedVersion), uno::UNO_QUERY_THROW ); + xMigrationData->getByName( aVersionIdentifiers ) >>= seqVersions; + xMigrationData->getByName( aPriorityIdentifier ) >>= nPriority; + + supported_migration aSupportedMigration; + aSupportedMigration.name = supportedVersion; + aSupportedMigration.nPriority = nPriority; + for (OUString const & s : std::as_const(seqVersions)) + aSupportedMigration.supported_versions.push_back(s.trim()); + insertSorted( rAvailableMigrations, aSupportedMigration ); + SAL_INFO( "desktop.migration", " available migration '" << aSupportedMigration.name << "'" ); + } +} + +migrations_vr MigrationImpl::readMigrationSteps(const OUString& rMigrationName) +{ + // get migration access + uno::Reference< XNameAccess > aMigrationAccess(getConfigAccess("org.openoffice.Setup/Migration/SupportedVersions"), uno::UNO_SET_THROW); + uno::Reference< XNameAccess > xMigrationData( aMigrationAccess->getByName(rMigrationName), uno::UNO_QUERY_THROW ); + + // get migration description from org.openoffice.Setup/Migration + // and build vector of migration steps + uno::Reference< XNameAccess > theNameAccess(xMigrationData->getByName("MigrationSteps"), uno::UNO_QUERY_THROW); + uno::Reference< XNameAccess > tmpAccess; + uno::Sequence< OUString > tmpSeq; + migrations_vr vrMigrations(new migrations_v); + const css::uno::Sequence<OUString> aMigrationSteps = theNameAccess->getElementNames(); + for (const OUString& rMigrationStep : aMigrationSteps) { + // get current migration step + theNameAccess->getByName(rMigrationStep) >>= tmpAccess; + migration_step tmpStep; + + // read included files from current step description + if (tmpAccess->getByName("IncludedFiles") >>= tmpSeq) { + for (const OUString& rSeqEntry : std::as_const(tmpSeq)) + tmpStep.includeFiles.push_back(rSeqEntry); + } + + // excluded files... + if (tmpAccess->getByName("ExcludedFiles") >>= tmpSeq) { + for (const OUString& rSeqEntry : std::as_const(tmpSeq)) + tmpStep.excludeFiles.push_back(rSeqEntry); + } + + // included nodes... + if (tmpAccess->getByName("IncludedNodes") >>= tmpSeq) { + for (const OUString& rSeqEntry : std::as_const(tmpSeq)) + tmpStep.includeConfig.push_back(rSeqEntry); + } + + // excluded nodes... + if (tmpAccess->getByName("ExcludedNodes") >>= tmpSeq) { + for (const OUString& rSeqEntry : std::as_const(tmpSeq)) + tmpStep.excludeConfig.push_back(rSeqEntry); + } + + // excluded extensions... + if (tmpAccess->getByName("ExcludedExtensions") >>= tmpSeq) { + for (const OUString& rSeqEntry : std::as_const(tmpSeq)) + tmpStep.excludeExtensions.push_back(rSeqEntry); + } + + // generic service + tmpAccess->getByName("MigrationService") >>= tmpStep.service; + + vrMigrations->push_back(tmpStep); + } + return vrMigrations; +} + +static FileBase::RC _checkAndCreateDirectory(INetURLObject const & dirURL) +{ + FileBase::RC result = Directory::create(dirURL.GetMainURL(INetURLObject::DecodeMechanism::ToIUri)); + if (result == FileBase::E_NOENT) { + INetURLObject baseURL(dirURL); + baseURL.removeSegment(); + _checkAndCreateDirectory(baseURL); + return Directory::create(dirURL.GetMainURL(INetURLObject::DecodeMechanism::ToIUri)); + } else + return result; +} + +#if defined UNX && ! defined MACOSX + +const char XDG_CONFIG_PART[] = "/.config/"; + +OUString MigrationImpl::preXDGConfigDir(const OUString& rConfigDir) +{ + OUString aPreXDGConfigPath; + const char* pXDGCfgHome = getenv("XDG_CONFIG_HOME"); + + // cater for XDG_CONFIG_HOME change + // If XDG_CONFIG_HOME is set then we; + // assume the user knows what they are doing ( room for improvement here, we could + // of course search the default config dir etc. also - but this is more complex, + // we would need to weigh results from the current config dir against matches in + // the 'old' config dir etc. ) - currently we just use the returned config dir. + // If XDG_CONFIG_HOME is NOT set; + // assume then we should now using the default $HOME/.config config location for + // our user profiles, however *all* previous libreoffice and openoffice.org + // configurations will be in the 'old' config directory and that's where we need + // to search - we convert the returned config dir to the 'old' dir + if ( !pXDGCfgHome && rConfigDir.endsWith( XDG_CONFIG_PART ) ) + // remove trailing '.config/' but leave the terminating '/' + aPreXDGConfigPath = rConfigDir.copy( 0, rConfigDir.getLength() - sizeof( XDG_CONFIG_PART ) + 2 ); + else + aPreXDGConfigPath = rConfigDir; + + // the application-specific config dir is no longer prefixed by '.' because it is hidden under ".config" + // we have to add the '.' for the pre-XDG directory names + aPreXDGConfigPath += "."; + + return aPreXDGConfigPath; +} +#endif + +void MigrationImpl::setInstallInfoIfExist( + install_info& aInfo, + std::u16string_view rConfigDir, + const OUString& rVersion) +{ + OUString url(INetURLObject(rConfigDir).GetMainURL(INetURLObject::DecodeMechanism::NONE)); + osl::DirectoryItem item; + osl::FileStatus stat(osl_FileStatus_Mask_Type); + + if (osl::DirectoryItem::get(url, item) == osl::FileBase::E_None + && item.getFileStatus(stat) == osl::FileBase::E_None + && stat.getFileType() == osl::FileStatus::Directory) { + aInfo.userdata = url; + aInfo.productname = rVersion; + } +} + +install_info MigrationImpl::findInstallation(const strings_v& rVersions) +{ + + OUString aTopConfigDir; + osl::Security().getConfigDir( aTopConfigDir ); + if ( !aTopConfigDir.isEmpty() && aTopConfigDir[ aTopConfigDir.getLength()-1 ] != '/' ) + aTopConfigDir += "/"; + +#if defined UNX && ! defined MACOSX + OUString aPreXDGTopConfigDir = preXDGConfigDir(aTopConfigDir); +#endif + + install_info aInfo; + for (auto const& elem : rVersions) + { + OUString aVersion, aProfileName; + sal_Int32 nSeparatorIndex = elem.indexOf('='); + if ( nSeparatorIndex != -1 ) { + aVersion = elem.copy( 0, nSeparatorIndex ); + aProfileName = elem.copy( nSeparatorIndex+1 ); + } + + if ( !aVersion.isEmpty() && !aProfileName.isEmpty() && + ( aInfo.userdata.isEmpty() || + aProfileName.equalsIgnoreAsciiCase( + utl::ConfigManager::getProductName() ) ) ) { + setInstallInfoIfExist(aInfo, Concat2View(aTopConfigDir + aProfileName), aVersion); +#if defined UNX && ! defined MACOSX + //try preXDG path if the new one does not exist + if ( aInfo.userdata.isEmpty()) + setInstallInfoIfExist(aInfo, Concat2View(aPreXDGTopConfigDir + aProfileName), aVersion); +#endif + } + } + + return aInfo; +} + +sal_Int32 MigrationImpl::findPreferredMigrationProcess(const migrations_available& rAvailableMigrations) +{ + sal_Int32 nIndex( -1 ); + sal_Int32 i( 0 ); + + for (auto const& availableMigration : rAvailableMigrations) + { + install_info aInstallInfo = findInstallation(availableMigration.supported_versions); + if (!aInstallInfo.productname.isEmpty() ) { + m_aInfo = aInstallInfo; + nIndex = i; + break; + } + ++i; + } + + SAL_INFO( "desktop.migration", " preferred migration is from product '" << m_aInfo.productname << "'"); + SAL_INFO( "desktop.migration", " and settings directory '" << m_aInfo.userdata << "'"); + + return nIndex; +} + +strings_vr MigrationImpl::applyPatterns(const strings_v& vSet, const strings_v& vPatterns) +{ + using namespace utl; + strings_vr vrResult(new strings_v); + for (auto const& pattern : vPatterns) + { + // find matches for this pattern in input set + // and copy them to the result + SearchParam param(pattern, SearchParam::SearchType::Regexp); + TextSearch ts(param, LANGUAGE_DONTKNOW); + sal_Int32 start = 0; + sal_Int32 end = 0; + for (auto const& elem : vSet) + { + end = elem.getLength(); + if (ts.SearchForward(elem, &start, &end)) + vrResult->push_back(elem); + } + } + return vrResult; +} + +strings_vr MigrationImpl::getAllFiles(const OUString& baseURL) const +{ + strings_vr vrResult(new strings_v); + + // get sub dirs + Directory dir(baseURL); + if (dir.open() == FileBase::E_None) { + strings_v vSubDirs; + strings_vr vrSubResult; + + // work through directory contents... + DirectoryItem item; + FileStatus fs(osl_FileStatus_Mask_Type | osl_FileStatus_Mask_FileURL); + while (dir.getNextItem(item) == FileBase::E_None) { + if (item.getFileStatus(fs) == FileBase::E_None) { + if (fs.getFileType() == FileStatus::Directory) + vSubDirs.push_back(fs.getFileURL()); + else + vrResult->push_back(fs.getFileURL()); + } + } + + // recurse subfolders + for (auto const& subDir : vSubDirs) + { + vrSubResult = getAllFiles(subDir); + vrResult->insert(vrResult->end(), vrSubResult->begin(), vrSubResult->end()); + } + } + return vrResult; +} + +namespace +{ + +// removes elements of vector 2 in vector 1 +strings_v subtract(strings_v && a, strings_v && b) +{ + std::sort(a.begin(), a.end()); + strings_v::iterator ae(std::unique(a.begin(), a.end())); + std::sort(b.begin(), b.end()); + strings_v::iterator be(std::unique(b.begin(), b.end())); + strings_v c; + std::set_difference(a.begin(), ae, b.begin(), be, std::back_inserter(c)); + return c; +} + +} + +strings_vr MigrationImpl::compileFileList() +{ + + strings_vr vrResult(new strings_v); + + // get a list of all files: + strings_vr vrFiles = getAllFiles(m_aInfo.userdata); + + // get a file list result for each migration step + for (auto const& rMigration : *m_vrMigrations) + { + strings_vr vrInclude = applyPatterns(*vrFiles, rMigration.includeFiles); + strings_vr vrExclude = applyPatterns(*vrFiles, rMigration.excludeFiles); + strings_v sub(subtract(std::move(*vrInclude), std::move(*vrExclude))); + vrResult->insert(vrResult->end(), sub.begin(), sub.end()); + } + return vrResult; +} + +namespace +{ + +struct componentParts { + std::set< OUString > includedPaths; + std::set< OUString > excludedPaths; +}; + +typedef std::map< OUString, componentParts > Components; + +bool getComponent(OUString const & path, OUString * component) +{ + OSL_ASSERT(component != nullptr); + if (path.isEmpty() || path[0] != '/') { + SAL_INFO( "desktop.migration", "configuration migration in/exclude path " << path << " ignored (does not start with slash)" ); + return false; + } + sal_Int32 i = path.indexOf('/', 1); + *component = i < 0 ? path.copy(1) : path.copy(1, i - 1); + return true; +} + +void renameMigratedSetElementTo( + css::uno::Reference<css::container::XNameContainer> const & set, OUString const & currentName, + OUString const & migratedName) +{ + // To avoid unexpected data loss, the code is careful to only rename from currentName to + // migratedName in the expected case where the currentName element exists and the migratedName + // element doesn't exist: + bool const hasCurrent = set->hasByName(currentName); + bool const hasMigrated = set->hasByName(migratedName); + if (hasCurrent && !hasMigrated) { + auto const elem = set->getByName(currentName); + set->removeByName(currentName); + set->insertByName(migratedName, elem); + } else { + SAL_INFO_IF(!hasCurrent, "desktop.migration", "unexpectedly missing " << currentName); + SAL_INFO_IF(hasMigrated, "desktop.migration", "unexpectedly present " << migratedName); + } +} + +void renameMigratedSetElementBack( + css::uno::Reference<css::container::XNameContainer> const & set, OUString const & currentName, + OUString const & migratedName) +{ + // To avoid unexpected data loss, the code is careful to ensure that in the end a currentName + // element exists, creating it from a template if the migratedName element had unexpectedly gone + // missing: + bool const hasMigrated = set->hasByName(migratedName); + css::uno::Any elem; + if (hasMigrated) { + elem = set->getByName(migratedName); + set->removeByName(migratedName); + } else { + SAL_INFO("desktop.migration", "unexpected loss of " << migratedName); + elem <<= css::uno::Reference<css::lang::XSingleServiceFactory>( + set, css::uno::UNO_QUERY_THROW)->createInstance(); + } + if (set->hasByName(currentName)) { + SAL_INFO("desktop.migration", "unexpected reappearance of " << currentName); + if (hasMigrated) { + SAL_INFO( + "desktop.migration", + "reappeared " << currentName << " overwritten with " << migratedName); + set->replaceByName(currentName, elem); + } + } else { + set->insertByName(currentName, elem); + } +} + +} + +void MigrationImpl::copyConfig() +{ + Components comps; + for (auto const& rMigrationStep : *m_vrMigrations) { + for (const OUString& rIncludePath : rMigrationStep.includeConfig) { + OUString comp; + if (getComponent(rIncludePath, &comp)) { + comps[comp].includedPaths.insert(rIncludePath); + } + } + for (const OUString& rExcludePath : rMigrationStep.excludeConfig) { + OUString comp; + if (getComponent(rExcludePath, &comp)) { + comps[comp].excludedPaths.insert(rExcludePath); + } + } + } + + // check if the shared registrymodifications.xcu file exists + bool bRegistryModificationsXcuExists = false; + OUString regFilePath = m_aInfo.userdata + "/user/registrymodifications.xcu"; + File regFile(regFilePath); + ::osl::FileBase::RC nError = regFile.open(osl_File_OpenFlag_Read); + if ( nError == ::osl::FileBase::E_None ) { + bRegistryModificationsXcuExists = true; + regFile.close(); + } + + // If the to-be-migrated data contains modifications of + // /org.openoffice.Office.UI/ColorScheme/ColorSchemes set elements named after the migrated + // product name, those modifications must instead be made to the corresponding set elements + // named after the current product name. However, if the current configuration data does not + // contain those old-named set elements at all, their modification data would silently be + // ignored by css.configuration.XUpdate::insertModificationXcuFile. So temporarily rename any + // new-named set elements to their old-named counterparts here, and rename them back again down + // below after importing the migrated data: + OUString sProductName = utl::ConfigManager::getProductName(); + OUString sProductNameDark = sProductName + " Dark"; + OUString sMigratedProductName = m_aInfo.productname; + // remove version number from the end of product name if there’s one + if (isdigit(sMigratedProductName[sMigratedProductName.getLength() - 1])) + sMigratedProductName = (sMigratedProductName.copy(0, m_aInfo.productname.getLength() - 1)).trim(); + OUString sMigratedProductNameDark = sMigratedProductName + " Dark"; + auto const tempRename = sMigratedProductName != sProductName; + if (tempRename) { + auto const batch = comphelper::ConfigurationChanges::create(); + auto const schemes = officecfg::Office::UI::ColorScheme::ColorSchemes::get(batch); + renameMigratedSetElementTo(schemes, sProductName, sMigratedProductName); + renameMigratedSetElementTo(schemes, sProductNameDark, sMigratedProductNameDark); + batch->commit(); + } + + for (auto const& comp : comps) + { + if (!comp.second.includedPaths.empty()) { + if (!bRegistryModificationsXcuExists) { + // shared registrymodifications.xcu does not exists + // the configuration is split in many registry files + // determine the file names from the first element in included paths + OUStringBuffer buf(m_aInfo.userdata + + "/user/registry/data"); + sal_Int32 n = 0; + do { + OUString seg(comp.first.getToken(0, '.', n)); + OUString enc( + rtl::Uri::encode( + seg, rtl_UriCharClassPchar, rtl_UriEncodeStrict, + RTL_TEXTENCODING_UTF8)); + if (enc.isEmpty() && !seg.isEmpty()) { + SAL_INFO( "desktop.migration", "configuration migration component " << comp.first << " ignored (cannot be encoded as file path)" ); + goto next; + } + buf.append("/" + enc); + } while (n >= 0); + buf.append(".xcu"); + regFilePath = buf.makeStringAndClear(); + } + configuration::Update::get( + comphelper::getProcessComponentContext())-> + insertModificationXcuFile( + regFilePath, + comphelper::containerToSequence(comp.second.includedPaths), + comphelper::containerToSequence(comp.second.excludedPaths)); + + } else { + SAL_INFO( "desktop.migration", "configuration migration component " << comp.first << " ignored (only excludes, no includes)" ); + } +next: + ; + } + if (tempRename) { + auto const batch = comphelper::ConfigurationChanges::create(); + auto const schemes = officecfg::Office::UI::ColorScheme::ColorSchemes::get(batch); + renameMigratedSetElementBack(schemes, sProductName, sMigratedProductName); + renameMigratedSetElementBack(schemes, sProductNameDark, sMigratedProductNameDark); + batch->commit(); + } + // checking the migrated (product name related) color scheme name, and replace it to the current version scheme name + try + { + OUString sMigratedColorScheme; + uno::Reference<XPropertySet> aPropertySet( + getConfigAccess("org.openoffice.Office.UI/ColorScheme", true), uno::UNO_QUERY_THROW); + if (aPropertySet->getPropertyValue("CurrentColorScheme") >>= sMigratedColorScheme) + { + if (sMigratedColorScheme.equals(sMigratedProductName)) + { + aPropertySet->setPropertyValue("CurrentColorScheme", + uno::Any(sProductName)); + uno::Reference<XChangesBatch>(aPropertySet, uno::UNO_QUERY_THROW)->commitChanges(); + } + else if (sMigratedColorScheme.equals(sMigratedProductNameDark)) + { + aPropertySet->setPropertyValue("CurrentColorScheme", + uno::Any(sProductNameDark)); + uno::Reference<XChangesBatch>(aPropertySet, uno::UNO_QUERY_THROW)->commitChanges(); + } + } + } catch (const Exception&) { + // fail silently... + } +} + +uno::Reference< XNameAccess > MigrationImpl::getConfigAccess(const char* pPath, bool bUpdate) +{ + uno::Reference< XNameAccess > xNameAccess; + try { + OUString sAccessSrvc; + if (bUpdate) + sAccessSrvc = "com.sun.star.configuration.ConfigurationUpdateAccess"; + else + sAccessSrvc = "com.sun.star.configuration.ConfigurationAccess"; + + OUString sConfigURL = OUString::createFromAscii(pPath); + + uno::Reference< XMultiServiceFactory > theConfigProvider( + configuration::theDefaultProvider::get( + comphelper::getProcessComponentContext())); + + // access the provider + uno::Sequence< uno::Any > theArgs {uno::Any(sConfigURL)}; + xNameAccess.set( + theConfigProvider->createInstanceWithArguments( + sAccessSrvc, theArgs ), uno::UNO_QUERY_THROW ); + } catch (const css::uno::Exception&) { + TOOLS_WARN_EXCEPTION("desktop.migration", "ignoring"); + } + return xNameAccess; +} + +void MigrationImpl::copyFiles() +{ + OUString localName; + OUString destName; + OUString userInstall; + utl::Bootstrap::PathStatus aStatus; + aStatus = utl::Bootstrap::locateUserInstallation(userInstall); + if (aStatus == utl::Bootstrap::PATH_EXISTS) { + for (auto const& rFile : *m_vrFileList) + { + // remove installation prefix from file + localName = rFile.copy(m_aInfo.userdata.getLength()); + if (localName.endsWith( "/autocorr/acor_.dat")) { + // Previous versions used an empty language tag for + // LANGUAGE_DONTKNOW with the "[All]" autocorrection entry. + // As of LibreOffice 4.0 it is 'und' for LANGUAGE_UNDETERMINED + // so the file name is "acor_und.dat". + localName = OUString::Concat(localName.subView( 0, localName.getLength() - 4)) + "und.dat"; + } + destName = userInstall + localName; + INetURLObject aURL(destName); + // check whether destination directory exists + aURL.removeSegment(); + _checkAndCreateDirectory(aURL); + FileBase::RC copyResult = File::copy(rFile, destName); + if (copyResult != FileBase::E_None) { + SAL_WARN( "desktop", "Cannot copy " << rFile << " to " << destName); + } + } + } else { + OSL_FAIL("copyFiles: UserInstall does not exist"); + } +} + +void MigrationImpl::runServices() +{ + // Build argument array + uno::Sequence< uno::Any > seqArguments(3); + auto pseqArguments = seqArguments.getArray(); + pseqArguments[0] <<= NamedValue("Productname", + uno::Any(m_aInfo.productname)); + pseqArguments[1] <<= NamedValue("UserData", + uno::Any(m_aInfo.userdata)); + + + // create an instance of every migration service + // and execute the migration job + uno::Reference< XJob > xMigrationJob; + + uno::Reference< uno::XComponentContext > xContext(comphelper::getProcessComponentContext()); + for (auto const& rMigration : *m_vrMigrations) + { + if( !rMigration.service.isEmpty()) { + + try { + // set black list for extension migration + uno::Sequence< OUString > seqExtDenyList; + sal_uInt32 nSize = rMigration.excludeExtensions.size(); + if ( nSize > 0 ) + seqExtDenyList = comphelper::arrayToSequence< OUString >( + rMigration.excludeExtensions.data(), nSize ); + pseqArguments[2] <<= NamedValue("ExtensionDenyList", + uno::Any( seqExtDenyList )); + + xMigrationJob.set( + xContext->getServiceManager()->createInstanceWithArgumentsAndContext(rMigration.service, seqArguments, xContext), + uno::UNO_QUERY_THROW); + + xMigrationJob->execute(uno::Sequence< NamedValue >()); + + + } catch (const Exception&) { + TOOLS_WARN_EXCEPTION( "desktop", "Execution of migration service failed. Service: " + << rMigration.service); + } catch (...) { + SAL_WARN( "desktop", "Execution of migration service failed (Exception caught).\nService: " + << rMigration.service << "\nNo message available"); + } + + } + } +} + +std::vector< MigrationModuleInfo > MigrationImpl::detectUIChangesForAllModules() const +{ + std::vector< MigrationModuleInfo > vModulesInfo; + static constexpr OUStringLiteral MENUBAR(u"menubar"); + static constexpr OUStringLiteral TOOLBAR(u"toolbar"); + + uno::Sequence< uno::Any > lArgs {uno::Any(m_aInfo.userdata + "/user/config/soffice.cfg/modules"), + uno::Any(embed::ElementModes::READ)}; + + uno::Reference< lang::XSingleServiceFactory > xStorageFactory( + embed::FileSystemStorageFactory::create(comphelper::getProcessComponentContext())); + uno::Reference< embed::XStorage > xModules; + + xModules.set(xStorageFactory->createInstanceWithArguments(lArgs), uno::UNO_QUERY); + if (!xModules.is()) + return vModulesInfo; + + uno::Sequence< OUString > lNames = xModules->getElementNames(); + sal_Int32 nLength = lNames.getLength(); + for (sal_Int32 i=0; i<nLength; ++i) { + OUString sModuleShortName = lNames[i]; + uno::Reference< embed::XStorage > xModule = xModules->openStorageElement(sModuleShortName, embed::ElementModes::READ); + if (xModule.is()) { + MigrationModuleInfo aModuleInfo; + + uno::Reference< embed::XStorage > xMenubar = xModule->openStorageElement(MENUBAR, embed::ElementModes::READ); + if (xMenubar.is()) { + if (xMenubar->getElementNames().hasElements()) { + aModuleInfo.sModuleShortName = sModuleShortName; + aModuleInfo.bHasMenubar = true; + } + } + + uno::Reference< embed::XStorage > xToolbar = xModule->openStorageElement(TOOLBAR, embed::ElementModes::READ); + if (xToolbar.is()) { + const ::uno::Sequence< OUString > lToolbars = xToolbar->getElementNames(); + for (OUString const & sToolbarName : lToolbars) { + if (sToolbarName.startsWith("custom_")) + continue; + + aModuleInfo.sModuleShortName = sModuleShortName; + sal_Int32 nIndex = sToolbarName.lastIndexOf('.'); + if (nIndex > 0) { + std::u16string_view sExtension(sToolbarName.subView(nIndex)); + OUString sToolbarResourceName(sToolbarName.copy(0, nIndex)); + if (!sToolbarResourceName.isEmpty() && sExtension == u".xml") + aModuleInfo.m_vToolbars.push_back(sToolbarResourceName); + } + } + } + + if (!aModuleInfo.sModuleShortName.isEmpty()) + vModulesInfo.push_back(aModuleInfo); + } + } + + return vModulesInfo; +} + +void MigrationImpl::compareOldAndNewConfig(const OUString& sParent, + const uno::Reference< container::XIndexContainer >& xIndexOld, + const uno::Reference< container::XIndexContainer >& xIndexNew, + const OUString& sResourceURL) +{ + static constexpr OUStringLiteral MENU_SEPARATOR(u" | "); + + std::vector< MigrationItem > vOldItems; + std::vector< MigrationItem > vNewItems; + uno::Sequence< beans::PropertyValue > aProps; + sal_Int32 nOldCount = xIndexOld->getCount(); + sal_Int32 nNewCount = xIndexNew->getCount(); + + for (int n=0; n<nOldCount; ++n) { + MigrationItem aMigrationItem; + if (xIndexOld->getByIndex(n) >>= aProps) { + for(beans::PropertyValue const & prop : std::as_const(aProps)) { + if ( prop.Name == ITEM_DESCRIPTOR_COMMANDURL ) + prop.Value >>= aMigrationItem.m_sCommandURL; + else if ( prop.Name == ITEM_DESCRIPTOR_CONTAINER ) + prop.Value >>= aMigrationItem.m_xPopupMenu; + } + + if (!aMigrationItem.m_sCommandURL.isEmpty()) + vOldItems.push_back(aMigrationItem); + } + } + + for (int n=0; n<nNewCount; ++n) { + MigrationItem aMigrationItem; + if (xIndexNew->getByIndex(n) >>= aProps) { + for(beans::PropertyValue const & prop : std::as_const(aProps)) { + if ( prop.Name == ITEM_DESCRIPTOR_COMMANDURL ) + prop.Value >>= aMigrationItem.m_sCommandURL; + else if ( prop.Name == ITEM_DESCRIPTOR_CONTAINER ) + prop.Value >>= aMigrationItem.m_xPopupMenu; + } + + if (!aMigrationItem.m_sCommandURL.isEmpty()) + vNewItems.push_back(aMigrationItem); + } + } + + OUString sSibling; + for (auto const& oldItem : vOldItems) + { + std::vector< MigrationItem >::iterator pFound = std::find(vNewItems.begin(), vNewItems.end(), oldItem); + if (pFound != vNewItems.end() && oldItem.m_xPopupMenu.is()) { + OUString sName; + if (!sParent.isEmpty()) + sName = sParent + MENU_SEPARATOR + oldItem.m_sCommandURL; + else + sName = oldItem.m_sCommandURL; + compareOldAndNewConfig(sName, oldItem.m_xPopupMenu, pFound->m_xPopupMenu, sResourceURL); + } else if (pFound == vNewItems.end()) { + MigrationItem aMigrationItem(sParent, sSibling, oldItem.m_sCommandURL, oldItem.m_xPopupMenu); + if (m_aOldVersionItemsHashMap.find(sResourceURL)==m_aOldVersionItemsHashMap.end()) { + std::vector< MigrationItem > vMigrationItems; + m_aOldVersionItemsHashMap.emplace(sResourceURL, vMigrationItems); + m_aOldVersionItemsHashMap[sResourceURL].push_back(aMigrationItem); + } else { + if (std::find(m_aOldVersionItemsHashMap[sResourceURL].begin(), m_aOldVersionItemsHashMap[sResourceURL].end(), aMigrationItem)==m_aOldVersionItemsHashMap[sResourceURL].end()) + m_aOldVersionItemsHashMap[sResourceURL].push_back(aMigrationItem); + } + } + + sSibling = oldItem.m_sCommandURL; + } +} + +void MigrationImpl::mergeOldToNewVersion(const uno::Reference< ui::XUIConfigurationManager >& xCfgManager, + const uno::Reference< container::XIndexContainer>& xIndexContainer, + const OUString& sModuleIdentifier, + const OUString& sResourceURL) +{ + MigrationHashMap::iterator pFound = m_aOldVersionItemsHashMap.find(sResourceURL); + if (pFound==m_aOldVersionItemsHashMap.end()) + return; + + for (auto const& elem : pFound->second) + { + uno::Reference< container::XIndexContainer > xTemp = xIndexContainer; + + OUString sParentNodeName = elem.m_sParentNodeName; + sal_Int32 nIndex = 0; + do { + std::u16string_view sToken( o3tl::trim(o3tl::getToken(sParentNodeName, 0, '|', nIndex)) ); + if (sToken.empty()) + break; + + sal_Int32 nCount = xTemp->getCount(); + for (sal_Int32 i=0; i<nCount; ++i) { + OUString sCommandURL; + OUString sLabel; + uno::Reference< container::XIndexContainer > xChild; + + uno::Sequence< beans::PropertyValue > aPropSeq; + xTemp->getByIndex(i) >>= aPropSeq; + for (beans::PropertyValue const & prop : std::as_const(aPropSeq)) { + OUString sPropName = prop.Name; + if ( sPropName == ITEM_DESCRIPTOR_COMMANDURL ) + prop.Value >>= sCommandURL; + else if ( sPropName == ITEM_DESCRIPTOR_LABEL ) + prop.Value >>= sLabel; + else if ( sPropName == ITEM_DESCRIPTOR_CONTAINER ) + prop.Value >>= xChild; + } + + if (sCommandURL == sToken) { + xTemp = xChild; + break; + } + } + + } while (nIndex >= 0); + + if (nIndex == -1) { + auto aProperties = vcl::CommandInfoProvider::GetCommandProperties(elem.m_sCommandURL, sModuleIdentifier); + uno::Sequence< beans::PropertyValue > aPropSeq { + beans::PropertyValue(ITEM_DESCRIPTOR_COMMANDURL, 0, uno::Any(elem.m_sCommandURL), beans::PropertyState_DIRECT_VALUE), + beans::PropertyValue(ITEM_DESCRIPTOR_LABEL, 0, uno::Any(vcl::CommandInfoProvider::GetLabelForCommand(aProperties)), beans::PropertyState_DIRECT_VALUE), + beans::PropertyValue(ITEM_DESCRIPTOR_CONTAINER, 0, uno::Any(elem.m_xPopupMenu), beans::PropertyState_DIRECT_VALUE) + }; + + if (elem.m_sPrevSibling.isEmpty()) + xTemp->insertByIndex(0, uno::Any(aPropSeq)); + else { + sal_Int32 nCount = xTemp->getCount(); + sal_Int32 i = 0; + for (; i<nCount; ++i) { + OUString sCmd; + uno::Sequence< beans::PropertyValue > aTempPropSeq; + xTemp->getByIndex(i) >>= aTempPropSeq; + for (beans::PropertyValue const & prop : std::as_const(aTempPropSeq)) { + if ( prop.Name == ITEM_DESCRIPTOR_COMMANDURL ) { + prop.Value >>= sCmd; + break; + } + } + + if (sCmd == elem.m_sPrevSibling) + break; + } + + xTemp->insertByIndex(i+1, uno::Any(aPropSeq)); + } + } + } + + if (xIndexContainer.is()) + xCfgManager->replaceSettings(sResourceURL, xIndexContainer); + + uno::Reference< ui::XUIConfigurationPersistence > xUIConfigurationPersistence(xCfgManager, uno::UNO_QUERY); + if (xUIConfigurationPersistence.is()) + xUIConfigurationPersistence->store(); +} + +uno::Reference< ui::XUIConfigurationManager > NewVersionUIInfo::getConfigManager(std::u16string_view sModuleShortName) const +{ + uno::Reference< ui::XUIConfigurationManager > xCfgManager; + + for ( const css::beans::PropertyValue& rProp : m_lCfgManagerSeq) { + if (rProp.Name == sModuleShortName) { + rProp.Value >>= xCfgManager; + break; + } + } + + return xCfgManager; +} + +uno::Reference< container::XIndexContainer > NewVersionUIInfo::getNewMenubarSettings(std::u16string_view sModuleShortName) const +{ + uno::Reference< container::XIndexContainer > xNewMenuSettings; + + for (auto const & prop : m_lNewVersionMenubarSettingsSeq) { + if (prop.Name == sModuleShortName) { + prop.Value >>= xNewMenuSettings; + break; + } + } + + return xNewMenuSettings; +} + +uno::Reference< container::XIndexContainer > NewVersionUIInfo::getNewToolbarSettings(std::u16string_view sModuleShortName, std::u16string_view sToolbarName) const +{ + uno::Reference< container::XIndexContainer > xNewToolbarSettings; + + for (auto const & newProp : m_lNewVersionToolbarSettingsSeq) { + if (newProp.Name == sModuleShortName) { + uno::Sequence< beans::PropertyValue > lToolbarSettingsSeq; + newProp.Value >>= lToolbarSettingsSeq; + for (auto const & prop : std::as_const(lToolbarSettingsSeq)) { + if (prop.Name == sToolbarName) { + prop.Value >>= xNewToolbarSettings; + break; + } + } + + break; + } + } + + return xNewToolbarSettings; +} + +void NewVersionUIInfo::init(const std::vector< MigrationModuleInfo >& vModulesInfo) +{ + m_lCfgManagerSeq.resize(vModulesInfo.size()); + m_lNewVersionMenubarSettingsSeq.realloc(vModulesInfo.size()); + auto p_lNewVersionMenubarSettingsSeq = m_lNewVersionMenubarSettingsSeq.getArray(); + m_lNewVersionToolbarSettingsSeq.realloc(vModulesInfo.size()); + auto p_lNewVersionToolbarSettingsSeq = m_lNewVersionToolbarSettingsSeq.getArray(); + + static constexpr OUStringLiteral sMenubarResourceURL(u"private:resource/menubar/menubar"); + static constexpr OUStringLiteral sToolbarResourcePre(u"private:resource/toolbar/"); + + uno::Reference< ui::XModuleUIConfigurationManagerSupplier > xModuleCfgSupplier = ui::theModuleUIConfigurationManagerSupplier::get( ::comphelper::getProcessComponentContext() ); + + for (size_t i=0; i<vModulesInfo.size(); ++i) { + OUString sModuleIdentifier = mapModuleShortNameToIdentifier(vModulesInfo[i].sModuleShortName); + if (!sModuleIdentifier.isEmpty()) { + uno::Reference< ui::XUIConfigurationManager > xCfgManager = xModuleCfgSupplier->getUIConfigurationManager(sModuleIdentifier); + m_lCfgManagerSeq[i].Name = vModulesInfo[i].sModuleShortName; + m_lCfgManagerSeq[i].Value <<= xCfgManager; + + if (vModulesInfo[i].bHasMenubar) { + p_lNewVersionMenubarSettingsSeq[i].Name = vModulesInfo[i].sModuleShortName; + p_lNewVersionMenubarSettingsSeq[i].Value <<= xCfgManager->getSettings(sMenubarResourceURL, true); + } + + sal_Int32 nToolbars = vModulesInfo[i].m_vToolbars.size(); + if (nToolbars > 0) { + uno::Sequence< beans::PropertyValue > lPropSeq(nToolbars); + auto plPropSeq = lPropSeq.getArray(); + for (sal_Int32 j=0; j<nToolbars; ++j) { + OUString sToolbarName = vModulesInfo[i].m_vToolbars[j]; + OUString sToolbarResourceURL = sToolbarResourcePre + sToolbarName; + + plPropSeq[j].Name = sToolbarName; + plPropSeq[j].Value <<= xCfgManager->getSettings(sToolbarResourceURL, true); + } + + p_lNewVersionToolbarSettingsSeq[i].Name = vModulesInfo[i].sModuleShortName; + p_lNewVersionToolbarSettingsSeq[i].Value <<= lPropSeq; + } + } + } +} + +} // namespace desktop + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/migration/migration_impl.hxx b/desktop/source/migration/migration_impl.hxx new file mode 100644 index 0000000000..6b0923d292 --- /dev/null +++ b/desktop/source/migration/migration_impl.hxx @@ -0,0 +1,198 @@ +/* -*- 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 <memory> +#include <string_view> +#include <unordered_map> +#include <utility> +#include <vector> + +#include <o3tl/string_view.hxx> +#include <sal/types.h> +#include <rtl/ustring.hxx> + +#include <com/sun/star/uno/Reference.hxx> + +#include <com/sun/star/container/XNameAccess.hpp> +#include <com/sun/star/container/XIndexContainer.hpp> +#include <com/sun/star/ui/XUIConfigurationManager.hpp> + +namespace desktop +{ + +struct install_info +{ + OUString productname; // human readable product name + OUString userdata; // file: url for user installation +}; + +typedef std::vector< OUString > strings_v; +typedef std::unique_ptr< strings_v > strings_vr; + +struct migration_step +{ + strings_v includeFiles; + strings_v excludeFiles; + strings_v includeConfig; + strings_v excludeConfig; + strings_v excludeExtensions; + OUString service; +}; + +struct supported_migration +{ + OUString name; + sal_Int32 nPriority; + strings_v supported_versions; +}; + +typedef std::vector< migration_step > migrations_v; +typedef std::unique_ptr< migrations_v > migrations_vr; +typedef std::vector< supported_migration > migrations_available; + +inline bool areBothOpenFrom(std::u16string_view cmd1, std::u16string_view cmd2) +{ + return cmd1 == u".uno:Open" && o3tl::starts_with(cmd2, u".uno:OpenFrom"); +} + +/** + define the item, e.g.:menuitem, toolbaritem, to be migrated. we keep the information + of the command URL, the previous sibling node and the parent node of an item +*/ +struct MigrationItem +{ + OUString m_sParentNodeName; + OUString m_sPrevSibling; + OUString m_sCommandURL; + css::uno::Reference< css::container::XIndexContainer > m_xPopupMenu; + + MigrationItem() + { + } + + MigrationItem(OUString sParentNodeName, + OUString sPrevSibling, + OUString sCommandURL, + css::uno::Reference< css::container::XIndexContainer > xPopupMenu) + : m_sParentNodeName(std::move(sParentNodeName)), m_sPrevSibling(std::move(sPrevSibling)), + m_sCommandURL(std::move(sCommandURL)), m_xPopupMenu(std::move(xPopupMenu)) + { + } + + bool operator==(const MigrationItem& aMigrationItem) const + { + return + (aMigrationItem.m_sCommandURL == m_sCommandURL + || areBothOpenFrom(aMigrationItem.m_sCommandURL, m_sCommandURL) + || areBothOpenFrom(m_sCommandURL, aMigrationItem.m_sCommandURL)) + && aMigrationItem.m_sParentNodeName == m_sParentNodeName + && aMigrationItem.m_sPrevSibling == m_sPrevSibling + && aMigrationItem.m_xPopupMenu.is() == m_xPopupMenu.is(); + } +}; + +typedef std::unordered_map< OUString, std::vector< MigrationItem > > MigrationHashMap; + +/** + information for the UI elements to be migrated for one module +*/ +struct MigrationModuleInfo +{ + OUString sModuleShortName; + bool bHasMenubar; + std::vector< OUString > m_vToolbars; + + MigrationModuleInfo() : bHasMenubar(false) {}; +}; + + +/** + get the information before copying the ui configuration files of old version to new version +*/ +class NewVersionUIInfo +{ +public: + + css::uno::Reference< css::ui::XUIConfigurationManager > getConfigManager(std::u16string_view sModuleShortName) const; + css::uno::Reference< css::container::XIndexContainer > getNewMenubarSettings(std::u16string_view sModuleShortName) const; + css::uno::Reference< css::container::XIndexContainer > getNewToolbarSettings(std::u16string_view sModuleShortName, std::u16string_view sToolbarName) const; + void init(const std::vector< MigrationModuleInfo >& vModulesInfo); + +private: + + std::vector< css::beans::PropertyValue > m_lCfgManagerSeq; + css::uno::Sequence< css::beans::PropertyValue > m_lNewVersionMenubarSettingsSeq; + css::uno::Sequence< css::beans::PropertyValue > m_lNewVersionToolbarSettingsSeq; +}; + +class MigrationImpl +{ + +private: + migrations_available m_vMigrationsAvailable; // list of all available migrations + migrations_vr m_vrMigrations; // list of all migration specs from config + install_info m_aInfo; // info about the version being migrated + strings_vr m_vrFileList; // final list of files to be copied + MigrationHashMap m_aOldVersionItemsHashMap; + + // functions to control the migration process + static void readAvailableMigrations(migrations_available&); + bool alreadyMigrated(); + static migrations_vr readMigrationSteps(const OUString& rMigrationName); + sal_Int32 findPreferredMigrationProcess(const migrations_available&); +#if defined UNX && ! defined MACOSX + static OUString preXDGConfigDir(const OUString& rConfigDir); +#endif + static void setInstallInfoIfExist(install_info& aInfo, std::u16string_view rConfigDir, const OUString& rVersion); + static install_info findInstallation(const strings_v& rVersions); + strings_vr compileFileList(); + + // helpers + strings_vr getAllFiles(const OUString& baseURL) const; + static strings_vr applyPatterns(const strings_v& vSet, const strings_v& vPatterns); + static css::uno::Reference< css::container::XNameAccess > getConfigAccess(const char* path, bool rw=false); + + std::vector< MigrationModuleInfo > detectUIChangesForAllModules() const; + void compareOldAndNewConfig(const OUString& sParentNodeName, + const css::uno::Reference< css::container::XIndexContainer >& xOldIndexContainer, + const css::uno::Reference< css::container::XIndexContainer >& xNewIndexContainer, + const OUString& sToolbarName); + void mergeOldToNewVersion(const css::uno::Reference< css::ui::XUIConfigurationManager >& xCfgManager, + const css::uno::Reference< css::container::XIndexContainer>& xIndexContainer, + const OUString& sModuleIdentifier, + const OUString& sResourceURL); + + // actual processing function that perform the migration steps + void copyFiles(); + void copyConfig(); + void runServices(); + + static void setMigrationCompleted(); + static bool checkMigrationCompleted(); + +public: + MigrationImpl(); + ~MigrationImpl(); + bool initializeMigration(); + bool doMigration(); +}; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/migration/services/basicmigration.cxx b/desktop/source/migration/services/basicmigration.cxx new file mode 100644 index 0000000000..94e8677de7 --- /dev/null +++ b/desktop/source/migration/services/basicmigration.cxx @@ -0,0 +1,202 @@ +/* -*- 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 "basicmigration.hxx" +#include <cppuhelper/supportsservice.hxx> +#include <tools/urlobj.hxx> +#include <unotools/bootstrap.hxx> +#include <sal/log.hxx> +#include <osl/file.hxx> +#include <com/sun/star/uno/XComponentContext.hpp> + + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; + + +namespace migration +{ + + + #define sSourceUserBasic "/user/basic" + #define sTargetUserBasic "/user/__basic_80" + + + // BasicMigration + + + BasicMigration::BasicMigration() + { + } + + + BasicMigration::~BasicMigration() + { + } + + + TStringVectorPtr BasicMigration::getFiles( const OUString& rBaseURL ) const + { + TStringVectorPtr aResult( new TStringVector ); + ::osl::Directory aDir( rBaseURL); + + if ( aDir.open() == ::osl::FileBase::E_None ) + { + // iterate over directory content + TStringVector aSubDirs; + ::osl::DirectoryItem aItem; + while ( aDir.getNextItem( aItem ) == ::osl::FileBase::E_None ) + { + ::osl::FileStatus aFileStatus( osl_FileStatus_Mask_Type | osl_FileStatus_Mask_FileURL ); + if ( aItem.getFileStatus( aFileStatus ) == ::osl::FileBase::E_None ) + { + if ( aFileStatus.getFileType() == ::osl::FileStatus::Directory ) + aSubDirs.push_back( aFileStatus.getFileURL() ); + else + aResult->push_back( aFileStatus.getFileURL() ); + } + } + + // iterate recursive over subfolders + for (auto const& subDir : aSubDirs) + { + TStringVectorPtr aSubResult = getFiles(subDir); + aResult->insert( aResult->end(), aSubResult->begin(), aSubResult->end() ); + } + } + + return aResult; + } + + + void BasicMigration::checkAndCreateDirectory( INetURLObject const & rDirURL ) + { + ::osl::FileBase::RC aResult = ::osl::Directory::create( rDirURL.GetMainURL( INetURLObject::DecodeMechanism::ToIUri ) ); + if ( aResult == ::osl::FileBase::E_NOENT ) + { + INetURLObject aBaseURL( rDirURL ); + aBaseURL.removeSegment(); + checkAndCreateDirectory( aBaseURL ); + ::osl::Directory::create( rDirURL.GetMainURL( INetURLObject::DecodeMechanism::ToIUri ) ); + } + } + + + void BasicMigration::copyFiles() + { + OUString sTargetDir; + ::utl::Bootstrap::PathStatus aStatus = ::utl::Bootstrap::locateUserInstallation( sTargetDir ); + if ( aStatus == ::utl::Bootstrap::PATH_EXISTS ) + { + sTargetDir += sTargetUserBasic; + TStringVectorPtr aFileList = getFiles( m_sSourceDir ); + for (auto const& elem : *aFileList) + { + std::u16string_view sLocalName = elem.subView( m_sSourceDir.getLength() ); + OUString sTargetName = sTargetDir + sLocalName; + INetURLObject aURL( sTargetName ); + aURL.removeSegment(); + checkAndCreateDirectory( aURL ); + ::osl::FileBase::RC aResult = ::osl::File::copy( elem, sTargetName ); + if ( aResult != ::osl::FileBase::E_None ) + { + SAL_WARN( "desktop", "BasicMigration::copyFiles: cannot copy " + << elem << " to " << sTargetName ); + } + } + } + else + { + OSL_FAIL( "BasicMigration::copyFiles: no user installation!" ); + } + } + + + // XServiceInfo + + + OUString BasicMigration::getImplementationName() + { + return "com.sun.star.comp.desktop.migration.Basic"; + } + + + sal_Bool BasicMigration::supportsService(OUString const & ServiceName) + { + return cppu::supportsService(this, ServiceName); + } + + + Sequence< OUString > BasicMigration::getSupportedServiceNames() + { + return { "com.sun.star.migration.Basic" }; + } + + + // XInitialization + + + void BasicMigration::initialize( const Sequence< Any >& aArguments ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + + const Any* pIter = aArguments.getConstArray(); + const Any* pEnd = pIter + aArguments.getLength(); + for ( ; pIter != pEnd ; ++pIter ) + { + beans::NamedValue aValue; + *pIter >>= aValue; + if ( aValue.Name == "UserData" ) + { + if ( !(aValue.Value >>= m_sSourceDir) ) + { + OSL_FAIL( "BasicMigration::initialize: argument UserData has wrong type!" ); + } + m_sSourceDir += sSourceUserBasic; + break; + } + } + } + + + // XJob + + + Any BasicMigration::execute( const Sequence< beans::NamedValue >& ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + + copyFiles(); + + return Any(); + } + + +} // namespace migration + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +desktop_BasicMigration_get_implementation( + css::uno::XComponentContext* , css::uno::Sequence<css::uno::Any> const&) +{ + return cppu::acquire(new migration::BasicMigration()); +} + + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/migration/services/basicmigration.hxx b/desktop/source/migration/services/basicmigration.hxx new file mode 100644 index 0000000000..889b9245d0 --- /dev/null +++ b/desktop/source/migration/services/basicmigration.hxx @@ -0,0 +1,71 @@ +/* -*- 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 "misc.hxx" +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/lang/XInitialization.hpp> +#include <com/sun/star/task/XJob.hpp> +#include <cppuhelper/implbase.hxx> +#include <osl/mutex.hxx> + + +class INetURLObject; + + +namespace migration +{ + typedef ::cppu::WeakImplHelper< + css::lang::XServiceInfo, + css::lang::XInitialization, + css::task::XJob > BasicMigration_BASE; + + class BasicMigration : public BasicMigration_BASE + { + private: + ::osl::Mutex m_aMutex; + OUString m_sSourceDir; + + TStringVectorPtr getFiles( const OUString& rBaseURL ) const; + void checkAndCreateDirectory( INetURLObject const & rDirURL ); + void copyFiles(); + + public: + BasicMigration(); + virtual ~BasicMigration() override; + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName() override; + virtual sal_Bool SAL_CALL supportsService( const OUString& rServiceName ) override; + virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames() override; + + // XInitialization + virtual void SAL_CALL initialize( const css::uno::Sequence< css::uno::Any >& aArguments ) override; + + // XJob + virtual css::uno::Any SAL_CALL execute( + const css::uno::Sequence< css::beans::NamedValue >& Arguments ) override; + }; + + +} // namespace migration + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/migration/services/cppumaker.mk b/desktop/source/migration/services/cppumaker.mk new file mode 100644 index 0000000000..57e070f80d --- /dev/null +++ b/desktop/source/migration/services/cppumaker.mk @@ -0,0 +1,27 @@ +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This file incorporates work covered by the following license notice: +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed +# with this work for additional information regarding copyright +# ownership. The ASF licenses this file to you under the Apache +# License, Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a copy of +# the License at http://www.apache.org/licenses/LICENSE-2.0 . +# + +.IF "$(debug)" != "" + +# MSVC++: no inlining +.IF "$(COM)" == "MSC" +CFLAGS += -Ob0 +.ENDIF + +.ENDIF + diff --git a/desktop/source/migration/services/jvmfwk.cxx b/desktop/source/migration/services/jvmfwk.cxx new file mode 100644 index 0000000000..892c651f29 --- /dev/null +++ b/desktop/source/migration/services/jvmfwk.cxx @@ -0,0 +1,395 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + + +#include <cppuhelper/implbase.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <rtl/ustring.hxx> +#include <rtl/bootstrap.hxx> +#include <sal/types.h> +#include <sal/config.h> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/lang/XInitialization.hpp> +#include <com/sun/star/lang/WrappedTargetException.hpp> +#include <com/sun/star/task/XJob.hpp> +#include <com/sun/star/configuration/backend/XLayer.hpp> +#include <com/sun/star/configuration/backend/XLayerHandler.hpp> +#include <com/sun/star/configuration/backend/MalformedDataException.hpp> +#include <com/sun/star/configuration/backend/TemplateIdentifier.hpp> +#include <jvmfwk/framework.hxx> +#include "jvmfwk.hxx" +#include <memory> +#include <stack> +#include <stdio.h> + +#include <osl/diagnose.h> + +constexpr OUString SERVICE_NAME = u"com.sun.star.migration.Java"_ustr; +#define IMPL_NAME "com.sun.star.comp.desktop.migration.Java" + +#define ENABLE_JAVA 1 +#define USER_CLASS_PATH 2 + +using namespace com::sun::star::uno; +using namespace com::sun::star::beans; +using namespace com::sun::star::lang; +using namespace com::sun::star::configuration::backend; + +namespace migration +{ + +namespace { + +class JavaMigration : public ::cppu::WeakImplHelper< + css::lang::XServiceInfo, + css::lang::XInitialization, + css::task::XJob, + css::configuration::backend::XLayerHandler> +{ +public: + // XServiceInfo + virtual OUString SAL_CALL getImplementationName() override; + virtual sal_Bool SAL_CALL supportsService( const OUString & rServiceName ) override; + virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames() override; + + //XInitialization + virtual void SAL_CALL initialize( const css::uno::Sequence< css::uno::Any >& aArguments ) override; + + //XJob + virtual css::uno::Any SAL_CALL execute( + const css::uno::Sequence<css::beans::NamedValue >& Arguments ) override; + + // XLayerHandler + virtual void SAL_CALL startLayer() override; + + virtual void SAL_CALL endLayer() override; + + virtual void SAL_CALL overrideNode( + const OUString& aName, + sal_Int16 aAttributes, + sal_Bool bClear) override; + + virtual void SAL_CALL addOrReplaceNode( + const OUString& aName, + sal_Int16 aAttributes) override; + + virtual void SAL_CALL addOrReplaceNodeFromTemplate( + const OUString& aName, + const css::configuration::backend::TemplateIdentifier& aTemplate, + sal_Int16 aAttributes ) override; + + virtual void SAL_CALL endNode() override; + + virtual void SAL_CALL dropNode( + const OUString& aName ) override; + + virtual void SAL_CALL overrideProperty( + const OUString& aName, + sal_Int16 aAttributes, + const css::uno::Type& aType, + sal_Bool bClear ) override; + + virtual void SAL_CALL setPropertyValue( + const css::uno::Any& aValue ) override; + + virtual void SAL_CALL setPropertyValueForLocale( + const css::uno::Any& aValue, + const OUString& aLocale ) override; + + virtual void SAL_CALL endProperty() override; + + virtual void SAL_CALL addProperty( + const OUString& aName, + sal_Int16 aAttributes, + const css::uno::Type& aType ) override; + + virtual void SAL_CALL addPropertyWithValue( + const OUString& aName, + sal_Int16 aAttributes, + const css::uno::Any& aValue ) override; + + + virtual ~JavaMigration() override; + +private: + OUString m_sUserDir; + css::uno::Reference< css::configuration::backend::XLayer> m_xLayer; + + void migrateJavarc(); + typedef std::pair< OUString, sal_Int16> TElementType; + typedef std::stack< TElementType > TElementStack; + TElementStack m_aStack; + +}; + +} + +JavaMigration::~JavaMigration() +{ + OSL_ASSERT(m_aStack.empty()); +} + +OUString jvmfwk_getImplementationName() +{ + return IMPL_NAME; +} + +css::uno::Sequence< OUString > jvmfwk_getSupportedServiceNames() +{ + return { SERVICE_NAME }; +} + +// XServiceInfo +OUString SAL_CALL JavaMigration::getImplementationName() +{ + return jvmfwk_getImplementationName(); +} + +sal_Bool JavaMigration::supportsService(OUString const & ServiceName) +{ + return cppu::supportsService(this, ServiceName); +} + +css::uno::Sequence< OUString > SAL_CALL JavaMigration::getSupportedServiceNames() +{ + return jvmfwk_getSupportedServiceNames(); +} + +//XInitialization ---------------------------------------------------------------------- +void SAL_CALL JavaMigration::initialize( const css::uno::Sequence< css::uno::Any >& aArguments ) +{ + const css::uno::Any* pIter = aArguments.getConstArray(); + const css::uno::Any* pEnd = pIter + aArguments.getLength(); + css::uno::Sequence<css::beans::NamedValue> aOldConfigValues; + css::beans::NamedValue aValue; + for(;pIter != pEnd;++pIter) + { + *pIter >>= aValue; + if ( aValue.Name == "OldConfiguration" ) + { + bool bSuccess = aValue.Value >>= aOldConfigValues; + OSL_ENSURE(bSuccess, "[Service implementation " IMPL_NAME + "] XInitialization::initialize: Argument OldConfiguration has wrong type."); + if (bSuccess) + { + const css::beans::NamedValue* pIter2 = aOldConfigValues.getConstArray(); + const css::beans::NamedValue* pEnd2 = pIter2 + aOldConfigValues.getLength(); + for(;pIter2 != pEnd2;++pIter2) + { + if ( pIter2->Name == "org.openoffice.Office.Java" ) + { + pIter2->Value >>= m_xLayer; + break; + } + } + } + } + else if ( aValue.Name == "UserData" ) + { + if ( !(aValue.Value >>= m_sUserDir) ) + { + OSL_FAIL( + "[Service implementation " IMPL_NAME + "] XInitialization::initialize: Argument UserData has wrong type."); + } + } + } + +} + +//XJob +css::uno::Any SAL_CALL JavaMigration::execute( + const css::uno::Sequence<css::beans::NamedValue >& ) +{ + migrateJavarc(); + if (m_xLayer.is()) + m_xLayer->readData(this); + + return css::uno::Any(); +} + +void JavaMigration::migrateJavarc() +{ + if (m_sUserDir.isEmpty()) + return; + + OUString sValue; + rtl::Bootstrap javaini(m_sUserDir + "/user/config/" SAL_CONFIGFILE("java")); + bool bSuccess = javaini.getFrom("Home", sValue); + OSL_ENSURE(bSuccess, "[Service implementation " IMPL_NAME + "] XJob::execute: Could not get Home entry from java.ini/javarc."); + if (!bSuccess || sValue.isEmpty()) + return; + + //get the directory + std::unique_ptr<JavaInfo> aInfo; + javaFrameworkError err = jfw_getJavaInfoByPath(sValue, &aInfo); + + if (err == JFW_E_NONE) + { + if (jfw_setSelectedJRE(aInfo.get()) != JFW_E_NONE) + { + OSL_FAIL("[Service implementation " IMPL_NAME + "] XJob::execute: jfw_setSelectedJRE failed."); + fprintf(stderr, "\nCannot migrate Java. An error occurred.\n"); + } + } + else if (err == JFW_E_FAILED_VERSION) + { + fprintf(stderr, "\nCannot migrate Java settings because the version of the Java " + "is not supported anymore.\n"); + } +} + + +// XLayerHandler +void SAL_CALL JavaMigration::startLayer() +{ +} + + +void SAL_CALL JavaMigration::endLayer() +{ +} + + +void SAL_CALL JavaMigration::overrideNode( + const OUString&, + sal_Int16, + sal_Bool) + +{ + +} + + +void SAL_CALL JavaMigration::addOrReplaceNode( + const OUString&, + sal_Int16) +{ + +} +void SAL_CALL JavaMigration::endNode() +{ +} + + +void SAL_CALL JavaMigration::dropNode( + const OUString& ) +{ +} + + +void SAL_CALL JavaMigration::overrideProperty( + const OUString& aName, + sal_Int16, + const Type&, + sal_Bool ) +{ + if ( aName == "Enable" ) + m_aStack.push(TElementStack::value_type(aName,ENABLE_JAVA)); + else if ( aName == "UserClassPath" ) + m_aStack.push(TElementStack::value_type(aName, USER_CLASS_PATH)); +} + + +void SAL_CALL JavaMigration::setPropertyValue( + const Any& aValue ) +{ + if ( m_aStack.empty()) + return; + + switch (m_aStack.top().second) + { + case ENABLE_JAVA: + { + bool val; + if (!(aValue >>= val)) + throw MalformedDataException( + "[Service implementation " IMPL_NAME + "] XLayerHandler::setPropertyValue received wrong type for Enable property", nullptr, Any()); + if (jfw_setEnabled(val) != JFW_E_NONE) + throw WrappedTargetException( + "[Service implementation " IMPL_NAME + "] XLayerHandler::setPropertyValue: jfw_setEnabled failed.", nullptr, Any()); + + break; + } + case USER_CLASS_PATH: + { + OUString cp; + if (!(aValue >>= cp)) + throw MalformedDataException( + "[Service implementation " IMPL_NAME + "] XLayerHandler::setPropertyValue received wrong type for UserClassPath property", nullptr, Any()); + + if (jfw_setUserClassPath(cp) != JFW_E_NONE) + throw WrappedTargetException( + "[Service implementation " IMPL_NAME + "] XLayerHandler::setPropertyValue: jfw_setUserClassPath failed.", nullptr, Any()); + break; + } + default: + OSL_ASSERT(false); + } +} + + +void SAL_CALL JavaMigration::setPropertyValueForLocale( + const Any&, + const OUString& ) +{ +} + + +void SAL_CALL JavaMigration::endProperty() +{ + if (!m_aStack.empty()) + m_aStack.pop(); +} + + +void SAL_CALL JavaMigration::addProperty( + const OUString&, + sal_Int16, + const Type& ) +{ +} + + +void SAL_CALL JavaMigration::addPropertyWithValue( + const OUString&, + sal_Int16, + const Any& ) +{ +} + +void SAL_CALL JavaMigration::addOrReplaceNodeFromTemplate( + const OUString&, + const TemplateIdentifier&, + sal_Int16 ) +{ +} + + +//ToDo enable java, user class path + +} //end namespace jfw + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/migration/services/jvmfwk.hxx b/desktop/source/migration/services/jvmfwk.hxx new file mode 100644 index 0000000000..63ec7e7179 --- /dev/null +++ b/desktop/source/migration/services/jvmfwk.hxx @@ -0,0 +1,33 @@ +/* -*- 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> +#include <com/sun/star/uno/Sequence.hxx> + +namespace migration +{ +OUString jvmfwk_getImplementationName(); + +css::uno::Sequence<OUString> jvmfwk_getSupportedServiceNames(); + +} //end blind namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/migration/services/migrationoo2.component b/desktop/source/migration/services/migrationoo2.component new file mode 100644 index 0000000000..2550235665 --- /dev/null +++ b/desktop/source/migration/services/migrationoo2.component @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + * 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 . + --> + +<component loader="com.sun.star.loader.SharedLibrary" environment="@CPPU_ENV@" + xmlns="http://openoffice.org/2010/uno-components"> + <implementation name="com.sun.star.comp.desktop.migration.Basic" + constructor="desktop_BasicMigration_get_implementation"> + <service name="com.sun.star.migration.Basic"/> + </implementation> + <implementation name="com.sun.star.comp.desktop.migration.Wordbooks" + constructor="desktop_WordbookMigration_get_implementation"> + <service name="com.sun.star.migration.Wordbooks"/> + </implementation> +</component> diff --git a/desktop/source/migration/services/migrationoo3.component b/desktop/source/migration/services/migrationoo3.component new file mode 100644 index 0000000000..74432e586d --- /dev/null +++ b/desktop/source/migration/services/migrationoo3.component @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + * 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 . + --> + +<component loader="com.sun.star.loader.SharedLibrary" environment="@CPPU_ENV@" + xmlns="http://openoffice.org/2010/uno-components"> + <implementation name="com.sun.star.comp.desktop.migration.OOo3Extensions" + constructor="desktop_OO3ExtensionMigration_get_implementation"> + <service name="com.sun.star.migration.Extensions"/> + </implementation> +</component> diff --git a/desktop/source/migration/services/misc.hxx b/desktop/source/migration/services/misc.hxx new file mode 100644 index 0000000000..c3b83b82e6 --- /dev/null +++ b/desktop/source/migration/services/misc.hxx @@ -0,0 +1,39 @@ +/* -*- 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> + +#include <vector> +#include <memory> + + +namespace migration +{ + + + typedef std::vector< OUString > TStringVector; + typedef std::unique_ptr< TStringVector > TStringVectorPtr; + + +} // namespace migration + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/migration/services/oo3extensionmigration.cxx b/desktop/source/migration/services/oo3extensionmigration.cxx new file mode 100644 index 0000000000..174f82ec69 --- /dev/null +++ b/desktop/source/migration/services/oo3extensionmigration.cxx @@ -0,0 +1,409 @@ +/* -*- 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 "oo3extensionmigration.hxx" +#include <sal/log.hxx> +#include <osl/file.hxx> +#include <comphelper/diagnose_ex.hxx> +#include <unotools/bootstrap.hxx> +#include <unotools/textsearch.hxx> +#include <comphelper/sequence.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <rtl/ref.hxx> + +#include <com/sun/star/task/XInteractionApprove.hpp> +#include <com/sun/star/ucb/CommandAbortedException.hpp> +#include <com/sun/star/ucb/XCommandEnvironment.hpp> +#include <com/sun/star/ucb/SimpleFileAccess.hpp> +#include <com/sun/star/xml/xpath/XPathAPI.hpp> +#include <com/sun/star/xml/xpath/XPathException.hpp> +#include <com/sun/star/xml/dom/DOMException.hpp> +#include <com/sun/star/xml/dom/DocumentBuilder.hpp> +#include <com/sun/star/beans/NamedValue.hpp> +#include <com/sun/star/deployment/ExtensionManager.hpp> +#include <com/sun/star/deployment/XExtensionManager.hpp> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; + +namespace migration +{ + +// ExtensionMigration + + +OO3ExtensionMigration::OO3ExtensionMigration(Reference< XComponentContext > const & ctx) : +m_ctx(ctx) +{ +} + + +OO3ExtensionMigration::~OO3ExtensionMigration() +{ +} + +void OO3ExtensionMigration::scanUserExtensions( const OUString& sSourceDir, TStringVector& aMigrateExtensions ) +{ + osl::Directory aScanRootDir( sSourceDir ); + osl::FileStatus fs(osl_FileStatus_Mask_Type | osl_FileStatus_Mask_FileURL); + osl::FileBase::RC nRetCode = aScanRootDir.open(); + if ( nRetCode != osl::Directory::E_None ) + return; + + sal_uInt32 nHint( 0 ); + osl::DirectoryItem aItem; + while ( aScanRootDir.getNextItem( aItem, nHint ) == osl::Directory::E_None ) + { + if (( aItem.getFileStatus(fs) == osl::FileBase::E_None ) && + ( fs.getFileType() == osl::FileStatus::Directory )) + { + //Check next folder as the "real" extension folder is below a temp folder! + OUString sExtensionFolderURL = fs.getFileURL(); + + osl::Directory aExtensionRootDir( sExtensionFolderURL ); + + nRetCode = aExtensionRootDir.open(); + if ( nRetCode == osl::Directory::E_None ) + { + osl::DirectoryItem aExtDirItem; + while ( aExtensionRootDir.getNextItem( aExtDirItem, nHint ) == osl::Directory::E_None ) + { + bool bFileStatus = aExtDirItem.getFileStatus(fs) == osl::FileBase::E_None; + bool bIsDir = fs.getFileType() == osl::FileStatus::Directory; + + if ( bFileStatus && bIsDir ) + { + sExtensionFolderURL = fs.getFileURL(); + ScanResult eResult = scanExtensionFolder( sExtensionFolderURL ); + if ( eResult == SCANRESULT_MIGRATE_EXTENSION ) + aMigrateExtensions.push_back( sExtensionFolderURL ); + break; + } + } + } + } + } +} + +OO3ExtensionMigration::ScanResult OO3ExtensionMigration::scanExtensionFolder( const OUString& sExtFolder ) +{ + ScanResult aResult = SCANRESULT_NOTFOUND; + osl::Directory aDir(sExtFolder); + + // get sub dirs + if (aDir.open() == osl::FileBase::E_None) + { + // work through directory contents... + osl::DirectoryItem item; + osl::FileStatus fs(osl_FileStatus_Mask_Type | osl_FileStatus_Mask_FileURL); + TStringVector aDirectories; + while ((aDir.getNextItem(item) == osl::FileBase::E_None ) && + ( aResult == SCANRESULT_NOTFOUND )) + { + if (item.getFileStatus(fs) == osl::FileBase::E_None) + { + if (fs.getFileType() == osl::FileStatus::Directory) + aDirectories.push_back( fs.getFileURL() ); + else + { + OUString aDirEntryURL = fs.getFileURL(); + if ( aDirEntryURL.indexOf( "/description.xml" ) > 0 ) + aResult = scanDescriptionXml( aDirEntryURL ) ? SCANRESULT_MIGRATE_EXTENSION : SCANRESULT_DONTMIGRATE_EXTENSION; + } + } + } + + for (auto const& directory : aDirectories) + { + aResult = scanExtensionFolder(directory); + if (aResult != SCANRESULT_NOTFOUND) + break; + } + } + return aResult; +} + +bool OO3ExtensionMigration::scanDescriptionXml( const OUString& sDescriptionXmlURL ) +{ + if ( !m_xDocBuilder.is() ) + { + m_xDocBuilder.set( xml::dom::DocumentBuilder::create(m_ctx) ); + } + + if ( !m_xSimpleFileAccess.is() ) + { + m_xSimpleFileAccess = ucb::SimpleFileAccess::create(m_ctx); + } + + OUString aExtIdentifier; + try + { + uno::Reference< io::XInputStream > xIn = + m_xSimpleFileAccess->openFileRead( sDescriptionXmlURL ); + + if ( xIn.is() ) + { + uno::Reference< xml::dom::XDocument > xDoc = m_xDocBuilder->parse( xIn ); + if ( xDoc.is() ) + { + uno::Reference< xml::dom::XElement > xRoot = xDoc->getDocumentElement(); + if ( xRoot.is() && xRoot->getTagName() == "description" ) + { + uno::Reference< xml::xpath::XXPathAPI > xPath = xml::xpath::XPathAPI::create(m_ctx); + + xPath->registerNS("desc", xRoot->getNamespaceURI()); + xPath->registerNS("xlink", "http://www.w3.org/1999/xlink"); + + try + { + uno::Reference< xml::dom::XNode > xNode( + xPath->selectSingleNode( + xRoot, "desc:identifier/@value" )); + if ( xNode.is() ) + aExtIdentifier = xNode->getNodeValue(); + } + catch ( const xml::xpath::XPathException& ) + { + } + catch ( const xml::dom::DOMException& ) + { + } + } + } + } + + if ( !aExtIdentifier.isEmpty() ) + { + // scan extension identifier and try to match with our black list entries + for (const OUString & i : m_aDenyList) + { + utl::SearchParam param(i, utl::SearchParam::SearchType::Regexp); + utl::TextSearch ts(param, LANGUAGE_DONTKNOW); + + sal_Int32 start = 0; + sal_Int32 end = aExtIdentifier.getLength(); + if (ts.SearchForward(aExtIdentifier, &start, &end)) + return false; + } + } + } + catch ( const ucb::CommandAbortedException& ) + { + } + catch ( const uno::RuntimeException& ) + { + } + + if ( aExtIdentifier.isEmpty() ) + { + // Fallback: + // Try to use the folder name to match our black list + // as some extensions don't provide an identifier in the + // description.xml! + for (const OUString & i : m_aDenyList) + { + utl::SearchParam param(i, utl::SearchParam::SearchType::Regexp); + utl::TextSearch ts(param, LANGUAGE_DONTKNOW); + + sal_Int32 start = 0; + sal_Int32 end = sDescriptionXmlURL.getLength(); + if (ts.SearchForward(sDescriptionXmlURL, &start, &end)) + return false; + } + } + + return true; +} + +void OO3ExtensionMigration::migrateExtension( const OUString& sSourceDir ) +{ + css::uno::Reference< css::deployment::XExtensionManager > extMgr( + deployment::ExtensionManager::get( m_ctx ) ); + try + { + rtl::Reference<TmpRepositoryCommandEnv> pCmdEnv = new TmpRepositoryCommandEnv(); + + uno::Reference< task::XAbortChannel > xAbortChannel; + extMgr->addExtension( + sSourceDir, uno::Sequence<beans::NamedValue>(), "user", + xAbortChannel, pCmdEnv ); + } + catch ( css::uno::Exception & ) + { + TOOLS_WARN_EXCEPTION( + "desktop.migration", + "Ignoring UNO Exception while migrating extension from <" << sSourceDir << ">"); + } +} + + +// XServiceInfo + + +OUString OO3ExtensionMigration::getImplementationName() +{ + return "com.sun.star.comp.desktop.migration.OOo3Extensions"; +} + + +sal_Bool OO3ExtensionMigration::supportsService(OUString const & ServiceName) +{ + return cppu::supportsService(this, ServiceName); +} + + +Sequence< OUString > OO3ExtensionMigration::getSupportedServiceNames() +{ + return { "com.sun.star.migration.Extensions" }; +} + + +// XInitialization + + +void OO3ExtensionMigration::initialize( const Sequence< Any >& aArguments ) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + + const Any* pIter = aArguments.getConstArray(); + const Any* pEnd = pIter + aArguments.getLength(); + for ( ; pIter != pEnd ; ++pIter ) + { + beans::NamedValue aValue; + *pIter >>= aValue; + if ( aValue.Name == "UserData" ) + { + if ( !(aValue.Value >>= m_sSourceDir) ) + { + OSL_FAIL( "ExtensionMigration::initialize: argument UserData has wrong type!" ); + } + } + else if ( aValue.Name == "ExtensionDenyList" ) + { + Sequence< OUString > aDenyList; + if ( (aValue.Value >>= aDenyList ) && aDenyList.hasElements()) + { + m_aDenyList.resize( aDenyList.getLength() ); + ::comphelper::sequenceToArray< OUString >( m_aDenyList.data(), aDenyList ); + } + } + } +} + +Any OO3ExtensionMigration::execute( const Sequence< beans::NamedValue >& ) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + + ::utl::Bootstrap::PathStatus aStatus = ::utl::Bootstrap::locateUserInstallation( m_sTargetDir ); + if ( aStatus == ::utl::Bootstrap::PATH_EXISTS ) + { + // copy all extensions + OUString sSourceDir = m_sSourceDir + + "/user/uno_packages/cache/uno_packages"; + TStringVector aExtensionToMigrate; + scanUserExtensions( sSourceDir, aExtensionToMigrate ); + for (auto const& extensionToMigrate : aExtensionToMigrate) + { + migrateExtension(extensionToMigrate); + } + } + + return Any(); +} + + +// TmpRepositoryCommandEnv + + +TmpRepositoryCommandEnv::TmpRepositoryCommandEnv() +{ +} + +TmpRepositoryCommandEnv::~TmpRepositoryCommandEnv() +{ +} +// XCommandEnvironment + +uno::Reference< task::XInteractionHandler > TmpRepositoryCommandEnv::getInteractionHandler() +{ + return this; +} + + +uno::Reference< ucb::XProgressHandler > TmpRepositoryCommandEnv::getProgressHandler() +{ + return this; +} + +// XInteractionHandler +void TmpRepositoryCommandEnv::handle( + uno::Reference< task::XInteractionRequest> const & xRequest ) +{ + OSL_ASSERT( xRequest->getRequest().getValueTypeClass() == uno::TypeClass_EXCEPTION ); + + bool approve = true; + + // select: + 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 (approve) { + uno::Reference< task::XInteractionApprove > xInteractionApprove( + pConts[ pos ], uno::UNO_QUERY ); + if (xInteractionApprove.is()) { + xInteractionApprove->select(); + // don't query again for ongoing continuations: + approve = false; + } + } + } +} + +// XProgressHandler +void TmpRepositoryCommandEnv::push( uno::Any const & /*Status*/ ) +{ +} + + +void TmpRepositoryCommandEnv::update( uno::Any const & /*Status */) +{ +} + +void TmpRepositoryCommandEnv::pop() +{ +} + + +} // namespace migration + + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +desktop_OO3ExtensionMigration_get_implementation( + css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const&) +{ + return cppu::acquire(new migration::OO3ExtensionMigration(context)); +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/migration/services/oo3extensionmigration.hxx b/desktop/source/migration/services/oo3extensionmigration.hxx new file mode 100644 index 0000000000..586e7e99e3 --- /dev/null +++ b/desktop/source/migration/services/oo3extensionmigration.hxx @@ -0,0 +1,117 @@ +/* -*- 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 "misc.hxx" +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/task/XJob.hpp> +#include <com/sun/star/lang/XInitialization.hpp> +#include <com/sun/star/xml/dom/XDocumentBuilder.hpp> +#include <com/sun/star/ucb/XSimpleFileAccess3.hpp> +#include <com/sun/star/ucb/XCommandEnvironment.hpp> +#include <com/sun/star/ucb/XProgressHandler.hpp> + +#include <osl/mutex.hxx> +#include <cppuhelper/implbase.hxx> + +namespace com::sun::star::uno { class XComponentContext; } + +class INetURLObject; + + +namespace migration +{ + + typedef ::cppu::WeakImplHelper< + css::lang::XServiceInfo, + css::lang::XInitialization, + css::task::XJob > ExtensionMigration_BASE; + + class OO3ExtensionMigration : public ExtensionMigration_BASE + { + private: + css::uno::Reference< css::uno::XComponentContext > m_ctx; + css::uno::Reference< css::xml::dom::XDocumentBuilder > m_xDocBuilder; + css::uno::Reference< css::ucb::XSimpleFileAccess3 > m_xSimpleFileAccess; + ::osl::Mutex m_aMutex; + OUString m_sSourceDir; + OUString m_sTargetDir; + TStringVector m_aDenyList; + + enum ScanResult + { + SCANRESULT_NOTFOUND, + SCANRESULT_MIGRATE_EXTENSION, + SCANRESULT_DONTMIGRATE_EXTENSION + }; + + ScanResult scanExtensionFolder( const OUString& sExtFolder ); + void scanUserExtensions( const OUString& sSourceDir, TStringVector& aMigrateExtensions ); + bool scanDescriptionXml( const OUString& sDescriptionXmlFilePath ); + void migrateExtension( const OUString& sSourceDir ); + + public: + explicit OO3ExtensionMigration(css::uno::Reference< + css::uno::XComponentContext > const & ctx); + virtual ~OO3ExtensionMigration() override; + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName() override; + virtual sal_Bool SAL_CALL supportsService( const OUString& rServiceName ) override; + virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames() override; + + // XInitialization + virtual void SAL_CALL initialize( const css::uno::Sequence< css::uno::Any >& aArguments ) override; + + // XJob + virtual css::uno::Any SAL_CALL execute( + const css::uno::Sequence< css::beans::NamedValue >& Arguments ) override; + }; + + class TmpRepositoryCommandEnv + : public ::cppu::WeakImplHelper< css::ucb::XCommandEnvironment, + css::task::XInteractionHandler, + css::ucb::XProgressHandler > + { + public: + virtual ~TmpRepositoryCommandEnv() override; + TmpRepositoryCommandEnv(); + + // XCommandEnvironment + virtual css::uno::Reference< css::task::XInteractionHandler > SAL_CALL + getInteractionHandler() override; + virtual css::uno::Reference< css::ucb::XProgressHandler > + SAL_CALL getProgressHandler() override; + + // XInteractionHandler + virtual void SAL_CALL handle( + css::uno::Reference< css::task::XInteractionRequest > const & xRequest ) override; + + // XProgressHandler + virtual void SAL_CALL push( css::uno::Any const & Status ) override; + virtual void SAL_CALL update( css::uno::Any const & Status ) override; + virtual void SAL_CALL pop() override; + }; + + +} // namespace migration + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/migration/services/wordbookmigration.cxx b/desktop/source/migration/services/wordbookmigration.cxx new file mode 100644 index 0000000000..a3fff88239 --- /dev/null +++ b/desktop/source/migration/services/wordbookmigration.cxx @@ -0,0 +1,231 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include "wordbookmigration.hxx" +#include <cppuhelper/supportsservice.hxx> +#include <tools/urlobj.hxx> +#include <unotools/bootstrap.hxx> +#include <unotools/ucbstreamhelper.hxx> +#include <sal/log.hxx> +#include <osl/file.hxx> +#include <com/sun/star/uno/XComponentContext.hpp> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; + + +namespace migration +{ + WordbookMigration::WordbookMigration() + { + } + + + WordbookMigration::~WordbookMigration() + { + } + + + TStringVectorPtr WordbookMigration::getFiles( const OUString& rBaseURL ) const + { + TStringVectorPtr aResult( new TStringVector ); + ::osl::Directory aDir( rBaseURL); + + if ( aDir.open() == ::osl::FileBase::E_None ) + { + // iterate over directory content + TStringVector aSubDirs; + ::osl::DirectoryItem aItem; + while ( aDir.getNextItem( aItem ) == ::osl::FileBase::E_None ) + { + ::osl::FileStatus aFileStatus( osl_FileStatus_Mask_Type | osl_FileStatus_Mask_FileURL ); + if ( aItem.getFileStatus( aFileStatus ) == ::osl::FileBase::E_None ) + { + if ( aFileStatus.getFileType() == ::osl::FileStatus::Directory ) + aSubDirs.push_back( aFileStatus.getFileURL() ); + else + aResult->push_back( aFileStatus.getFileURL() ); + } + } + + // iterate recursive over subfolders + for (auto const& subDir : aSubDirs) + { + TStringVectorPtr aSubResult = getFiles(subDir); + aResult->insert( aResult->end(), aSubResult->begin(), aSubResult->end() ); + } + } + + return aResult; + } + + + void WordbookMigration::checkAndCreateDirectory( INetURLObject const & rDirURL ) + { + ::osl::FileBase::RC aResult = ::osl::Directory::create( rDirURL.GetMainURL( INetURLObject::DecodeMechanism::ToIUri ) ); + if ( aResult == ::osl::FileBase::E_NOENT ) + { + INetURLObject aBaseURL( rDirURL ); + aBaseURL.removeSegment(); + checkAndCreateDirectory( aBaseURL ); + ::osl::Directory::create( rDirURL.GetMainURL( INetURLObject::DecodeMechanism::ToIUri ) ); + } + } + +#define MAX_HEADER_LENGTH 16 +static bool IsUserWordbook( const OUString& rFile ) +{ + bool bRet = false; + std::unique_ptr<SvStream> pStream = ::utl::UcbStreamHelper::CreateStream( rFile, StreamMode::STD_READ ); + if ( pStream && !pStream->GetError() ) + { + static const char* const pVerOOo7 = "OOoUserDict1"; + sal_uInt64 const nSniffPos = pStream->Tell(); + static std::size_t nVerOOo7Len = sal::static_int_cast< std::size_t >(strlen( pVerOOo7 )); + char pMagicHeader[MAX_HEADER_LENGTH]; + pMagicHeader[ nVerOOo7Len ] = '\0'; + if (pStream->ReadBytes(static_cast<void *>(pMagicHeader), nVerOOo7Len) == nVerOOo7Len) + { + if ( !strcmp(pMagicHeader, pVerOOo7) ) + bRet = true; + else + { + sal_uInt16 nLen; + pStream->Seek (nSniffPos); + pStream->ReadUInt16( nLen ); + if ( nLen < MAX_HEADER_LENGTH ) + { + pStream->ReadBytes(pMagicHeader, nLen); + pMagicHeader[nLen] = '\0'; + if ( !strcmp(pMagicHeader, "WBSWG2") + || !strcmp(pMagicHeader, "WBSWG5") + || !strcmp(pMagicHeader, "WBSWG6") ) + bRet = true; + } + } + } + } + + return bRet; +} + + + void WordbookMigration::copyFiles() + { + OUString sTargetDir; + ::utl::Bootstrap::PathStatus aStatus = ::utl::Bootstrap::locateUserInstallation( sTargetDir ); + if ( aStatus == ::utl::Bootstrap::PATH_EXISTS ) + { + sTargetDir += "/user/wordbook"; + TStringVectorPtr aFileList = getFiles( m_sSourceDir ); + for (auto const& elem : *aFileList) + { + if (IsUserWordbook(elem) ) + { + std::u16string_view sSourceLocalName = elem.subView( m_sSourceDir.getLength() ); + OUString sTargetName = sTargetDir + sSourceLocalName; + INetURLObject aURL( sTargetName ); + aURL.removeSegment(); + checkAndCreateDirectory( aURL ); + ::osl::FileBase::RC aResult = ::osl::File::copy( elem, sTargetName ); + if ( aResult != ::osl::FileBase::E_None ) + { + SAL_WARN( "desktop", "WordbookMigration::copyFiles: cannot copy " + << elem << " to " << sTargetName); + } + } + } + } + else + { + OSL_FAIL( "WordbookMigration::copyFiles: no user installation!" ); + } + } + + + // XServiceInfo + + + OUString WordbookMigration::getImplementationName() + { + return "com.sun.star.comp.desktop.migration.Wordbooks"; + } + + + sal_Bool WordbookMigration::supportsService(OUString const & ServiceName) + { + return cppu::supportsService(this, ServiceName); + } + + + Sequence< OUString > WordbookMigration::getSupportedServiceNames() + { + return { "com.sun.star.migration.Wordbooks" }; + } + + + // XInitialization + + + void WordbookMigration::initialize( const Sequence< Any >& aArguments ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + + const Any* pIter = aArguments.getConstArray(); + const Any* pEnd = pIter + aArguments.getLength(); + for ( ; pIter != pEnd ; ++pIter ) + { + beans::NamedValue aValue; + *pIter >>= aValue; + if ( aValue.Name == "UserData" ) + { + if ( !(aValue.Value >>= m_sSourceDir) ) + { + OSL_FAIL( "WordbookMigration::initialize: argument UserData has wrong type!" ); + } + m_sSourceDir += "/user/wordbook"; + break; + } + } + } + + + // XJob + + + Any WordbookMigration::execute( const Sequence< beans::NamedValue >& ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + + copyFiles(); + + return Any(); + } + +} // namespace migration + + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +desktop_WordbookMigration_get_implementation( + css::uno::XComponentContext* , css::uno::Sequence<css::uno::Any> const&) +{ + return cppu::acquire(new migration::WordbookMigration()); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/migration/services/wordbookmigration.hxx b/desktop/source/migration/services/wordbookmigration.hxx new file mode 100644 index 0000000000..a6388fa421 --- /dev/null +++ b/desktop/source/migration/services/wordbookmigration.hxx @@ -0,0 +1,72 @@ +/* -*- 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 "misc.hxx" +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/lang/XInitialization.hpp> +#include <com/sun/star/task/XJob.hpp> +#include <cppuhelper/implbase.hxx> +#include <osl/mutex.hxx> + + +class INetURLObject; + + +namespace migration +{ + + typedef ::cppu::WeakImplHelper< + css::lang::XServiceInfo, + css::lang::XInitialization, + css::task::XJob > WordbookMigration_BASE; + + class WordbookMigration : public WordbookMigration_BASE + { + private: + ::osl::Mutex m_aMutex; + OUString m_sSourceDir; + + TStringVectorPtr getFiles( const OUString& rBaseURL ) const; + void checkAndCreateDirectory( INetURLObject const & rDirURL ); + void copyFiles(); + + public: + WordbookMigration(); + virtual ~WordbookMigration() override; + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName() override; + virtual sal_Bool SAL_CALL supportsService( const OUString& rServiceName ) override; + virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames() override; + + // XInitialization + virtual void SAL_CALL initialize( const css::uno::Sequence< css::uno::Any >& aArguments ) override; + + // XJob + virtual css::uno::Any SAL_CALL execute( + const css::uno::Sequence< css::beans::NamedValue >& Arguments ) override; + }; + + +} // namespace migration + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/minidump/minidump.cxx b/desktop/source/minidump/minidump.cxx new file mode 100644 index 0000000000..0a31fff6f2 --- /dev/null +++ b/desktop/source/minidump/minidump.cxx @@ -0,0 +1,239 @@ +/* -*- 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/minidump.hxx> +#include <sal/log.hxx> + +#include <map> +#include <fstream> +#include <sstream> +#include <string> + +#include <curl/curl.h> + +#include <systools/curlinit.hxx> + +#ifdef _WIN32 +#include <memory> +#include <windows.h> +#endif + +const char kUserAgent[] = "Breakpad/1.0 (Linux)"; + +static std::map<std::string, std::string> readStrings(std::istream& file) +{ + std::map<std::string, std::string> parameters; + + // when file is not readable, the status eof would not be set + // better test of state is okay + while (file) + { + std::string line; + std::getline(file, line); + int sep = line.find('='); + if (sep >= 0) + { + std::string key = line.substr(0, sep); + std::string value = line.substr(sep + 1); + parameters[key] = value; + } + } + + return parameters; +} + +// Callback to get the response data from server. +static size_t WriteCallback(void const *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 const *>(ptr), real_size); + return real_size; +} + +static void getProperty(const std::string& key, std::string& value, + std::map<std::string, std::string>& parameters) +{ + auto itr = parameters.find(key); + if (itr != parameters.end()) + { + value = itr->second; + parameters.erase(itr); + } +} + +static std::string generate_json(const std::map<std::string, std::string>& parameters) +{ + std::ostringstream stream; + stream << "{\n"; + bool first = true; + for (auto itr = parameters.begin(), itrEnd = parameters.end(); itr != itrEnd; ++itr) + { + if (!first) + { + stream << ",\n"; + } + first = false; + stream << "\"" << itr->first << "\": \"" << itr->second << "\""; + } + stream << "\n}"; + + return stream.str(); +} + +static bool uploadContent(std::map<std::string, std::string>& parameters, std::string& response) +{ + CURL* curl = curl_easy_init(); + if (!curl) + return false; + + ::InitCurl_easy(curl); + + std::string proxy, proxy_user_pwd, ca_certificate_file, file, url, version; + + getProperty("Proxy", proxy, parameters); + getProperty("ProxyUserPW", proxy_user_pwd, parameters); + getProperty("CAFile", ca_certificate_file, parameters); + + getProperty("DumpFile", file, parameters); + getProperty("URL", url, parameters); + getProperty("Version", version, parameters); + if (url.empty()) + return false; + + if (file.empty()) + return false; + + if (version.empty()) + return false; + + curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl, CURLOPT_USERAGENT, kUserAgent); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, false); + // Set proxy information if necessary. + if (!proxy.empty()) + { + curl_easy_setopt(curl, CURLOPT_PROXY, proxy.c_str()); + + curl_easy_setopt(curl, CURLOPT_PROXYAUTH, CURLAUTH_ANYSAFE); + + if (!proxy_user_pwd.empty()) + curl_easy_setopt(curl, CURLOPT_PROXYUSERPWD, proxy_user_pwd.c_str()); + else + curl_easy_setopt(curl, CURLOPT_PROXYUSERPWD, ":"); + } + + if (!ca_certificate_file.empty()) + curl_easy_setopt(curl, CURLOPT_CAINFO, ca_certificate_file.c_str()); + + curl_mime* mime = curl_mime_init(curl); + std::string additional_data = generate_json(parameters); + curl_mimepart* part = curl_mime_addpart(mime); + curl_mime_name(part, "AdditionalData"); + curl_mime_data(part, additional_data.c_str(), CURL_ZERO_TERMINATED); + + part = curl_mime_addpart(mime); + curl_mime_name(part, "Version"); + curl_mime_data(part, version.c_str(), CURL_ZERO_TERMINATED); + + part = curl_mime_addpart(mime); + curl_mime_name(part, "upload_file_minidump"); + curl_mime_filedata(part, file.c_str()); + + curl_easy_setopt(curl, CURLOPT_MIMEPOST, mime); + + + // Disable 100-continue header. + char buf[] = "Expect:"; + curl_slist* headerlist = nullptr; + headerlist = curl_slist_append(headerlist, buf); + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headerlist); + + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback); + std::string response_body; + curl_easy_setopt(curl, CURLOPT_WRITEDATA, + static_cast<void *>(&response_body)); + + // Fail if 400+ is returned from the web server. + curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1); + + CURLcode cc = curl_easy_perform(curl); + long response_code; + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code); + SAL_WARN_IF(cc != CURLE_OK, "desktop", + "Failed to send http request to " << + url.c_str() << + ", error: " << + curl_easy_strerror(cc)); + + if (headerlist != nullptr) + { + curl_slist_free_all(headerlist); + } + + response = response_body; + + if( CURLE_OK != cc ) + return false; + + return true; +} + +namespace crashreport { + +bool readConfig(const std::string& iniPath, std::string * response) +{ +#if defined _WIN32 + 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::ifstream file = iniPathW.empty() ? std::ifstream(iniPath) : std::ifstream(iniPathW); +#else + std::ifstream file(iniPath); +#endif + std::map<std::string, std::string> parameters = readStrings(file); + + // make sure that at least the mandatory parameters are in there + if (parameters.find("DumpFile") == parameters.end()) + { + if(response != nullptr) + *response = "ini file needs to contain a key DumpFile!"; + return false; + } + + if (parameters.find("Version") == parameters.end()) + { + if (response != nullptr) + *response = "ini file needs to contain a key Version!"; + return false; + } + + if (parameters.find("URL") == parameters.end()) + { + if (response != nullptr) + *response = "ini file needs to contain a key URL!"; + return false; + } + + if (response != nullptr) + return uploadContent(parameters, *response); + + return true; +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/minidump/minidump_upload.cxx b/desktop/source/minidump/minidump_upload.cxx new file mode 100644 index 0000000000..0434fda684 --- /dev/null +++ b/desktop/source/minidump/minidump_upload.cxx @@ -0,0 +1,47 @@ +/* -*- 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/minidump.hxx> + +#include <iostream> +#include <string> + +#ifdef _WIN32 +#include <memory> +#include <windows.h> + +int wmain(int argc, wchar_t** argv) +#else +int main(int argc, char** argv) +#endif +{ + if (argc < 2) + { + std::cerr << "minidump_upload path_to_ini_file" << std::endl; + return EXIT_FAILURE; + } + +#ifdef _WIN32 + const int nBytes = WideCharToMultiByte(CP_UTF8, 0, argv[1], -1, nullptr, 0, nullptr, nullptr); + auto buf = std::make_unique<char[]>(nBytes); + if (WideCharToMultiByte(CP_UTF8, 0, argv[1], -1, buf.get(), nBytes, nullptr, nullptr) == 0) + return EXIT_FAILURE; + std::string iniPath(buf.get()); +#else + std::string iniPath(argv[1]); +#endif + std::string response; + if (!crashreport::readConfig(iniPath, &response)) + return EXIT_FAILURE; + + std::cout << "Response: " << response << std::endl; + return EXIT_SUCCESS; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/offacc/acceptor.cxx b/desktop/source/offacc/acceptor.cxx new file mode 100644 index 0000000000..9598466d9c --- /dev/null +++ b/desktop/source/offacc/acceptor.cxx @@ -0,0 +1,254 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include "acceptor.hxx" +#include <com/sun/star/bridge/BridgeFactory.hpp> +#include <com/sun/star/connection/Acceptor.hpp> +#include <com/sun/star/uno/XNamingService.hpp> +#include <officecfg/Office/Security.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <sal/log.hxx> +#include <comphelper/diagnose_ex.hxx> +#include <o3tl/string_view.hxx> + +using namespace css::bridge; +using namespace css::connection; +using namespace css::lang; +using namespace css::uno; + +namespace desktop +{ + +extern "C" { + +static void offacc_workerfunc (void * acc) +{ + osl_setThreadName("URP Acceptor"); + + static_cast<Acceptor*>(acc)->run(); +} + +} + +Acceptor::Acceptor( const Reference< XComponentContext >& rxContext ) + : m_thread(nullptr) + , m_rContext(rxContext) + , m_bInit(false) + , m_bDying(false) +{ + m_rAcceptor = css::connection::Acceptor::create(m_rContext); + m_rBridgeFactory = BridgeFactory::create(m_rContext); +} + + +Acceptor::~Acceptor() +{ + m_rAcceptor->stopAccepting(); + oslThread t; + { + std::unique_lock g(m_aMutex); + t = m_thread; + } + //prevent locking if the thread is still waiting + m_bDying = true; + m_cEnable.set(); + osl_joinWithThread(t); + osl_destroyThread(t); + { + // Make the final state of m_bridges visible to this thread (since + // m_thread is joined, the code that follows is the only one left + // accessing m_bridges): + std::unique_lock g(m_aMutex); + } + for (;;) { + css::uno::Reference< css::bridge::XBridge > b(m_bridges.remove()); + if (!b.is()) { + break; + } + css::uno::Reference< css::lang::XComponent >( + b, css::uno::UNO_QUERY_THROW)->dispose(); + } +} + +void Acceptor::run() +{ + SAL_INFO( "desktop.offacc", "Acceptor::run" ); + for (;;) + { + try + { + // wait until we get enabled + SAL_INFO( "desktop.offacc", + "Acceptor::run waiting for office to come up"); + m_cEnable.wait(); + if (m_bDying) //see destructor + break; + SAL_INFO( "desktop.offacc", + "Acceptor::run now enabled and continuing"); + + // accept connection + Reference< XConnection > rConnection = m_rAcceptor->accept( m_aConnectString ); + // if we return without a valid connection we must assume that the acceptor + // is destructed so we break out of the run method terminating the thread + if (! rConnection.is()) break; + OUString aDescription = rConnection->getDescription(); + SAL_INFO( "desktop.offacc", "Acceptor::run connection " << aDescription ); + + // create instanceprovider for this connection + Reference< XInstanceProvider > rInstanceProvider(new AccInstanceProvider(m_rContext)); + // create the bridge. The remote end will have a reference to this bridge + // thus preventing the bridge from being disposed. When the remote end releases + // the bridge, it will be destructed. + Reference< XBridge > rBridge = m_rBridgeFactory->createBridge( + "", m_aProtocol, rConnection, rInstanceProvider); + std::unique_lock g(m_aMutex); + m_bridges.add(rBridge); + } catch (const Exception&) { + TOOLS_WARN_EXCEPTION("desktop.offacc", ""); + // connection failed... + // something went wrong during connection setup. + // just wait for a new connection to accept + } + } +} + +// XInitialize +void Acceptor::initialize( const Sequence<Any>& aArguments ) +{ + // prevent multiple initialization + std::unique_lock aGuard( m_aMutex ); + SAL_INFO( "desktop.offacc", "Acceptor::initialize()" ); + + bool bOk = false; + + // arg count + int nArgs = aArguments.getLength(); + + // not yet initialized and accept-string + if (!m_bInit && nArgs > 0 && (aArguments[0] >>= m_aAcceptString)) + { + SAL_INFO( "desktop.offacc", "Acceptor::initialize string=" << m_aAcceptString ); + + // get connect string and protocol from accept string + // "<connectString>;<protocol>" + sal_Int32 nIndex1 = m_aAcceptString.indexOf( ';' ); + if (nIndex1 < 0) + throw IllegalArgumentException( + "Invalid accept-string format", m_rContext, 1); + m_aConnectString = o3tl::trim(m_aAcceptString.subView( 0 , nIndex1 )); + nIndex1++; + sal_Int32 nIndex2 = m_aAcceptString.indexOf( ';' , nIndex1 ); + if (nIndex2 < 0) nIndex2 = m_aAcceptString.getLength(); + m_aProtocol = m_aAcceptString.copy( nIndex1, nIndex2 - nIndex1 ); + + // start accepting in new thread... + m_thread = osl_createThread(offacc_workerfunc, this); + m_bInit = true; + bOk = true; + } + + // do we want to enable accepting? + bool bEnable = false; + if (((nArgs == 1 && (aArguments[0] >>= bEnable)) || + (nArgs == 2 && (aArguments[1] >>= bEnable))) && + bEnable ) + { + m_cEnable.set(); + bOk = true; + } + + if (!bOk) + { + throw IllegalArgumentException( "invalid initialization", m_rContext, 1); + } +} + +// XServiceInfo +OUString Acceptor::getImplementationName() +{ + return "com.sun.star.office.comp.Acceptor"; +} +Sequence<OUString> Acceptor::getSupportedServiceNames() +{ + return { "com.sun.star.office.Acceptor" }; +} + +sal_Bool Acceptor::supportsService(OUString const & ServiceName) +{ + return cppu::supportsService(this, ServiceName); +} + + +// InstanceProvider +AccInstanceProvider::AccInstanceProvider(const Reference<XComponentContext>& rxContext) + : m_rContext(rxContext) +{ +} + +AccInstanceProvider::~AccInstanceProvider() +{ +} + +Reference<XInterface> AccInstanceProvider::getInstance (const OUString& aName ) +{ + + Reference<XInterface> rInstance; + + if ( aName == "StarOffice.ServiceManager" ) + { + rInstance.set( m_rContext->getServiceManager() ); + } + else if ( aName == "StarOffice.ComponentContext" ) + { + rInstance = m_rContext; + } + else if ( aName == "StarOffice.NamingService" ) + { + Reference< XNamingService > rNamingService( + m_rContext->getServiceManager()->createInstanceWithContext("com.sun.star.uno.NamingService", m_rContext), + UNO_QUERY ); + if ( rNamingService.is() ) + { + rNamingService->registerObject( "StarOffice.ServiceManager", m_rContext->getServiceManager() ); + rNamingService->registerObject( "StarOffice.ComponentContext", m_rContext ); + rInstance = rNamingService; + } + } + return rInstance; +} + +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +desktop_Acceptor_get_implementation( + css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const&) +{ + if (!officecfg::Office::Security::Net::AllowInsecureUNORemoteProtocol::get()) + { + // this is not allowed to throw + SAL_WARN("desktop", "UNO Remote Protocol is disabled by configuration"); + return nullptr; + } + return cppu::acquire(new desktop::Acceptor(context)); +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/offacc/acceptor.hxx b/desktop/source/offacc/acceptor.hxx new file mode 100644 index 0000000000..9e210459a5 --- /dev/null +++ b/desktop/source/offacc/acceptor.hxx @@ -0,0 +1,95 @@ +/* -*- 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 <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/uno/Reference.h> +#include <com/sun/star/connection/XAcceptor.hpp> +#include <com/sun/star/lang/XInitialization.hpp> +#include <com/sun/star/bridge/XInstanceProvider.hpp> +#include <com/sun/star/bridge/XBridgeFactory2.hpp> +#include <cppuhelper/implbase.hxx> + +#include <comphelper/weakbag.hxx> +#include <osl/conditn.hxx> +#include <osl/thread.hxx> + +#include <mutex> + +namespace com::sun::star::uno { class XComponentContext; } + +namespace desktop { + +class Acceptor + : public ::cppu::WeakImplHelper<css::lang::XServiceInfo, css::lang::XInitialization> +{ +private: + std::mutex m_aMutex; + + oslThread m_thread; + comphelper::WeakBag< css::bridge::XBridge > m_bridges; + + ::osl::Condition m_cEnable; + + css::uno::Reference< css::uno::XComponentContext > m_rContext; + css::uno::Reference< css::connection::XAcceptor > m_rAcceptor; + css::uno::Reference< css::bridge::XBridgeFactory2 > m_rBridgeFactory; + + OUString m_aAcceptString; + OUString m_aConnectString; + OUString m_aProtocol; + + bool m_bInit; + bool m_bDying; + +public: + explicit Acceptor( const css::uno::Reference< css::uno::XComponentContext >& rxContext ); + virtual ~Acceptor() override; + + void run(); + + // XService info + virtual OUString SAL_CALL getImplementationName() override; + virtual css::uno::Sequence<OUString> SAL_CALL getSupportedServiceNames() override; + virtual sal_Bool SAL_CALL supportsService( const OUString& aName ) override; + + // XInitialize + virtual void SAL_CALL initialize( const css::uno::Sequence<css::uno::Any>& aArguments ) override; +}; + +class AccInstanceProvider : public ::cppu::WeakImplHelper<css::bridge::XInstanceProvider> +{ +private: + css::uno::Reference<css::uno::XComponentContext> m_rContext; + +public: + AccInstanceProvider(const css::uno::Reference< css::uno::XComponentContext >& rxContext); + virtual ~AccInstanceProvider() override; + + // XInstanceProvider + virtual css::uno::Reference<css::uno::XInterface> SAL_CALL getInstance (const OUString& aName ) override; +}; + + +} //namespace desktop + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/offacc/offacc.component b/desktop/source/offacc/offacc.component new file mode 100644 index 0000000000..d46f8fac7c --- /dev/null +++ b/desktop/source/offacc/offacc.component @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + * 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 . + --> + +<component loader="com.sun.star.loader.SharedLibrary" environment="@CPPU_ENV@" + xmlns="http://openoffice.org/2010/uno-components"> + <implementation name="com.sun.star.office.comp.Acceptor" + constructor="desktop_Acceptor_get_implementation"> + <service name="com.sun.star.office.Acceptor"/> + </implementation> +</component> diff --git a/desktop/source/pkgchk/unopkg/unopkg_app.cxx b/desktop/source/pkgchk/unopkg/unopkg_app.cxx new file mode 100644 index 0000000000..0ecb328b7c --- /dev/null +++ b/desktop/source/pkgchk/unopkg/unopkg_app.cxx @@ -0,0 +1,655 @@ +/* -*- 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 <dp_misc.h> +#include "unopkg_main.h" +#include "unopkg_shared.h" +#include <dp_identifier.hxx> +#include <tools/extendapplicationenvironment.hxx> +#include <rtl/bootstrap.hxx> +#include <rtl/textenc.h> +#include <rtl/ustring.hxx> +#include <osl/process.h> +#include <osl/conditn.hxx> +#include <unotools/tempfile.hxx> +#include <cppuhelper/implbase.hxx> +#include <cppuhelper/exc_hlp.hxx> +#include <comphelper/anytostring.hxx> +#include <comphelper/logging.hxx> +#include <comphelper/sequence.hxx> +#include <com/sun/star/deployment/DeploymentException.hpp> +#include <com/sun/star/deployment/ExtensionManager.hpp> + +#include <com/sun/star/deployment/ui/PackageManagerDialog.hpp> +#include <com/sun/star/lang/IllegalArgumentException.hpp> +#include <com/sun/star/logging/ConsoleHandler.hpp> +#include <com/sun/star/logging/FileHandler.hpp> +#include <com/sun/star/logging/LogLevel.hpp> +#include <com/sun/star/logging/SimpleTextFormatter.hpp> +#include <com/sun/star/logging/XLogger.hpp> +#include <com/sun/star/ucb/CommandAbortedException.hpp> +#include <com/sun/star/ucb/CommandFailedException.hpp> +#include <com/sun/star/ui/dialogs/XDialogClosedListener.hpp> +#if defined(UNX) + #include <unistd.h> +#endif +#include <iostream> +#include <utility> +#include <vector> + + +using namespace ::com::sun::star; +using namespace ::com::sun::star::logging; +using namespace ::com::sun::star::uno; +using namespace ::unopkg; + +namespace { + +struct ExtensionName +{ + OUString m_str; + explicit ExtensionName( OUString str ) : m_str(std::move( str )) {} + bool operator () ( Reference<deployment::XPackage> const & e ) const + { + return m_str == dp_misc::getIdentifier(e) + || m_str == e->getName(); + } +}; + + +const char16_t s_usingText [] = +u"\n" +"using: " APP_NAME " add <options> extension-path...\n" +" " APP_NAME " validate <options> extension-identifier...\n" +" " APP_NAME " remove <options> extension-identifier...\n" +" " APP_NAME " list <options> extension-identifier...\n" +" " APP_NAME " reinstall <options>\n" +" " APP_NAME " gui\n" +" " APP_NAME " -V\n" +" " APP_NAME " -h\n" +"\n" +"sub-commands:\n" +" add add extension\n" +" validate checks the prerequisites of an installed extension and\n" +" registers it if possible\n" +" remove remove extensions by identifier\n" +" reinstall expert feature: reinstall all deployed extensions\n" +" list list information about deployed extensions\n" +" gui raise Extensions dialog\n" +"\n" +"options:\n" +" -h, --help this help\n" +" -V, --version version information\n" +" -v, --verbose verbose output\n" +" -f, --force force overwriting existing extensions\n" +" -s, --suppress-license prevents showing the license\n" +" --log-file <file> custom log file; default: <cache-dir>/log.txt\n" +" --shared expert feature: operate on shared installation\n" +" deployment context;\n" +" run only when no concurrent Office\n" +" process(es) are running!\n" +" --bundled expert feature: operate on bundled extensions. Only\n" +" works with list, validate, reinstall;\n" +" --deployment-context expert feature: explicit deployment context\n" +" <context>\n" +"\n" +"To learn more about extensions, see:\n" +"https://wiki.documentfoundation.org/Documentation/DevGuide/Extensions\n\n"; + + +const OptionInfo s_option_infos [] = { + { RTL_CONSTASCII_STRINGPARAM("help"), 'h', false }, + { RTL_CONSTASCII_STRINGPARAM("version"), 'V', false }, + { RTL_CONSTASCII_STRINGPARAM("verbose"), 'v', false }, + { RTL_CONSTASCII_STRINGPARAM("force"), 'f', false }, + { RTL_CONSTASCII_STRINGPARAM("log-file"), '\0', true }, + { RTL_CONSTASCII_STRINGPARAM("shared"), '\0', false }, + { RTL_CONSTASCII_STRINGPARAM("deployment-context"), '\0', true }, + { RTL_CONSTASCII_STRINGPARAM("bundled"), '\0', false}, + { RTL_CONSTASCII_STRINGPARAM("suppress-license"), 's', false}, + + { nullptr, 0, '\0', false } +}; + +void logFatal( + comphelper::EventLogger const * logger, sal_Int32 level, OUString const & message, + OUString const & argument) +{ + if (logger == nullptr) { + // Best effort; potentially loses data due to conversion failures (stray surrogate halves) + // and embedded null characters: + std::cerr + << OUStringToOString(message.replaceFirst("$1$", argument), RTL_TEXTENCODING_UTF8) + << '\n'; + } else { + logger->log(level, message, argument); + } +} + +class DialogClosedListenerImpl : + public ::cppu::WeakImplHelper< ui::dialogs::XDialogClosedListener > +{ + osl::Condition & m_rDialogClosedCondition; + +public: + explicit DialogClosedListenerImpl( osl::Condition & rDialogClosedCondition ) + : m_rDialogClosedCondition( rDialogClosedCondition ) {} + + // XEventListener (base of XDialogClosedListener) + virtual void SAL_CALL disposing( lang::EventObject const & Source ) override; + + // XDialogClosedListener + virtual void SAL_CALL dialogClosed( + ui::dialogs::DialogClosedEvent const & aEvent ) override; +}; + +// XEventListener (base of XDialogClosedListener) +void DialogClosedListenerImpl::disposing( lang::EventObject const & ) +{ + // nothing to do +} + +// XDialogClosedListener +void DialogClosedListenerImpl::dialogClosed( + ui::dialogs::DialogClosedEvent const & ) +{ + m_rDialogClosedCondition.set(); +} + +// If a package had been installed with a pre OOo 2.2, it could not normally be +// found via its identifier; similarly (and for ease of use), a package +// installed with OOo 2.2 or later could not normally be found via its file +// name. +Reference<deployment::XPackage> findPackage( + OUString const & repository, + Reference<deployment::XExtensionManager> const & manager, + Reference<ucb::XCommandEnvironment > const & environment, + std::u16string_view idOrFileName ) +{ + const Sequence< Reference<deployment::XPackage> > ps( + manager->getDeployedExtensions(repository, + Reference<task::XAbortChannel>(), environment ) ); + for ( auto const & package : ps ) + if ( dp_misc::getIdentifier( package ) == idOrFileName ) + return package; + for ( auto const & package : ps ) + if ( package->getName() == idOrFileName ) + return package; + return Reference<deployment::XPackage>(); +} + +} // anon namespace + +extern "C" int unopkg_main() +{ + tools::extendApplicationEnvironment(); + bool bShowFailedMsg = true; + OUString subCommand; + bool option_shared = false; + bool option_force = false; + bool option_verbose = false; + bool option_bundled = false; + bool option_suppressLicense = false; + bool option_help = false; + bool subcmd_gui = false; + OUString logFile; + OUString repository; + OUString cmdArg; + std::vector<OUString> cmdPackages; + Reference<XLogHandler> xFileHandler; + Reference<XLogHandler> xConsoleHandler; + std::unique_ptr<comphelper::EventLogger> logger; + std::unique_ptr<utl::TempFileNamed> pUserProfileTempDir; + + OptionInfo const * info_shared = getOptionInfo( + s_option_infos, "shared" ); + OptionInfo const * info_force = getOptionInfo( + s_option_infos, "force" ); + OptionInfo const * info_verbose = getOptionInfo( + s_option_infos, "verbose" ); + OptionInfo const * info_log = getOptionInfo( + s_option_infos, "log-file" ); + OptionInfo const * info_context = getOptionInfo( + s_option_infos, "deployment-context" ); + OptionInfo const * info_help = getOptionInfo( + s_option_infos, "help" ); + OptionInfo const * info_version = getOptionInfo( + s_option_infos, "version" ); + OptionInfo const * info_bundled = getOptionInfo( + s_option_infos, "bundled" ); + OptionInfo const * info_suppressLicense = getOptionInfo( + s_option_infos, "suppress-license" ); + + + Reference<XComponentContext> xComponentContext; + Reference<XComponentContext> xLocalComponentContext; + + try { + sal_uInt32 nPos = 0; + sal_uInt32 nCount = osl_getCommandArgCount(); + if (nCount == 0 || isOption( info_help, &nPos )) + { + dp_misc::writeConsole(s_usingText); + return 0; + } + else if (isOption( info_version, &nPos )) { + dp_misc::writeConsole(u"\n" APP_NAME " Version 3.3\n"); + return 0; + } + //consume all bootstrap variables which may occur before the sub-command + while(isBootstrapVariable(&nPos)) + ; + + if(nPos >= nCount) + return 0; + //get the sub-command + osl_getCommandArg( nPos, &subCommand.pData ); + ++nPos; + subCommand = subCommand.trim(); + bool subcmd_add = subCommand == "add"; + subcmd_gui = subCommand == "gui"; + + // sub-command options and packages: + while (nPos < nCount) + { + if (readArgument( &cmdArg, info_log, &nPos )) { + logFile = makeAbsoluteFileUrl( + cmdArg.trim(), getProcessWorkingDir() ); + } + else if (!readOption( &option_verbose, info_verbose, &nPos ) && + !readOption( &option_shared, info_shared, &nPos ) && + !readOption( &option_force, info_force, &nPos ) && + !readOption( &option_bundled, info_bundled, &nPos ) && + !readOption( &option_suppressLicense, info_suppressLicense, &nPos ) && + !readOption( &option_help, info_help, &nPos ) && + !readArgument( &repository, info_context, &nPos ) && + !isBootstrapVariable(&nPos)) + { + osl_getCommandArg( nPos, &cmdArg.pData ); + ++nPos; + cmdArg = cmdArg.trim(); + if (!cmdArg.isEmpty()) + { + if (cmdArg[ 0 ] == '-') + { + // is option: + dp_misc::writeConsoleError(Concat2View( + "\nERROR: unexpected option " + + cmdArg + + "!\n Use " APP_NAME " " + + toString(info_help) + + " to print all options.\n")); + return 1; + } + else + { + // is package: + cmdPackages.push_back( + subcmd_add || subcmd_gui + ? makeAbsoluteFileUrl( + cmdArg, getProcessWorkingDir() ) + : cmdArg ); + } + } + } + } + + // tdf#129917 Use temp user profile when installing shared extensions + if (option_shared) + { + pUserProfileTempDir.reset(new utl::TempFileNamed(nullptr, true)); + pUserProfileTempDir->EnableKillingFile(); + } + + xComponentContext = getUNO(option_verbose, subcmd_gui, + pUserProfileTempDir ? pUserProfileTempDir->GetURL() : "", + xLocalComponentContext); + + // Initialize logging. This will log errors to the console and + // also to file if the --log-file parameter was provided. + logger.reset(new comphelper::EventLogger(xLocalComponentContext, "unopkg")); + const Reference<XLogger> xLogger(logger->getLogger()); + xLogger->setLevel(LogLevel::WARNING); + Reference<XLogFormatter> xLogFormatter(SimpleTextFormatter::create(xLocalComponentContext)); + Sequence < beans::NamedValue > aSeq { { "Formatter", Any(xLogFormatter) } }; + + xConsoleHandler.set(ConsoleHandler::createWithSettings(xLocalComponentContext, aSeq)); + xLogger->addLogHandler(xConsoleHandler); + xConsoleHandler->setLevel(LogLevel::WARNING); + xLogger->setLevel(LogLevel::WARNING); + + + if (!logFile.isEmpty()) + { + Sequence < beans::NamedValue > aSeq2 { { "Formatter", Any(xLogFormatter) }, {"FileURL", Any(logFile)} }; + xFileHandler.set(css::logging::FileHandler::createWithSettings(xLocalComponentContext, aSeq2)); + xFileHandler->setLevel(LogLevel::WARNING); + xLogger->addLogHandler(xFileHandler); + } + + if (option_verbose) + { + xLogger->setLevel(LogLevel::INFO); + xConsoleHandler->setLevel(LogLevel::INFO); + if (xFileHandler.is()) + xFileHandler->setLevel(LogLevel::INFO); + } + + if (repository.isEmpty()) + { + if (option_shared) + repository = "shared"; + else if (option_bundled) + repository = "bundled"; + else + repository = "user"; + } + else + { + if ( repository == "shared" ) { + option_shared = true; + } + else if (option_shared) + { + logger->log(LogLevel::WARNING, "Explicit context given! Ignoring option '$1$'", toString(info_shared)); + } + } +#if defined(UNX) + if ( geteuid() == 0 ) + { + if ( !(option_shared || option_bundled || option_help) ) + { + logger->log(LogLevel::SEVERE, "Cannot run $1$ as root without $2$ or $3$ option.", + APP_NAME, toString(info_shared), toString(info_bundled)); + return 1; + } + } +#endif + + if (subCommand == "reinstall") + { + //We must prevent that services and types are loaded by UNO, + //otherwise we cannot delete the registry data folder. + OUString extensionUnorc; + if (repository == "user") + extensionUnorc = "$UNO_USER_PACKAGES_CACHE/registry/com.sun.star.comp.deployment.component.PackageRegistryBackend/unorc"; + else if (repository == "shared") + extensionUnorc = "$SHARED_EXTENSIONS_USER/registry/com.sun.star.comp.deployment.component.PackageRegistryBackend/unorc"; + else if (repository == "bundled") + extensionUnorc = "$BUNDLED_EXTENSIONS_USER/registry/com.sun.star.comp.deployment.component.PackageRegistryBackend/unorc"; + else + OSL_ASSERT(false); + + ::rtl::Bootstrap::expandMacros(extensionUnorc); + oslFileError e = osl_removeFile(extensionUnorc.pData); + if (e != osl_File_E_None && e != osl_File_E_NOENT) + throw Exception("Could not delete " + extensionUnorc, nullptr); + } + + Reference<deployment::XExtensionManager> xExtensionManager( + deployment::ExtensionManager::get( xComponentContext ) ); + + Reference<css::ucb::XCommandEnvironment> xCmdEnv( + createCmdEnv(xComponentContext, option_force, option_verbose, option_suppressLicense)); + + //synchronize bundled/shared extensions + //Do not synchronize when command is "reinstall". This could add types and services to UNO and + //prevent the deletion of the registry data folder + //syncing is done in XExtensionManager.reinstall + if (!subcmd_gui && subCommand != "reinstall" + && ! dp_misc::office_is_running()) + dp_misc::syncRepositories(false, xCmdEnv); + + if ( subcmd_add || subCommand == "remove" ) + { + for (const OUString & cmdPackage : cmdPackages) + { + if (subcmd_add) + { + beans::NamedValue nvSuppress( + "SUPPRESS_LICENSE", option_suppressLicense ? + Any(OUString("1")):Any(OUString("0"))); + xExtensionManager->addExtension( + cmdPackage, Sequence<beans::NamedValue>(&nvSuppress, 1), + repository, Reference<task::XAbortChannel>(), xCmdEnv); + } + else + { + try + { + xExtensionManager->removeExtension( + cmdPackage, cmdPackage, repository, + Reference<task::XAbortChannel>(), xCmdEnv ); + } + catch (const lang::IllegalArgumentException &) + { + Reference<deployment::XPackage> p( + findPackage(repository, + xExtensionManager, xCmdEnv, cmdPackage ) ); + if ( !p.is()) + throw; + else if (p.is()) + xExtensionManager->removeExtension( + ::dp_misc::getIdentifier(p), p->getName(), + repository, + Reference<task::XAbortChannel>(), xCmdEnv ); + } + } + } + } + else if ( subCommand == "reinstall" ) + { + xExtensionManager->reinstallDeployedExtensions( + false, repository, Reference<task::XAbortChannel>(), xCmdEnv); + } + else if ( subCommand == "list" ) + { + std::vector<Reference<deployment::XPackage> > vecExtUnaccepted; + ::comphelper::sequenceToContainer(vecExtUnaccepted, + xExtensionManager->getExtensionsWithUnacceptedLicenses( + repository, xCmdEnv)); + + //This vector tells what XPackage in allExtensions has an + //unaccepted license. + std::vector<bool> vecUnaccepted; + std::vector<Reference<deployment::XPackage> > allExtensions; + if (cmdPackages.empty()) + { + Sequence< Reference<deployment::XPackage> > + packages = xExtensionManager->getDeployedExtensions( + repository, Reference<task::XAbortChannel>(), xCmdEnv ); + + std::vector<Reference<deployment::XPackage> > vec_packages; + ::comphelper::sequenceToContainer(vec_packages, packages); + + //First copy the extensions with the unaccepted license + //to vector allExtensions. + allExtensions.resize(vecExtUnaccepted.size() + vec_packages.size()); + + std::vector<Reference<deployment::XPackage> >::iterator i_all_ext = + std::copy(vecExtUnaccepted.begin(), vecExtUnaccepted.end(), + allExtensions.begin()); + //Now copy those we got from getDeployedExtensions + std::copy(vec_packages.begin(), vec_packages.end(), i_all_ext); + + //Now prepare the vector which tells what extension has an + //unaccepted license + vecUnaccepted.resize(vecExtUnaccepted.size() + vec_packages.size()); + std::fill_n(vecUnaccepted.begin(), vecExtUnaccepted.size(), true); + std::fill_n(vecUnaccepted.begin() + vecExtUnaccepted.size(), + vec_packages.size(), false); + + dp_misc::writeConsole( + Concat2View("All deployed " + repository + " extensions:\n\n")); + } + else + { + //The user provided the names (ids or file names) of the extensions + //which shall be listed + for (const OUString & cmdPackage : cmdPackages) + { + Reference<deployment::XPackage> extension; + try + { + extension = xExtensionManager->getDeployedExtension( + repository, cmdPackage, cmdPackage, xCmdEnv ); + } + catch (const lang::IllegalArgumentException &) + { + extension = findPackage(repository, + xExtensionManager, xCmdEnv, cmdPackage ); + } + + //Now look if the requested extension has an unaccepted license + bool bUnacceptedLic = false; + if (!extension.is()) + { + std::vector<Reference<deployment::XPackage> >::const_iterator + i = std::find_if( + vecExtUnaccepted.begin(), + vecExtUnaccepted.end(), ExtensionName(cmdPackage)); + if (i != vecExtUnaccepted.end()) + { + extension = *i; + bUnacceptedLic = true; + } + } + + if (!extension.is()) + throw lang::IllegalArgumentException( + "There is no such extension deployed: " + + cmdPackage,nullptr,-1); + allExtensions.push_back(extension); + vecUnaccepted.push_back(bUnacceptedLic); + } + + } + + printf_packages(allExtensions, vecUnaccepted, xCmdEnv ); + } + else if ( subCommand == "validate" ) + { + std::vector<Reference<deployment::XPackage> > vecExtUnaccepted; + ::comphelper::sequenceToContainer( + vecExtUnaccepted, xExtensionManager->getExtensionsWithUnacceptedLicenses( + repository, xCmdEnv)); + + for (const OUString & cmdPackage : cmdPackages) + { + Reference<deployment::XPackage> extension; + try + { + extension = xExtensionManager->getDeployedExtension( + repository, cmdPackage, cmdPackage, xCmdEnv ); + } + catch (const lang::IllegalArgumentException &) + { + extension = findPackage( + repository, xExtensionManager, xCmdEnv, cmdPackage ); + } + + if (!extension.is()) + { + std::vector<Reference<deployment::XPackage> >::const_iterator + i = std::find_if( + vecExtUnaccepted.begin(), + vecExtUnaccepted.end(), ExtensionName(cmdPackage)); + if (i != vecExtUnaccepted.end()) + { + extension = *i; + } + } + + if (extension.is()) + xExtensionManager->checkPrerequisitesAndEnable( + extension, Reference<task::XAbortChannel>(), xCmdEnv); + } + } + else if ( subCommand == "gui" ) + { + Reference<ui::dialogs::XAsynchronousExecutableDialog> xDialog( + deployment::ui::PackageManagerDialog::createAndInstall( + xComponentContext, + !cmdPackages.empty() ? cmdPackages[0] : OUString() )); + + osl::Condition dialogEnded; + dialogEnded.reset(); + + Reference< ui::dialogs::XDialogClosedListener > xListener( + new DialogClosedListenerImpl( dialogEnded ) ); + + xDialog->startExecuteModal(xListener); + dialogEnded.wait(); + return 0; + } + else + { + logger->log(LogLevel::SEVERE, + "Unknown sub-command: '$1$'. Use $2$ $3$ to print all options.", + subCommand, APP_NAME, toString(info_help)); + return 1; + } + + logger->log(LogLevel::INFO, "$1$ done.", APP_NAME); + //Force to release all bridges which connect us to the child processes + dp_misc::disposeBridges(xLocalComponentContext); + css::uno::Reference<css::lang::XComponent>( + xLocalComponentContext, css::uno::UNO_QUERY_THROW)->dispose(); + return 0; + } + catch (const ucb::CommandFailedException &e) + { + logFatal(logger.get(), LogLevel::SEVERE, "Exception occurred: $1$", e.Message); + } + catch (const ucb::CommandAbortedException &) + { + logFatal(logger.get(), LogLevel::SEVERE, "$1$ aborted.", APP_NAME); + bShowFailedMsg = false; + } + catch (const deployment::DeploymentException & exc) + { + logFatal(logger.get(), LogLevel::SEVERE, "Exception occurred: $1$", exc.Message); + logFatal( + logger.get(), LogLevel::INFO, " Cause: $1$", comphelper::anyToString(exc.Cause)); + } + catch (const LockFileException & e) + { + // No logger since it requires UNO which we don't have here + dp_misc::writeConsoleError(Concat2View(e.Message + "\n")); + bShowFailedMsg = false; + } + catch (const css::uno::Exception & e ) { + Any exc( ::cppu::getCaughtException() ); + + logFatal(logger.get(), LogLevel::SEVERE, "Exception occurred: $1$", e.Message); + logFatal(logger.get(), LogLevel::INFO, " Cause: $1$", comphelper::anyToString(exc)); + } + if (bShowFailedMsg) + logFatal(logger.get(), LogLevel::SEVERE, "$1$ failed.", APP_NAME); + dp_misc::disposeBridges(xLocalComponentContext); + if (xLocalComponentContext.is()) { + css::uno::Reference<css::lang::XComponent>( + xLocalComponentContext, css::uno::UNO_QUERY_THROW)->dispose(); + } + return 1; +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/pkgchk/unopkg/unopkg_cmdenv.cxx b/desktop/source/pkgchk/unopkg/unopkg_cmdenv.cxx new file mode 100644 index 0000000000..581922a3c3 --- /dev/null +++ b/desktop/source/pkgchk/unopkg/unopkg_cmdenv.cxx @@ -0,0 +1,387 @@ +/* -*- 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 <string_view> + +#include <strings.hrc> +#include <dp_misc.h> +#include <dp_shared.hxx> +#include "unopkg_shared.h" +#include <i18nlangtag/languagetag.hxx> +#include <rtl/ustrbuf.hxx> +#include <cppuhelper/implbase.hxx> +#include <comphelper/anytostring.hxx> +#include <comphelper/diagnose_ex.hxx> +#include <unotools/configmgr.hxx> +#include <com/sun/star/lang/WrappedTargetException.hpp> +#include <com/sun/star/task/XInteractionAbort.hpp> +#include <com/sun/star/task/XInteractionApprove.hpp> +#include <com/sun/star/deployment/DeploymentException.hpp> +#include <com/sun/star/deployment/InstallException.hpp> +#include <com/sun/star/deployment/LicenseException.hpp> +#include <com/sun/star/deployment/VersionException.hpp> +#include <com/sun/star/deployment/PlatformException.hpp> +#include <com/sun/star/i18n/Collator.hpp> +#include <com/sun/star/i18n/CollatorOptions.hpp> + +#include <dp_version.hxx> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::ucb; +using namespace ::com::sun::star::uno; +using namespace ::unopkg; + + +namespace { + + +class CommandEnvironmentImpl + : public ::cppu::WeakImplHelper< XCommandEnvironment, + task::XInteractionHandler, + XProgressHandler > +{ + sal_Int32 m_logLevel; + bool m_option_force_overwrite; + bool m_option_verbose; + bool m_option_suppress_license; + Reference< XComponentContext > m_xComponentContext; + Reference< XProgressHandler > m_xLogFile; + + /// @throws RuntimeException + void update_( Any const & Status ); + void printLicense(std::u16string_view sName,std::u16string_view sLicense, + bool & accept, bool & decline); + +public: + virtual ~CommandEnvironmentImpl() override; + CommandEnvironmentImpl( + Reference<XComponentContext> const & xComponentContext, + bool option_force_overwrite, + bool option_verbose, + bool option_suppress_license); + + // XCommandEnvironment + virtual Reference< task::XInteractionHandler > SAL_CALL + getInteractionHandler() override; + virtual Reference< XProgressHandler > SAL_CALL getProgressHandler() override; + + // XInteractionHandler + virtual void SAL_CALL handle( + Reference< task::XInteractionRequest > const & xRequest ) override; + + // XProgressHandler + virtual void SAL_CALL push( Any const & Status ) override; + virtual void SAL_CALL update( Any const & Status ) override; + virtual void SAL_CALL pop() override; +}; + + +CommandEnvironmentImpl::CommandEnvironmentImpl( + Reference<XComponentContext> const & xComponentContext, + bool option_force_overwrite, + bool option_verbose, + bool option_suppressLicense) + : m_logLevel(0), + m_option_force_overwrite( option_force_overwrite ), + m_option_verbose( option_verbose ), + m_option_suppress_license( option_suppressLicense ), + m_xComponentContext(xComponentContext) +{ + m_xLogFile.set( + xComponentContext->getServiceManager() + ->createInstanceWithArgumentsAndContext( + "com.sun.star.comp.deployment.ProgressLog", + Sequence<Any>(), xComponentContext ), + UNO_QUERY_THROW ); +} + + +CommandEnvironmentImpl::~CommandEnvironmentImpl() +{ + try { + Reference< lang::XComponent > xComp( m_xLogFile, UNO_QUERY ); + if (xComp.is()) + xComp->dispose(); + } + catch (const RuntimeException &) { + TOOLS_WARN_EXCEPTION( "desktop", "" ); + } +} + +//May throw exceptions +void CommandEnvironmentImpl::printLicense( + std::u16string_view sName, std::u16string_view sLicense, bool & accept, bool &decline) +{ + OUString s1tmp(DpResId(RID_STR_UNOPKG_ACCEPT_LIC_1)); + OUString s1(s1tmp.replaceAll("$NAME", sName)); + OUString s2 = DpResId(RID_STR_UNOPKG_ACCEPT_LIC_2); + OUString s3 = DpResId(RID_STR_UNOPKG_ACCEPT_LIC_3); + OUString s4 = DpResId(RID_STR_UNOPKG_ACCEPT_LIC_4); + OUString sYES = DpResId(RID_STR_UNOPKG_ACCEPT_LIC_YES); + OUString sY = DpResId(RID_STR_UNOPKG_ACCEPT_LIC_Y); + OUString sNO = DpResId(RID_STR_UNOPKG_ACCEPT_LIC_NO); + OUString sN = DpResId(RID_STR_UNOPKG_ACCEPT_LIC_N); + + OUString sNewLine("\n"); + + dp_misc::writeConsole(Concat2View(sNewLine + sNewLine + s1 + sNewLine + sNewLine)); + dp_misc::writeConsole(Concat2View(sLicense + sNewLine + sNewLine)); + dp_misc::writeConsole(Concat2View(s2 + sNewLine)); + dp_misc::writeConsole(s3); + + //the user may enter "yes" or "no", we compare in a case insensitive way + Reference< css::i18n::XCollator > xCollator = + css::i18n::Collator::create( m_xComponentContext ); + xCollator->loadDefaultCollator( + LanguageTag(utl::ConfigManager::getUILocale()).getLocale(), + css::i18n::CollatorOptions::CollatorOptions_IGNORE_CASE); + + do + { + OUString sAnswer = dp_misc::readConsole(); + if (xCollator->compareString(sAnswer, sYES) == 0 + || xCollator->compareString(sAnswer, sY) == 0) + { + accept = true; + break; + } + else if(xCollator->compareString(sAnswer, sNO) == 0 + || xCollator->compareString(sAnswer, sN) == 0) + { + decline = true; + break; + } + else + { + dp_misc::writeConsole(Concat2View(sNewLine + sNewLine + s4 + sNewLine)); + } + } + while(true); +} + +// XCommandEnvironment + +Reference< task::XInteractionHandler > +CommandEnvironmentImpl::getInteractionHandler() +{ + return this; +} + + +Reference< XProgressHandler > CommandEnvironmentImpl::getProgressHandler() +{ + return this; +} + +// XInteractionHandler + +void CommandEnvironmentImpl::handle( + Reference<task::XInteractionRequest> const & xRequest ) +{ + Any request( xRequest->getRequest() ); + OSL_ASSERT( request.getValueTypeClass() == TypeClass_EXCEPTION ); + dp_misc::TRACE("[unopkg_cmdenv.cxx] incoming request:\n" + + ::comphelper::anyToString(request) + "\n\n"); + + // selections: + bool approve = false; + bool abort = false; + + lang::WrappedTargetException wtExc; + deployment::LicenseException licExc; + deployment::InstallException instExc; + deployment::PlatformException platExc; + + if (request >>= wtExc) { + // ignore intermediate errors of legacy packages, i.e. + // former pkgchk behaviour: + const Reference<deployment::XPackage> xPackage( + wtExc.Context, UNO_QUERY ); + OSL_ASSERT( xPackage.is() ); + if (xPackage.is()) { + const Reference<deployment::XPackageTypeInfo> xPackageType( + xPackage->getPackageType() ); + OSL_ASSERT( xPackageType.is() ); + if (xPackageType.is()) { + approve = (xPackage->isBundle() && + xPackageType->getMediaType().match( + "application/vnd.sun.star.legacy-package-bundle") ); + } + } + abort = !approve; + if (abort) { + // notify cause as error: + request = wtExc.TargetException; + } + else { + // handable deployment error signalled, e.g. + // bundle item registration failed, notify as warning: + update_( wtExc.TargetException ); + } + } + else if (request >>= licExc) + { + if ( !m_option_suppress_license ) + printLicense(licExc.ExtensionName, licExc.Text, approve, abort); + else + { + approve = true; + abort = false; + } + } + else if (request >>= instExc) + { + //Only if the unopgk was started with gui + extension then the user is asked. + //In console mode there is no asking. + approve = true; + } + else if (request >>= platExc) + { + OUString sMsg(DpResId(RID_STR_UNSUPPORTED_PLATFORM)); + sMsg = sMsg.replaceAll("%Name", platExc.package->getDisplayName()); + dp_misc::writeConsole(Concat2View("\n" + sMsg + "\n\n")); + approve = true; + } + else { + deployment::VersionException nc_exc; + if (request >>= nc_exc) { + approve = m_option_force_overwrite || + (::dp_misc::compareVersions(nc_exc.NewVersion, nc_exc.Deployed->getVersion()) + == ::dp_misc::GREATER); + abort = !approve; + } + else + return; // unknown request => no selection at all + } + + if (abort && m_option_verbose) + { + OUString msg = ::comphelper::anyToString(request); + dp_misc::writeConsoleError(Concat2View("\nERROR: " + msg + "\n")); + } + + // select: + const css::uno::Sequence<css::uno::Reference<css::task::XInteractionContinuation>> xIC = xRequest->getContinuations(); + for ( auto const& rCont : xIC ) + { + if (approve) { + Reference<task::XInteractionApprove> xInteractionApprove( + rCont, UNO_QUERY ); + if (xInteractionApprove.is()) { + xInteractionApprove->select(); + break; + } + } + else if (abort) { + Reference<task::XInteractionAbort> xInteractionAbort( + rCont, UNO_QUERY ); + if (xInteractionAbort.is()) { + xInteractionAbort->select(); + break; + } + } + } +} + +// XProgressHandler + +void CommandEnvironmentImpl::push( Any const & Status ) +{ + update_( Status ); + OSL_ASSERT( m_logLevel >= 0 ); + ++m_logLevel; + if (m_xLogFile.is()) + m_xLogFile->push( Status ); +} + + +void CommandEnvironmentImpl::update_( Any const & Status ) +{ + if (! Status.hasValue()) + return; + bool bUseErr = false; + OUString msg; + if (Status >>= msg) { + if (! m_option_verbose) + return; + } + else { + OUStringBuffer buf( "WARNING: " ); + deployment::DeploymentException dp_exc; + if (Status >>= dp_exc) { + buf.append( dp_exc.Message + ", Cause: " + ::comphelper::anyToString(dp_exc.Cause) ); + } + else { + buf.append( ::comphelper::anyToString(Status) ); + } + msg = buf.makeStringAndClear(); + bUseErr = true; + } + OSL_ASSERT( m_logLevel >= 0 ); + for ( sal_Int32 n = 0; n < m_logLevel; ++n ) + { + if (bUseErr) + dp_misc::writeConsoleError(u" "); + else + dp_misc::writeConsole(u" "); + } + + if (bUseErr) + dp_misc::writeConsoleError(Concat2View(msg + "\n")); + else + dp_misc::writeConsole(Concat2View(msg + "\n")); +} + + +void CommandEnvironmentImpl::update( Any const & Status ) +{ + update_( Status ); + if (m_xLogFile.is()) + m_xLogFile->update( Status ); +} + + +void CommandEnvironmentImpl::pop() +{ + OSL_ASSERT( m_logLevel > 0 ); + --m_logLevel; + if (m_xLogFile.is()) + m_xLogFile->pop(); +} + + +} // anon namespace + +namespace unopkg { + + +Reference< XCommandEnvironment > createCmdEnv( + Reference< XComponentContext > const & xContext, + bool option_force_overwrite, + bool option_verbose, + bool option_suppress_license) +{ + return new CommandEnvironmentImpl( + xContext, option_force_overwrite, option_verbose, option_suppress_license); +} +} // unopkg + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/pkgchk/unopkg/unopkg_main.c b/desktop/source/pkgchk/unopkg/unopkg_main.c new file mode 100644 index 0000000000..83b20b0b48 --- /dev/null +++ b/desktop/source/pkgchk/unopkg/unopkg_main.c @@ -0,0 +1,26 @@ +/* -*- 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 "unopkg_main.h" + +SAL_IMPLEMENT_MAIN() { return unopkg_main(); } + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/pkgchk/unopkg/unopkg_main.h b/desktop/source/pkgchk/unopkg/unopkg_main.h new file mode 100644 index 0000000000..0fcb1013d3 --- /dev/null +++ b/desktop/source/pkgchk/unopkg/unopkg_main.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 + +DESKTOP_DLLPUBLIC int unopkg_main(void); + +#if defined __cplusplus +} +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/pkgchk/unopkg/unopkg_misc.cxx b/desktop/source/pkgchk/unopkg/unopkg_misc.cxx new file mode 100644 index 0000000000..5539529177 --- /dev/null +++ b/desktop/source/pkgchk/unopkg/unopkg_misc.cxx @@ -0,0 +1,455 @@ +/* -*- 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 <string_view> + +#include <config_folders.h> + +#include <vcl/svapp.hxx> +#include <vcl/weld.hxx> +#include <rtl/bootstrap.hxx> +#include <rtl/ustrbuf.hxx> +#include <sal/log.hxx> +#include <osl/process.h> +#include <osl/file.hxx> +#include <unotools/configmgr.hxx> +#include <unotools/bootstrap.hxx> +#include <cppuhelper/bootstrap.hxx> +#include <comphelper/sequence.hxx> +#include <comphelper/processfactory.hxx> + +#include <com/sun/star/lang/XMultiServiceFactory.hpp> +#include <com/sun/star/ucb/UniversalContentBroker.hpp> + +#include <strings.hrc> +#include "unopkg_shared.h" +#include <dp_identifier.hxx> +#include <dp_misc.h> +#include <dp_shared.hxx> +#include <lockfile.hxx> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::ucb; + +namespace unopkg { + +OUString toString( OptionInfo const * info ) +{ + assert(info != nullptr); + OUStringBuffer buf("--"); + buf.appendAscii(info->m_name); + if (info->m_short_option != '\0') + { + buf.append(" (short -" + OUStringChar(info->m_short_option) + ")"); + } + if (info->m_has_argument) + buf.append(" <argument>" ); + return buf.makeStringAndClear(); +} + + +OptionInfo const * getOptionInfo( + OptionInfo const * list, + OUString const & opt ) +{ + for ( ; list->m_name != nullptr; ++list ) + { + OptionInfo const & option_info = *list; + if (!opt.isEmpty()) + { + if (opt.equalsAsciiL( + option_info.m_name, option_info.m_name_length )) + { + return &option_info; + } + } + } + SAL_WARN( "desktop", opt ); + return nullptr; +} + + +bool isOption( OptionInfo const * option_info, sal_uInt32 * pIndex ) +{ + assert(option_info != nullptr); + if (osl_getCommandArgCount() <= *pIndex) + return false; + + OUString arg; + osl_getCommandArg( *pIndex, &arg.pData ); + sal_Int32 len = arg.getLength(); + + if (len < 2 || arg[ 0 ] != '-') + return false; + + if (len == 2 && arg[ 1 ] == option_info->m_short_option) + { + ++(*pIndex); + dp_misc::TRACE(__FILE__ ": identified option \'\'" + + OUStringChar( option_info->m_short_option ) + "\n"); + return true; + } + if (arg[ 1 ] == '-' && rtl_ustr_ascii_compare( + arg.pData->buffer + 2, option_info->m_name ) == 0) + { + ++(*pIndex); + dp_misc::TRACE(__FILE__ ": identified option \'" + + OUString::createFromAscii(option_info->m_name) + "\'\n"); + return true; + } + return false; +} + + +bool isBootstrapVariable(sal_uInt32 * pIndex) +{ + OSL_ASSERT(osl_getCommandArgCount() >= *pIndex); + + OUString arg; + osl_getCommandArg(*pIndex, &arg.pData); + if (arg.match("-env:")) + { + ++(*pIndex); + return true; + } + return false; +} + + +bool readArgument( + OUString * pValue, OptionInfo const * option_info, sal_uInt32 * pIndex ) +{ + if (isOption( option_info, pIndex )) + { + if (*pIndex < osl_getCommandArgCount()) + { + OSL_ASSERT( pValue != nullptr ); + osl_getCommandArg( *pIndex, &pValue->pData ); + dp_misc::TRACE(__FILE__ ": argument value: " + + *pValue + "\n"); + ++(*pIndex); + return true; + } + --(*pIndex); + } + return false; +} + + +OUString const & getExecutableDir() +{ + static const OUString EXEC = + []() + { + OUString path; + if (osl_getExecutableFile( &path.pData ) != osl_Process_E_None) { + throw RuntimeException("cannot locate executable directory!",nullptr); + } + return path.copy( 0, path.lastIndexOf( '/' ) ); + }(); + return EXEC; +} + + +OUString const & getProcessWorkingDir() +{ + static const OUString WORKING = + []() + { + OUString workingDir; + utl::Bootstrap::getProcessWorkingDir(workingDir); + return workingDir; + }(); + return WORKING; +} + + +OUString makeAbsoluteFileUrl( + OUString const & sys_path, OUString const & base_url ) +{ + // system path to file url + OUString file_url; + oslFileError rc = osl_getFileURLFromSystemPath( sys_path.pData, &file_url.pData ); + if ( rc != osl_File_E_None) { + OUString tempPath; + if ( osl_getSystemPathFromFileURL( sys_path.pData, &tempPath.pData) != osl_File_E_None ) + { + throw RuntimeException("cannot get file url from system path: " + + sys_path ); + } + file_url = sys_path; + } + + OUString abs; + if (osl_getAbsoluteFileURL( + base_url.pData, file_url.pData, &abs.pData ) != osl_File_E_None) + { + throw RuntimeException( + "making absolute file url failed: \"" + base_url + + "\" (base-url) and \"" + file_url + "\" (file-url)!" ); + } + return abs[ abs.getLength() -1 ] == '/' + ? abs.copy( 0, abs.getLength() -1 ) : abs; +} + + +namespace { + + +void printf_space( sal_Int32 space ) +{ + while (space--) + dp_misc::writeConsole(u" "); +} + + +void printf_line( + std::u16string_view name, std::u16string_view value, sal_Int32 level ) +{ + printf_space( level ); + dp_misc::writeConsole(Concat2View(OUString::Concat(name) + ": " + value + "\n")); +} + + +void printf_package( + Reference<deployment::XPackage> const & xPackage, + Reference<XCommandEnvironment> const & xCmdEnv, sal_Int32 level ) +{ + beans::Optional< OUString > id( + level == 0 + ? beans::Optional< OUString >( + true, dp_misc::getIdentifier( xPackage ) ) + : xPackage->getIdentifier() ); + if (id.IsPresent) + printf_line( u"Identifier", id.Value, level ); + OUString version(xPackage->getVersion()); + if (!version.isEmpty()) + printf_line( u"Version", version, level + 1 ); + printf_line( u"URL", xPackage->getURL(), level + 1 ); + + beans::Optional< beans::Ambiguous<sal_Bool> > option( + xPackage->isRegistered( Reference<task::XAbortChannel>(), xCmdEnv ) ); + OUString value; + if (option.IsPresent) { + beans::Ambiguous<sal_Bool> const & reg = option.Value; + if (reg.IsAmbiguous) + value = "unknown"; + else + value = reg.Value ? std::u16string_view(u"yes") : std::u16string_view(u"no"); + } + else + value = "n/a"; + printf_line( u"is registered", value, level + 1 ); + + const Reference<deployment::XPackageTypeInfo> xPackageType( + xPackage->getPackageType() ); + OSL_ASSERT( xPackageType.is() ); + if (xPackageType.is()) { + printf_line( u"Media-Type", xPackageType->getMediaType(), level + 1 ); + } + printf_line( u"Description", xPackage->getDescription(), level + 1 ); + if (!xPackage->isBundle()) + return; + + Sequence< Reference<deployment::XPackage> > seq( + xPackage->getBundle( Reference<task::XAbortChannel>(), xCmdEnv ) ); + printf_space( level + 1 ); + dp_misc::writeConsole(u"bundled Packages: {\n"); + std::vector<Reference<deployment::XPackage> >vec_bundle; + ::comphelper::sequenceToContainer(vec_bundle, seq); + printf_packages( vec_bundle, std::vector<bool>(vec_bundle.size()), + xCmdEnv, level + 2 ); + printf_space( level + 1 ); + dp_misc::writeConsole(u"}\n"); +} + +} // anon namespace + +static void printf_unaccepted_licenses( + Reference<deployment::XPackage> const & ext) +{ + OUString id( + dp_misc::getIdentifier(ext) ); + printf_line( u"Identifier", id, 0 ); + printf_space(1); + dp_misc::writeConsole(u"License not accepted\n\n"); +} + + +void printf_packages( + std::vector< Reference<deployment::XPackage> > const & allExtensions, + std::vector<bool> const & vecUnaccepted, + Reference<XCommandEnvironment> const & xCmdEnv, sal_Int32 level ) +{ + OSL_ASSERT(allExtensions.size() == vecUnaccepted.size()); + + if (allExtensions.empty()) + { + printf_space( level ); + dp_misc::writeConsole(u"<none>\n"); + } + else + { + int index = 0; + for (auto const& extension : allExtensions) + { + if (vecUnaccepted[index]) + printf_unaccepted_licenses(extension); + else + printf_package( extension, xCmdEnv, level ); + dp_misc::writeConsole(u"\n"); + ++index; + } + } +} + + +namespace { + + +Reference<XComponentContext> bootstrapStandAlone() +{ + Reference<XComponentContext> xContext = + ::cppu::defaultBootstrap_InitialComponentContext(); + + Reference<lang::XMultiServiceFactory> xServiceManager( + xContext->getServiceManager(), UNO_QUERY_THROW ); + // set global process service factory used by unotools config helpers + ::comphelper::setProcessServiceFactory( xServiceManager ); + + // Initialize the UCB (for backwards compatibility, in case some code still + // uses plain createInstance w/o args directly to obtain an instance): + UniversalContentBroker::create( xContext ); + + return xContext; +} + + +Reference<XComponentContext> connectToOffice( + Reference<XComponentContext> const & xLocalComponentContext, + bool verbose ) +{ + OUString pipeId( ::dp_misc::generateRandomPipeId() ); + OUString acceptArg = "--accept=pipe,name=" + pipeId + ";urp;"; + + Sequence<OUString> args { "--nologo", "--nodefault", acceptArg }; + OUString appURL( getExecutableDir() + "/soffice" ); + + if (verbose) + { + dp_misc::writeConsole(Concat2View( + "Raising process: " + appURL + + "\nArguments: --nologo --nodefault " + args[2] + + "\n")); + } + + ::dp_misc::raiseProcess( appURL, args ); + + if (verbose) + dp_misc::writeConsole(u"OK. Connecting..."); + + OUString sUnoUrl = "uno:pipe,name=" + pipeId + ";urp;StarOffice.ComponentContext"; + Reference<XComponentContext> xRet( + ::dp_misc::resolveUnoURL( + sUnoUrl, xLocalComponentContext ), + UNO_QUERY_THROW ); + if (verbose) + dp_misc::writeConsole(u"OK.\n"); + + return xRet; +} + +} // anon namespace + +/** returns the path to the lock file used by unopkg. + @return the path. An empty string signifies an error. +*/ +static OUString getLockFilePath() +{ + OUString ret; + OUString sBootstrap("${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE("bootstrap") ":UserInstallation}"); + rtl::Bootstrap::expandMacros(sBootstrap); + OUString sAbs; + if (::osl::File::E_None == ::osl::File::getAbsoluteFileURL( + sBootstrap, ".lock", sAbs)) + { + if (::osl::File::E_None == + ::osl::File::getSystemPathFromFileURL(sAbs, sBootstrap)) + { + ret = sBootstrap; + } + } + + return ret; +} + +Reference<XComponentContext> getUNO( + bool verbose, bool bGui, const OUString& sTempDir, + Reference<XComponentContext> & out_localContext) +{ + // do not create any user data (for the root user) in --shared mode: + if (!sTempDir.isEmpty()) + rtl::Bootstrap::set("UserInstallation", sTempDir); + + // hold lock during process runtime: + static ::desktop::Lockfile s_lockfile( false /* no IPC server */ ); + Reference<XComponentContext> xComponentContext( bootstrapStandAlone() ); + out_localContext = xComponentContext; + if (::dp_misc::office_is_running()) { + xComponentContext.set( + connectToOffice( xComponentContext, verbose ) ); + } + else + { + if (! s_lockfile.check( nullptr )) + { + OUString sMsg(DpResId(RID_STR_CONCURRENTINSTANCE)); + OUString sError(DpResId(RID_STR_UNOPKG_ERROR)); + + sMsg += "\n" + getLockFilePath(); + + if (bGui) + { + //We show a message box or print to the console that there + //is another instance already running + if ( ! InitVCL() ) + throw RuntimeException( "Cannot initialize VCL!" ); + { + std::unique_ptr<weld::MessageDialog> xWarn(Application::CreateMessageDialog(nullptr, + VclMessageType::Warning, VclButtonsType::Ok, + sMsg)); + xWarn->set_title(utl::ConfigManager::getProductName()); + xWarn->run(); + } + DeInitVCL(); + } + + throw LockFileException(sError + sMsg); + } + } + + return xComponentContext; +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/pkgchk/unopkg/unopkg_shared.h b/desktop/source/pkgchk/unopkg/unopkg_shared.h new file mode 100644 index 0000000000..21d0f6a928 --- /dev/null +++ b/desktop/source/pkgchk/unopkg/unopkg_shared.h @@ -0,0 +1,119 @@ +/* -*- 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 <com/sun/star/uno/XComponentContext.hpp> +#include <com/sun/star/ucb/XCommandEnvironment.hpp> +#include <com/sun/star/deployment/XPackage.hpp> +#include <osl/diagnose.h> +#include <rtl/ustring.hxx> + +#include <utility> +#include <vector> + +#define APP_NAME "unopkg" + +namespace unopkg { + +struct OptionInfo +{ + char const * m_name; + sal_uInt32 m_name_length; + sal_Unicode m_short_option; + bool m_has_argument; +}; + +struct LockFileException +{ + explicit LockFileException(OUString sMessage) : + Message(std::move(sMessage)) {} + + OUString Message; +}; + + +OUString toString( OptionInfo const * info ); + + +OptionInfo const * getOptionInfo( + OptionInfo const * list, + OUString const & opt ); + + +bool isOption( OptionInfo const * option_info, sal_uInt32 * pIndex ); + + +bool readArgument( + OUString * pValue, OptionInfo const * option_info, + sal_uInt32 * pIndex ); + + +inline bool readOption( + bool * flag, OptionInfo const * option_info, sal_uInt32 * pIndex ) +{ + if (isOption( option_info, pIndex )) { + OSL_ASSERT( flag != nullptr ); + *flag = true; + return true; + } + return false; +} + + +/** checks if an argument is a bootstrap variable. These start with -env:. For example + -env:UNO_JAVA_JFW_USER_DATA=file:///d:/user +*/ +bool isBootstrapVariable(sal_uInt32 * pIndex); + +OUString const & getExecutableDir(); + + +OUString const & getProcessWorkingDir(); + + +OUString makeAbsoluteFileUrl( + OUString const & sys_path, OUString const & base_url ); + + + + +css::uno::Reference<css::ucb::XCommandEnvironment> createCmdEnv( + css::uno::Reference<css::uno::XComponentContext> const & xContext, + bool option_force_overwrite, + bool option_verbose, + bool option_suppressLicense); + +void printf_packages( + std::vector< + css::uno::Reference<css::deployment::XPackage> > const & allExtensions, + std::vector<bool> const & vecUnaccepted, + css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv, + sal_Int32 level = 0 ); + + + + +css::uno::Reference<css::uno::XComponentContext> getUNO( + bool verbose, bool bGui, const OUString& sTempDir, + css::uno::Reference<css::uno::XComponentContext> & out_LocalComponentContext); + +} + + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/splash/spl.component b/desktop/source/splash/spl.component new file mode 100644 index 0000000000..a15cbdf544 --- /dev/null +++ b/desktop/source/splash/spl.component @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + * 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 . + --> + +<component loader="com.sun.star.loader.SharedLibrary" environment="@CPPU_ENV@" + xmlns="http://openoffice.org/2010/uno-components"> + <implementation name="com.sun.star.office.comp.SplashScreen" + constructor="desktop_SplashScreen_get_implementation"> + <service name="com.sun.star.office.SplashScreen"/> + </implementation> + <implementation name="com.sun.star.office.comp.PipeSplashScreen" + constructor="desktop_UnxSplash_get_implementation" single-instance="true"> + <service name="com.sun.star.office.PipeSplashScreen"/> + </implementation> +</component> diff --git a/desktop/source/splash/splash.cxx b/desktop/source/splash/splash.cxx new file mode 100644 index 0000000000..17cc7fb534 --- /dev/null +++ b/desktop/source/splash/splash.cxx @@ -0,0 +1,622 @@ +/* -*- 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/log.hxx> +#include <vcl/bitmapex.hxx> +#include <vcl/svapp.hxx> +#include <vcl/salnativewidgets.hxx> + +#include <com/sun/star/lang/XInitialization.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/task/XStatusIndicator.hpp> +#include <comphelper/string.hxx> +#include <cppuhelper/implbase.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <rtl/bootstrap.hxx> +#include <rtl/strbuf.hxx> +#include <rtl/math.hxx> +#include <vcl/introwin.hxx> +#include <vcl/virdev.hxx> +#include <o3tl/string_view.hxx> + +#include <mutex> + +#define NOT_LOADED (tools::Long(-1)) +#define NOT_LOADED_COLOR (Color(ColorTransparency, 0xffffffff)) + +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::task; +using namespace ::com::sun::star::uno; + +namespace { + +class SplashScreen; + +class SplashScreenWindow : public IntroWindow +{ +public: + SplashScreen *pSpl; + ScopedVclPtr<VirtualDevice> _vdev; + explicit SplashScreenWindow(SplashScreen *); + virtual ~SplashScreenWindow() override { disposeOnce(); } + virtual void dispose() override; + // workwindow + virtual void Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle&) override; + void Redraw(); + +}; + +class SplashScreen + : public ::cppu::WeakImplHelper< XStatusIndicator, XInitialization, XServiceInfo > +{ + friend class SplashScreenWindow; +private: + VclPtr<SplashScreenWindow> pWindow; + + DECL_LINK( AppEventListenerHdl, VclSimpleEvent&, void ); + virtual ~SplashScreen() override; + void loadConfig(); + void updateStatus(); + void SetScreenBitmap(BitmapEx &rBitmap); + static void determineProgressRatioValues( double& rXRelPos, double& rYRelPos, double& rRelWidth, double& rRelHeight ); + + BitmapEx _aIntroBmp; + Color _cProgressFrameColor; + Color _cProgressBarColor; + Color _cProgressTextColor; + bool _bNativeProgress; + OUString _sAppName; + OUString _sProgressText; + + sal_Int32 _iMax; + sal_Int32 _iProgress; + bool _bPaintProgress; + bool _bVisible; + bool _bShowLogo; + bool _bFullScreenSplash; + bool _bProgressEnd; + tools::Long _height, _width, _tlx, _tly, _barwidth; + tools::Long _barheight, _barspace, _textBaseline; + double _fXPos, _fYPos; + double _fWidth, _fHeight; + static constexpr tools::Long _xoffset = 12, _yoffset = 18; + +public: + SplashScreen(); + + // XStatusIndicator + virtual void SAL_CALL end() override; + virtual void SAL_CALL reset() override; + virtual void SAL_CALL setText(const OUString& aText) override; + virtual void SAL_CALL setValue(sal_Int32 nValue) override; + virtual void SAL_CALL start(const OUString& aText, sal_Int32 nRange) override; + + // XInitialize + virtual void SAL_CALL initialize( const css::uno::Sequence< css::uno::Any>& aArguments ) override; + + virtual OUString SAL_CALL getImplementationName() override + { return "com.sun.star.office.comp.SplashScreen"; } + + virtual sal_Bool SAL_CALL supportsService(OUString const & ServiceName) override + { return cppu::supportsService(this, ServiceName); } + + virtual css::uno::Sequence<OUString> SAL_CALL getSupportedServiceNames() override + { return { "com.sun.star.office.SplashScreen" }; } +}; + +SplashScreenWindow::SplashScreenWindow(SplashScreen *pSplash) + : pSpl( pSplash ) + , _vdev(VclPtr<VirtualDevice>::Create(*GetOutDev())) +{ + _vdev->EnableRTL(IsRTLEnabled()); +} + +void SplashScreenWindow::dispose() +{ + pSpl = nullptr; + IntroWindow::dispose(); +} + +void SplashScreenWindow::Redraw() +{ + Invalidate(); + // Trigger direct painting too - otherwise the splash screen won't be + // shown in some cases (when the idle timer won't be hit). + Paint(*GetOutDev(), tools::Rectangle()); + GetOutDev()->Flush(); +} + +SplashScreen::SplashScreen() + : pWindow( VclPtr<SplashScreenWindow>::Create(this) ) + , _cProgressFrameColor(NOT_LOADED_COLOR) + , _cProgressBarColor(NOT_LOADED_COLOR) + , _cProgressTextColor(NOT_LOADED_COLOR) + , _bNativeProgress(true) + , _iMax(100) + , _iProgress(0) + , _bPaintProgress(false) + , _bVisible(true) + , _bShowLogo(true) + , _bFullScreenSplash(false) + , _bProgressEnd(false) + , _height(0) + , _width(0) + , _tlx(NOT_LOADED) + , _tly(NOT_LOADED) + , _barwidth(NOT_LOADED) + , _barheight(NOT_LOADED) + , _barspace(2) + , _textBaseline(NOT_LOADED) + , _fXPos(-1.0) + , _fYPos(-1.0) + , _fWidth(-1.0) + , _fHeight(-1.0) +{ + loadConfig(); +} + +SplashScreen::~SplashScreen() +{ + Application::RemoveEventListener( + LINK( this, SplashScreen, AppEventListenerHdl ) ); + pWindow->Hide(); + pWindow.disposeAndClear(); +} + +void SAL_CALL SplashScreen::start(const OUString&, sal_Int32 nRange) +{ + _iMax = nRange; + if (_bVisible) { + _bProgressEnd = false; + SolarMutexGuard aSolarGuard; + pWindow->Show(); + pWindow->Redraw(); + } +} + +void SAL_CALL SplashScreen::end() +{ + _iProgress = _iMax; + if (_bVisible ) + { + pWindow->Hide(); + } + _bProgressEnd = true; +} + +void SAL_CALL SplashScreen::reset() +{ + _iProgress = 0; + if (_bVisible && !_bProgressEnd ) + { + pWindow->Show(); + updateStatus(); + } +} + +void SAL_CALL SplashScreen::setText(const OUString& rText) +{ + SolarMutexGuard aSolarGuard; + if ( _sProgressText != rText ) + { + _sProgressText = rText; + + if (_bVisible && !_bProgressEnd) + { + pWindow->Show(); + updateStatus(); + } + } +} + +void SAL_CALL SplashScreen::setValue(sal_Int32 nValue) +{ + SAL_INFO( "desktop.splash", "setValue: " << nValue ); + + SolarMutexGuard aSolarGuard; + if (_bVisible && !_bProgressEnd) { + pWindow->Show(); + if (nValue >= _iMax) + _iProgress = _iMax; + else + _iProgress = nValue; + updateStatus(); + } +} + +// XInitialize +void SAL_CALL +SplashScreen::initialize( const css::uno::Sequence< css::uno::Any>& aArguments ) +{ + static std::mutex aMutex; + std::lock_guard aGuard( aMutex ); + if (!aArguments.hasElements()) + return; + + aArguments[0] >>= _bVisible; + if (aArguments.getLength() > 1 ) + aArguments[1] >>= _sAppName; + + // start to determine bitmap and all other required value + if ( _bShowLogo ) + SetScreenBitmap (_aIntroBmp); + Size aSize = _aIntroBmp.GetSizePixel(); + pWindow->SetOutputSizePixel( aSize ); + pWindow->_vdev->SetOutputSizePixel( aSize ); + _height = aSize.Height(); + _width = aSize.Width(); + if (_width > 500) + { + Point xtopleft(212,216); + if ( NOT_LOADED == _tlx || NOT_LOADED == _tly ) + { + _tlx = xtopleft.X(); // top-left x + _tly = xtopleft.Y(); // top-left y + } + if ( NOT_LOADED == _barwidth ) + _barwidth = 263; + if ( NOT_LOADED == _barheight ) + _barheight = 8; + } + else + { + if ( NOT_LOADED == _barwidth ) + _barwidth = _width - (2 * _xoffset); + if ( NOT_LOADED == _barheight ) + _barheight = 6; + if ( NOT_LOADED == _tlx || NOT_LOADED == _tly ) + { + _tlx = _xoffset; // top-left x + _tly = _height - _yoffset; // top-left y + } + } + + if ( NOT_LOADED == _textBaseline ) + _textBaseline = _height; + + if ( NOT_LOADED_COLOR == _cProgressFrameColor ) + _cProgressFrameColor = COL_LIGHTGRAY; + + if ( NOT_LOADED_COLOR == _cProgressBarColor ) + { + // progress bar: new color only for big bitmap format + if ( _width > 500 ) + _cProgressBarColor = Color( 157, 202, 18 ); + else + _cProgressBarColor = COL_BLUE; + } + + if ( NOT_LOADED_COLOR == _cProgressTextColor ) + _cProgressTextColor = COL_BLACK; + + Application::AddEventListener( + LINK( this, SplashScreen, AppEventListenerHdl ) ); +} + +void SplashScreen::updateStatus() +{ + if (!_bVisible || _bProgressEnd) + return; + if (!_bPaintProgress) + _bPaintProgress = true; + pWindow->Redraw(); +} + +// internal private methods +IMPL_LINK( SplashScreen, AppEventListenerHdl, VclSimpleEvent&, inEvent, void ) +{ + if (static_cast<VclWindowEvent&>(inEvent).GetWindow() == pWindow) + { + switch ( inEvent.GetId() ) + { + case VclEventId::WindowShow: + pWindow->Redraw(); + break; + default: + break; + } + } +} + +// Read keys from soffice{.ini|rc}: +OUString implReadBootstrapKey( const OUString& _rKey ) +{ + OUString sValue; + rtl::Bootstrap::get(_rKey, sValue); + return sValue; +} + +void SplashScreen::loadConfig() +{ + _bShowLogo = implReadBootstrapKey( "Logo" ) != "0"; + + OUString sProgressFrameColor = implReadBootstrapKey( "ProgressFrameColor" ); + OUString sProgressBarColor = implReadBootstrapKey( "ProgressBarColor" ); + OUString sProgressTextColor = implReadBootstrapKey( "ProgressTextColor" ); + OUString sProgressTextBaseline = implReadBootstrapKey( "ProgressTextBaseline" ); + OUString sSize = implReadBootstrapKey( "ProgressSize" ); + OUString sPosition = implReadBootstrapKey( "ProgressPosition" ); + OUString sFullScreenSplash = implReadBootstrapKey( "FullScreenSplash" ); + OUString sNativeProgress = implReadBootstrapKey( "NativeProgress" ); + + + // Determine full screen splash mode + _bFullScreenSplash = (( !sFullScreenSplash.isEmpty() ) && + ( sFullScreenSplash != "0" )); + + // Try to retrieve the relative values for the progress bar. The current + // schema uses the screen ratio to retrieve the associated values. + if ( _bFullScreenSplash ) + determineProgressRatioValues( _fXPos, _fYPos, _fWidth, _fHeight ); + + if ( !sProgressFrameColor.isEmpty() ) + { + sal_uInt8 nRed = 0; + sal_Int32 idx = 0; + sal_Int32 temp = o3tl::toInt32(o3tl::getToken(sProgressFrameColor, 0, ',', idx )); + if ( idx != -1 ) + { + nRed = static_cast< sal_uInt8 >( temp ); + temp = o3tl::toInt32(o3tl::getToken(sProgressFrameColor, 0, ',', idx )); + } + if ( idx != -1 ) + { + sal_uInt8 nGreen = static_cast< sal_uInt8 >( temp ); + sal_uInt8 nBlue = static_cast< sal_uInt8 >( o3tl::toInt32(o3tl::getToken(sProgressFrameColor, 0, ',', idx )) ); + _cProgressFrameColor = Color( nRed, nGreen, nBlue ); + } + } + + if ( !sProgressBarColor.isEmpty() ) + { + sal_uInt8 nRed = 0; + sal_Int32 idx = 0; + sal_Int32 temp = o3tl::toInt32(o3tl::getToken(sProgressBarColor, 0, ',', idx )); + if ( idx != -1 ) + { + nRed = static_cast< sal_uInt8 >( temp ); + temp = o3tl::toInt32(o3tl::getToken(sProgressBarColor, 0, ',', idx )); + } + if ( idx != -1 ) + { + sal_uInt8 nGreen = static_cast< sal_uInt8 >( temp ); + sal_uInt8 nBlue = static_cast< sal_uInt8 >( o3tl::toInt32(o3tl::getToken(sProgressBarColor, 0, ',', idx )) ); + _cProgressBarColor = Color( nRed, nGreen, nBlue ); + } + } + + if ( !sProgressTextColor.isEmpty() ) + { + sal_uInt8 nRed = 0; + sal_Int32 idx = 0; + sal_Int32 temp = o3tl::toInt32(o3tl::getToken(sProgressTextColor, 0, ',', idx )); + if ( idx != -1 ) + { + nRed = static_cast< sal_uInt8 >( temp ); + temp = o3tl::toInt32(o3tl::getToken(sProgressTextColor, 0, ',', idx )); + } + if ( idx != -1 ) + { + sal_uInt8 nGreen = static_cast< sal_uInt8 >( temp ); + sal_uInt8 nBlue = static_cast< sal_uInt8 >( o3tl::toInt32(o3tl::getToken(sProgressTextColor, 0, ',', idx )) ); + _cProgressTextColor = Color( nRed, nGreen, nBlue ); + } + } + + if ( !sProgressTextBaseline.isEmpty() ) + { + _textBaseline = sProgressTextBaseline.toInt32(); + } + + if( !sNativeProgress.isEmpty() ) + { + _bNativeProgress = sNativeProgress.toBoolean(); + } + + if ( !sSize.isEmpty() ) + { + sal_Int32 idx = 0; + sal_Int32 temp = o3tl::toInt32(o3tl::getToken(sSize, 0, ',', idx )); + if ( idx != -1 ) + { + _barwidth = temp; + _barheight = o3tl::toInt32(o3tl::getToken(sSize, 0, ',', idx )); + } + } + + if ( _barheight >= 10 ) + _barspace = 3; // more space between frame and bar + + if ( !sPosition.isEmpty() ) + { + sal_Int32 idx = 0; + sal_Int32 temp = o3tl::toInt32(o3tl::getToken(sPosition, 0, ',', idx )); + if ( idx != -1 ) + { + _tlx = temp; + _tly = o3tl::toInt32(o3tl::getToken(sPosition, 0, ',', idx )); + } + } +} + +void SplashScreen::SetScreenBitmap(BitmapEx &rBitmap) +{ + sal_Int32 nWidth( 0 ); + sal_Int32 nHeight( 0 ); + + // determine desktop resolution + sal_uInt32 nCount = Application::GetScreenCount(); + if ( nCount > 0 ) + { + // retrieve size from first screen + AbsoluteScreenPixelRectangle aScreenArea = Application::GetScreenPosSizePixel(static_cast<unsigned int>(0)); + nWidth = aScreenArea.GetWidth(); + nHeight = aScreenArea.GetHeight(); + } + + // create file name from screen resolution information + OUString aResBuf = "_" + OUString::number(nWidth) + "x" + OUString::number(nHeight); + if ( !_sAppName.isEmpty() ) + if (Application::LoadBrandBitmap(Concat2View("intro_" + _sAppName + aResBuf), rBitmap)) + return; + + if (Application::LoadBrandBitmap(Concat2View("intro" + aResBuf), rBitmap)) + return; + + (void)Application::LoadBrandBitmap (u"intro", rBitmap); +} + +void SplashScreen::determineProgressRatioValues( + double& rXRelPos, double& rYRelPos, + double& rRelWidth, double& rRelHeight ) +{ + sal_Int32 nScreenRatio( 0 ); + + // determine desktop resolution + sal_uInt32 nCount = Application::GetScreenCount(); + if ( nCount > 0 ) + { + // retrieve size from first screen + AbsoluteScreenPixelRectangle aScreenArea = Application::GetScreenPosSizePixel(static_cast<unsigned int>(0)); + sal_Int32 nWidth = aScreenArea.GetWidth(); + sal_Int32 nHeight = aScreenArea.GetHeight(); + nScreenRatio = nHeight ? sal_Int32( rtl::math::round( double( nWidth ) / double( nHeight ), 2 ) * 100 ) : 0; + } + + char szFullScreenProgressRatio[] = "FullScreenProgressRatio0"; + char szFullScreenProgressPos[] = "FullScreenProgressPos0"; + char szFullScreenProgressSize[] = "FullScreenProgressSize0"; + for ( sal_Int32 i = 0; i <= 9; i++ ) + { + char cNum = '0' + char( i ); + szFullScreenProgressRatio[23] = cNum; + szFullScreenProgressPos[21] = cNum; + szFullScreenProgressSize[22] = cNum; + + OUString sFullScreenProgressRatio = implReadBootstrapKey( + OUString::createFromAscii( szFullScreenProgressRatio ) ); + + if ( !sFullScreenProgressRatio.isEmpty() ) + { + double fRatio = sFullScreenProgressRatio.toDouble(); + sal_Int32 nRatio = sal_Int32( rtl::math::round( fRatio, 2 ) * 100 ); + if ( nRatio == nScreenRatio ) + { + OUString sFullScreenProgressPos = implReadBootstrapKey( + OUString::createFromAscii( szFullScreenProgressPos ) ); + OUString sFullScreenProgressSize = implReadBootstrapKey( + OUString::createFromAscii( szFullScreenProgressSize ) ); + + if ( !sFullScreenProgressPos.isEmpty() ) + { + sal_Int32 idx = 0; + double temp = o3tl::toDouble(o3tl::getToken(sFullScreenProgressPos, 0, ',', idx )); + if ( idx != -1 ) + { + rXRelPos = temp; + rYRelPos = o3tl::toDouble(o3tl::getToken(sFullScreenProgressPos, 0, ',', idx )); + } + } + + if ( !sFullScreenProgressSize.isEmpty() ) + { + sal_Int32 idx = 0; + double temp = o3tl::toDouble(o3tl::getToken(sFullScreenProgressSize, 0, ',', idx )); + if ( idx != -1 ) + { + rRelWidth = temp; + rRelHeight = o3tl::toDouble(o3tl::getToken(sFullScreenProgressSize, 0, ',', idx )); + } + } + } + } + else + break; + } +} + +void SplashScreenWindow::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle&) +{ + if (!pSpl || !pSpl->_bVisible) + return; + + //native drawing + // in case of native controls we need to draw directly to the window + if (pSpl->_bNativeProgress && rRenderContext.IsNativeControlSupported(ControlType::IntroProgress, ControlPart::Entire)) + { + rRenderContext.DrawBitmapEx(Point(), pSpl->_aIntroBmp); + + ImplControlValue aValue( pSpl->_iProgress * pSpl->_barwidth / pSpl->_iMax); + tools::Rectangle aDrawRect( Point(pSpl->_tlx, pSpl->_tly), Size( pSpl->_barwidth, pSpl->_barheight)); + tools::Rectangle aNativeControlRegion, aNativeContentRegion; + + if (rRenderContext.GetNativeControlRegion(ControlType::IntroProgress, ControlPart::Entire, aDrawRect, + ControlState::ENABLED, aValue, + aNativeControlRegion, aNativeContentRegion)) + { + tools::Long nProgressHeight = aNativeControlRegion.GetHeight(); + aDrawRect.AdjustTop( -((nProgressHeight - pSpl->_barheight)/2) ); + aDrawRect.AdjustBottom((nProgressHeight - pSpl->_barheight)/2 ); + } + + if (rRenderContext.DrawNativeControl(ControlType::IntroProgress, ControlPart::Entire, aDrawRect, + ControlState::ENABLED, aValue, pSpl->_sProgressText)) + { + return; + } + } + + // non native drawing + // draw bitmap + _vdev->DrawBitmapEx(Point(), pSpl->_aIntroBmp); + + if (pSpl->_bPaintProgress) { + // draw progress... + tools::Long length = (pSpl->_iProgress * pSpl->_barwidth / pSpl->_iMax) - (2 * pSpl->_barspace); + if (length < 0) length = 0; + + // border + _vdev->SetFillColor(); + _vdev->SetLineColor( pSpl->_cProgressFrameColor ); + _vdev->DrawRect(tools::Rectangle(pSpl->_tlx, pSpl->_tly, pSpl->_tlx+pSpl->_barwidth, pSpl->_tly+pSpl->_barheight)); + _vdev->SetFillColor( pSpl->_cProgressBarColor ); + _vdev->SetLineColor(); + _vdev->DrawRect(tools::Rectangle(pSpl->_tlx+pSpl->_barspace, pSpl->_tly+pSpl->_barspace, pSpl->_tlx+pSpl->_barspace+length, pSpl->_tly+pSpl->_barheight-pSpl->_barspace)); + vcl::Font aFont; + aFont.SetFontSize(Size(0, 12)); + aFont.SetAlignment(ALIGN_BASELINE); + _vdev->SetFont(aFont); + _vdev->SetTextColor(pSpl->_cProgressTextColor); + _vdev->DrawText(Point(pSpl->_tlx, pSpl->_textBaseline), pSpl->_sProgressText); + } + rRenderContext.DrawOutDev(Point(), GetOutputSizePixel(), Point(), _vdev->GetOutputSizePixel(), *_vdev); +} + +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +desktop_SplashScreen_get_implementation( + css::uno::XComponentContext* , css::uno::Sequence<css::uno::Any> const&) +{ + return cppu::acquire(new SplashScreen()); +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/splash/unxsplash.cxx b/desktop/source/splash/unxsplash.cxx new file mode 100644 index 0000000000..b218b75919 --- /dev/null +++ b/desktop/source/splash/unxsplash.cxx @@ -0,0 +1,128 @@ +/* -*- 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 "unxsplash.hxx" +#include <stdio.h> +#include <osl/process.h> +#include <cppuhelper/supportsservice.hxx> +#include <sal/log.hxx> +#include <com/sun/star/uno/XComponentContext.hpp> + +using namespace com::sun::star; + +namespace desktop +{ + UnxSplashScreen::UnxSplashScreen() + : m_pOutFd( nullptr ) +{ +} + +UnxSplashScreen::~UnxSplashScreen() +{ + SAL_INFO("desktop.splash", "UnxSplashScreen::~UnxSplashScreen()"); + if ( m_pOutFd ) + { + fclose( m_pOutFd ); + m_pOutFd = nullptr; + } +} + +void SAL_CALL UnxSplashScreen::start( const OUString& /*aText*/, sal_Int32 /*nRange*/ ) +{ +} + +void SAL_CALL UnxSplashScreen::end() +{ + SAL_INFO("desktop.splash", "UnxSplashScreen::end()"); + if( !m_pOutFd ) + return; + + fprintf( m_pOutFd, "end\n" ); + fflush( m_pOutFd ); +} + +void SAL_CALL UnxSplashScreen::reset() +{ + SAL_INFO("desktop.splash", "UNXSplashScreen::reset()"); + if( !m_pOutFd ) + return; + + fprintf( m_pOutFd, "restart\n" ); + fflush( m_pOutFd ); +} + +void SAL_CALL UnxSplashScreen::setText( const OUString& /*aText*/ ) +{ + // TODO? +} + +void SAL_CALL UnxSplashScreen::setValue( sal_Int32 nValue ) +{ + if ( m_pOutFd ) + { + fprintf( m_pOutFd, "%" SAL_PRIdINT32 "%%\n", nValue ); + fflush( m_pOutFd ); + } +} + +// XInitialize +void SAL_CALL +UnxSplashScreen::initialize( const css::uno::Sequence< css::uno::Any>& ) +{ + for ( sal_uInt32 i = 0; i < osl_getCommandArgCount(); i++ ) + { + OUString aArg; + osl_getCommandArg( i, &aArg.pData ); + OUString aNum; + if ( aArg.startsWithIgnoreAsciiCase("--splash-pipe=", &aNum) ) + { + auto fd = aNum.toUInt32(); + m_pOutFd = fdopen( fd, "w" ); + SAL_INFO("desktop.splash", "Got argument '--splash-pipe=" << fd << " ('" + << aNum << "') (" + << static_cast<void *>(m_pOutFd) << ")"); + } + } +} + +OUString UnxSplashScreen::getImplementationName() +{ + return "com.sun.star.office.comp.PipeSplashScreen"; +} + +sal_Bool UnxSplashScreen::supportsService(OUString const & ServiceName) +{ + return cppu::supportsService(this, ServiceName); +} + +css::uno::Sequence<OUString> UnxSplashScreen::getSupportedServiceNames() +{ + return { "com.sun.star.office.PipeSplashScreen" }; +} + +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +desktop_UnxSplash_get_implementation( + css::uno::XComponentContext* , css::uno::Sequence<css::uno::Any> const&) +{ + return cppu::acquire(new desktop::UnxSplashScreen()); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/splash/unxsplash.hxx b/desktop/source/splash/unxsplash.hxx new file mode 100644 index 0000000000..52b148abf0 --- /dev/null +++ b/desktop/source/splash/unxsplash.hxx @@ -0,0 +1,53 @@ +/* -*- 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 <stdio.h> + +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/task/XStatusIndicator.hpp> +#include <com/sun/star/lang/XInitialization.hpp> +#include <cppuhelper/implbase.hxx> + +namespace desktop { + +class UnxSplashScreen : public ::cppu::WeakImplHelper< css::task::XStatusIndicator, css::lang::XInitialization, css::lang::XServiceInfo > +{ +private: + UnxSplashScreen( const UnxSplashScreen& ) = delete; + UnxSplashScreen operator =( const UnxSplashScreen& ) = delete; + + virtual ~UnxSplashScreen() override; + + FILE *m_pOutFd; + +public: + explicit UnxSplashScreen(); + + // XStatusIndicator + virtual void SAL_CALL start( const OUString& aText, sal_Int32 nRange ) override; + virtual void SAL_CALL end() override; + virtual void SAL_CALL reset() override; + virtual void SAL_CALL setText( const OUString& aText ) override; + virtual void SAL_CALL setValue( sal_Int32 nValue ) override; + + // XInitialize + virtual void SAL_CALL initialize( const css::uno::Sequence< css::uno::Any>& aArguments ) override; + + virtual OUString SAL_CALL getImplementationName() override; + + virtual sal_Bool SAL_CALL supportsService(OUString const & ServiceName) override; + + virtual css::uno::Sequence<OUString> SAL_CALL getSupportedServiceNames() override; +}; + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/test/deployment/active/Addons.xcu b/desktop/test/deployment/active/Addons.xcu new file mode 100644 index 0000000000..d3b481ef43 --- /dev/null +++ b/desktop/test/deployment/active/Addons.xcu @@ -0,0 +1,58 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + * 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 . + --> + +<o:component-data xmlns:o="http://openoffice.org/2001/registry" + o:package="org.openoffice.Office" o:name="Addons"> + <node o:name="AddonUI"> + <node o:name="OfficeMenuBar"> + <node o:name="org.openoffice.test.desktop.deployment.active" + o:op="replace"> + <prop o:name="Title" xml:lang="en-US"> + <value>active</value> + </prop> + <node o:name="Submenu"> + <node o:name="1" o:op="replace"> + <prop o:name="URL"> + <value>vnd.org.openoffice.test.desktop.deployment.active_native:</value> + </prop> + <prop o:name="Title" xml:lang="en-US"> + <value>native</value> + </prop> + </node> + <node o:name="2" o:op="replace"> + <prop o:name="URL"> + <value>vnd.org.openoffice.test.desktop.deployment.active_java:</value> + </prop> + <prop o:name="Title" xml:lang="en-US"> + <value>java</value> + </prop> + </node> + <node o:name="3" o:op="replace"> + <prop o:name="URL"> + <value>vnd.org.openoffice.test.desktop.deployment.active_python:</value> + </prop> + <prop o:name="Title" xml:lang="en-US"> + <value>python</value> + </prop> + </node> + </node> + </node> + </node> + </node> +</o:component-data> diff --git a/desktop/test/deployment/active/MANIFEST.MF b/desktop/test/deployment/active/MANIFEST.MF new file mode 100644 index 0000000000..63480874dd --- /dev/null +++ b/desktop/test/deployment/active/MANIFEST.MF @@ -0,0 +1,3 @@ +Sealed: true +RegistrationClassName: com.sun.star.comp.test.deployment.active_java.Services +UNO-Type-Path: diff --git a/desktop/test/deployment/active/META-INF/manifest.xml b/desktop/test/deployment/active/META-INF/manifest.xml new file mode 100644 index 0000000000..7cca7841d4 --- /dev/null +++ b/desktop/test/deployment/active/META-INF/manifest.xml @@ -0,0 +1,34 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + * 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 . + --> + +<m:manifest xmlns:m="http://openoffice.org/2001/manifest"> + <m:file-entry m:media-type="application/vnd.sun.star.configuration-data" + m:full-path="Addons.xcu"/> + <m:file-entry m:media-type="application/vnd.sun.star.configuration-data" + m:full-path="ProtocolHandler.xcu"/> + <m:file-entry + m:media-type="application/vnd.sun.star.uno-component;type=native;platform=@PLATFORM@" + m:full-path="active_native.uno@SHARED_EXTENSION@"/> + <m:file-entry + m:media-type="application/vnd.sun.star.uno-component;type=Java" + m:full-path="active_java.jar"/> + <m:file-entry + m:media-type="application/vnd.sun.star.uno-component;type=Python" + m:full-path="active_python.py"/> +</m:manifest> diff --git a/desktop/test/deployment/active/ProtocolHandler.xcu b/desktop/test/deployment/active/ProtocolHandler.xcu new file mode 100644 index 0000000000..984ed981d9 --- /dev/null +++ b/desktop/test/deployment/active/ProtocolHandler.xcu @@ -0,0 +1,39 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + * 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 . + --> + +<o:component-data xmlns:o="http://openoffice.org/2001/registry" + o:package="org.openoffice.Office" o:name="ProtocolHandler"> + <node o:name="HandlerSet"> + <node o:name="com.sun.star.test.deployment.active_native" o:op="replace"> + <prop o:name="Protocols"> + <value>vnd.org.openoffice.test.desktop.deployment.active_native:*</value> + </prop> + </node> + <node o:name="com.sun.star.test.deployment.active_java" o:op="replace"> + <prop o:name="Protocols"> + <value>vnd.org.openoffice.test.desktop.deployment.active_java:*</value> + </prop> + </node> + <node o:name="com.sun.star.test.deployment.active_python" o:op="replace"> + <prop o:name="Protocols"> + <value>vnd.org.openoffice.test.desktop.deployment.active_python:*</value> + </prop> + </node> + </node> +</o:component-data> diff --git a/desktop/test/deployment/active/active_native.cxx b/desktop/test/deployment/active/active_native.cxx new file mode 100644 index 0000000000..753f0da773 --- /dev/null +++ b/desktop/test/deployment/active/active_native.cxx @@ -0,0 +1,276 @@ +/* -*- 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 <cassert> + +#include <com/sun/star/awt/MessageBoxButtons.hpp> +#include <com/sun/star/awt/Rectangle.hpp> +#include <com/sun/star/awt/Toolkit.hpp> +#include <com/sun/star/awt/XMessageBox.hpp> +#include <com/sun/star/awt/XWindowPeer.hpp> +#include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/frame/DispatchDescriptor.hpp> +#include <com/sun/star/frame/Desktop.hpp> +#include <com/sun/star/frame/XDispatch.hpp> +#include <com/sun/star/frame/XDispatchProvider.hpp> +#include <com/sun/star/frame/XFrame.hpp> +#include <com/sun/star/frame/XStatusListener.hpp> +#include <com/sun/star/lang/XComponent.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/registry/XRegistryKey.hpp> +#include <com/sun/star/uno/DeploymentException.hpp> +#include <com/sun/star/uno/Exception.hpp> +#include <com/sun/star/uno/Reference.hxx> +#include <com/sun/star/uno/RuntimeException.hpp> +#include <com/sun/star/uno/Sequence.hxx> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <com/sun/star/uno/XInterface.hpp> +#include <com/sun/star/util/URL.hpp> +#include <cppuhelper/factory.hxx> +#include <cppuhelper/implbase2.hxx> +#include <cppuhelper/implementationentry.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <cppuhelper/weak.hxx> +#include <rtl/textenc.h> +#include <rtl/ustring.hxx> +#include <sal/log.hxx> +#include <sal/types.h> +#include <uno/lbnames.h> + +namespace { + +class Provider: + public cppu::WeakImplHelper2< + css::lang::XServiceInfo, css::frame::XDispatchProvider > +{ +public: + Provider(const Provider&) = delete; + const Provider& operator=(const Provider&) = delete; + + static css::uno::Reference< css::uno::XInterface > SAL_CALL static_create( + css::uno::Reference< css::uno::XComponentContext > const & xContext) + { return static_cast< cppu::OWeakObject * >(new Provider(xContext)); } + + static rtl::OUString SAL_CALL static_getImplementationName(); + + static css::uno::Sequence< rtl::OUString > SAL_CALL + static_getSupportedServiceNames(); + +private: + explicit Provider( + css::uno::Reference< css::uno::XComponentContext > const & context): + context_(context) { assert(context.is()); } + + virtual ~Provider() {} + + virtual rtl::OUString SAL_CALL getImplementationName() override + { return static_getImplementationName(); } + + virtual sal_Bool SAL_CALL supportsService(rtl::OUString const & ServiceName) override + { return cppu::supportsService(this, ServiceName); } + + virtual css::uno::Sequence< rtl::OUString > SAL_CALL + getSupportedServiceNames() override + { return static_getSupportedServiceNames(); } + + virtual css::uno::Reference< css::frame::XDispatch > SAL_CALL queryDispatch( + css::util::URL const &, rtl::OUString const &, sal_Int32) override; + + virtual css::uno::Sequence< css::uno::Reference< css::frame::XDispatch > > + SAL_CALL queryDispatches( + css::uno::Sequence< css::frame::DispatchDescriptor > const & Requests) override; + + css::uno::Reference< css::uno::XComponentContext > context_; +}; + +rtl::OUString Provider::static_getImplementationName() { + return rtl::OUString("com.sun.star.comp.test.deployment.active_native"); +} + +css::uno::Sequence< rtl::OUString > Provider::static_getSupportedServiceNames() +{ + rtl::OUString name("com.sun.star.test.deployment.active_native"); + return css::uno::Sequence< rtl::OUString >(&name, 1); +} + +css::uno::Reference< css::frame::XDispatch > Provider::queryDispatch( + css::util::URL const &, rtl::OUString const &, sal_Int32) +{ + css::uno::Reference< css::frame::XDispatch > dispatch; + if (!(context_->getValueByName( + "/singletons/com.sun.star.test.deployment." + "active_native_singleton") >>= + dispatch) || + !dispatch.is()) + { + throw css::uno::DeploymentException( + "component context fails to supply singleton" + " com.sun.star.test.deployment.active_native_singleton of type" + " com.sun.star.frame.XDispatch", + context_); + } + return dispatch; +} + +css::uno::Sequence< css::uno::Reference< css::frame::XDispatch > > +Provider::queryDispatches( + css::uno::Sequence< css::frame::DispatchDescriptor > const & Requests) +{ + css::uno::Sequence< css::uno::Reference< css::frame::XDispatch > > s( + Requests.getLength()); + for (sal_Int32 i = 0; i < s.getLength(); ++i) { + s[i] = queryDispatch( + Requests[i].FeatureURL, Requests[i].FrameName, + Requests[i].SearchFlags); + } + return s; +} + +class Dispatch: + public cppu::WeakImplHelper2< + css::lang::XServiceInfo, css::frame::XDispatch > +{ +public: + Dispatch(const Dispatch&) = delete; + const Dispatch& operator=(const Dispatch&) = delete; + + static css::uno::Reference< css::uno::XInterface > SAL_CALL static_create( + css::uno::Reference< css::uno::XComponentContext > const & xContext) + { return static_cast< cppu::OWeakObject * >(new Dispatch(xContext)); } + + static rtl::OUString SAL_CALL static_getImplementationName(); + + static css::uno::Sequence< rtl::OUString > SAL_CALL + static_getSupportedServiceNames() + { return css::uno::Sequence< rtl::OUString >(); } + +private: + explicit Dispatch( + css::uno::Reference< css::uno::XComponentContext > const & context): + context_(context) { assert(context.is()); } + + virtual ~Dispatch() {} + + virtual rtl::OUString SAL_CALL getImplementationName() override + { return static_getImplementationName(); } + + virtual sal_Bool SAL_CALL supportsService(rtl::OUString const & ServiceName) override + { return cppu::supportsService(this, ServiceName); } + + virtual css::uno::Sequence< rtl::OUString > SAL_CALL + getSupportedServiceNames() override + { return static_getSupportedServiceNames(); } + + virtual void SAL_CALL dispatch( + css::util::URL const &, + css::uno::Sequence< css::beans::PropertyValue > const &) override; + + virtual void SAL_CALL addStatusListener( + css::uno::Reference< css::frame::XStatusListener > const &, + css::util::URL const &) override + {} + + virtual void SAL_CALL removeStatusListener( + css::uno::Reference< css::frame::XStatusListener > const &, + css::util::URL const &) override + {} + + css::uno::Reference< css::uno::XComponentContext > context_; +}; + +rtl::OUString Dispatch::static_getImplementationName() { + return rtl::OUString( + "com.sun.star.comp.test.deployment.active_native_singleton"); +} + +void Dispatch::dispatch( + css::util::URL const &, + css::uno::Sequence< css::beans::PropertyValue > const &) +{ + css::uno::Reference< css::frame::XDesktop2 > xDesktop = css::frame::Desktop::create(context_); + css::uno::Reference< css::frame::XFrame > xFrame = xDesktop->getCurrentFrame(); + css::uno::Reference< css::awt::XWindowPeer > xWindowPeer( xFrame->getComponentWindow(), css::uno::UNO_QUERY_THROW ); + css::uno::Reference< css::awt::XToolkit2 > xToolkit = css::awt::Toolkit::create(context_); + css::uno::Reference< css::awt::XMessageBox > box( + xToolkit->createMessageBox( + xWindowPeer, + css::awt::MessageBoxType_INFOBOX, + css::awt::MessageBoxButtons::BUTTONS_OK, "active", "native"), + css::uno::UNO_SET_THROW); + + box->execute(); + + css::uno::Reference< css::lang::XComponent > xComponent(box, css::uno::UNO_QUERY_THROW); + xComponent->dispose(); +} + +cppu::ImplementationEntry const services[] = { + { &Provider::static_create, &Provider::static_getImplementationName, + &Provider::static_getSupportedServiceNames, + &cppu::createSingleComponentFactory, nullptr, 0 }, + { &Dispatch::static_create, &Dispatch::static_getImplementationName, + &Dispatch::static_getSupportedServiceNames, + &cppu::createSingleComponentFactory, nullptr, 0 }, + { nullptr, nullptr, nullptr, nullptr, nullptr, 0 } +}; + +} + +extern "C" SAL_DLLPUBLIC_EXPORT void * component_getFactory( + char const * pImplName, void * pServiceManager, void * pRegistryKey) +{ + return cppu::component_getFactoryHelper( + pImplName, pServiceManager, pRegistryKey, services); +} + +extern "C" SAL_DLLPUBLIC_EXPORT void +component_getImplementationEnvironment( + char const ** ppEnvTypeName, uno_Environment **) +{ + *ppEnvTypeName = CPPU_CURRENT_LANGUAGE_BINDING_NAME; +} + +extern "C" SAL_DLLPUBLIC_EXPORT sal_Bool component_writeInfo( + void * pServiceManager, void * pRegistryKey) +{ + if (!component_writeInfoHelper(pServiceManager, pRegistryKey, services)) { + return false; + } + try { + css::uno::Reference< css::registry::XRegistryKey >( + (css::uno::Reference< css::registry::XRegistryKey >( + static_cast< css::registry::XRegistryKey * >(pRegistryKey))-> + createKey( + "/" + Dispatch::static_getImplementationName() + + ("/UNO/SINGLETONS/com.sun.star.test.deployment." + "active_native_singleton"))), + css::uno::UNO_SET_THROW)-> + setStringValue(Dispatch::static_getImplementationName()); + } catch (const css::uno::Exception & e) { + SAL_INFO( + "desktop.test", + "active_native component_writeInfo exception: " << e.Message); + return false; + } + return true; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/test/deployment/active/active_python.py b/desktop/test/deployment/active/active_python.py new file mode 100644 index 0000000000..7f6e5bbd44 --- /dev/null +++ b/desktop/test/deployment/active/active_python.py @@ -0,0 +1,112 @@ +# +# 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 . +# + +import uno +import unohelper + +from com.sun.star.awt import Rectangle +from com.sun.star.awt.MessageBoxButtons import BUTTONS_OK +from com.sun.star.awt.MessageBoxType import INFOBOX +from com.sun.star.frame import XDispatch, XDispatchProvider +from com.sun.star.lang import XServiceInfo +from com.sun.star.registry import InvalidRegistryException + +class Provider(unohelper.Base, XServiceInfo, XDispatchProvider): + implementationName = "com.sun.star.comp.test.deployment.active_python" + + serviceNames = ("com.sun.star.test.deployment.active_python",) + + def __init__(self, context): + self.context = context + + def getImplementationName(self): + return self.implementationName + + def supportsService(self, ServiceName): + return ServiceName in self.serviceNames + + def getSupportedServiceNames(self): + return self.serviceNames + + def queryDispatch(self, URL, TargetFrame, SearchFlags): + return self.context.getValueByName( \ + "/singletons/com.sun.star.test.deployment.active_python_singleton") + + def queryDispatches(self, Requests): + tuple( \ + self.queryDispatch(i.FeatureURL, i.FrameName, i.SearchFlags) \ + for i in Requests) + +class Dispatch(unohelper.Base, XServiceInfo, XDispatch): + implementationName = \ + "com.sun.star.comp.test.deployment.active_python_singleton" + + serviceNames = () + + def __init__(self, context): + self.context = context + + def getImplementationName(self): + return self.implementationName + + def supportsService(self, ServiceName): + return ServiceName in self.serviceNames + + def getSupportedServiceNames(self): + return self.serviceNames + + def dispatch(self, URL, Arguments): + smgr = self.context.getServiceManager() + box = smgr.createInstanceWithContext( \ + "com.sun.star.awt.Toolkit", self.context).createMessageBox( \ + smgr.createInstanceWithContext( \ + "com.sun.star.frame.Desktop", self.context). \ + getCurrentFrame().getComponentWindow(), \ + INFOBOX, BUTTONS_OK, "active", "python") + box.execute(); + box.dispose(); + + def addStatusListener(self, Control, URL): + pass + + def removeStatusListener(self, Control, URL): + pass + +def getComponentFactory(implementationName, smgr, regKey): + if implementationName == Provider.implementationName: + return unohelper.createSingleServiceFactory( \ + Provider, Provider.implementationName, Provider.serviceNames) + elif implementationName == Dispatch.implementationName: + return unohelper.createSingleServiceFactory( \ + Dispatch, Dispatch.implementationName, Dispatch.serviceNames) + else: + return None + +def writeRegistryInfo(smgr, regKey): + try: + for i in (Provider, Dispatch): + key = regKey.createKey("/" + i.implementationName + "/UNO") + for j in i.serviceNames: + key.createKey("/SERVICES/" + j); + regKey.createKey( \ + "/" + Dispatch.implementationName + "/UNO/SINGLETONS/" \ + "com.sun.star.test.deployment.active_python_singleton"). \ + setStringValue(Dispatch.implementationName) + except InvalidRegistryException: + return False + return True diff --git a/desktop/test/deployment/active/com/sun/star/comp/test/deployment/Dispatch.java b/desktop/test/deployment/active/com/sun/star/comp/test/deployment/Dispatch.java new file mode 100644 index 0000000000..e224c94183 --- /dev/null +++ b/desktop/test/deployment/active/com/sun/star/comp/test/deployment/Dispatch.java @@ -0,0 +1,95 @@ +/* + * 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 . + */ + +package com.sun.star.comp.test.deployment.active_java; + +import com.sun.star.awt.MessageBoxButtons; +import com.sun.star.awt.MessageBoxType; +import com.sun.star.awt.Rectangle; +import com.sun.star.awt.XMessageBox; +import com.sun.star.awt.XMessageBoxFactory; +import com.sun.star.awt.XWindowPeer; +import com.sun.star.beans.PropertyValue; +import com.sun.star.frame.DispatchDescriptor; +import com.sun.star.frame.XDesktop; +import com.sun.star.frame.XDispatch; +import com.sun.star.frame.XStatusListener; +import com.sun.star.lang.WrappedTargetRuntimeException; +import com.sun.star.lang.XComponent; +import com.sun.star.lang.XMultiComponentFactory; +import com.sun.star.lang.XServiceInfo; +import com.sun.star.lib.uno.helper.WeakBase; +import com.sun.star.uno.UnoRuntime; +import com.sun.star.uno.XComponentContext; +import com.sun.star.util.URL; + +public final class Dispatch extends WeakBase implements XServiceInfo, XDispatch +{ + public Dispatch(XComponentContext context) { + this.context = context; + } + + public String getImplementationName() { return implementationName; } + + public boolean supportsService(String ServiceName) { + return false; //TODO + } + + public String[] getSupportedServiceNames() { + return serviceNames; + } + + public void dispatch(URL URL, PropertyValue[] Arguments) { + try { + XMultiComponentFactory smgr = UnoRuntime.queryInterface( + XMultiComponentFactory.class, context.getServiceManager()); + XMessageBox box = UnoRuntime.queryInterface( + XMessageBoxFactory.class, + smgr.createInstanceWithContext( + "com.sun.star.awt.Toolkit", context)). + createMessageBox( + UnoRuntime.queryInterface( + XWindowPeer.class, + (UnoRuntime.queryInterface( + XDesktop.class, + smgr.createInstanceWithContext( + "com.sun.star.frame.Desktop", context)). + getCurrentFrame().getComponentWindow())), + MessageBoxType.INFOBOX, MessageBoxButtons.BUTTONS_OK, + "active", "java"); + box.execute(); + UnoRuntime.queryInterface(XComponent.class, box).dispose(); + } catch (com.sun.star.uno.RuntimeException e) { + throw e; + } catch (com.sun.star.uno.Exception e) { + throw new WrappedTargetRuntimeException(e, + "wrapped: " + e.getMessage(), this, e); + } + } + + public void addStatusListener(XStatusListener Control, URL URL) {} + + public void removeStatusListener(XStatusListener Control, URL URL) {} + + private final XComponentContext context; + + static final String implementationName = + "com.sun.star.comp.test.deployment.active_java_singleton"; + + static final String[] serviceNames = new String[0]; +} diff --git a/desktop/test/deployment/active/com/sun/star/comp/test/deployment/Provider.java b/desktop/test/deployment/active/com/sun/star/comp/test/deployment/Provider.java new file mode 100644 index 0000000000..3b9f8b88ee --- /dev/null +++ b/desktop/test/deployment/active/com/sun/star/comp/test/deployment/Provider.java @@ -0,0 +1,74 @@ +/* + * 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 . + */ + +package com.sun.star.comp.test.deployment.active_java; + +import com.sun.star.frame.DispatchDescriptor; +import com.sun.star.frame.XDispatch; +import com.sun.star.frame.XDispatchProvider; +import com.sun.star.lang.XServiceInfo; +import com.sun.star.lib.uno.helper.WeakBase; +import com.sun.star.uno.UnoRuntime; +import com.sun.star.uno.XComponentContext; +import com.sun.star.util.URL; + +public final class Provider extends WeakBase + implements XServiceInfo, XDispatchProvider +{ + public Provider(XComponentContext context) { + this.context = context; + } + + public String getImplementationName() { return implementationName; } + + public boolean supportsService(String ServiceName) { + return ServiceName.equals(getSupportedServiceNames()[0]); //TODO + } + + public String[] getSupportedServiceNames() { + return serviceNames; + } + + public XDispatch queryDispatch( + URL URL, String TargetFrameName, int SearchFlags) + { + return UnoRuntime.queryInterface( + XDispatch.class, + context.getValueByName( + "/singletons/" + + "com.sun.star.test.deployment.active_java_singleton")); + } + + public XDispatch[] queryDispatches(DispatchDescriptor[] Requests) { + XDispatch[] s = new XDispatch[Requests.length]; + for (int i = 0; i < s.length; ++i) { + s[i] = queryDispatch( + Requests[i].FeatureURL, Requests[i].FrameName, + Requests[i].SearchFlags); + } + return s; + } + + private final XComponentContext context; + + static final String implementationName = + "com.sun.star.comp.test.deployment.active_java"; + + static final String[] serviceNames = new String[] { + "com.sun.star.test.deployment.active_java" }; +} diff --git a/desktop/test/deployment/active/com/sun/star/comp/test/deployment/Services.java b/desktop/test/deployment/active/com/sun/star/comp/test/deployment/Services.java new file mode 100644 index 0000000000..1af3f42b7d --- /dev/null +++ b/desktop/test/deployment/active/com/sun/star/comp/test/deployment/Services.java @@ -0,0 +1,65 @@ +/* + * 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 . + */ + +package com.sun.star.comp.test.deployment.active_java; + +import com.sun.star.lang.XSingleComponentFactory; +import com.sun.star.lib.uno.helper.Factory; +import com.sun.star.registry.InvalidRegistryException; +import com.sun.star.registry.XRegistryKey; + +public final class Services { + private Services() {} + + public static XSingleComponentFactory __getComponentFactory( + String implementation) + { + if (implementation.equals(Dispatch.implementationName)) { + return Factory.createComponentFactory( + Dispatch.class, Dispatch.implementationName, + Dispatch.serviceNames); + } else if (implementation.equals(Provider.implementationName)) { + return Factory.createComponentFactory( + Provider.class, Provider.implementationName, + Provider.serviceNames); + } else { + return null; + } + } + + public static boolean __writeRegistryServiceInfo(XRegistryKey key) { + if (!(Factory.writeRegistryServiceInfo( + Dispatch.implementationName, Dispatch.serviceNames, key) && + Factory.writeRegistryServiceInfo( + Provider.implementationName, Provider.serviceNames, key))) + { + return false; + } + try { + key. + createKey( + "/" + Dispatch.implementationName + + "/UNO/SINGLETONS/" + + "com.sun.star.test.deployment.active_java_singleton"). + setStringValue(Dispatch.implementationName); + } catch (InvalidRegistryException e) { + return false; + } + return true; + } +} diff --git a/desktop/test/deployment/active/description.xml b/desktop/test/deployment/active/description.xml new file mode 100644 index 0000000000..685d721322 --- /dev/null +++ b/desktop/test/deployment/active/description.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + * 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 . + --> + +<d:description xmlns:d="http://openoffice.org/extensions/description/2006"> + <d:identifier + value="org.openoffice/framework/desktop/test/deployment/active"/> + <d:version value="1"/> + <d:dependencies> + <d:OpenOffice.org-minimal-version d:name="OpenOffice.org 3.4" value="3.4"/> + </d:dependencies> +</d:description> diff --git a/desktop/test/deployment/crashextension/Addons.xcu b/desktop/test/deployment/crashextension/Addons.xcu new file mode 100644 index 0000000000..019a068e72 --- /dev/null +++ b/desktop/test/deployment/crashextension/Addons.xcu @@ -0,0 +1,62 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + * 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/. + * +--> + +<o:items xmlns:o="http://openoffice.org/2001/registry"> + <item o:path="/org.openoffice.Office.Addons"> + <node o:name="AddonUI"> + <node o:name="Images"> + <node o:name="org.libreoffice.test.desktop.deployment.crashextension.imageCrash" o:op="replace"> + <prop o:name="URL"> + <value>vnd.org.libreoffice.test.desktop.deployment.crashextension:</value> + </prop> + <node o:name="UserDefinedImages"> + <prop o:name="ImageSmallURL"> + <value>%origin%/crash.png</value> + </prop> + <prop o:name="ImageBigURL"> + <value>%origin%/crash.png</value> + </prop> + </node> + </node> + </node> + <node o:name="OfficeToolbarMerging"> + <node o:name="org.libreoffice.test.desktop.deployment.crashextension.toolbarmerge" o:op="replace"> + <node o:name="T1" o:op="replace"> + <prop o:name="MergeToolBar"> + <value>standardbar</value> + </prop> + <prop o:name="MergePoint"> + <value>.uno:HelpIndex</value> + </prop> + <prop o:name="MergeCommand"> + <value>AddAfter</value> + </prop> + <prop o:name="MergeFallback"> + <value>AddLast</value> + </prop> + <prop o:name="MergeContext"> + <value/> + </prop> + <node o:name="ToolBarItems"> + <node o:name="B1" o:op="replace"> + <prop o:name="URL"> + <value>vnd.org.libreoffice.test.desktop.deployment.crashextension:</value> + </prop> + <prop o:name="Title"> + <value xml:lang="en-US">Crash LibreOffice</value> + </prop> + </node> + </node> + </node> + </node> + </node> + </node> + </item> +</o:items> diff --git a/desktop/test/deployment/crashextension/META-INF/manifest.xml b/desktop/test/deployment/crashextension/META-INF/manifest.xml new file mode 100644 index 0000000000..cbb8019473 --- /dev/null +++ b/desktop/test/deployment/crashextension/META-INF/manifest.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + * 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/. + * +--> + +<m:manifest xmlns:m="http://openoffice.org/2001/manifest"> + <m:file-entry m:media-type="application/vnd.sun.star.configuration-data" + m:full-path="Addons.xcu"/> + <m:file-entry m:media-type="application/vnd.sun.star.configuration-data" + m:full-path="ProtocolHandler.xcu"/> + <m:file-entry m:media-type="application/vnd.sun.star.configuration-data" + m:full-path="ToolbarMerge.xcu"/> + <m:file-entry + m:media-type="application/vnd.sun.star.uno-components;platform=@PLATFORM@" + m:full-path="platform.components"/> +</m:manifest> diff --git a/desktop/test/deployment/crashextension/ProtocolHandler.xcu b/desktop/test/deployment/crashextension/ProtocolHandler.xcu new file mode 100644 index 0000000000..f4011743bf --- /dev/null +++ b/desktop/test/deployment/crashextension/ProtocolHandler.xcu @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + * 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/. + * +--> + +<o:component-data xmlns:o="http://openoffice.org/2001/registry" + o:package="org.openoffice.Office" o:name="ProtocolHandler"> + <node o:name="HandlerSet"> + <node o:name="org.libreoffice.test.desktop.deployment.crashextension" o:op="replace"> + <prop o:name="Protocols"> + <value>vnd.org.libreoffice.test.desktop.deployment.crashextension:*</value> + </prop> + </node> + </node> +</o:component-data> diff --git a/desktop/test/deployment/crashextension/crash.png b/desktop/test/deployment/crashextension/crash.png Binary files differnew file mode 100644 index 0000000000..693df038c3 --- /dev/null +++ b/desktop/test/deployment/crashextension/crash.png diff --git a/desktop/test/deployment/crashextension/crashextension.component b/desktop/test/deployment/crashextension/crashextension.component new file mode 100644 index 0000000000..2597e444a8 --- /dev/null +++ b/desktop/test/deployment/crashextension/crashextension.component @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + * 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/. + * +--> + +<component loader="com.sun.star.loader.SharedLibrary" + xmlns="http://openoffice.org/2010/uno-components"> + <implementation name="org.libreoffice.test.desktop.deployment.crashextension.impl"> + <service name="org.libreoffice.test.desktop.deployment.crashextension"/> + </implementation> +</component> diff --git a/desktop/test/deployment/crashextension/crashextension.cxx b/desktop/test/deployment/crashextension/crashextension.cxx new file mode 100644 index 0000000000..2e64f3af02 --- /dev/null +++ b/desktop/test/deployment/crashextension/crashextension.cxx @@ -0,0 +1,152 @@ +/* -*- 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/. + */ + +#include <sal/config.h> + +#include <cassert> +#include <cstddef> + +#include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/frame/DispatchDescriptor.hpp> +#include <com/sun/star/frame/XDispatch.hpp> +#include <com/sun/star/frame/XDispatchProvider.hpp> +#include <com/sun/star/frame/XStatusListener.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/uno/DeploymentException.hpp> +#include <com/sun/star/uno/Reference.hxx> +#include <com/sun/star/uno/Sequence.hxx> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <com/sun/star/uno/XInterface.hpp> +#include <com/sun/star/util/URL.hpp> +#include <cppuhelper/implbase3.hxx> +#include <cppuhelper/implementationentry.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <rtl/ustring.hxx> +#include <uno/lbnames.h> + +namespace +{ +class Provider : public cppu::WeakImplHelper3<css::lang::XServiceInfo, + css::frame::XDispatchProvider, css::frame::XDispatch> +{ +public: + Provider(const Provider&) = delete; + const Provider& operator=(const Provider&) = delete; + + static css::uno::Reference<css::uno::XInterface> + SAL_CALL static_create(css::uno::Reference<css::uno::XComponentContext> const& xContext) + { + return static_cast<cppu::OWeakObject*>(new Provider(xContext)); + } + + static rtl::OUString SAL_CALL static_getImplementationName(); + + static css::uno::Sequence<rtl::OUString> SAL_CALL static_getSupportedServiceNames(); + +private: + explicit Provider(css::uno::Reference<css::uno::XComponentContext> const& context) + : context_(context) + { + assert(context.is()); + } + + virtual ~Provider() {} + + virtual rtl::OUString SAL_CALL getImplementationName() override + { + return static_getImplementationName(); + } + + virtual sal_Bool SAL_CALL supportsService(rtl::OUString const& ServiceName) override + { + return cppu::supportsService(this, ServiceName); + } + + virtual css::uno::Sequence<rtl::OUString> SAL_CALL getSupportedServiceNames() override + { + return static_getSupportedServiceNames(); + } + + virtual css::uno::Reference<css::frame::XDispatch> + SAL_CALL queryDispatch(css::util::URL const&, rtl::OUString const&, sal_Int32) override; + + virtual css::uno::Sequence<css::uno::Reference<css::frame::XDispatch>> SAL_CALL + queryDispatches(css::uno::Sequence<css::frame::DispatchDescriptor> const& Requests) override; + + virtual void SAL_CALL dispatch(css::util::URL const&, + css::uno::Sequence<css::beans::PropertyValue> const&) override; + + virtual void SAL_CALL addStatusListener(css::uno::Reference<css::frame::XStatusListener> const&, + css::util::URL const&) override + { + } + + virtual void SAL_CALL removeStatusListener( + css::uno::Reference<css::frame::XStatusListener> const&, css::util::URL const&) override + { + } + + css::uno::Reference<css::uno::XComponentContext> context_; +}; + +rtl::OUString Provider::static_getImplementationName() +{ + return rtl::OUString("org.libreoffice.test.desktop.deployment.crashextension.impl"); +} + +css::uno::Sequence<rtl::OUString> Provider::static_getSupportedServiceNames() +{ + rtl::OUString name("org.libreoffice.test.desktop.deployment.crashextension"); + return css::uno::Sequence<rtl::OUString>(&name, 1); +} + +css::uno::Reference<css::frame::XDispatch> Provider::queryDispatch(css::util::URL const&, + rtl::OUString const&, sal_Int32) +{ + return this; +} + +css::uno::Sequence<css::uno::Reference<css::frame::XDispatch>> +Provider::queryDispatches(css::uno::Sequence<css::frame::DispatchDescriptor> const& Requests) +{ + css::uno::Sequence<css::uno::Reference<css::frame::XDispatch>> s(Requests.getLength()); + for (sal_Int32 i = 0; i < s.getLength(); ++i) + { + s[i] + = queryDispatch(Requests[i].FeatureURL, Requests[i].FrameName, Requests[i].SearchFlags); + } + return s; +} + +void Provider::dispatch(css::util::URL const&, css::uno::Sequence<css::beans::PropertyValue> const&) +{ + // Crash LibreOffice + *((char*)NULL) = 0; +} + +cppu::ImplementationEntry const services[] + = { { &Provider::static_create, &Provider::static_getImplementationName, + &Provider::static_getSupportedServiceNames, &cppu::createSingleComponentFactory, NULL, + 0 }, + { NULL, NULL, NULL, NULL, NULL, 0 } }; +} + +extern "C" SAL_DLLPUBLIC_EXPORT void* +component_getFactory(char const* pImplName, void* pServiceManager, void* pRegistryKey) +{ + return cppu::component_getFactoryHelper(pImplName, pServiceManager, pRegistryKey, services); +} + +extern "C" SAL_DLLPUBLIC_EXPORT void +component_getImplementationEnvironment(char const** ppEnvTypeName, uno_Environment**) +{ + *ppEnvTypeName = CPPU_CURRENT_LANGUAGE_BINDING_NAME; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/desktop/test/deployment/crashextension/description.xml b/desktop/test/deployment/crashextension/description.xml new file mode 100644 index 0000000000..3084b17ae4 --- /dev/null +++ b/desktop/test/deployment/crashextension/description.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + * 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/. + * +--> + +<d:description xmlns:d="http://openoffice.org/extensions/description/2006"> + <d:identifier + value="org.libreoffice.test.desktop.deployment.crashextension"/> + <d:version value="1.0"/> + <d:display-name> + <d:name lang="en">Crash LibreOffice</d:name> + </d:display-name> + <d:dependencies> + <d:OpenOffice.org-minimal-version d:name="OpenOffice.org 3.4" value="3.4"/> + </d:dependencies> +</d:description> diff --git a/desktop/test/deployment/dependencies/broken-dependency.oxt b/desktop/test/deployment/dependencies/broken-dependency.oxt Binary files differnew file mode 100644 index 0000000000..11bab0a950 --- /dev/null +++ b/desktop/test/deployment/dependencies/broken-dependency.oxt diff --git a/desktop/test/deployment/dependencies/double-dependencies.oxt b/desktop/test/deployment/dependencies/double-dependencies.oxt Binary files differnew file mode 100644 index 0000000000..055c27ea53 --- /dev/null +++ b/desktop/test/deployment/dependencies/double-dependencies.oxt diff --git a/desktop/test/deployment/dependencies/empty-dependencies.oxt b/desktop/test/deployment/dependencies/empty-dependencies.oxt Binary files differnew file mode 100644 index 0000000000..ebb18dcbf5 --- /dev/null +++ b/desktop/test/deployment/dependencies/empty-dependencies.oxt diff --git a/desktop/test/deployment/dependencies/funny-dependency.oxt b/desktop/test/deployment/dependencies/funny-dependency.oxt Binary files differnew file mode 100644 index 0000000000..9b683e6d1e --- /dev/null +++ b/desktop/test/deployment/dependencies/funny-dependency.oxt diff --git a/desktop/test/deployment/dependencies/license-dependency.oxt b/desktop/test/deployment/dependencies/license-dependency.oxt Binary files differnew file mode 100644 index 0000000000..b01da4b5ca --- /dev/null +++ b/desktop/test/deployment/dependencies/license-dependency.oxt diff --git a/desktop/test/deployment/dependencies/loversion35.oxt b/desktop/test/deployment/dependencies/loversion35.oxt Binary files differnew file mode 100644 index 0000000000..ecd509cd9f --- /dev/null +++ b/desktop/test/deployment/dependencies/loversion35.oxt diff --git a/desktop/test/deployment/dependencies/loversion36.oxt b/desktop/test/deployment/dependencies/loversion36.oxt Binary files differnew file mode 100644 index 0000000000..f38630e0c7 --- /dev/null +++ b/desktop/test/deployment/dependencies/loversion36.oxt diff --git a/desktop/test/deployment/dependencies/many-dependencies.oxt b/desktop/test/deployment/dependencies/many-dependencies.oxt Binary files differnew file mode 100644 index 0000000000..3675681437 --- /dev/null +++ b/desktop/test/deployment/dependencies/many-dependencies.oxt diff --git a/desktop/test/deployment/dependencies/maxversion33.oxt b/desktop/test/deployment/dependencies/maxversion33.oxt Binary files differnew file mode 100644 index 0000000000..fe0998c81f --- /dev/null +++ b/desktop/test/deployment/dependencies/maxversion33.oxt diff --git a/desktop/test/deployment/dependencies/maxversion34.oxt b/desktop/test/deployment/dependencies/maxversion34.oxt Binary files differnew file mode 100644 index 0000000000..0a284b388d --- /dev/null +++ b/desktop/test/deployment/dependencies/maxversion34.oxt diff --git a/desktop/test/deployment/dependencies/maxversion35.oxt b/desktop/test/deployment/dependencies/maxversion35.oxt Binary files differnew file mode 100644 index 0000000000..e95b97cd68 --- /dev/null +++ b/desktop/test/deployment/dependencies/maxversion35.oxt diff --git a/desktop/test/deployment/dependencies/maxversion36.oxt b/desktop/test/deployment/dependencies/maxversion36.oxt Binary files differnew file mode 100644 index 0000000000..786ed4ce17 --- /dev/null +++ b/desktop/test/deployment/dependencies/maxversion36.oxt diff --git a/desktop/test/deployment/dependencies/minattr22.oxt b/desktop/test/deployment/dependencies/minattr22.oxt Binary files differnew file mode 100644 index 0000000000..a6c8e3758c --- /dev/null +++ b/desktop/test/deployment/dependencies/minattr22.oxt diff --git a/desktop/test/deployment/dependencies/minattr23.oxt b/desktop/test/deployment/dependencies/minattr23.oxt Binary files differnew file mode 100644 index 0000000000..83d17938c4 --- /dev/null +++ b/desktop/test/deployment/dependencies/minattr23.oxt diff --git a/desktop/test/deployment/dependencies/minattr24.oxt b/desktop/test/deployment/dependencies/minattr24.oxt Binary files differnew file mode 100644 index 0000000000..00f053f487 --- /dev/null +++ b/desktop/test/deployment/dependencies/minattr24.oxt diff --git a/desktop/test/deployment/dependencies/no-dependencies.oxt b/desktop/test/deployment/dependencies/no-dependencies.oxt Binary files differnew file mode 100644 index 0000000000..6487eb66ae --- /dev/null +++ b/desktop/test/deployment/dependencies/no-dependencies.oxt diff --git a/desktop/test/deployment/dependencies/no-description.oxt b/desktop/test/deployment/dependencies/no-description.oxt Binary files differnew file mode 100644 index 0000000000..1e6579cd7d --- /dev/null +++ b/desktop/test/deployment/dependencies/no-description.oxt diff --git a/desktop/test/deployment/dependencies/readme.txt b/desktop/test/deployment/dependencies/readme.txt new file mode 100644 index 0000000000..8ba90ce4aa --- /dev/null +++ b/desktop/test/deployment/dependencies/readme.txt @@ -0,0 +1,73 @@ +# +# 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 . +# + +no-description.oxt, no-dependencies.oxt, empty-dependencies.oxt effectively have +no dependencies and should thus install successfully. + +broken-dependencies.oxt contains a malformed description.xml and should thus +display an error and not install. + +double-dependencies.oxt contains a description.xml with two dependencies +elements. This is not allowed by the spec but behaviour is unspecified. In the +current implementation, it combines the two elements, and thus finds two +unsatisfied dependencies, displays the Unsatisfied Dependencies dialog and does +not install. + +version21.oxt contains a dependency on OOo 2.1 (and should thus only install in +OOo 2.1 or later); version21ns.oxt is the same, but with a different way of +using XML namespaces; version21other.oxt additionally contains an unsatisfied +dependency (and should thus not install in any OOo version). version22.oxt +contains a dependency on OOo 2.2 (and should thus only install in OOo 2.2 or +later). version23.oxt contains a dependency on OOo 2.3 (and should thus only +install in OOo 2.3 or later). version10000.oxt contains a dependency on the +hypothetical OOo version 10000 (and should thus not install in any OOo version). +versionempty.oxt contains an empty value attribute and versionnone.oxt lacks the +value attribute; neither is allowed by the spec, but the current implementation +treats both as pre OOo 2.1 versions (and the extensions should thus install in +OOo 2.1 or later). + +maxversion30.oxt contains a maximal version dependency on OOo 3.0 (and should +thus only install in OOo 3.0 or earlier, back to OOo 2.3, thanks to the +additionally specified OpenOffice.org-minimal-version attribute). +maxversion10000.oxt contains a maximal version dependency on the hypothetical +OOo version 10000 (and should thus install in any OOo version 3.1 or later; +OpenOffice.org-maximal-version was introduced in OOo 3.1, and no OpenOffice.org- +minimal-version attribute is specified). bad-minmaxversion.oxt contains a +minimal version dependency on OOo 3.2 and a maximal version dependency on +OOo 3.1 (and should thus not install in any OOo version). + +minattr22.oxt contains a (hypothetical, most probably never satisfied) +UNSATISFIED dependency with an OpenOffice.org-minimal-version attribute of +"2.2" (and should thus install in OOo 2.3 or later); minattr23.oxt is similar, +but with an OpenOffice.org-minimal-version attribute of "2.3" (and should thus +also install in OOo 2.3 or later); minattr24.oxt is similar, but with an +OpenOffice.org-minimal-version attribute of "2.4" (and should thus only install +in OOo 2.4 or later). + +All of the following testcases should result in the Unsatisfied Dependencies +dialog being displayed and the extension not being installed: + +unknown-dependency.oxt contains a dependency without a name attribute, and +should thus display "Unknown" (localized). + +funny-dependency.oxt, many-dependencies.oxt contain somewhat extreme input. + +license-dependency.oxt contains both a license to be accepted by the user and +dependencies. What is important here is that the Unsatisfied Dependencies +dialog is displayed, but not the license (as installation aborts as soon as +unsatisfied dependencies are found). diff --git a/desktop/test/deployment/dependencies/unknown-dependency.oxt b/desktop/test/deployment/dependencies/unknown-dependency.oxt Binary files differnew file mode 100644 index 0000000000..7c2a22c6d5 --- /dev/null +++ b/desktop/test/deployment/dependencies/unknown-dependency.oxt diff --git a/desktop/test/deployment/dependencies/version10000.oxt b/desktop/test/deployment/dependencies/version10000.oxt Binary files differnew file mode 100644 index 0000000000..c15b7a117c --- /dev/null +++ b/desktop/test/deployment/dependencies/version10000.oxt diff --git a/desktop/test/deployment/dependencies/version21.oxt b/desktop/test/deployment/dependencies/version21.oxt Binary files differnew file mode 100644 index 0000000000..922b279555 --- /dev/null +++ b/desktop/test/deployment/dependencies/version21.oxt diff --git a/desktop/test/deployment/dependencies/version21ns.oxt b/desktop/test/deployment/dependencies/version21ns.oxt Binary files differnew file mode 100644 index 0000000000..5efb2ed902 --- /dev/null +++ b/desktop/test/deployment/dependencies/version21ns.oxt diff --git a/desktop/test/deployment/dependencies/version21other.oxt b/desktop/test/deployment/dependencies/version21other.oxt Binary files differnew file mode 100644 index 0000000000..d88a8155af --- /dev/null +++ b/desktop/test/deployment/dependencies/version21other.oxt diff --git a/desktop/test/deployment/dependencies/version22.oxt b/desktop/test/deployment/dependencies/version22.oxt Binary files differnew file mode 100644 index 0000000000..4c8a207b68 --- /dev/null +++ b/desktop/test/deployment/dependencies/version22.oxt diff --git a/desktop/test/deployment/dependencies/version23.oxt b/desktop/test/deployment/dependencies/version23.oxt Binary files differnew file mode 100644 index 0000000000..6c08d2949c --- /dev/null +++ b/desktop/test/deployment/dependencies/version23.oxt diff --git a/desktop/test/deployment/dependencies/version34.oxt b/desktop/test/deployment/dependencies/version34.oxt Binary files differnew file mode 100644 index 0000000000..ee2a82d936 --- /dev/null +++ b/desktop/test/deployment/dependencies/version34.oxt diff --git a/desktop/test/deployment/dependencies/version35.oxt b/desktop/test/deployment/dependencies/version35.oxt Binary files differnew file mode 100644 index 0000000000..6e99cf1b26 --- /dev/null +++ b/desktop/test/deployment/dependencies/version35.oxt diff --git a/desktop/test/deployment/dependencies/versionempty.oxt b/desktop/test/deployment/dependencies/versionempty.oxt Binary files differnew file mode 100644 index 0000000000..a06bb01294 --- /dev/null +++ b/desktop/test/deployment/dependencies/versionempty.oxt diff --git a/desktop/test/deployment/dependencies/versionnone.oxt b/desktop/test/deployment/dependencies/versionnone.oxt Binary files differnew file mode 100644 index 0000000000..ace2a11651 --- /dev/null +++ b/desktop/test/deployment/dependencies/versionnone.oxt diff --git a/desktop/test/deployment/description/desc1.oxt b/desktop/test/deployment/description/desc1.oxt Binary files differnew file mode 100644 index 0000000000..e447fd6eae --- /dev/null +++ b/desktop/test/deployment/description/desc1.oxt diff --git a/desktop/test/deployment/description/desc2.oxt b/desktop/test/deployment/description/desc2.oxt Binary files differnew file mode 100644 index 0000000000..8df2f33fa6 --- /dev/null +++ b/desktop/test/deployment/description/desc2.oxt diff --git a/desktop/test/deployment/description/desc3.oxt b/desktop/test/deployment/description/desc3.oxt Binary files differnew file mode 100644 index 0000000000..fbd1136b03 --- /dev/null +++ b/desktop/test/deployment/description/desc3.oxt diff --git a/desktop/test/deployment/description/desc4.oxt b/desktop/test/deployment/description/desc4.oxt Binary files differnew file mode 100644 index 0000000000..0c97f5fd44 --- /dev/null +++ b/desktop/test/deployment/description/desc4.oxt diff --git a/desktop/test/deployment/description/desc5.oxt b/desktop/test/deployment/description/desc5.oxt Binary files differnew file mode 100644 index 0000000000..8110073499 --- /dev/null +++ b/desktop/test/deployment/description/desc5.oxt diff --git a/desktop/test/deployment/description/readme.txt b/desktop/test/deployment/description/readme.txt new file mode 100644 index 0000000000..bb133ba516 --- /dev/null +++ b/desktop/test/deployment/description/readme.txt @@ -0,0 +1,23 @@ +The folder contains extensions which use in the description.xml the following:
+-The <extension-description> element The element contains localized child
+elements.
+
+The following table shows what localized item is used, when the Office the locale
+en-US uses. The displayed extension description contains the locale.
+
+
+Localization:
+
+Installed office: en-US
+ | locale
+=========================
+desc1.oxt | en-US
+-------------------------
+desc2.oxt | en-US-region1
+--------------------------
+desc3.oxt | en
+--------------------------
+desc4.oxt | en-GB
+--------------------------
+desc5.oxt | de
+
diff --git a/desktop/test/deployment/display_name/name1.oxt b/desktop/test/deployment/display_name/name1.oxt Binary files differnew file mode 100644 index 0000000000..5a53690d69 --- /dev/null +++ b/desktop/test/deployment/display_name/name1.oxt diff --git a/desktop/test/deployment/display_name/name2.oxt b/desktop/test/deployment/display_name/name2.oxt Binary files differnew file mode 100644 index 0000000000..f6cbcae3bc --- /dev/null +++ b/desktop/test/deployment/display_name/name2.oxt diff --git a/desktop/test/deployment/display_name/name3.oxt b/desktop/test/deployment/display_name/name3.oxt Binary files differnew file mode 100644 index 0000000000..8df750ce62 --- /dev/null +++ b/desktop/test/deployment/display_name/name3.oxt diff --git a/desktop/test/deployment/display_name/name4.oxt b/desktop/test/deployment/display_name/name4.oxt Binary files differnew file mode 100644 index 0000000000..6ce4822e37 --- /dev/null +++ b/desktop/test/deployment/display_name/name4.oxt diff --git a/desktop/test/deployment/display_name/name5.oxt b/desktop/test/deployment/display_name/name5.oxt Binary files differnew file mode 100644 index 0000000000..56973be781 --- /dev/null +++ b/desktop/test/deployment/display_name/name5.oxt diff --git a/desktop/test/deployment/display_name/readme.txt b/desktop/test/deployment/display_name/readme.txt new file mode 100644 index 0000000000..23173bde63 --- /dev/null +++ b/desktop/test/deployment/display_name/readme.txt @@ -0,0 +1,26 @@ +The folder contains extensions which use in the description.xml the following: +-The <display-name> element +The element contains localized child elements. + +To test the display name in the update dialog use the extensions in +desktop/test/deployment/update/simple + + +The following table shows what localized item is used, when the Office the locale +en-US uses. + + +Localization: + +Installed office: en-US + | publisher | release notes +============================================= +name1.oxt | en-US | en-US +--------------------------------------------- +name2.oxt | en-US-region1 | en-US-region1 +--------------------------------------------- +name3.oxt | en | en +--------------------------------------------- +name4.oxt | en-GB | en-GB +--------------------------------------------- +name5.oxt | de | de diff --git a/desktop/test/deployment/executable_content/build/hello.c b/desktop/test/deployment/executable_content/build/hello.c new file mode 100644 index 0000000000..d527e71524 --- /dev/null +++ b/desktop/test/deployment/executable_content/build/hello.c @@ -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 . + */ + +#include <stdio.h> + +int main(int argc, char** argv, char** envp) +{ + //prevent warning about unused parameters + //we need to provide parameter names in C + (void)argc; + (void)argv; + (void)envp; + + fprintf(stdout, "Hello world!\n"); + return 0; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/test/deployment/executable_content/build/makefile.mk b/desktop/test/deployment/executable_content/build/makefile.mk new file mode 100644 index 0000000000..513d640cfd --- /dev/null +++ b/desktop/test/deployment/executable_content/build/makefile.mk @@ -0,0 +1,42 @@ +# +# 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 . +# + +PRJ = ..$/..$/..$/.. + +PRJNAME = desktop +TARGET = hello +LIBTARGET=NO +NO_DEFAULT_STL=TRUE +LIBSALCPPRT= + +.INCLUDE : settings.mk + + +APP1NOSAL = TRUE +APP1OBJS = \ + $(OBJ)$/hello.obj + + +APP1TARGET = $(TARGET) + +DEPOBJFILES = \ + $(OBJ)$/hello.obj + + +.INCLUDE : target.mk + diff --git a/desktop/test/deployment/executable_content/build/readme.txt b/desktop/test/deployment/executable_content/build/readme.txt new file mode 100644 index 0000000000..4f956e573e --- /dev/null +++ b/desktop/test/deployment/executable_content/build/readme.txt @@ -0,0 +1,2 @@ +This folder contains the sources to build the hello executable which is contained +in the hello.oxt. diff --git a/desktop/test/deployment/executable_content/hello.oxt b/desktop/test/deployment/executable_content/hello.oxt Binary files differnew file mode 100644 index 0000000000..97d6d14a31 --- /dev/null +++ b/desktop/test/deployment/executable_content/hello.oxt diff --git a/desktop/test/deployment/executable_content/readme.txt b/desktop/test/deployment/executable_content/readme.txt new file mode 100644 index 0000000000..2c336de723 --- /dev/null +++ b/desktop/test/deployment/executable_content/readme.txt @@ -0,0 +1,12 @@ +When the executable is installed try to execute the executable "hello". The executable +file attribute (not on Windows) should be set. + +CD into the extension directory in /user|share)/uno_packages/cache/uno_packages/xyz_ +Then there are the directories for different platforms: + +windows, +solaris, +linux + +Each directory contains a hello executable. On linux one should execute it in a +shell with a build environment, so that the C++ runtime is found. diff --git a/desktop/test/deployment/identifier/explicit/identifier.oxt b/desktop/test/deployment/identifier/explicit/identifier.oxt Binary files differnew file mode 100644 index 0000000000..3851e291c9 --- /dev/null +++ b/desktop/test/deployment/identifier/explicit/identifier.oxt diff --git a/desktop/test/deployment/identifier/legacy/identifier.oxt b/desktop/test/deployment/identifier/legacy/identifier.oxt Binary files differnew file mode 100644 index 0000000000..df8bb84492 --- /dev/null +++ b/desktop/test/deployment/identifier/legacy/identifier.oxt diff --git a/desktop/test/deployment/identifier/readme.txt b/desktop/test/deployment/identifier/readme.txt new file mode 100644 index 0000000000..c268ca4afd --- /dev/null +++ b/desktop/test/deployment/identifier/readme.txt @@ -0,0 +1,24 @@ +# +# 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 . +# + +legacy/identifier.oxt and explicit/identifier.oxt are two different extensions +that happen to have the same file name. legacy/identifier.oxt does not have an +explicit extension identifier, so it gets the implicit one "org.openoffice. +legacy.identifier.oxt". explicit/identifier.oxt has the +explicit extension identifier "org.openoffice/framework/desktop/test/deployment/ +identifier/explicit/identifier.oxt". diff --git a/desktop/test/deployment/locationtest/LocationTest.idl b/desktop/test/deployment/locationtest/LocationTest.idl new file mode 100644 index 0000000000..75421e2455 --- /dev/null +++ b/desktop/test/deployment/locationtest/LocationTest.idl @@ -0,0 +1,27 @@ +/* -*- 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 . + */ + + +module com { module sun { module star { module comp { module smoketest { + // example service, XServiceInfo is implemented here for demonstration + // issues. XServiceInfo must be implemented by all components. + service TestExtension: ::com::sun::star::lang::XServiceInfo; +};};};};}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/test/deployment/locationtest/LocationTest.java b/desktop/test/deployment/locationtest/LocationTest.java new file mode 100644 index 0000000000..ca70b5a1e9 --- /dev/null +++ b/desktop/test/deployment/locationtest/LocationTest.java @@ -0,0 +1,156 @@ +/* + * 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 . + */ +package com.sun.star.comp.smoketest; + +import com.sun.star.lib.uno.helper.Factory; +import com.sun.star.lang.XMultiComponentFactory; +import com.sun.star.lang.XSingleComponentFactory; +import com.sun.star.lib.uno.helper.WeakBase; +import com.sun.star.uno.UnoRuntime; +import com.sun.star.uno.XComponentContext; +import com.sun.star.registry.XRegistryKey; +import com.sun.star.lang.XInitialization; +import com.sun.star.lang.XTypeProvider; +import com.sun.star.lang.XServiceInfo; +import com.sun.star.uno.Type; + +/** This class capsulates the class, that implements the minimal component, a + * factory for creating the service (<CODE>__getComponentFactory</CODE>) and a + * method, that writes the information into the given registry key + * (<CODE>__writeRegistryServiceInfo</CODE>). + */ +public class LocationTest { + /** This class implements the component. At least the interfaces XServiceInfo, + * XTypeProvider, and XInitialization should be provided by the service. + */ + public static class _LocationTest extends WeakBase + implements XServiceInfo { + /** The service name, that must be used to get an instance of this service. + */ + private static final String __serviceName = + "com.sun.star.comp.smoketest.LocationTest"; + + /** The initial component contextr, that gives access to + * the service manager, supported singletons, ... + * It's often later used + */ + private XComponentContext m_cmpCtx; + + /** The service manager, that gives access to all registered services. + * It's often later used + */ + private XMultiComponentFactory m_xMCF; + + /** The constructor of the inner class has a XMultiServiceFactory parameter. + * @param xmultiservicefactoryInitialization A special service factory + * could be introduced while initializing. + */ + public _LocationTest(XComponentContext xCompContext) { + try { + m_cmpCtx = xCompContext; + m_xMCF = m_cmpCtx.getServiceManager(); + } + catch( Exception e ) { + e.printStackTrace(); + } + } + + /** This method returns an array of all supported service names. + * @return Array of supported service names. + */ + public String[] getSupportedServiceNames() { + return getServiceNames(); + } + + /** This method is a simple helper function to used in the + * static component initialisation functions as well as in + * getSupportedServiceNames. + */ + public static String[] getServiceNames() { + String[] sSupportedServiceNames = { __serviceName }; + return sSupportedServiceNames; + } + + /** This method returns true, if the given service will be + * supported by the component. + * @param sServiceName Service name. + * @return True, if the given service name will be supported. + */ + public boolean supportsService( String sServiceName ) { + return sServiceName.equals( __serviceName ); + } + + /** Return the class name of the component. + * @return Class name of the component. + */ + public String getImplementationName() { + return _LocationTest.class.getName(); + } + } + + + /** + * Gives a factory for creating the service. + * This method is called by the <code>JavaLoader</code> + * <p> + * @return returns a <code>XSingleComponentFactory</code> for creating + * the component + * @param sImplName the name of the implementation for which a + * service is desired + * @see com.sun.star.comp.loader.JavaLoader + */ + public static XSingleComponentFactory __getComponentFactory(String sImplName) + { + XSingleComponentFactory xFactory = null; + + if ( sImplName.equals( _LocationTest.class.getName() ) ) + xFactory = Factory.createComponentFactory(_LocationTest.class, + _LocationTest.getServiceNames()); + + return xFactory; + } + + /** + * Writes the service information into the given registry key. + * This method is called by the <code>JavaLoader</code> + * <p> + * @return returns true if the operation succeeded + * @param regKey the registryKey + * @see com.sun.star.comp.loader.JavaLoader + */ + public static boolean __writeRegistryServiceInfo(XRegistryKey regKey) { + return Factory.writeRegistryServiceInfo(_LocationTest.class.getName(), + _LocationTest.getServiceNames(), + regKey); + } + + /** This method is a member of the interface for initializing an object + * directly after its creation. + * @param object This array of arbitrary objects will be passed to the + * component after its creation. + * @throws Exception Every exception will not be handled, but will be + * passed to the caller. + */ + public void initialize( Object[] object ) + throws com.sun.star.uno.Exception { + /* The component describes what arguments are expected and in which + * order! At this point you can read the objects and initialize + * your component using these objects. + */ + } +} diff --git a/desktop/test/deployment/locationtest/LocationTest.odt b/desktop/test/deployment/locationtest/LocationTest.odt Binary files differnew file mode 100644 index 0000000000..8e1aa70078 --- /dev/null +++ b/desktop/test/deployment/locationtest/LocationTest.odt diff --git a/desktop/test/deployment/locationtest/MANIFEST.MF b/desktop/test/deployment/locationtest/MANIFEST.MF new file mode 100644 index 0000000000..a2fa8c34b7 --- /dev/null +++ b/desktop/test/deployment/locationtest/MANIFEST.MF @@ -0,0 +1,2 @@ +RegistrationClassName: com.sun.star.comp.smoketest.LocationTest + diff --git a/desktop/test/deployment/locationtest/description.xml b/desktop/test/deployment/locationtest/description.xml new file mode 100644 index 0000000000..0d2b712949 --- /dev/null +++ b/desktop/test/deployment/locationtest/description.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + * 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 . +--> +<description xmlns="http://openoffice.org/extensions/description/2006" + xmlns:xlink="http://www.w3.org/1999/xlink" + xmlns:d="http://openoffice.org/extensions/description/2006" > + <identifier value="org.openoffice.extensions.testarea.desktop.location"/> + <version value="1.0" /> + <dependencies > + <OpenOffice.org-minimal-version value="2.2" d:name="OpenOffice.org 2.2"/> + </dependencies> + <update-information> + <src xlink:href="http://update.services.openoffice.org/ProductUpdateService/check.Update?product=extension&extensionid=org.openoffice.extensions.testarea.desktop.updateinfo&refresh=true"/> + </update-information> +</description> diff --git a/desktop/test/deployment/locationtest/makefile.mk b/desktop/test/deployment/locationtest/makefile.mk new file mode 100644 index 0000000000..de586915e2 --- /dev/null +++ b/desktop/test/deployment/locationtest/makefile.mk @@ -0,0 +1,76 @@ +# +# 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 . +# + +PRJ = ..$/..$/.. +PRJNAME = location_test +PACKAGE = com$/sun$/star$/comp$/smoketest +TARGET = com_sun_star_comp_smoketest + +# --- Settings ----------------------------------------------------- + +.INCLUDE : settings.mk + +JARFILES = ridl.jar jurt.jar unoil.jar juh.jar + +JARTARGET = LocationTest.jar +JARCOMPRESS = TRUE +CUSTOMMANIFESTFILE = MANIFEST.MF + +ZIP1TARGET=locationtest +ZIP1LIST=* +ZIPFLAGS=-r +ZIP1DIR=$(MISC)$/$(TARGET) +ZIP1EXT=.oxt + +# --- Files -------------------------------------------------------- + +COPY_OXT_MANIFEST:= $(MISC)$/$(TARGET)$/META-INF$/manifest.xml +JAVAFILES = LocationTest.java + +# --- Targets ------------------------------------------------------ + +.INCLUDE : target.mk + +$(JARTARGETN) : $(MISC)$/$(TARGET).javamaker.done + +$(JAVACLASSFILES) : $(MISC)$/$(TARGET).javamaker.done + +$(MISC)$/$(TARGET).javamaker.done: $(BIN)$/LocationTest.rdb + $(JAVAMAKER) -O$(CLASSDIR) -BUCR -nD -X$(SOLARBINDIR)/types.rdb $< + $(TOUCH) $@ + +$(BIN)$/LocationTest.rdb: LocationTest.idl + $(IDLC) -O$(MISC) -I$(SOLARIDLDIR) -cid -we $< + +-$(RM) $@ + $(REGMERGE) $@ /UCR $(MISC)$/LocationTest.urd + +$(MISC)$/$(ZIP1TARGET).createdir : + +$(MKDIRHIER) $(MISC)$/$(TARGET)$/META-INF >& $(NULLDEV) && $(TOUCH) $@ + +$(MISC)$/$(TARGET)_resort : manifest.xml $(JARTARGETN) $(MISC)$/$(ZIP1TARGET).createdir $(BIN)$/LocationTest.rdb description.xml + $(COPY) manifest.xml $(MISC)$/$(TARGET)$/META-INF$/manifest.xml + $(COPY) $(JARTARGETN) $(MISC)$/$(TARGET)$/$(JARTARGET) + $(COPY) $(BIN)$/LocationTest.rdb $(MISC)$/$(TARGET)$/LocationTest.rdb + $(COPY) description.xml $(MISC)$/$(TARGET)$/description.xml + $(TOUCH) $@ + +.IF "$(ZIP1TARGETN)"!="" +$(ZIP1TARGETN) : $(MISC)$/$(TARGET)_resort $(MISC)$/$(ZIP1TARGET).createdir + +.ENDIF # "$(ZIP1TARGETN)"!="" + diff --git a/desktop/test/deployment/locationtest/manifest.xml b/desktop/test/deployment/locationtest/manifest.xml new file mode 100644 index 0000000000..3dd6460faf --- /dev/null +++ b/desktop/test/deployment/locationtest/manifest.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="UTF-8"?> +<manifest:manifest xmlns:manifest="http://openoffice.org/2001/manifest"> + <manifest:file-entry manifest:full-path="LocationTest.jar" manifest:media-type="application/vnd.sun.star.uno-component;type=Java"/> + <manifest:file-entry manifest:full-path="LocationTest.rdb" manifest:media-type="application/vnd.sun.star.uno-typelibrary;type=RDB"/> +</manifest:manifest> diff --git a/desktop/test/deployment/options/handler/com/sun/star/comp/extensionoptions/MANIFEST.MF b/desktop/test/deployment/options/handler/com/sun/star/comp/extensionoptions/MANIFEST.MF new file mode 100644 index 0000000000..fba55a6e0d --- /dev/null +++ b/desktop/test/deployment/options/handler/com/sun/star/comp/extensionoptions/MANIFEST.MF @@ -0,0 +1,2 @@ +RegistrationClassName: com.sun.star.comp.extensionoptions.OptionsEventHandler + diff --git a/desktop/test/deployment/options/handler/com/sun/star/comp/extensionoptions/OptionsEventHandler.java b/desktop/test/deployment/options/handler/com/sun/star/comp/extensionoptions/OptionsEventHandler.java new file mode 100644 index 0000000000..b360f470be --- /dev/null +++ b/desktop/test/deployment/options/handler/com/sun/star/comp/extensionoptions/OptionsEventHandler.java @@ -0,0 +1,417 @@ +/* + * 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 . + */ +package com.sun.star.comp.extensionoptions; + +import com.sun.star.configuration.theDefaultProvider; +import com.sun.star.lib.uno.helper.Factory; +import com.sun.star.lib.uno.helper.WeakBase; +import com.sun.star.lang.XSingleComponentFactory; +import com.sun.star.lang.XMultiServiceFactory; +import com.sun.star.lang.WrappedTargetException; +import com.sun.star.lang.XServiceInfo; +import com.sun.star.uno.UnoRuntime; +import com.sun.star.uno.AnyConverter; +import com.sun.star.uno.XComponentContext; +import com.sun.star.uno.Exception; +import com.sun.star.registry.XRegistryKey; +import com.sun.star.awt.XContainerWindowEventHandler; +import com.sun.star.awt.XControl; +import com.sun.star.awt.XControlModel; +import com.sun.star.awt.XControlContainer; +import com.sun.star.container.XNameAccess; +import com.sun.star.beans.NamedValue; +import com.sun.star.beans.XPropertySet; +import com.sun.star.util.XChangesBatch; + +/** A handler which supports multiple options pages which all + * have the same controls. + */ +public class OptionsEventHandler { + + public static class _OptionsEventHandler extends WeakBase + implements XServiceInfo, XContainerWindowEventHandler { + + private static final String __serviceName = + "com.sun.star.comp.extensionoptions.OptionsEventHandler"; + + private final XComponentContext m_cmpCtx; + + private XNameAccess m_xAccessLeaves; + + /**Names of supported options pages. + */ + private final String[] m_arWindowNames = { + "Writer1", "Writer2", "Writer3", "Calc1", "Calc2", "Calc3", + "Draw1", "Draw2", "Draw3", "Node1_1", "Node1_2", "Node1_3", + "Node2_1", "Node2_2", "Node2_3", "Node3_1", "Node3_2", "Node3_3"}; + + /**Names of the controls which are supported by this handler. All these + *controls must have a "Text" property. + */ + private final String[] m_arStringControls = { + "String0", "String1", "String2", "String3", "String4"}; + + public _OptionsEventHandler(XComponentContext xCompContext) { + m_cmpCtx = xCompContext; + + //Create the com.sun.star.configuration.ConfigurationUpdateAccess + //for the registry node which contains the data for our option + //pages. + XMultiServiceFactory xConfig = theDefaultProvider.get(m_cmpCtx); + + //One argument for creating the ConfigurationUpdateAccess is the "nodepath". + //Our nodepath point to the node of which the direct subnodes represent the + //different options pages. + Object[] args = new Object[1]; + args[0] = new NamedValue( + "nodepath", + "/org.openoffice.desktop.deployment.options.ExtensionData/Leaves"); + + //We get the com.sun.star.container.XNameAccess from the instance of + //ConfigurationUpdateAccess and save it for later use. + try { + m_xAccessLeaves = UnoRuntime.queryInterface( + XNameAccess.class, xConfig.createInstanceWithArguments( + "com.sun.star.configuration.ConfigurationUpdateAccess", args)); + + } catch (com.sun.star.uno.Exception e) { + e.printStackTrace(); + return; + } + } + + /** This method returns an array of all supported service names. + * @return Array of supported service names. + */ + public String[] getSupportedServiceNames() { + return getServiceNames(); + } + + /** This method is a simple helper function to used in the + * static component initialisation functions as well as in + * getSupportedServiceNames. + */ + private static String[] getServiceNames() { + String[] sSupportedServiceNames = { __serviceName }; + return sSupportedServiceNames; + } + + /** This method returns true, if the given service will be + * supported by the component. + * @param sServiceName Service name. + * @return True, if the given service name will be supported. + */ + public boolean supportsService( String sServiceName ) { + return sServiceName.equals( __serviceName ); + } + + /** Return the class name of the component. + * @return Class name of the component. + */ + public String getImplementationName() { + return _OptionsEventHandler.class.getName(); + } + + //XContainerWindowEventHandler + public boolean callHandlerMethod(com.sun.star.awt.XWindow aWindow, + Object aEventObject, String sMethod) + throws WrappedTargetException { + if (sMethod.equals("external_event") ){ + try { + return handleExternalEvent(aWindow, aEventObject); + } catch (com.sun.star.uno.Exception e) { + throw new WrappedTargetException(e, sMethod, this, e); + } + } + + return true; + } + + //XContainerWindowEventHandler + public String[] getSupportedMethodNames() { + return new String[] {"external_event"}; + } + + private boolean handleExternalEvent(com.sun.star.awt.XWindow aWindow, Object aEventObject) + throws com.sun.star.uno.Exception { + try { + String sMethod = AnyConverter.toString(aEventObject); + if (sMethod.equals("ok")) { + saveData(aWindow); + } else if (sMethod.equals("back") || sMethod.equals("initialize")) { + loadData(aWindow); + } + } catch (com.sun.star.lang.IllegalArgumentException ex) { + throw new com.sun.star.lang.IllegalArgumentException(ex, + "Method external_event requires a string in the event object argument.", + this, (short) -1); + } + + return true; + } + + private void saveData(com.sun.star.awt.XWindow aWindow) + throws com.sun.star.lang.IllegalArgumentException, + com.sun.star.uno.Exception { + + //Determine the name of the options page. This serves two purposes. First, if this + //options page is supported by this handler and second we use the name two locate + //the corresponding data in the registry. + String sWindowName = getWindowName(aWindow); + if (sWindowName == null) + throw new com.sun.star.lang.IllegalArgumentException( + "This window is not supported by this handler", this, (short) -1); + + //To access the separate controls of the window we need to obtain the + //XControlContainer from the window implementation + XControlContainer xContainer = UnoRuntime.queryInterface( + XControlContainer.class, aWindow); + if (xContainer == null) + throw new com.sun.star.uno.Exception( + "Could not get XControlContainer from window.", this); + + //This is an implementation which will be used for several options pages + //which all have the same controls. m_arStringControls is an array which + //contains the names. + for (int i = 0; i < m_arStringControls.length; i++) { + + //To obtain the data from the controls we need to get their model. + //First get the respective control from the XControlContainer. + XControl xControl = xContainer.getControl(m_arStringControls[i]); + + //This generic handler and the corresponding registry schema support + //up to five text controls. However, if an options page does not use all + //five controls then we will not complain here. + if (xControl == null) + continue; + + //From the control we get the model, which in turn supports the + //XPropertySet interface, which we finally use to get the data from + //the control. + XPropertySet xProp = UnoRuntime.queryInterface( + XPropertySet.class, xControl.getModel()); + + if (xProp == null) + throw new com.sun.star.uno.Exception( + "Could not get XPropertySet from control.", this); + //Get the "Text" property. + Object aText = xProp.getPropertyValue("Text"); + String sValue = null; + + //The value is still contained in a com.sun.star.uno.Any - so convert it. + try { + sValue = AnyConverter.toString(aText); + } catch (com.sun.star.lang.IllegalArgumentException ex) { + throw new com.sun.star.lang.IllegalArgumentException(ex, + "Wrong property type.", this, (short) -1); + } + + //Now we have the actual string value of the control. What we need now is + //the XPropertySet of the respective property in the registry, so that we + //can store the value. + //To access the registry we have previously created a service instance + //of com.sun.star.configuration.ConfigurationUpdateAccess which supports + //com.sun.star.container.XNameAccess. The XNameAccess is used to get the + //particular registry node which represents this options page. + //Fortunately the name of the window is the same as the registry node. + XPropertySet xLeaf = UnoRuntime.queryInterface( + XPropertySet.class, m_xAccessLeaves.getByName(sWindowName)); + if (xLeaf == null) + throw new com.sun.star.uno.Exception( + "XPropertySet not supported.", this); + + //Finally we can set the value + xLeaf.setPropertyValue(m_arStringControls[i], sValue); + } + + //Committing the changes will cause or changes to be written to the registry. + XChangesBatch xUpdateCommit = + UnoRuntime.queryInterface(XChangesBatch.class, m_xAccessLeaves); + xUpdateCommit.commitChanges(); + } + + private void loadData(com.sun.star.awt.XWindow aWindow) + throws com.sun.star.uno.Exception { + + //Determine the name of the window. This serves two purposes. First, if this + //window is supported by this handler and second we use the name two locate + //the corresponding data in the registry. + String sWindowName = getWindowName(aWindow); + if (sWindowName == null) + throw new com.sun.star.lang.IllegalArgumentException( + "The window is not supported by this handler", this, (short) -1); + + //To access the separate controls of the window we need to obtain the + //XControlContainer from window implementation + XControlContainer xContainer = UnoRuntime.queryInterface( + XControlContainer.class, aWindow); + if (xContainer == null) + throw new com.sun.star.uno.Exception( + "Could not get XControlContainer from window.", this); + + //This is an implementation which will be used for several options pages + //which all have the same controls. m_arStringControls is an array which + //contains the names. + for (int i = 0; i < m_arStringControls.length; i++) { + + //load the values from the registry + //To access the registry we have previously created a service instance + //of com.sun.star.configuration.ConfigurationUpdateAccess which supports + //com.sun.star.container.XNameAccess. We obtain now the section + //of the registry which is assigned to this options page. + XPropertySet xLeaf = UnoRuntime.queryInterface( + XPropertySet.class, m_xAccessLeaves.getByName(sWindowName)); + if (xLeaf == null) + throw new com.sun.star.uno.Exception( + "XPropertySet not supported.", this); + + //The properties in the registry have the same name as the respective + //controls. We use the names now to obtain the property values. + Object aValue = xLeaf.getPropertyValue(m_arStringControls[i]); + + //Now that we have the value we need to set it at the corresponding + //control in the window. The XControlContainer, which we obtained earlier + //is the means to get hold of all the controls. + XControl xControl = xContainer.getControl(m_arStringControls[i]); + + //This generic handler and the corresponding registry schema support + //up to five text controls. However, if an options page does not use all + //five controls then we will not complain here. + if (xControl == null) + continue; + + //From the control we get the model, which in turn supports the + //XPropertySet interface, which we finally use to set the data at the + //control + XPropertySet xProp = UnoRuntime.queryInterface( + XPropertySet.class, xControl.getModel()); + + if (xProp == null) + throw new com.sun.star.uno.Exception( + "Could not get XPropertySet from control.", this); + + //This handler supports only text controls, which are named "Pattern Field" + //in the dialog editor. We set the "Text" property. + xProp.setPropertyValue("Text", aValue); + } + } + + //Checks if the name property of the window is one of the supported names and returns + //always a valid string or null + private String getWindowName(com.sun.star.awt.XWindow aWindow) + throws com.sun.star.uno.Exception { + + if (aWindow == null) + throw new com.sun.star.lang.IllegalArgumentException( + "Method external_event requires that a window is passed as argument", + this, (short) -1); + + //We need to get the control model of the window. Therefore the first step is + //to query for it. + XControl xControlDlg = UnoRuntime.queryInterface( + XControl.class, aWindow); + + if (xControlDlg == null) + throw new com.sun.star.uno.Exception( + "Cannot obtain XControl from XWindow in method external_event."); + //Now get model + XControlModel xModelDlg = xControlDlg.getModel(); + + if (xModelDlg == null) + throw new com.sun.star.uno.Exception( + "Cannot obtain XControlModel from XWindow in method external_event.", this); + //The model itself does not provide any information except that its + //implementation supports XPropertySet which is used to access the data. + XPropertySet xPropDlg = UnoRuntime.queryInterface( + XPropertySet.class, xModelDlg); + if (xPropDlg == null) + throw new com.sun.star.uno.Exception( + "Cannot obtain XPropertySet from window in method external_event.", this); + + //Get the "Name" property of the window + Object aWindowName = xPropDlg.getPropertyValue("Name"); + + //Get the string from the returned com.sun.star.uno.Any + String sName = null; + try { + sName = AnyConverter.toString(aWindowName); + } catch (com.sun.star.lang.IllegalArgumentException ex) { + throw new com.sun.star.uno.Exception(ex, + "Name - property of window is not a string.", this); + } + + //Eventually we can check if we this handler can "handle" this options page. + //The class has a member m_arWindowNames which contains all names of windows + //for which it is intended + for (int i = 0; i < m_arWindowNames.length; i++) { + if (m_arWindowNames[i].equals(sName)) { + return sName; + } + } + return null; + } + } + + + /** + * Gives a factory for creating the service. + * This method is called by the <code>JavaLoader</code> + * <p> + * @return returns a <code>XSingleComponentFactory</code> for creating + * the component + * @param sImplName the name of the implementation for which a + * service is desired + * @see com.sun.star.comp.loader.JavaLoader + */ + public static XSingleComponentFactory __getComponentFactory(String sImplName) + { + XSingleComponentFactory xFactory = null; + + if ( sImplName.equals( _OptionsEventHandler.class.getName() ) ) + xFactory = Factory.createComponentFactory(_OptionsEventHandler.class, + _OptionsEventHandler.getServiceNames()); + + return xFactory; + } + + /** + * Writes the service information into the given registry key. + * This method is called by the <code>JavaLoader</code> + * <p> + * @return returns true if the operation succeeded + * @param regKey the registryKey + * @see com.sun.star.comp.loader.JavaLoader + */ + public static boolean __writeRegistryServiceInfo(XRegistryKey regKey) { + return Factory.writeRegistryServiceInfo(_OptionsEventHandler.class.getName(), + _OptionsEventHandler.getServiceNames(), + regKey); + } + + /** This method is a member of the interface for initializing an object + * directly after its creation. + * @param object This array of arbitrary objects will be passed to the + * component after its creation. + * @throws Exception Every exception will not be handled, but will be + * passed to the caller. + */ + public void initialize( Object[] object ) + throws com.sun.star.uno.Exception { + } + +} diff --git a/desktop/test/deployment/options/handler/com/sun/star/comp/extensionoptions/makefile.mk b/desktop/test/deployment/options/handler/com/sun/star/comp/extensionoptions/makefile.mk new file mode 100644 index 0000000000..4174e3bd71 --- /dev/null +++ b/desktop/test/deployment/options/handler/com/sun/star/comp/extensionoptions/makefile.mk @@ -0,0 +1,44 @@ +# +# 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 . +# + +PRJ = ..$/..$/..$/..$/..$/..$/..$/..$/.. +PRJNAME = desktop +PACKAGE = com$/sun$/star$/comp$/extensionoptions +TARGET = options + +# --- Settings ----------------------------------------------------- + +.INCLUDE : settings.mk + +JARFILES = ridl.jar jurt.jar unoil.jar juh.jar + + +JARTARGET = extensionoptions.jar +JARCOMPRESS = TRUE +CUSTOMMANIFESTFILE = MANIFEST.MF +JARCLASSDIRS=com + + +# --- Files -------------------------------------------------------- + +JAVAFILES = OptionsEventHandler.java + +# --- Targets ------------------------------------------------------ + +.INCLUDE : target.mk + diff --git a/desktop/test/deployment/options/leaf1.oxt b/desktop/test/deployment/options/leaf1.oxt Binary files differnew file mode 100644 index 0000000000..9c3ff86985 --- /dev/null +++ b/desktop/test/deployment/options/leaf1.oxt diff --git a/desktop/test/deployment/options/leaf1mod.oxt b/desktop/test/deployment/options/leaf1mod.oxt Binary files differnew file mode 100644 index 0000000000..d5d9fe6896 --- /dev/null +++ b/desktop/test/deployment/options/leaf1mod.oxt diff --git a/desktop/test/deployment/options/leaf2.oxt b/desktop/test/deployment/options/leaf2.oxt Binary files differnew file mode 100644 index 0000000000..b95628900c --- /dev/null +++ b/desktop/test/deployment/options/leaf2.oxt diff --git a/desktop/test/deployment/options/leaves1.oxt b/desktop/test/deployment/options/leaves1.oxt Binary files differnew file mode 100644 index 0000000000..037389a018 --- /dev/null +++ b/desktop/test/deployment/options/leaves1.oxt diff --git a/desktop/test/deployment/options/leaves2.oxt b/desktop/test/deployment/options/leaves2.oxt Binary files differnew file mode 100644 index 0000000000..531b385663 --- /dev/null +++ b/desktop/test/deployment/options/leaves2.oxt diff --git a/desktop/test/deployment/options/leaves3.oxt b/desktop/test/deployment/options/leaves3.oxt Binary files differnew file mode 100644 index 0000000000..f5bb0f2262 --- /dev/null +++ b/desktop/test/deployment/options/leaves3.oxt diff --git a/desktop/test/deployment/options/modules1.oxt b/desktop/test/deployment/options/modules1.oxt Binary files differnew file mode 100644 index 0000000000..bae652ffbc --- /dev/null +++ b/desktop/test/deployment/options/modules1.oxt diff --git a/desktop/test/deployment/options/modules2.oxt b/desktop/test/deployment/options/modules2.oxt Binary files differnew file mode 100644 index 0000000000..d6d7956d45 --- /dev/null +++ b/desktop/test/deployment/options/modules2.oxt diff --git a/desktop/test/deployment/options/nodes1.oxt b/desktop/test/deployment/options/nodes1.oxt Binary files differnew file mode 100644 index 0000000000..b1dfa18d3e --- /dev/null +++ b/desktop/test/deployment/options/nodes1.oxt diff --git a/desktop/test/deployment/options/nodes2.oxt b/desktop/test/deployment/options/nodes2.oxt Binary files differnew file mode 100644 index 0000000000..a35cfaba9d --- /dev/null +++ b/desktop/test/deployment/options/nodes2.oxt diff --git a/desktop/test/deployment/options/nodes3.oxt b/desktop/test/deployment/options/nodes3.oxt Binary files differnew file mode 100644 index 0000000000..db0bc49da5 --- /dev/null +++ b/desktop/test/deployment/options/nodes3.oxt diff --git a/desktop/test/deployment/options/nodes4.oxt b/desktop/test/deployment/options/nodes4.oxt Binary files differnew file mode 100644 index 0000000000..fe0550fdc6 --- /dev/null +++ b/desktop/test/deployment/options/nodes4.oxt diff --git a/desktop/test/deployment/options/nodes5.oxt b/desktop/test/deployment/options/nodes5.oxt Binary files differnew file mode 100644 index 0000000000..893e9ee3e2 --- /dev/null +++ b/desktop/test/deployment/options/nodes5.oxt diff --git a/desktop/test/deployment/options/readme.txt b/desktop/test/deployment/options/readme.txt new file mode 100644 index 0000000000..58274ece79 --- /dev/null +++ b/desktop/test/deployment/options/readme.txt @@ -0,0 +1,200 @@ +Important: The handler component extensionoptions.jar in the extensions may not +contain exactly the same sources as the one build in the handler directory. To +make sure that debugging works build the handler directory and put the +extensionoptions.jar into the extension. + + + +leaf1.oxt: Defines a leaf under the node WriterNode +================================================================================ + +leaf1mod.oxt: Defines a leaf under the node WriterNode + +It has a duplicate entry in the manifest.xml (OptionsDialog.xcu). This would cause a DisposedException when uninstalling on OOo 3.0 and prevent the extension from being uninstalled. This is actually a bug of the extensions. However, the error is difficult to investigate. Therefore this was fixed to make OOo more robust (i96690). +================================================================================ + +leaf2.oxt: Defines a leaf under a node that has a name which requires special +"xml encoding". The name is "My Writer's & Settings". The node is not assigned +to a Module and the Node/AllModules property is not true. This is a typical +scenario when a Node had been added to an existing Module and later the Module +was removed. This is a situation which actually should not occur. In this case +DO NOT show the Node in the OOo's options dialog, because it shows only nodes +for a particular module and in this case the Module for the Node is unknown. +In the Extension Manager's +options dialog this Node can be shown because the Module is irrelevant. +See also nodes5.oxt. +================================================================================ + +leaves1.oxt: multiple ordered leaves under available nodes. The leaves Labels are +localized for en-US and de. The following leaves should appear: + +Writer: +-leaves1 Writer 1 en-US +-leaves1 Writer 2 en-US +-leaves1 Writer 3 en-US + +Calc: +-leaves1 Calc 3 en-US +-leaves1 Calc 3 en-US +-leaves1 Calc 3 en-US + +Draw: +-leaves1 Draw 3 en-US +-leaves1 Draw 3 en-US +-leaves1 Draw 3 en-US + +If a german office is used then the strings contain "de" instead of "en-US". +================================================================================ + +leaves2.oxt: Same as leaves1.oxt. Use together with leaves1.oxt to test the +grouping of leaves. +================================================================================ + +leaves3.oxt: Same as leaves1.oxt, but the leaves are not ordered. +================================================================================ + +nodes1.oxt: Defines one node which has AllModules set and which has +no children. Therefore this node should not be displayed. +================================================================================ + +nodes2.oxt: Defines 3 nodes which use AllModules and which form an +ordered group. Every node defines also 3 nodes which have a determined order. + +-nodes2 node 1 en-US + -nodes2 node 1 leaf 1 en-US + -nodes2 node 1 leaf 2 en-US + -nodes2 node 1 leaf 3 en-US + +-nodes2 node 2 en-US + -nodes2 node 2 leaf 1 en-US + -nodes2 node 2 leaf 2 en-US + -nodes2 node 2 leaf 3 en-US + +-nodes2 node 3 en-US + -nodes2 node 3 leaf 1 en-US + -nodes2 node 3 leaf 2 en-US + -nodes2 node 3 leaf 3 en-US + +================================================================================ + +nodes3.oxt: Defines 3 nodes which are placed under different existing Modules. +The nodes and there leaves are ordered. + +Context Writer: +- nodes3 node 1 + nodes3 node 1 leaf 1 en-US + nodes3 node 1 leaf 2 en-US + nodes3 node 1 leaf 3 en-US + +- nodes3 node 2 + nodes3 node 2 leaf 1 en-US + nodes3 node 2 leaf 2 en-US + nodes3 node 2 leaf 3 en-US + +- nodes3 node 3 + nodes3 node 3 leaf 1 en-US + nodes3 node 3 leaf 2 en-US + nodes3 node 3 leaf 3 en-US + +Context Calc: +- nodes3 node 1 + nodes3 node 1 leaf 1 en-US + nodes3 node 1 leaf 2 en-US + nodes3 node 1 leaf 3 en-US + +- nodes3 node 3 + nodes3 node 3 leaf 1 en-US + nodes3 node 3 leaf 2 en-US + nodes3 node 3 leaf 3 en-US + +Context Draw: +- nodes3 node 2 + nodes3 node 2 leaf 1 en-US + nodes3 node 2 leaf 2 en-US + nodes3 node 2 leaf 3 en-US + +================================================================================ + +nodes4.oxt: Same as nodes3.oxt. Use together with nodes3.txt to test the +grouping of nodes. +================================================================================ + +nodes5.oxt: Defines a node which in turn defines 3 leaves. The Node +is not assigned to a Module and the AllModule property is false (which is the +default).This may happen when a node +had been added to an already existing Module and then this Module was removed. For +example, an extension adds a node to the "Writer Module" and the +next office update removes the "Writer Module" (which is rather inconceivable). +Then the node and its leaves MUST NOT be displayed in OOo's options dialog, +because the Module is not known. However, it can be displayed in the +options dialog of the Extension Manager. See also the description for leaf2.oxt. +================================================================================ + +modules1.oxt: Defines two Modules and three Nodes. The Nodes may not +be displayed in OOo's options dialog because there is currently no application +which uses this Module. However the Nodes are displayed in the options dialog +of the Extension Manager. +There are three Nodes defined. The relationship is this: + +-module1 + -node 1 + -leaf 1 + -leaf 2 + -leaf 3 + -node 2 + -leaf 1 + -leaf 2 + -leaf 3 + -node 3 + -leaf 1 + -leaf 2 + -leaf 3 + +-module2 + -node1 + -leaf 1 + -leaf 2 + -leaf 3 + -node3 + -leaf 1 + -leaf 2 + -leaf 3 + +The options dialog of the Extension Manager shall display only three nodes: + + -node 1 + -leaf 1 + -leaf 2 + -leaf 3 + -node 2 + -leaf 1 + -leaf 2 + -leaf 3 + -node 3 + -leaf 1 + -leaf 2 + -leaf 3 + +or + + -node 1 + -leaf 1 + -leaf 2 + -leaf 3 + -node 3 + -leaf 1 + -leaf 2 + -leaf 3 + -node 2 + -leaf 1 + -leaf 2 + -leaf 3 + +Since the order of Module|s is not defined, the dialog may display first the +Nodes from module2 and then from module1. If a node is already displayed then +it is not shown again. + +================================================================================ + +modules2.oxt: Same as modules1, except that the order of nodes and leaves +is not defined. diff --git a/desktop/test/deployment/passive/Addons.xcu b/desktop/test/deployment/passive/Addons.xcu new file mode 100644 index 0000000000..3cc7c45ed6 --- /dev/null +++ b/desktop/test/deployment/passive/Addons.xcu @@ -0,0 +1,74 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + * 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 . + --> + +<o:items xmlns:o="http://openoffice.org/2001/registry"> + <item o:path="/org.openoffice.Office.Addons"> + <node o:name="AddonUI"> + <node o:name="OfficeMenuBar"> + <node o:name="org.openoffice.test.desktop.deployment.passive" + o:op="replace"> + <prop o:name="Title" xml:lang="en-US"> + <value>passive</value> + </prop> + <node o:name="Submenu"> + <node o:name="1" o:op="replace"> + <prop o:name="URL"> + <value>vnd.org.openoffice.test.desktop.deployment.passive_native:</value> + </prop> + <prop o:name="Title" xml:lang="en-US"> + <value>native</value> + </prop> + </node> + <node o:name="2" o:op="replace"> + <prop o:name="URL"> + <value>vnd.org.openoffice.test.desktop.deployment.passive_java:</value> + </prop> + <prop o:name="Title" xml:lang="en-US"> + <value>java</value> + </prop> + </node> + <node o:name="3" o:op="replace"> + <prop o:name="URL"> + <value>vnd.org.openoffice.test.desktop.deployment.passive_python:</value> + </prop> + <prop o:name="Title" xml:lang="en-US"> + <value>python</value> + </prop> + </node> + </node> + </node> + </node> + <node o:name="OfficeToolBar"> + <node o:name="org.openoffice.test.desktop.deployment.passive" o:op="replace"> + <node o:name="1" o:op="replace"> + <prop o:name="URL"> + <value>vnd.org.openoffice.test.desktop.deployment.passive_native:</value> + </prop> + <prop o:name="Title" xml:lang="en-US"> + <value>native</value> + </prop> + <prop o:name="Context"> + <value/> + </prop> + </node> + </node> + </node> + </node> + </item> +</o:items> diff --git a/desktop/test/deployment/passive/MANIFEST.MF b/desktop/test/deployment/passive/MANIFEST.MF new file mode 100644 index 0000000000..45a04bf263 --- /dev/null +++ b/desktop/test/deployment/passive/MANIFEST.MF @@ -0,0 +1,3 @@ +Sealed: true +RegistrationClassName: com.sun.star.comp.test.deployment.passive_java.Services +UNO-Type-Path: diff --git a/desktop/test/deployment/passive/META-INF/manifest.xml b/desktop/test/deployment/passive/META-INF/manifest.xml new file mode 100644 index 0000000000..c387b4be44 --- /dev/null +++ b/desktop/test/deployment/passive/META-INF/manifest.xml @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + * 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 . + --> + +<m:manifest xmlns:m="http://openoffice.org/2001/manifest"> + <m:file-entry m:media-type="application/vnd.sun.star.configuration-data" + m:full-path="Addons.xcu"/> + <m:file-entry m:media-type="application/vnd.sun.star.configuration-data" + m:full-path="ProtocolHandler.xcu"/> + <m:file-entry m:media-type="application/vnd.sun.star.help" m:full-path="help"/> + <m:file-entry + m:media-type="application/vnd.sun.star.uno-components;platform=@PLATFORM@" + m:full-path="platform.components"/> + <m:file-entry + m:media-type="application/vnd.sun.star.uno-components" + m:full-path="generic.components"/> +</m:manifest> diff --git a/desktop/test/deployment/passive/ProtocolHandler.xcu b/desktop/test/deployment/passive/ProtocolHandler.xcu new file mode 100644 index 0000000000..f546fd1193 --- /dev/null +++ b/desktop/test/deployment/passive/ProtocolHandler.xcu @@ -0,0 +1,39 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + * 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 . + --> + +<o:component-data xmlns:o="http://openoffice.org/2001/registry" + o:package="org.openoffice.Office" o:name="ProtocolHandler"> + <node o:name="HandlerSet"> + <node o:name="com.sun.star.test.deployment.passive_native" o:op="replace"> + <prop o:name="Protocols"> + <value>vnd.org.openoffice.test.desktop.deployment.passive_native:*</value> + </prop> + </node> + <node o:name="com.sun.star.test.deployment.passive_java" o:op="replace"> + <prop o:name="Protocols"> + <value>vnd.org.openoffice.test.desktop.deployment.passive_java:*</value> + </prop> + </node> + <node o:name="com.sun.star.test.deployment.passive_python" o:op="replace"> + <prop o:name="Protocols"> + <value>vnd.org.openoffice.test.desktop.deployment.passive_python:*</value> + </prop> + </node> + </node> +</o:component-data> diff --git a/desktop/test/deployment/passive/com/sun/star/comp/test/deployment/Dispatch.java b/desktop/test/deployment/passive/com/sun/star/comp/test/deployment/Dispatch.java new file mode 100644 index 0000000000..74882bca55 --- /dev/null +++ b/desktop/test/deployment/passive/com/sun/star/comp/test/deployment/Dispatch.java @@ -0,0 +1,95 @@ +/* + * 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 . + */ + +package com.sun.star.comp.test.deployment.passive_java; + +import com.sun.star.awt.MessageBoxButtons; +import com.sun.star.awt.MessageBoxType; +import com.sun.star.awt.Rectangle; +import com.sun.star.awt.XMessageBox; +import com.sun.star.awt.XMessageBoxFactory; +import com.sun.star.awt.XWindowPeer; +import com.sun.star.beans.PropertyValue; +import com.sun.star.frame.DispatchDescriptor; +import com.sun.star.frame.XDesktop; +import com.sun.star.frame.XDispatch; +import com.sun.star.frame.XStatusListener; +import com.sun.star.lang.WrappedTargetRuntimeException; +import com.sun.star.lang.XComponent; +import com.sun.star.lang.XMultiComponentFactory; +import com.sun.star.lang.XServiceInfo; +import com.sun.star.lib.uno.helper.WeakBase; +import com.sun.star.uno.UnoRuntime; +import com.sun.star.uno.XComponentContext; +import com.sun.star.util.URL; + +public final class Dispatch extends WeakBase implements XServiceInfo, XDispatch +{ + public Dispatch(XComponentContext context) { + this.context = context; + } + + public String getImplementationName() { return implementationName; } + + public boolean supportsService(String ServiceName) { + return false; //TODO + } + + public String[] getSupportedServiceNames() { + return serviceNames; + } + + public void dispatch(URL URL, PropertyValue[] Arguments) { + try { + XMultiComponentFactory smgr = UnoRuntime.queryInterface( + XMultiComponentFactory.class, context.getServiceManager()); + XMessageBox box = UnoRuntime.queryInterface( + XMessageBoxFactory.class, + smgr.createInstanceWithContext( + "com.sun.star.awt.Toolkit", context)). + createMessageBox( + UnoRuntime.queryInterface( + XWindowPeer.class, + (UnoRuntime.queryInterface( + XDesktop.class, + smgr.createInstanceWithContext( + "com.sun.star.frame.Desktop", context)). + getCurrentFrame().getComponentWindow())), + MessageBoxType.INFOBOX, MessageBoxButtons.BUTTONS_OK, + "passive", "java"); + box.execute(); + UnoRuntime.queryInterface(XComponent.class, box).dispose(); + } catch (com.sun.star.uno.RuntimeException e) { + throw e; + } catch (com.sun.star.uno.Exception e) { + throw new WrappedTargetRuntimeException(e, + "wrapped: " + e.getMessage(), this, e); + } + } + + public void addStatusListener(XStatusListener Control, URL URL) {} + + public void removeStatusListener(XStatusListener Control, URL URL) {} + + private final XComponentContext context; + + static final String implementationName = + "com.sun.star.comp.test.deployment.passive_java_singleton"; + + static final String[] serviceNames = new String[0]; +} diff --git a/desktop/test/deployment/passive/com/sun/star/comp/test/deployment/Provider.java b/desktop/test/deployment/passive/com/sun/star/comp/test/deployment/Provider.java new file mode 100644 index 0000000000..13d59ecf1b --- /dev/null +++ b/desktop/test/deployment/passive/com/sun/star/comp/test/deployment/Provider.java @@ -0,0 +1,74 @@ +/* + * 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 . + */ + +package com.sun.star.comp.test.deployment.passive_java; + +import com.sun.star.frame.DispatchDescriptor; +import com.sun.star.frame.XDispatch; +import com.sun.star.frame.XDispatchProvider; +import com.sun.star.lang.XServiceInfo; +import com.sun.star.lib.uno.helper.WeakBase; +import com.sun.star.uno.UnoRuntime; +import com.sun.star.uno.XComponentContext; +import com.sun.star.util.URL; + +public final class Provider extends WeakBase + implements XServiceInfo, XDispatchProvider +{ + public Provider(XComponentContext context) { + this.context = context; + } + + public String getImplementationName() { return implementationName; } + + public boolean supportsService(String ServiceName) { + return ServiceName.equals(getSupportedServiceNames()[0]); //TODO + } + + public String[] getSupportedServiceNames() { + return serviceNames; + } + + public XDispatch queryDispatch( + URL URL, String TargetFrameName, int SearchFlags) + { + return UnoRuntime.queryInterface( + XDispatch.class, + context.getValueByName( + "/singletons/" + + "com.sun.star.test.deployment.passive_java_singleton")); + } + + public XDispatch[] queryDispatches(DispatchDescriptor[] Requests) { + XDispatch[] s = new XDispatch[Requests.length]; + for (int i = 0; i < s.length; ++i) { + s[i] = queryDispatch( + Requests[i].FeatureURL, Requests[i].FrameName, + Requests[i].SearchFlags); + } + return s; + } + + private final XComponentContext context; + + static final String implementationName = + "com.sun.star.comp.test.deployment.passive_java"; + + static final String[] serviceNames = new String[] { + "com.sun.star.test.deployment.passive_java" }; +} diff --git a/desktop/test/deployment/passive/com/sun/star/comp/test/deployment/Services.java b/desktop/test/deployment/passive/com/sun/star/comp/test/deployment/Services.java new file mode 100644 index 0000000000..b14ba71027 --- /dev/null +++ b/desktop/test/deployment/passive/com/sun/star/comp/test/deployment/Services.java @@ -0,0 +1,42 @@ +/* + * 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 . + */ + +package com.sun.star.comp.test.deployment.passive_java; + +import com.sun.star.lang.XSingleComponentFactory; +import com.sun.star.lib.uno.helper.Factory; + +public final class Services { + private Services() {} + + public static XSingleComponentFactory __getComponentFactory( + String implementation) + { + if (implementation.equals(Dispatch.implementationName)) { + return Factory.createComponentFactory( + Dispatch.class, Dispatch.implementationName, + Dispatch.serviceNames); + } else if (implementation.equals(Provider.implementationName)) { + return Factory.createComponentFactory( + Provider.class, Provider.implementationName, + Provider.serviceNames); + } else { + return null; + } + } +} diff --git a/desktop/test/deployment/passive/description.xml b/desktop/test/deployment/passive/description.xml new file mode 100644 index 0000000000..b0fdea19e6 --- /dev/null +++ b/desktop/test/deployment/passive/description.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + * 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 . + --> + +<d:description xmlns:d="http://openoffice.org/extensions/description/2006"> + <d:identifier + value="org.openoffice/framework/desktop/test/deployment/passive"/> + <d:version value="1"/> + <d:dependencies> + <d:OpenOffice.org-minimal-version d:name="OpenOffice.org 3.4" value="3.4"/> + </d:dependencies> +</d:description> diff --git a/desktop/test/deployment/passive/help/en/help.tree b/desktop/test/deployment/passive/help/en/help.tree new file mode 100644 index 0000000000..76a07991d8 --- /dev/null +++ b/desktop/test/deployment/passive/help/en/help.tree @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + * 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/. + * +--> +<tree_view version="10-Aug-2010"> + <help_section application="OrgOpenofficeFrameworkDesktopTestDeploymentPassive" id="" + title="The test-passive Extension"> + <topic + id="OrgOpenofficeFrameworkDesktopTestDeploymentPassive/org.openoffice%2Fframework%2Fdesktop%2Ftest%2Fdeployment%2Fpassive/main.xhp">The test-passive Extension</topic> + </help_section> +</tree_view> diff --git a/desktop/test/deployment/passive/help/en/main.xhp b/desktop/test/deployment/passive/help/en/main.xhp new file mode 100644 index 0000000000..ff62c6b682 --- /dev/null +++ b/desktop/test/deployment/passive/help/en/main.xhp @@ -0,0 +1,35 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + * 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/. + * +--> +<helpdocument version="1.0"> + <meta> + <topic id="" indexer="include"> + <title xml-lang="en" id="">The test-passive Extension</title> + <filename>/org.openoffice%2Fframework%2Fdesktop%2Ftest%2Fdeployment%2Fpassive/main.xhp</filename> + <bookmark branch="index" xml-lang="en" id=""> + <bookmark_value>test-passive extension</bookmark_value> + <bookmark_value>extensions;test-passive</bookmark_value> + </bookmark> + <bookmark branch="index" xml-lang="en" id="_scalc_swriter"> + <bookmark_value>test-passive extension in Calc and Writer</bookmark_value> + </bookmark> + </topic> + </meta> + <body> + <bookmark branch="hid/vnd.org.openoffice.test.desktop.deployment.passive_native:" xml-lang="en" + id=""/> + <paragraph role="paragraph" id="" xml-lang="en"> + <ahelp hid="vnd.org.openoffice.test.desktop.deployment.passive_native:" visibility="hidden"> + Show the test-passive extension's native dialog + </ahelp> + </paragraph> + <paragraph role="heading" level="1" id="" xml-lang="en">The test-passive Extension</paragraph> + <paragraph role="paragraph" id="" xml-lang="en">Bla bla bla.</paragraph> + </body> +</helpdocument> diff --git a/desktop/test/deployment/passive/passive_java.component b/desktop/test/deployment/passive/passive_java.component new file mode 100644 index 0000000000..35ff0fcdfd --- /dev/null +++ b/desktop/test/deployment/passive/passive_java.component @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + * 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 . + --> + +<component loader="com.sun.star.loader.Java2" + xmlns="http://openoffice.org/2010/uno-components"> + <implementation name="com.sun.star.comp.test.deployment.passive_java"> + <service name="com.sun.star.test.deployment.passive_java"/> + </implementation> + <implementation + name="com.sun.star.comp.test.deployment.passive_java_singleton"> + <singleton name="com.sun.star.test.deployment.passive_java_singleton"/> + </implementation> +</component> diff --git a/desktop/test/deployment/passive/passive_native.component b/desktop/test/deployment/passive/passive_native.component new file mode 100644 index 0000000000..37d13e53dd --- /dev/null +++ b/desktop/test/deployment/passive/passive_native.component @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + * 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 . + --> + +<component loader="com.sun.star.loader.SharedLibrary" + xmlns="http://openoffice.org/2010/uno-components"> + <implementation name="com.sun.star.comp.test.deployment.passive_native"> + <service name="com.sun.star.test.deployment.passive_native"/> + </implementation> + <implementation + name="com.sun.star.comp.test.deployment.passive_native_singleton"> + <singleton name="com.sun.star.test.deployment.passive_native_singleton"/> + </implementation> +</component> diff --git a/desktop/test/deployment/passive/passive_native.cxx b/desktop/test/deployment/passive/passive_native.cxx new file mode 100644 index 0000000000..9bfbcb7b16 --- /dev/null +++ b/desktop/test/deployment/passive/passive_native.cxx @@ -0,0 +1,248 @@ +/* -*- 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 <cassert> + +#include <com/sun/star/awt/MessageBoxButtons.hpp> +#include <com/sun/star/awt/Rectangle.hpp> +#include <com/sun/star/awt/Toolkit.hpp> +#include <com/sun/star/awt/XMessageBox.hpp> +#include <com/sun/star/awt/XWindowPeer.hpp> +#include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/frame/DispatchDescriptor.hpp> +#include <com/sun/star/frame/Desktop.hpp> +#include <com/sun/star/frame/XDispatch.hpp> +#include <com/sun/star/frame/XDispatchProvider.hpp> +#include <com/sun/star/frame/XFrame.hpp> +#include <com/sun/star/frame/XStatusListener.hpp> +#include <com/sun/star/lang/XComponent.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/uno/DeploymentException.hpp> +#include <com/sun/star/uno/Exception.hpp> +#include <com/sun/star/uno/Reference.hxx> +#include <com/sun/star/uno/RuntimeException.hpp> +#include <com/sun/star/uno/Sequence.hxx> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <com/sun/star/uno/XInterface.hpp> +#include <com/sun/star/util/URL.hpp> +#include <cppuhelper/factory.hxx> +#include <cppuhelper/implbase2.hxx> +#include <cppuhelper/implementationentry.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <cppuhelper/weak.hxx> +#include <rtl/ustring.hxx> +#include <sal/types.h> +#include <uno/lbnames.h> + +namespace { + +class Provider: + public cppu::WeakImplHelper2< + css::lang::XServiceInfo, css::frame::XDispatchProvider > +{ +public: + Provider(const Provider&) = delete; + const Provider& operator=(const Provider&) = delete; + + static css::uno::Reference< css::uno::XInterface > SAL_CALL static_create( + css::uno::Reference< css::uno::XComponentContext > const & xContext) + { return static_cast< cppu::OWeakObject * >(new Provider(xContext)); } + + static rtl::OUString SAL_CALL static_getImplementationName(); + + static css::uno::Sequence< rtl::OUString > SAL_CALL + static_getSupportedServiceNames(); + +private: + explicit Provider( + css::uno::Reference< css::uno::XComponentContext > const & context): + context_(context) { assert(context.is()); } + + virtual ~Provider() {} + + virtual rtl::OUString SAL_CALL getImplementationName() override + { return static_getImplementationName(); } + + virtual sal_Bool SAL_CALL supportsService(rtl::OUString const & ServiceName) override + { return cppu::supportsService(this, ServiceName); } + + virtual css::uno::Sequence< rtl::OUString > SAL_CALL + getSupportedServiceNames() override + { return static_getSupportedServiceNames(); } + + virtual css::uno::Reference< css::frame::XDispatch > SAL_CALL queryDispatch( + css::util::URL const &, rtl::OUString const &, sal_Int32) override; + + virtual css::uno::Sequence< css::uno::Reference< css::frame::XDispatch > > + SAL_CALL queryDispatches( + css::uno::Sequence< css::frame::DispatchDescriptor > const & Requests) override; + + css::uno::Reference< css::uno::XComponentContext > context_; +}; + +rtl::OUString Provider::static_getImplementationName() { + return rtl::OUString("com.sun.star.comp.test.deployment.passive_native"); +} + +css::uno::Sequence< rtl::OUString > Provider::static_getSupportedServiceNames() +{ + rtl::OUString name("com.sun.star.test.deployment.passive_native"); + return css::uno::Sequence< rtl::OUString >(&name, 1); +} + +css::uno::Reference< css::frame::XDispatch > Provider::queryDispatch( + css::util::URL const &, rtl::OUString const &, sal_Int32) +{ + css::uno::Reference< css::frame::XDispatch > dispatch; + if (!(context_->getValueByName( + "/singletons/com.sun.star.test.deployment." + "passive_native_singleton") >>= + dispatch) || + !dispatch.is()) + { + throw css::uno::DeploymentException( + "component context fails to supply singleton" + " com.sun.star.test.deployment.passive_native_singleton of type" + " com.sun.star.frame.XDispatch", + context_); + } + return dispatch; +} + +css::uno::Sequence< css::uno::Reference< css::frame::XDispatch > > +Provider::queryDispatches( + css::uno::Sequence< css::frame::DispatchDescriptor > const & Requests) +{ + css::uno::Sequence< css::uno::Reference< css::frame::XDispatch > > s( + Requests.getLength()); + for (sal_Int32 i = 0; i < s.getLength(); ++i) { + s[i] = queryDispatch( + Requests[i].FeatureURL, Requests[i].FrameName, + Requests[i].SearchFlags); + } + return s; +} + +class Dispatch: + public cppu::WeakImplHelper2< + css::lang::XServiceInfo, css::frame::XDispatch > +{ +public: + Dispatch(const Dispatch&) = delete; + const Dispatch& operator=(const Dispatch&) = delete; + + static css::uno::Reference< css::uno::XInterface > SAL_CALL static_create( + css::uno::Reference< css::uno::XComponentContext > const & xContext) + { return static_cast< cppu::OWeakObject * >(new Dispatch(xContext)); } + + static rtl::OUString SAL_CALL static_getImplementationName(); + + static css::uno::Sequence< rtl::OUString > SAL_CALL + static_getSupportedServiceNames() + { return css::uno::Sequence< rtl::OUString >(); } + +private: + explicit Dispatch( + css::uno::Reference< css::uno::XComponentContext > const & context): + context_(context) { assert(context.is()); } + + virtual ~Dispatch() {} + + virtual rtl::OUString SAL_CALL getImplementationName() override + { return static_getImplementationName(); } + + virtual sal_Bool SAL_CALL supportsService(rtl::OUString const & ServiceName) override + { return cppu::supportsService(this, ServiceName); } + + virtual css::uno::Sequence< rtl::OUString > SAL_CALL + getSupportedServiceNames() override + { return static_getSupportedServiceNames(); } + + virtual void SAL_CALL dispatch( + css::util::URL const &, + css::uno::Sequence< css::beans::PropertyValue > const &) override; + + virtual void SAL_CALL addStatusListener( + css::uno::Reference< css::frame::XStatusListener > const &, + css::util::URL const &) override + {} + + virtual void SAL_CALL removeStatusListener( + css::uno::Reference< css::frame::XStatusListener > const &, + css::util::URL const &) override + {} + + css::uno::Reference< css::uno::XComponentContext > context_; +}; + +rtl::OUString Dispatch::static_getImplementationName() { + return rtl::OUString( + "com.sun.star.comp.test.deployment.passive_native_singleton"); +} + +void Dispatch::dispatch( + css::util::URL const &, + css::uno::Sequence< css::beans::PropertyValue > const &) +{ + css::uno::Reference< css::frame::XDesktop2 > xDesktop = css::frame::Desktop::create(context_); + css::uno::Reference< css::frame::XFrame > xFrame = xDesktop->getCurrentFrame(); + css::uno::Reference< css::awt::XWindowPeer > xWindowPeer( xFrame->getComponentWindow(), css::uno::UNO_QUERY_THROW ); + css::uno::Reference< css::awt::XToolkit2 > xToolkit = css::awt::Toolkit::create(context_); + css::uno::Reference< css::awt::XMessageBox > box( + xToolkit->createMessageBox( + xWindowPeer, + css::awt::MessageBoxType_INFOBOX, + css::awt::MessageBoxButtons::BUTTONS_OK, "passive", "native"), + css::uno::UNO_SET_THROW); + + box->execute(); + + css::uno::Reference< css::lang::XComponent > xComponent(box, css::uno::UNO_QUERY_THROW); + xComponent->dispose(); +} + +cppu::ImplementationEntry const services[] = { + { &Provider::static_create, &Provider::static_getImplementationName, + &Provider::static_getSupportedServiceNames, + &cppu::createSingleComponentFactory, nullptr, 0 }, + { &Dispatch::static_create, &Dispatch::static_getImplementationName, + &Dispatch::static_getSupportedServiceNames, + &cppu::createSingleComponentFactory, nullptr, 0 }, + { nullptr, nullptr, nullptr, nullptr, nullptr, 0 } +}; + +} + +extern "C" SAL_DLLPUBLIC_EXPORT void * component_getFactory( + char const * pImplName, void * pServiceManager, void * pRegistryKey) +{ + return cppu::component_getFactoryHelper( + pImplName, pServiceManager, pRegistryKey, services); +} + +extern "C" SAL_DLLPUBLIC_EXPORT void +component_getImplementationEnvironment( + char const ** ppEnvTypeName, uno_Environment **) +{ + *ppEnvTypeName = CPPU_CURRENT_LANGUAGE_BINDING_NAME; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/test/deployment/passive/passive_python.component b/desktop/test/deployment/passive/passive_python.component new file mode 100644 index 0000000000..6007f9fcf1 --- /dev/null +++ b/desktop/test/deployment/passive/passive_python.component @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + * 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 . + --> + +<component loader="com.sun.star.loader.Python" + xmlns="http://openoffice.org/2010/uno-components"> + <implementation name="com.sun.star.comp.test.deployment.passive_python"> + <service name="com.sun.star.test.deployment.passive_python"/> + </implementation> + <implementation + name="com.sun.star.comp.test.deployment.passive_python_singleton"> + <singleton name="com.sun.star.test.deployment.passive_python_singleton"/> + </implementation> +</component> diff --git a/desktop/test/deployment/passive/passive_python.py b/desktop/test/deployment/passive/passive_python.py new file mode 100644 index 0000000000..f16797e501 --- /dev/null +++ b/desktop/test/deployment/passive/passive_python.py @@ -0,0 +1,93 @@ +# +# 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 . +# + +import uno +import unohelper + +from com.sun.star.awt import Rectangle +from com.sun.star.awt.MessageBoxButtons import BUTTONS_OK +from com.sun.star.awt.MessageBoxType import INFOBOX +from com.sun.star.frame import XDispatch, XDispatchProvider +from com.sun.star.lang import XServiceInfo + +class Provider(unohelper.Base, XServiceInfo, XDispatchProvider): + implementationName = "com.sun.star.comp.test.deployment.passive_python" + + serviceNames = ("com.sun.star.test.deployment.passive_python",) + + def __init__(self, context): + self.context = context + + def getImplementationName(self): + return self.implementationName + + def supportsService(self, ServiceName): + return ServiceName in self.serviceNames + + def getSupportedServiceNames(self): + return self.serviceNames + + def queryDispatch(self, URL, TargetFrame, SearchFlags): + return self.context.getValueByName( \ + "/singletons/com.sun.star.test.deployment.passive_python_singleton") + + def queryDispatches(self, Requests): + tuple( \ + self.queryDispatch(i.FeatureURL, i.FrameName, i.SearchFlags) \ + for i in Requests) + +class Dispatch(unohelper.Base, XServiceInfo, XDispatch): + implementationName = \ + "com.sun.star.comp.test.deployment.passive_python_singleton" + + serviceNames = () + + def __init__(self, context): + self.context = context + + def getImplementationName(self): + return self.implementationName + + def supportsService(self, ServiceName): + return ServiceName in self.serviceNames + + def getSupportedServiceNames(self): + return self.serviceNames + + def dispatch(self, URL, Arguments): + smgr = self.context.getServiceManager() + box = smgr.createInstanceWithContext( \ + "com.sun.star.awt.Toolkit", self.context).createMessageBox( \ + smgr.createInstanceWithContext( \ + "com.sun.star.frame.Desktop", self.context). \ + getCurrentFrame().getComponentWindow(), \ + INFOBOX, BUTTONS_OK, "passive", "python") + box.execute(); + box.dispose(); + + def addStatusListener(self, Control, URL): + pass + + def removeStatusListener(self, Control, URL): + pass + +g_ImplementationHelper = unohelper.ImplementationHelper() +g_ImplementationHelper.addImplementation( \ + Provider, Provider.implementationName, Provider.serviceNames) +g_ImplementationHelper.addImplementation( \ + Dispatch, Dispatch.implementationName, Dispatch.serviceNames) diff --git a/desktop/test/deployment/simple_license/BadDesc.oxt b/desktop/test/deployment/simple_license/BadDesc.oxt Binary files differnew file mode 100644 index 0000000000..436778d54d --- /dev/null +++ b/desktop/test/deployment/simple_license/BadDesc.oxt diff --git a/desktop/test/deployment/simple_license/BadNamespace.oxt b/desktop/test/deployment/simple_license/BadNamespace.oxt Binary files differnew file mode 100644 index 0000000000..e439c9e171 --- /dev/null +++ b/desktop/test/deployment/simple_license/BadNamespace.oxt diff --git a/desktop/test/deployment/simple_license/BadRoot.oxt b/desktop/test/deployment/simple_license/BadRoot.oxt Binary files differnew file mode 100644 index 0000000000..1f6c60c992 --- /dev/null +++ b/desktop/test/deployment/simple_license/BadRoot.oxt diff --git a/desktop/test/deployment/simple_license/Locale1.oxt b/desktop/test/deployment/simple_license/Locale1.oxt Binary files differnew file mode 100644 index 0000000000..51ecb5c75c --- /dev/null +++ b/desktop/test/deployment/simple_license/Locale1.oxt diff --git a/desktop/test/deployment/simple_license/Locale2.oxt b/desktop/test/deployment/simple_license/Locale2.oxt Binary files differnew file mode 100644 index 0000000000..bb6b236a5d --- /dev/null +++ b/desktop/test/deployment/simple_license/Locale2.oxt diff --git a/desktop/test/deployment/simple_license/Locale3.oxt b/desktop/test/deployment/simple_license/Locale3.oxt Binary files differnew file mode 100644 index 0000000000..56bfedc240 --- /dev/null +++ b/desktop/test/deployment/simple_license/Locale3.oxt diff --git a/desktop/test/deployment/simple_license/Locale4.oxt b/desktop/test/deployment/simple_license/Locale4.oxt Binary files differnew file mode 100644 index 0000000000..9a465bc7cf --- /dev/null +++ b/desktop/test/deployment/simple_license/Locale4.oxt diff --git a/desktop/test/deployment/simple_license/Locale5.oxt b/desktop/test/deployment/simple_license/Locale5.oxt Binary files differnew file mode 100644 index 0000000000..ce16830c13 --- /dev/null +++ b/desktop/test/deployment/simple_license/Locale5.oxt diff --git a/desktop/test/deployment/simple_license/Locale6.oxt b/desktop/test/deployment/simple_license/Locale6.oxt Binary files differnew file mode 100644 index 0000000000..770d32506e --- /dev/null +++ b/desktop/test/deployment/simple_license/Locale6.oxt diff --git a/desktop/test/deployment/simple_license/LongLic.oxt b/desktop/test/deployment/simple_license/LongLic.oxt Binary files differnew file mode 100644 index 0000000000..a0a49daeba --- /dev/null +++ b/desktop/test/deployment/simple_license/LongLic.oxt diff --git a/desktop/test/deployment/simple_license/MissingLic.oxt b/desktop/test/deployment/simple_license/MissingLic.oxt Binary files differnew file mode 100644 index 0000000000..04d58fd117 --- /dev/null +++ b/desktop/test/deployment/simple_license/MissingLic.oxt diff --git a/desktop/test/deployment/simple_license/MissingLicRef.oxt b/desktop/test/deployment/simple_license/MissingLicRef.oxt Binary files differnew file mode 100644 index 0000000000..01c9d19a28 --- /dev/null +++ b/desktop/test/deployment/simple_license/MissingLicRef.oxt diff --git a/desktop/test/deployment/simple_license/NoDefLang.oxt b/desktop/test/deployment/simple_license/NoDefLang.oxt Binary files differnew file mode 100644 index 0000000000..3eadd5254c --- /dev/null +++ b/desktop/test/deployment/simple_license/NoDefLang.oxt diff --git a/desktop/test/deployment/simple_license/NoDesc.oxt b/desktop/test/deployment/simple_license/NoDesc.oxt Binary files differnew file mode 100644 index 0000000000..ac83dac97e --- /dev/null +++ b/desktop/test/deployment/simple_license/NoDesc.oxt diff --git a/desktop/test/deployment/simple_license/NoLang.oxt b/desktop/test/deployment/simple_license/NoLang.oxt Binary files differnew file mode 100644 index 0000000000..a4f3dd43a0 --- /dev/null +++ b/desktop/test/deployment/simple_license/NoLang.oxt diff --git a/desktop/test/deployment/simple_license/Prefix.oxt b/desktop/test/deployment/simple_license/Prefix.oxt Binary files differnew file mode 100644 index 0000000000..3e09b8d804 --- /dev/null +++ b/desktop/test/deployment/simple_license/Prefix.oxt diff --git a/desktop/test/deployment/simple_license/ShortLicense.oxt b/desktop/test/deployment/simple_license/ShortLicense.oxt Binary files differnew file mode 100644 index 0000000000..efcfdc98e1 --- /dev/null +++ b/desktop/test/deployment/simple_license/ShortLicense.oxt diff --git a/desktop/test/deployment/simple_license/ShortLicenseShared.oxt b/desktop/test/deployment/simple_license/ShortLicenseShared.oxt Binary files differnew file mode 100644 index 0000000000..775559a2c7 --- /dev/null +++ b/desktop/test/deployment/simple_license/ShortLicenseShared.oxt diff --git a/desktop/test/deployment/simple_license/suppress_license.oxt b/desktop/test/deployment/simple_license/suppress_license.oxt Binary files differnew file mode 100644 index 0000000000..2bacd6aa37 --- /dev/null +++ b/desktop/test/deployment/simple_license/suppress_license.oxt diff --git a/desktop/test/deployment/simple_license/tests_simple_license.odt b/desktop/test/deployment/simple_license/tests_simple_license.odt Binary files differnew file mode 100644 index 0000000000..b0c86e11c6 --- /dev/null +++ b/desktop/test/deployment/simple_license/tests_simple_license.odt diff --git a/desktop/test/deployment/update/changing_display_name/change1.oxt b/desktop/test/deployment/update/changing_display_name/change1.oxt Binary files differnew file mode 100644 index 0000000000..c919129ab6 --- /dev/null +++ b/desktop/test/deployment/update/changing_display_name/change1.oxt diff --git a/desktop/test/deployment/update/changing_display_name/change1_mod.oxt b/desktop/test/deployment/update/changing_display_name/change1_mod.oxt Binary files differnew file mode 100644 index 0000000000..5ab99d7bf2 --- /dev/null +++ b/desktop/test/deployment/update/changing_display_name/change1_mod.oxt diff --git a/desktop/test/deployment/update/changing_display_name/readme.txt b/desktop/test/deployment/update/changing_display_name/readme.txt new file mode 100644 index 0000000000..905f0be9a9 --- /dev/null +++ b/desktop/test/deployment/update/changing_display_name/readme.txt @@ -0,0 +1,13 @@ + +The default display name, if nothing is provided by the extension, is the file name. +The display name could be changed in different versions. There are three versions +of change1.oxt available: + +v1: no display name +v2: change1 de +v3: change1 de - changed display name - + +change1_mod.oxt is the same as change1.oxt version 1 except that is has a display name. +This situation should actually never arise, because the version should always be +changed when the extension is changed - and be it only the display name. + diff --git a/desktop/test/deployment/update/changing_display_name/update1/change1.oxt b/desktop/test/deployment/update/changing_display_name/update1/change1.oxt Binary files differnew file mode 100644 index 0000000000..ef034f9445 --- /dev/null +++ b/desktop/test/deployment/update/changing_display_name/update1/change1.oxt diff --git a/desktop/test/deployment/update/changing_display_name/update1/change1.update.xml b/desktop/test/deployment/update/changing_display_name/update1/change1.update.xml new file mode 100644 index 0000000000..a7010d13d3 --- /dev/null +++ b/desktop/test/deployment/update/changing_display_name/update1/change1.update.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + * 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 . +--> +<description xmlns="http://openoffice.org/extensions/update/2006" + xmlns:xlink="http://www.w3.org/1999/xlink"> + <identifier value="org.openoffice/framework/desktop/changing_display_name/change1" /> + <version value="2.0" /> + <update-download> + <src xlink:href="http://extensions.openoffice.org/testarea/desktop/changing_display_name/update1/change1.oxt" /> + </update-download> +</description> + diff --git a/desktop/test/deployment/update/changing_display_name/update2/change1.oxt b/desktop/test/deployment/update/changing_display_name/update2/change1.oxt Binary files differnew file mode 100644 index 0000000000..551f5a3f48 --- /dev/null +++ b/desktop/test/deployment/update/changing_display_name/update2/change1.oxt diff --git a/desktop/test/deployment/update/changing_display_name/update2/change1.update.xml b/desktop/test/deployment/update/changing_display_name/update2/change1.update.xml new file mode 100644 index 0000000000..88458d6705 --- /dev/null +++ b/desktop/test/deployment/update/changing_display_name/update2/change1.update.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + * 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 . +--> +<description xmlns="http://openoffice.org/extensions/update/2006" + xmlns:xlink="http://www.w3.org/1999/xlink"> + <identifier value="org.openoffice/framework/desktop/changing_display_name/change1" /> + <version value="3.0" /> + <update-download> + <src xlink:href="http://extensions.openoffice.org/testarea/desktop/changing_display_name/update2/change1.oxt" /> + </update-download> +</description> + diff --git a/desktop/test/deployment/update/default_url/default1.oxt b/desktop/test/deployment/update/default_url/default1.oxt Binary files differnew file mode 100644 index 0000000000..3fa8c9f08f --- /dev/null +++ b/desktop/test/deployment/update/default_url/default1.oxt diff --git a/desktop/test/deployment/update/default_url/default2.oxt b/desktop/test/deployment/update/default_url/default2.oxt Binary files differnew file mode 100644 index 0000000000..d54ce88c51 --- /dev/null +++ b/desktop/test/deployment/update/default_url/default2.oxt diff --git a/desktop/test/deployment/update/default_url/readme.txt b/desktop/test/deployment/update/default_url/readme.txt new file mode 100644 index 0000000000..a34c77cea8 --- /dev/null +++ b/desktop/test/deployment/update/default_url/readme.txt @@ -0,0 +1,9 @@ +Tests for using the default URL for update information. This URL is currently contained in +the version.ini (ExtensionUpdateURL) and is used to obtain update information for extensions which do not provide +a URL themselves. + +The extensions default1.oxt and default2.oxt do not have a URL for update information. + +To test this one has to put this entry into the version.ini: + +ExtensionUpdateURL=http://extensions.openoffice.org/testarea/desktop/default_url/update/feed1.xml diff --git a/desktop/test/deployment/update/default_url/update/default1.oxt b/desktop/test/deployment/update/default_url/update/default1.oxt Binary files differnew file mode 100644 index 0000000000..198395c76e --- /dev/null +++ b/desktop/test/deployment/update/default_url/update/default1.oxt diff --git a/desktop/test/deployment/update/default_url/update/default1.update.xml b/desktop/test/deployment/update/default_url/update/default1.update.xml new file mode 100644 index 0000000000..5c240d6d51 --- /dev/null +++ b/desktop/test/deployment/update/default_url/update/default1.update.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + * 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 . +--> +<description xmlns="http://openoffice.org/extensions/update/2006" + xmlns:xlink="http://www.w3.org/1999/xlink"> + <identifier value="org.openoffice.legacy.default1.oxt"/> + <version value="2.0" /> + <update-download> + <src xlink:href="http://extensions.openoffice.org/testarea/desktop/default_url/update/default1.oxt" /> + </update-download> +</description> + diff --git a/desktop/test/deployment/update/default_url/update/default2.oxt b/desktop/test/deployment/update/default_url/update/default2.oxt Binary files differnew file mode 100644 index 0000000000..198395c76e --- /dev/null +++ b/desktop/test/deployment/update/default_url/update/default2.oxt diff --git a/desktop/test/deployment/update/default_url/update/default2.update.xml b/desktop/test/deployment/update/default_url/update/default2.update.xml new file mode 100644 index 0000000000..9e7e5db1aa --- /dev/null +++ b/desktop/test/deployment/update/default_url/update/default2.update.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + * 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 . +--> +<description xmlns="http://openoffice.org/extensions/update/2006" + xmlns:xlink="http://www.w3.org/1999/xlink"> + <identifier value="org.openoffice.legacy.default2.oxt"/> + <version value="2.0" /> + <update-download> + <src xlink:href="http://extensions.openoffice.org/testarea/desktop/default_url/update/default2.oxt" /> + </update-download> +</description> + diff --git a/desktop/test/deployment/update/default_url/update/feed1.xml b/desktop/test/deployment/update/default_url/update/feed1.xml new file mode 100644 index 0000000000..3f9a2e1d20 --- /dev/null +++ b/desktop/test/deployment/update/default_url/update/feed1.xml @@ -0,0 +1,50 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + * 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 . +--> +<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en-US"> + + <title>OpenOffice.org Update Feed</title> + <link rel="alternate" type="text/html" href="http://update.services.openoffice.org/ooo/snapshot.html"/> + <updated>2006-11-06T18:30:02Z</updated> + <author> + <name>The OpenOffice.org Project</name> + <uri>http://openoffice.org</uri> + <email>updatefeed@openoffice.org</email> + </author> + <id>urn:uuid:a4ccd383-1dd1-11b2-a95c-0003ba566e9d</id> + <entry> + <title>default1.oxt version 2.0 available</title> + <link rel="alternate" type="text/html" + href="http://extensions.openoffice.org"/> + <id>urn:uuid:a4ccd383-1dd1-11b2-a95c-0003ba566e9f</id> + <category term="org.openoffice.legacy.default1.oxt" label="default1.oxt" /> + <updated>2006-11-06T18:30:02Z</updated> + <summary>Click here to go to the download page.</summary> + <content type="application/xml" src="http://extensions.openoffice.org/testarea/desktop/default_url/update/default1.update.xml" /> + </entry> + <entry> + <title>default2.oxt version 2.0 available</title> + <link rel="alternate" type="text/html" + href="http://extensions.openoffice.org"/> + <id>urn:uuid:a4ccd383-1dd1-11b2-a95c-0003ba566eaf</id> + <category term="org.openoffice.legacy.default2.oxt" label="default2.oxt" /> + <updated>2006-11-06T18:30:02Z</updated> + <summary>Click here to go to the download page.</summary> + <content type="application/xml" src="http://extensions.openoffice.org/testarea/desktop/default_url/update/default2.update.xml" /> + </entry> +</feed> diff --git a/desktop/test/deployment/update/defect/fail1.oxt b/desktop/test/deployment/update/defect/fail1.oxt Binary files differnew file mode 100644 index 0000000000..5b5cdba2cd --- /dev/null +++ b/desktop/test/deployment/update/defect/fail1.oxt diff --git a/desktop/test/deployment/update/defect/fail2.oxt b/desktop/test/deployment/update/defect/fail2.oxt Binary files differnew file mode 100644 index 0000000000..61b0306f09 --- /dev/null +++ b/desktop/test/deployment/update/defect/fail2.oxt diff --git a/desktop/test/deployment/update/defect/fail3.oxt b/desktop/test/deployment/update/defect/fail3.oxt Binary files differnew file mode 100644 index 0000000000..9da26d48a6 --- /dev/null +++ b/desktop/test/deployment/update/defect/fail3.oxt diff --git a/desktop/test/deployment/update/defect/fail4.oxt b/desktop/test/deployment/update/defect/fail4.oxt Binary files differnew file mode 100644 index 0000000000..66b87caa14 --- /dev/null +++ b/desktop/test/deployment/update/defect/fail4.oxt diff --git a/desktop/test/deployment/update/defect/info1.oxt b/desktop/test/deployment/update/defect/info1.oxt Binary files differnew file mode 100644 index 0000000000..9ffd373fa1 --- /dev/null +++ b/desktop/test/deployment/update/defect/info1.oxt diff --git a/desktop/test/deployment/update/defect/info2.oxt b/desktop/test/deployment/update/defect/info2.oxt Binary files differnew file mode 100644 index 0000000000..229a52c3bc --- /dev/null +++ b/desktop/test/deployment/update/defect/info2.oxt diff --git a/desktop/test/deployment/update/defect/info3.oxt b/desktop/test/deployment/update/defect/info3.oxt Binary files differnew file mode 100644 index 0000000000..b702f3e004 --- /dev/null +++ b/desktop/test/deployment/update/defect/info3.oxt diff --git a/desktop/test/deployment/update/defect/readme.txt b/desktop/test/deployment/update/defect/readme.txt new file mode 100644 index 0000000000..5e8322f5cf --- /dev/null +++ b/desktop/test/deployment/update/defect/readme.txt @@ -0,0 +1,15 @@ +The updates, that is the newer versions, are defect. However, only fail2.oxt fails to install. The other extensions can be installed directly and through an update.
+
+fail1.oxt: in version2 the contained t.rdb was renamed so that it is not found (t.rdb is referenced in the manifest.xml).
+
+fail2.oxt: in version 2 the contained t.rdb is corrupted. It is a renamed .txt file which contains some text.
+
+fail3.oxt: in version 2 the contained t.rdb is corrupted. It is a renamed .txt file which does not contain any text.
+
+fail4.oxt: the version 2 references by fail4.update.xml is empty.
+
+info1.oxt: The update information file has length null.
+
+info2.oxt: The update information does not contain xml.
+
+info3.oxt: The update information contain an error: the tag update information contains two opening brackets (<<update-information>)
\ No newline at end of file diff --git a/desktop/test/deployment/update/defect/update/fail1.oxt b/desktop/test/deployment/update/defect/update/fail1.oxt Binary files differnew file mode 100644 index 0000000000..dbcc7cd73e --- /dev/null +++ b/desktop/test/deployment/update/defect/update/fail1.oxt diff --git a/desktop/test/deployment/update/defect/update/fail1.update.xml b/desktop/test/deployment/update/defect/update/fail1.update.xml new file mode 100644 index 0000000000..87de4a25ae --- /dev/null +++ b/desktop/test/deployment/update/defect/update/fail1.update.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + * 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 . +--> +<description xmlns="http://openoffice.org/extensions/update/2006" + xmlns:xlink="http://www.w3.org/1999/xlink"> + <identifier value="org.openoffice.legacy.fail1.oxt"/> + <version value="2.0" /> + <update-download> + <src xlink:href="http://extensions.openoffice.org/testarea/desktop/defect/update/fail1.oxt" /> + </update-download> +</description> + diff --git a/desktop/test/deployment/update/defect/update/fail2.oxt b/desktop/test/deployment/update/defect/update/fail2.oxt Binary files differnew file mode 100644 index 0000000000..6df0c3cf99 --- /dev/null +++ b/desktop/test/deployment/update/defect/update/fail2.oxt diff --git a/desktop/test/deployment/update/defect/update/fail2.update.xml b/desktop/test/deployment/update/defect/update/fail2.update.xml new file mode 100644 index 0000000000..254f61f34f --- /dev/null +++ b/desktop/test/deployment/update/defect/update/fail2.update.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + * 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 . +--> +<description xmlns="http://openoffice.org/extensions/update/2006" + xmlns:xlink="http://www.w3.org/1999/xlink"> + <identifier value="org.openoffice.legacy.fail2.oxt"/> + <version value="2.0" /> + <update-download> + <src xlink:href="http://extensions.openoffice.org/testarea/desktop/defect/update/fail2.oxt" /> + </update-download> +</description> + diff --git a/desktop/test/deployment/update/defect/update/fail3.oxt b/desktop/test/deployment/update/defect/update/fail3.oxt Binary files differnew file mode 100644 index 0000000000..2d340f4144 --- /dev/null +++ b/desktop/test/deployment/update/defect/update/fail3.oxt diff --git a/desktop/test/deployment/update/defect/update/fail3.update.xml b/desktop/test/deployment/update/defect/update/fail3.update.xml new file mode 100644 index 0000000000..ff7f82db08 --- /dev/null +++ b/desktop/test/deployment/update/defect/update/fail3.update.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + * 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 . +--> +<description xmlns="http://openoffice.org/extensions/update/2006" + xmlns:xlink="http://www.w3.org/1999/xlink"> + <identifier value="org.openoffice.legacy.fail3.oxt"/> + <version value="2.0" /> + <update-download> + <src xlink:href="http://extensions.openoffice.org/testarea/desktop/defect/update/fail3.oxt" /> + </update-download> +</description> + diff --git a/desktop/test/deployment/update/defect/update/fail4.oxt b/desktop/test/deployment/update/defect/update/fail4.oxt new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/desktop/test/deployment/update/defect/update/fail4.oxt diff --git a/desktop/test/deployment/update/defect/update/fail4.update.xml b/desktop/test/deployment/update/defect/update/fail4.update.xml new file mode 100644 index 0000000000..b321b16bab --- /dev/null +++ b/desktop/test/deployment/update/defect/update/fail4.update.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + * 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 . +--> +<description xmlns="http://openoffice.org/extensions/update/2006" + xmlns:xlink="http://www.w3.org/1999/xlink"> + <identifier value="org.openoffice.legacy.fail4.oxt"/> + <version value="2.0" /> + <update-download> + <src xlink:href="http://extensions.openoffice.org/testarea/desktop/defect/update/fail4.oxt" /> + </update-download> +</description> + diff --git a/desktop/test/deployment/update/defect/update/info1.update.xml b/desktop/test/deployment/update/defect/update/info1.update.xml new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/desktop/test/deployment/update/defect/update/info1.update.xml diff --git a/desktop/test/deployment/update/defect/update/info2.update.xml b/desktop/test/deployment/update/defect/update/info2.update.xml new file mode 100644 index 0000000000..7e26b0b179 --- /dev/null +++ b/desktop/test/deployment/update/defect/update/info2.update.xml @@ -0,0 +1 @@ +This is an invalid update information file!!! diff --git a/desktop/test/deployment/update/defect/update/info3.oxt b/desktop/test/deployment/update/defect/update/info3.oxt Binary files differnew file mode 100644 index 0000000000..60debac57c --- /dev/null +++ b/desktop/test/deployment/update/defect/update/info3.oxt diff --git a/desktop/test/deployment/update/defect/update/info3.update.xml b/desktop/test/deployment/update/defect/update/info3.update.xml new file mode 100644 index 0000000000..69b54c2595 --- /dev/null +++ b/desktop/test/deployment/update/defect/update/info3.update.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + * 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 . +--> +<description xmlns="http://openoffice.org/extensions/update/2006" + xmlns:xlink="http://www.w3.org/1999/xlink"> + <identifier value="org.openoffice.legacy.info3.oxt"/> + <version value="2.0" /> + <<update-download> + <src xlink:href="http://extensions.openoffice.org/testarea/desktop/defect/update/info3.oxt" /> + </update-download> +</description> + diff --git a/desktop/test/deployment/update/dependencies/publisher_en.html b/desktop/test/deployment/update/dependencies/publisher_en.html new file mode 100644 index 0000000000..37dbc2b9d6 --- /dev/null +++ b/desktop/test/deployment/update/dependencies/publisher_en.html @@ -0,0 +1,9 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> +<HTML> +<HEAD> +</HEAD> +<BODY> +<H1>My Extension Company</H1> +</BODY> +</HTML> + diff --git a/desktop/test/deployment/update/dependencies/readme.txt b/desktop/test/deployment/update/dependencies/readme.txt new file mode 100644 index 0000000000..a64f749b12 --- /dev/null +++ b/desktop/test/deployment/update/dependencies/readme.txt @@ -0,0 +1,23 @@ +# +# 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 . +# + +update-dependencies.oxt is an extension that itself has no dependencies, but +whose update has unsatisfied dependencies (and also uses update-website). + +The update information contain also publisher and release notes information, +which should be displayed in the update dialog. diff --git a/desktop/test/deployment/update/dependencies/release-notes_en.html b/desktop/test/deployment/update/dependencies/release-notes_en.html new file mode 100644 index 0000000000..0971f78d14 --- /dev/null +++ b/desktop/test/deployment/update/dependencies/release-notes_en.html @@ -0,0 +1,8 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> +<HTML> +<HEAD> +</HEAD> +<BODY> +<H1>Release Notes</H1> +</BODY> +</HTML> diff --git a/desktop/test/deployment/update/dependencies/update-dependencies.oxt b/desktop/test/deployment/update/dependencies/update-dependencies.oxt Binary files differnew file mode 100644 index 0000000000..513b25d207 --- /dev/null +++ b/desktop/test/deployment/update/dependencies/update-dependencies.oxt diff --git a/desktop/test/deployment/update/dependencies/update/update-dependencies.update.xml b/desktop/test/deployment/update/dependencies/update/update-dependencies.update.xml new file mode 100644 index 0000000000..7f9d0606fa --- /dev/null +++ b/desktop/test/deployment/update/dependencies/update/update-dependencies.update.xml @@ -0,0 +1,62 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + * 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 . + --> +<description xmlns="http://openoffice.org/extensions/update/2006" + xmlns:d="http://openoffice.org/extensions/description/2006" + xmlns:xlink="http://www.w3.org/1999/xlink"> + <identifier value="org.openoffice/framework/desktop/test/deployment/update/dependencies/update-dependencies.oxt"/> + <version value="2.0"/> + <dependencies> + <dependency d:name="& < > ' " > ' tab	. crlf
. em-dash—. line-separator
. paragraph-separator
. xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"/> + <dependency d:name="Dependency 1"/> + <dependency d:name="Dependency 2"/> + <dependency d:name="Dependency 3"/> + <dependency d:name="Dependency 4"/> + <dependency d:name="Dependency 5"/> + <dependency d:name="Dependency 6"/> + <dependency d:name="Dependency 7"/> + <dependency d:name="Dependency 8"/> + <dependency d:name="Dependency 9"/> + <dependency d:name="Dependency 10"/> + <dependency d:name="Dependency 11"/> + <dependency d:name="Dependency 12"/> + <dependency d:name="Dependency 13"/> + <dependency d:name="Dependency 14"/> + <dependency d:name="Dependency 15"/> + <dependency d:name="Dependency 16"/> + <dependency d:name="Dependency 17"/> + <dependency d:name="Dependency 18"/> + <dependency d:name="Dependency 19"/> + <dependency d:name="Dependency 20"/> + <dependency/> + <d:OpenOffice.org-minimal-version value="2.1" d:name="OpenOffice.org 2.1"/> + </dependencies> + <update-website> + <src xlink:href="http://nowhere.openoffice.org"/> + <src xlink:href="http://nowhere.openoffice.org/2"/> + </update-website> + + <publisher> + <name xlink:href="http://extensions.openoffice.org/testarea/desktop/dependencies/publisher_en.html" lang="en">My Extension Company</name> + </publisher> + + <release-notes> + <src xlink:href="http://extensions.openoffice.org/testarea/desktop/dependencies/release-notes_en.html" lang="en" /> + </release-notes> + +</description> diff --git a/desktop/test/deployment/update/license/lic1.oxt b/desktop/test/deployment/update/license/lic1.oxt Binary files differnew file mode 100644 index 0000000000..43bfe3b77b --- /dev/null +++ b/desktop/test/deployment/update/license/lic1.oxt diff --git a/desktop/test/deployment/update/license/lic2.oxt b/desktop/test/deployment/update/license/lic2.oxt Binary files differnew file mode 100644 index 0000000000..266a45e9a8 --- /dev/null +++ b/desktop/test/deployment/update/license/lic2.oxt diff --git a/desktop/test/deployment/update/license/lic3.oxt b/desktop/test/deployment/update/license/lic3.oxt Binary files differnew file mode 100644 index 0000000000..3f1b989600 --- /dev/null +++ b/desktop/test/deployment/update/license/lic3.oxt diff --git a/desktop/test/deployment/update/license/readme.txt b/desktop/test/deployment/update/license/readme.txt new file mode 100644 index 0000000000..6040da04c4 --- /dev/null +++ b/desktop/test/deployment/update/license/readme.txt @@ -0,0 +1,9 @@ +The extensions contain a license which is displayed during installation. If the license is displayed during an update can be determined by the attribute +/description/registration/simple-license/@suppress-on-update + +The default value is false, which means that the attribute is not set, then the license is displayed during an update. + +lic1.oxt: attribute not set +lic2.oxt: attribute set to false +lic3.oxt: attribute set to true + diff --git a/desktop/test/deployment/update/license/update/lic1.oxt b/desktop/test/deployment/update/license/update/lic1.oxt Binary files differnew file mode 100644 index 0000000000..cc91e1ff16 --- /dev/null +++ b/desktop/test/deployment/update/license/update/lic1.oxt diff --git a/desktop/test/deployment/update/license/update/lic1.update.xml b/desktop/test/deployment/update/license/update/lic1.update.xml new file mode 100644 index 0000000000..2a2761aa9e --- /dev/null +++ b/desktop/test/deployment/update/license/update/lic1.update.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + * 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 . +--> +<description xmlns="http://openoffice.org/extensions/update/2006" + xmlns:xlink="http://www.w3.org/1999/xlink"> + <identifier value="org.openoffice.legacy.lic1.oxt"/> + <version value="2.0" /> + <update-download> + <src xlink:href="http://extensions.openoffice.org/testarea/desktop/license/update/lic1.oxt" /> + </update-download> +</description> + diff --git a/desktop/test/deployment/update/license/update/lic2.oxt b/desktop/test/deployment/update/license/update/lic2.oxt Binary files differnew file mode 100644 index 0000000000..3510007924 --- /dev/null +++ b/desktop/test/deployment/update/license/update/lic2.oxt diff --git a/desktop/test/deployment/update/license/update/lic2.update.xml b/desktop/test/deployment/update/license/update/lic2.update.xml new file mode 100644 index 0000000000..7dc57ca0ea --- /dev/null +++ b/desktop/test/deployment/update/license/update/lic2.update.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + * 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 . +--> +<description xmlns="http://openoffice.org/extensions/update/2006" + xmlns:xlink="http://www.w3.org/1999/xlink"> + <identifier value="org.openoffice.legacy.lic2.oxt"/> + <version value="2.0" /> + <update-download> + <src xlink:href="http://extensions.openoffice.org/testarea/desktop/license/update/lic2.oxt" /> + </update-download> +</description> + diff --git a/desktop/test/deployment/update/license/update/lic3.oxt b/desktop/test/deployment/update/license/update/lic3.oxt Binary files differnew file mode 100644 index 0000000000..6ac6e0fd0f --- /dev/null +++ b/desktop/test/deployment/update/license/update/lic3.oxt diff --git a/desktop/test/deployment/update/license/update/lic3.update.xml b/desktop/test/deployment/update/license/update/lic3.update.xml new file mode 100644 index 0000000000..55f5556b6f --- /dev/null +++ b/desktop/test/deployment/update/license/update/lic3.update.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + * 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 . +--> +<description xmlns="http://openoffice.org/extensions/update/2006" + xmlns:xlink="http://www.w3.org/1999/xlink"> + <identifier value="org.openoffice.legacy.lic3.oxt"/> + <version value="2.0" /> + <update-download> + <src xlink:href="http://extensions.openoffice.org/testarea/desktop/license/update/lic3.oxt" /> + </update-download> +</description> + diff --git a/desktop/test/deployment/update/platform/all1.oxt b/desktop/test/deployment/update/platform/all1.oxt Binary files differnew file mode 100644 index 0000000000..ad9662a7c2 --- /dev/null +++ b/desktop/test/deployment/update/platform/all1.oxt diff --git a/desktop/test/deployment/update/platform/all2.oxt b/desktop/test/deployment/update/platform/all2.oxt Binary files differnew file mode 100644 index 0000000000..632d11b429 --- /dev/null +++ b/desktop/test/deployment/update/platform/all2.oxt diff --git a/desktop/test/deployment/update/platform/all3.oxt b/desktop/test/deployment/update/platform/all3.oxt Binary files differnew file mode 100644 index 0000000000..ab781552a5 --- /dev/null +++ b/desktop/test/deployment/update/platform/all3.oxt diff --git a/desktop/test/deployment/update/platform/freebsd_x86.oxt b/desktop/test/deployment/update/platform/freebsd_x86.oxt Binary files differnew file mode 100644 index 0000000000..338f5761de --- /dev/null +++ b/desktop/test/deployment/update/platform/freebsd_x86.oxt diff --git a/desktop/test/deployment/update/platform/freebsd_x86_64.oxt b/desktop/test/deployment/update/platform/freebsd_x86_64.oxt Binary files differnew file mode 100644 index 0000000000..39fee6de1a --- /dev/null +++ b/desktop/test/deployment/update/platform/freebsd_x86_64.oxt diff --git a/desktop/test/deployment/update/platform/invalid1.oxt b/desktop/test/deployment/update/platform/invalid1.oxt Binary files differnew file mode 100644 index 0000000000..13d709f438 --- /dev/null +++ b/desktop/test/deployment/update/platform/invalid1.oxt diff --git a/desktop/test/deployment/update/platform/invalid2.oxt b/desktop/test/deployment/update/platform/invalid2.oxt Binary files differnew file mode 100644 index 0000000000..f14257191b --- /dev/null +++ b/desktop/test/deployment/update/platform/invalid2.oxt diff --git a/desktop/test/deployment/update/platform/invalid3.oxt b/desktop/test/deployment/update/platform/invalid3.oxt Binary files differnew file mode 100644 index 0000000000..cadffa4f2a --- /dev/null +++ b/desktop/test/deployment/update/platform/invalid3.oxt diff --git a/desktop/test/deployment/update/platform/linux_arm_eabi.oxt b/desktop/test/deployment/update/platform/linux_arm_eabi.oxt Binary files differnew file mode 100644 index 0000000000..9c504e841b --- /dev/null +++ b/desktop/test/deployment/update/platform/linux_arm_eabi.oxt diff --git a/desktop/test/deployment/update/platform/linux_arm_oabi.oxt b/desktop/test/deployment/update/platform/linux_arm_oabi.oxt Binary files differnew file mode 100644 index 0000000000..f2c987f645 --- /dev/null +++ b/desktop/test/deployment/update/platform/linux_arm_oabi.oxt diff --git a/desktop/test/deployment/update/platform/linux_ia64.oxt b/desktop/test/deployment/update/platform/linux_ia64.oxt Binary files differnew file mode 100644 index 0000000000..f579a18ab9 --- /dev/null +++ b/desktop/test/deployment/update/platform/linux_ia64.oxt diff --git a/desktop/test/deployment/update/platform/linux_mips64_eb.oxt b/desktop/test/deployment/update/platform/linux_mips64_eb.oxt Binary files differnew file mode 100644 index 0000000000..5441216940 --- /dev/null +++ b/desktop/test/deployment/update/platform/linux_mips64_eb.oxt diff --git a/desktop/test/deployment/update/platform/linux_mips64_el.oxt b/desktop/test/deployment/update/platform/linux_mips64_el.oxt Binary files differnew file mode 100644 index 0000000000..17499092fa --- /dev/null +++ b/desktop/test/deployment/update/platform/linux_mips64_el.oxt diff --git a/desktop/test/deployment/update/platform/linux_mips_eb.oxt b/desktop/test/deployment/update/platform/linux_mips_eb.oxt Binary files differnew file mode 100644 index 0000000000..bf0bd94233 --- /dev/null +++ b/desktop/test/deployment/update/platform/linux_mips_eb.oxt diff --git a/desktop/test/deployment/update/platform/linux_mips_el.oxt b/desktop/test/deployment/update/platform/linux_mips_el.oxt Binary files differnew file mode 100644 index 0000000000..6bd5644683 --- /dev/null +++ b/desktop/test/deployment/update/platform/linux_mips_el.oxt diff --git a/desktop/test/deployment/update/platform/linux_powerpc.oxt b/desktop/test/deployment/update/platform/linux_powerpc.oxt Binary files differnew file mode 100644 index 0000000000..e301a3fb3a --- /dev/null +++ b/desktop/test/deployment/update/platform/linux_powerpc.oxt diff --git a/desktop/test/deployment/update/platform/linux_powerpc64.oxt b/desktop/test/deployment/update/platform/linux_powerpc64.oxt Binary files differnew file mode 100644 index 0000000000..e5f3ae0639 --- /dev/null +++ b/desktop/test/deployment/update/platform/linux_powerpc64.oxt diff --git a/desktop/test/deployment/update/platform/linux_s390x.oxt b/desktop/test/deployment/update/platform/linux_s390x.oxt Binary files differnew file mode 100644 index 0000000000..2ed250833f --- /dev/null +++ b/desktop/test/deployment/update/platform/linux_s390x.oxt diff --git a/desktop/test/deployment/update/platform/linux_sparc.oxt b/desktop/test/deployment/update/platform/linux_sparc.oxt Binary files differnew file mode 100644 index 0000000000..53dfc71e0c --- /dev/null +++ b/desktop/test/deployment/update/platform/linux_sparc.oxt diff --git a/desktop/test/deployment/update/platform/linux_x86.oxt b/desktop/test/deployment/update/platform/linux_x86.oxt Binary files differnew file mode 100644 index 0000000000..8379539cad --- /dev/null +++ b/desktop/test/deployment/update/platform/linux_x86.oxt diff --git a/desktop/test/deployment/update/platform/linux_x86_64.oxt b/desktop/test/deployment/update/platform/linux_x86_64.oxt Binary files differnew file mode 100644 index 0000000000..0fb1822752 --- /dev/null +++ b/desktop/test/deployment/update/platform/linux_x86_64.oxt diff --git a/desktop/test/deployment/update/platform/macosx_powerpc.oxt b/desktop/test/deployment/update/platform/macosx_powerpc.oxt Binary files differnew file mode 100644 index 0000000000..7c14634712 --- /dev/null +++ b/desktop/test/deployment/update/platform/macosx_powerpc.oxt diff --git a/desktop/test/deployment/update/platform/macosx_x86.oxt b/desktop/test/deployment/update/platform/macosx_x86.oxt Binary files differnew file mode 100644 index 0000000000..a20aadfeff --- /dev/null +++ b/desktop/test/deployment/update/platform/macosx_x86.oxt diff --git a/desktop/test/deployment/update/platform/mul1.oxt b/desktop/test/deployment/update/platform/mul1.oxt Binary files differnew file mode 100644 index 0000000000..b3b555969b --- /dev/null +++ b/desktop/test/deployment/update/platform/mul1.oxt diff --git a/desktop/test/deployment/update/platform/os2_x86.oxt b/desktop/test/deployment/update/platform/os2_x86.oxt Binary files differnew file mode 100644 index 0000000000..1c7fd40bef --- /dev/null +++ b/desktop/test/deployment/update/platform/os2_x86.oxt diff --git a/desktop/test/deployment/update/platform/readme.txt b/desktop/test/deployment/update/platform/readme.txt new file mode 100644 index 0000000000..222895ad73 --- /dev/null +++ b/desktop/test/deployment/update/platform/readme.txt @@ -0,0 +1,50 @@ +Extension which only support one platform +======================================================== + +freebsd_x86.oxt: freebsd_x86 +freebsd_x86_86.oxt: freebsd_x86_64 +linux_arm_eabi.oxt: linux_arm_eabi +linux_arm_oabi.oxt: linux_arm_oabi +linux_ia64.oxt: linux_ia64 +linux_mips_eb.oxt: linux_mips_eb +linux_mips64_eb.oxt: linux_mips64_eb +linux_mips_el.oxt: linux_mips_el +linux_mips64_el.oxt: linux_mips64_el +linux_powerpc64.oxt: linux_powerpc64 +linux_powerpc.oxt: linux_powerpc +linux_s390x.oxt: linux_s390x +linux_sparc.oxt: linux_sparc +linux_x86.oxt: linux_x86 +linux_x86_64.oxt: linux_x86_64 +macos_powerpc.oxt: macos_powerpc +macos_x86.oxt: macos_x86 +solaris_sparc.oxt: solaris_sparc +solaris_x86.oxt: solaris_x86 +windows_x86.oxt: windows_x86 +os2_x86.oxt: os/2_x86 + +Extensions which support multiple platforms +======================================================= +mul1.oxt: windows_x86, linux_x86, solaris_x86 + + +All platforms +========================================================= +all1.oxt: all, The <platform> element is missing. Default is "all". + +all2.oxt: all, <platform value="all" /> + +all3.oxt: all, no description.xml + + + +Invalid platforms +========================================================= +The following extensions cannot be installed because the platform element +is not correct. We assume that no valid platform is defined. + +invalid1.oxt: <platform /> + +invalid2.oxt: <platform value=""/> + +invalid3.oxt: <platform value="," /> diff --git a/desktop/test/deployment/update/platform/solaris_sparc.oxt b/desktop/test/deployment/update/platform/solaris_sparc.oxt Binary files differnew file mode 100644 index 0000000000..a61f81f439 --- /dev/null +++ b/desktop/test/deployment/update/platform/solaris_sparc.oxt diff --git a/desktop/test/deployment/update/platform/solaris_x86.oxt b/desktop/test/deployment/update/platform/solaris_x86.oxt Binary files differnew file mode 100644 index 0000000000..44d43df691 --- /dev/null +++ b/desktop/test/deployment/update/platform/solaris_x86.oxt diff --git a/desktop/test/deployment/update/platform/windows_x86.oxt b/desktop/test/deployment/update/platform/windows_x86.oxt Binary files differnew file mode 100644 index 0000000000..c66a9b1418 --- /dev/null +++ b/desktop/test/deployment/update/platform/windows_x86.oxt diff --git a/desktop/test/deployment/update/publisher/pub1.oxt b/desktop/test/deployment/update/publisher/pub1.oxt Binary files differnew file mode 100644 index 0000000000..c44ee9f3bc --- /dev/null +++ b/desktop/test/deployment/update/publisher/pub1.oxt diff --git a/desktop/test/deployment/update/publisher/pub10.oxt b/desktop/test/deployment/update/publisher/pub10.oxt Binary files differnew file mode 100644 index 0000000000..1e7410ec1b --- /dev/null +++ b/desktop/test/deployment/update/publisher/pub10.oxt diff --git a/desktop/test/deployment/update/publisher/pub11.oxt b/desktop/test/deployment/update/publisher/pub11.oxt Binary files differnew file mode 100644 index 0000000000..ef7fbca5e6 --- /dev/null +++ b/desktop/test/deployment/update/publisher/pub11.oxt diff --git a/desktop/test/deployment/update/publisher/pub2.oxt b/desktop/test/deployment/update/publisher/pub2.oxt Binary files differnew file mode 100644 index 0000000000..438bcae830 --- /dev/null +++ b/desktop/test/deployment/update/publisher/pub2.oxt diff --git a/desktop/test/deployment/update/publisher/pub3.oxt b/desktop/test/deployment/update/publisher/pub3.oxt Binary files differnew file mode 100644 index 0000000000..62fd69f559 --- /dev/null +++ b/desktop/test/deployment/update/publisher/pub3.oxt diff --git a/desktop/test/deployment/update/publisher/pub4.oxt b/desktop/test/deployment/update/publisher/pub4.oxt Binary files differnew file mode 100644 index 0000000000..4f6224f780 --- /dev/null +++ b/desktop/test/deployment/update/publisher/pub4.oxt diff --git a/desktop/test/deployment/update/publisher/pub5.oxt b/desktop/test/deployment/update/publisher/pub5.oxt Binary files differnew file mode 100644 index 0000000000..1774e6cd35 --- /dev/null +++ b/desktop/test/deployment/update/publisher/pub5.oxt diff --git a/desktop/test/deployment/update/publisher/pub6.oxt b/desktop/test/deployment/update/publisher/pub6.oxt Binary files differnew file mode 100644 index 0000000000..791a37f8e7 --- /dev/null +++ b/desktop/test/deployment/update/publisher/pub6.oxt diff --git a/desktop/test/deployment/update/publisher/pub7.oxt b/desktop/test/deployment/update/publisher/pub7.oxt Binary files differnew file mode 100644 index 0000000000..96e96887d0 --- /dev/null +++ b/desktop/test/deployment/update/publisher/pub7.oxt diff --git a/desktop/test/deployment/update/publisher/pub8.oxt b/desktop/test/deployment/update/publisher/pub8.oxt Binary files differnew file mode 100644 index 0000000000..dc9f0ce34d --- /dev/null +++ b/desktop/test/deployment/update/publisher/pub8.oxt diff --git a/desktop/test/deployment/update/publisher/pub9.oxt b/desktop/test/deployment/update/publisher/pub9.oxt Binary files differnew file mode 100644 index 0000000000..5e8ba9ebc1 --- /dev/null +++ b/desktop/test/deployment/update/publisher/pub9.oxt diff --git a/desktop/test/deployment/update/publisher/publisher_de-DE-altmark.html b/desktop/test/deployment/update/publisher/publisher_de-DE-altmark.html new file mode 100644 index 0000000000..c770b914ad --- /dev/null +++ b/desktop/test/deployment/update/publisher/publisher_de-DE-altmark.html @@ -0,0 +1,9 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> +<HTML> +<HEAD> +</HEAD> +<BODY> +<H1>My OpenOffice en-DE-altmark</H1> +</BODY> +</HTML> + diff --git a/desktop/test/deployment/update/publisher/publisher_de-DE.html b/desktop/test/deployment/update/publisher/publisher_de-DE.html new file mode 100644 index 0000000000..b06ed7088f --- /dev/null +++ b/desktop/test/deployment/update/publisher/publisher_de-DE.html @@ -0,0 +1,9 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> +<HTML> +<HEAD> +</HEAD> +<BODY> +<H1>My OpenOffice de-DE</H1> +</BODY> +</HTML> + diff --git a/desktop/test/deployment/update/publisher/publisher_de.html b/desktop/test/deployment/update/publisher/publisher_de.html new file mode 100644 index 0000000000..4cba9f423d --- /dev/null +++ b/desktop/test/deployment/update/publisher/publisher_de.html @@ -0,0 +1,9 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> +<HTML> +<HEAD> +</HEAD> +<BODY> +<H1>My OpenOffice de</H1> +</BODY> +</HTML> + diff --git a/desktop/test/deployment/update/publisher/publisher_en-GB.html b/desktop/test/deployment/update/publisher/publisher_en-GB.html new file mode 100644 index 0000000000..c73cf6219b --- /dev/null +++ b/desktop/test/deployment/update/publisher/publisher_en-GB.html @@ -0,0 +1,9 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> +<HTML> +<HEAD> +</HEAD> +<BODY> +<H1>My OpenOffice en-GB</H1> +</BODY> +</HTML> + diff --git a/desktop/test/deployment/update/publisher/publisher_en-US-region1.html b/desktop/test/deployment/update/publisher/publisher_en-US-region1.html new file mode 100644 index 0000000000..68beac7248 --- /dev/null +++ b/desktop/test/deployment/update/publisher/publisher_en-US-region1.html @@ -0,0 +1,9 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> +<HTML> +<HEAD> +</HEAD> +<BODY> +<H1>My OpenOffice en-US-region1</H1> +</BODY> +</HTML> + diff --git a/desktop/test/deployment/update/publisher/publisher_en-US-region2.html b/desktop/test/deployment/update/publisher/publisher_en-US-region2.html new file mode 100644 index 0000000000..501adb6596 --- /dev/null +++ b/desktop/test/deployment/update/publisher/publisher_en-US-region2.html @@ -0,0 +1,9 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> +<HTML> +<HEAD> +</HEAD> +<BODY> +<H1>My OpenOffice en-US-region2</H1> +</BODY> +</HTML> + diff --git a/desktop/test/deployment/update/publisher/publisher_en-US.html b/desktop/test/deployment/update/publisher/publisher_en-US.html new file mode 100644 index 0000000000..fd25751503 --- /dev/null +++ b/desktop/test/deployment/update/publisher/publisher_en-US.html @@ -0,0 +1,9 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> +<HTML> +<HEAD> +</HEAD> +<BODY> +<H1>My OpenOffice en-US</H1> +</BODY> +</HTML> + diff --git a/desktop/test/deployment/update/publisher/publisher_en-region3.html b/desktop/test/deployment/update/publisher/publisher_en-region3.html new file mode 100644 index 0000000000..b9fdc9d657 --- /dev/null +++ b/desktop/test/deployment/update/publisher/publisher_en-region3.html @@ -0,0 +1,9 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> +<HTML> +<HEAD> +</HEAD> +<BODY> +<H1>My OpenOffice en-region3</H1> +</BODY> +</HTML> + diff --git a/desktop/test/deployment/update/publisher/publisher_en.html b/desktop/test/deployment/update/publisher/publisher_en.html new file mode 100644 index 0000000000..416ab81243 --- /dev/null +++ b/desktop/test/deployment/update/publisher/publisher_en.html @@ -0,0 +1,9 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> +<HTML> +<HEAD> +</HEAD> +<BODY> +<H1>My OpenOffice en</H1> +</BODY> +</HTML> + diff --git a/desktop/test/deployment/update/publisher/readme.txt b/desktop/test/deployment/update/publisher/readme.txt new file mode 100644 index 0000000000..148dab776c --- /dev/null +++ b/desktop/test/deployment/update/publisher/readme.txt @@ -0,0 +1,212 @@ +--The folder contains extensions which use in the description.xml the following: + +-The <publisher> element +-The <release-notes> element + +Both element contain localized child elements. + +The following table shows what localized item is used, when the Office the locale +en-US uses. + + +Localization: + +Installed office en-US + | publisher | release notes +============================================= +pub1.oxt | en-US | en-US +--------------------------------------------- +pub2.oxt | en-US-region1 | en-US-region1 +--------------------------------------------- +pub3.oxt | en | en +--------------------------------------------- +pub4.oxt | en-GB | en-GB +--------------------------------------------- +pub5.oxt | de | de + + +================================================================================ +pub6.oxt +================================================================================ +like pub1 but without release notes. + + +================================================================================ +pub7.oxt +================================================================================ +like pub1 but without publisher name. + +================================================================================ +pub8.oxt +================================================================================ +Need not be committed in extensions/www/testarea/desktop. + +pub8.oxt is intended for tests with extensions.services.openoffice.org. +It does not contain <update-information>. That is, the Extension Manager will obtain +the update information from the repository as feed: + +http://updateext.services.openoffice.org/ProductUpdateService/check.Update + +pub8.oxt provides <publisher> and <release-notes>. This information should be transferred +in the update feed and not those entered in the repository. + +Test +---- +Repository: + +-Create the new extension in the repository. +-Provide a company name and a URL to the company website. This setting should be ignored + when generating the update feed. Instead the publisher name from the extension is used. + Enter "Publisher Title" : some arbitrary company + "Publisher URL": any arbitrary URL but not: + http://extensions.openoffice.org/testarea/desktop/publisher/publisher_en-US.html + +-Create a new release for the extension. Upload the version 2.0 (update/pub8.oxt). + Provide release notes. These release notes should later not be seen when clicking on + the release notes link. + + +Office: +-Install version 1.0 of the extension: + desktop/test/deployment/update/publisher/pub8.oxt + +-Run the update in the Extension Manager + + +Result: +The Update Dialog should show the publisher name as provided in the description.xml. For example, +when lang=en-US was selected: My OpenOffice en-US + +A release notes link is displayed with a URL to the release notes as provided in +the description.xml. For example, when lang=en-US was selected: +"http://extensions.openoffice.org/testarea/desktop/publisher/publisher_en-US.html + + +================================================================================ +pub9.oxt +================================================================================ +Need not be committed in extensions/www/testarea/desktop. + +pub9.oxt is intended for tests with extensions.services.openoffice.org. +It does not contain <update-information>. That is, the Extension Manager will obtain +the update information from the repository as feed: + +http://updateext.services.openoffice.org/ProductUpdateService/check.Update + +pub9.oxt provides <publisher>. That means the update feed should +contain the <publisher> as provided by the extension and the release notes as entered +in the repository. + +Test +---- +Repository: + +-Create the new extension in the repository. +-Provide a company name and a URL to the company website. This setting should be ignored + when generating the update feed. Instead the publisher name from the extension is used. + Enter "Publisher Title" : some arbitrary company name + "Publisher URL": any arbitrary URL but not: + http://extensions.openoffice.org/testarea/desktop/publisher/publisher_en-US.html + +-Create a new release for the extension. Upload the version 2.0 (update/pub9.oxt). + Provide release notes. These release notes should later be displayed when clicking on + the release notes link. + + +Office: +-Install version 1.0 of the extension: + desktop/test/deployment/update/publisher/pub9.oxt + +-Run the update in the Extension Manager + + +Result: +The Update Dialog should show the publisher name as provided in the description.xml. For example, +when lang=en-US was selected: My OpenOffice en-US + +A release notes link is displayed with a URL to the release notes as provided in the release notes +field on the edit page for the extension in the repository. + +================================================================================ +pub10.oxt +================================================================================ +Need not be committed in extensions/www/testarea/desktop. + +pub10.oxt is intended for tests with extensions.services.openoffice.org. +It does not contain <update-information>. That is, the Extension Manager will obtain +the update information from the repository as feed: + +http://updateext.services.openoffice.org/ProductUpdateService/check.Update + +pub10.oxt provides <release-notes>. That means the update feed should +contain the <release-notes> as provided by the extension and the publisher name/URLs as entered +in the repository. + +Test +---- +Repository: + +-Create the new extension in the repository. +-Provide a company name and a URL to the company website. They should then be in the update + feed. + +-Create a new release for the extension. Upload the version 2.0 (update/pub10.oxt). + Provide release notes. These release notes should NOT be displayed when clicking on + the release notes link. Instead the release notes provided by pub10.oxt should be displayed. + + +Office: +-Install version 1.0 of the extension: + desktop/test/deployment/update/publisher/pub10.oxt + +-Run the update in the Extension Manager + + +Result: +The Update Dialog should show the publisher name as provided in the repository. + +A release notes link is displayed with a URL to the release notes as provided in the +pub10.oxt. For example, when the locale of the office is en-US then this page will be +displayed: +For example, +when lang=en-US was selected: My OpenOffice en-US + +================================================================================ +pub11.oxt +================================================================================ +Need not be committed in extensions/www/testarea/desktop. + +pub11.oxt is intended for tests with extensions.services.openoffice.org. +It does not contain <update-information>. That is, the Extension Manager will obtain +the update information from the repository as feed: + +http://updateext.services.openoffice.org/ProductUpdateService/check.Update + +pub10.oxt neither provides <release-notes> nor <publisher>. That means the update feed should +contain these data as provided by the user on the repository web site. + +Test +---- +Repository: + +-Create the new extension in the repository. +-Provide a company name and a URL to the company website. They should then be in the update + feed. + +-Create a new release for the extension. Upload the version 2.0 (update/pub11.oxt). + Provide release notes. These release notes should be displayed when clicking on + the release notes link. + + +Office: +-Install version 1.0 of the extension: + desktop/test/deployment/update/publisher/pub11.oxt + +-Run the update in the Extension Manager + + +Result: +The Update Dialog should show the publisher name as provided in the repository. + +A release notes link is displayed which leads to the release notes kept in the repository. + diff --git a/desktop/test/deployment/update/publisher/release-notes_de-DE-altmark.html b/desktop/test/deployment/update/publisher/release-notes_de-DE-altmark.html new file mode 100644 index 0000000000..81b38a9f5b --- /dev/null +++ b/desktop/test/deployment/update/publisher/release-notes_de-DE-altmark.html @@ -0,0 +1,8 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> +<HTML> +<HEAD> +</HEAD> +<BODY> +<H1>Release Notes de-DE-altmark</H1> +</BODY> +</HTML> diff --git a/desktop/test/deployment/update/publisher/release-notes_de-DE.html b/desktop/test/deployment/update/publisher/release-notes_de-DE.html new file mode 100644 index 0000000000..f8f0121f02 --- /dev/null +++ b/desktop/test/deployment/update/publisher/release-notes_de-DE.html @@ -0,0 +1,8 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> +<HTML> +<HEAD> +</HEAD> +<BODY> +<H1>Release Notes de-DE</H1> +</BODY> +</HTML> diff --git a/desktop/test/deployment/update/publisher/release-notes_de.html b/desktop/test/deployment/update/publisher/release-notes_de.html new file mode 100644 index 0000000000..a9e1dc3647 --- /dev/null +++ b/desktop/test/deployment/update/publisher/release-notes_de.html @@ -0,0 +1,8 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> +<HTML> +<HEAD> +</HEAD> +<BODY> +<H1>Release Notes de</H1> +</BODY> +</HTML> diff --git a/desktop/test/deployment/update/publisher/release-notes_en-GB.html b/desktop/test/deployment/update/publisher/release-notes_en-GB.html new file mode 100644 index 0000000000..ca72ec1b9c --- /dev/null +++ b/desktop/test/deployment/update/publisher/release-notes_en-GB.html @@ -0,0 +1,8 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> +<HTML> +<HEAD> +</HEAD> +<BODY> +<H1>Release Notes en-GB</H1> +</BODY> +</HTML> diff --git a/desktop/test/deployment/update/publisher/release-notes_en-US-region1.html b/desktop/test/deployment/update/publisher/release-notes_en-US-region1.html new file mode 100644 index 0000000000..0e6f99ce4c --- /dev/null +++ b/desktop/test/deployment/update/publisher/release-notes_en-US-region1.html @@ -0,0 +1,8 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> +<HTML> +<HEAD> +</HEAD> +<BODY> +<H1>Release Notes en-US-region1</H1> +</BODY> +</HTML> diff --git a/desktop/test/deployment/update/publisher/release-notes_en-US-region2.html b/desktop/test/deployment/update/publisher/release-notes_en-US-region2.html new file mode 100644 index 0000000000..597bca0ebe --- /dev/null +++ b/desktop/test/deployment/update/publisher/release-notes_en-US-region2.html @@ -0,0 +1,8 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> +<HTML> +<HEAD> +</HEAD> +<BODY> +<H1>Release Notes en-US-region2</H1> +</BODY> +</HTML> diff --git a/desktop/test/deployment/update/publisher/release-notes_en-US.html b/desktop/test/deployment/update/publisher/release-notes_en-US.html new file mode 100644 index 0000000000..7f9d73e338 --- /dev/null +++ b/desktop/test/deployment/update/publisher/release-notes_en-US.html @@ -0,0 +1,8 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> +<HTML> +<HEAD> +</HEAD> +<BODY> +<H1>Release Notes en-US</H1> +</BODY> +</HTML> diff --git a/desktop/test/deployment/update/publisher/release-notes_en-region3.html b/desktop/test/deployment/update/publisher/release-notes_en-region3.html new file mode 100644 index 0000000000..5d62c7bcb4 --- /dev/null +++ b/desktop/test/deployment/update/publisher/release-notes_en-region3.html @@ -0,0 +1,8 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> +<HTML> +<HEAD> +</HEAD> +<BODY> +<H1>Release Notes en-region3</H1> +</BODY> +</HTML> diff --git a/desktop/test/deployment/update/publisher/release-notes_en.html b/desktop/test/deployment/update/publisher/release-notes_en.html new file mode 100644 index 0000000000..d02e4f3330 --- /dev/null +++ b/desktop/test/deployment/update/publisher/release-notes_en.html @@ -0,0 +1,8 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> +<HTML> +<HEAD> +</HEAD> +<BODY> +<H1>Release Notes en</H1> +</BODY> +</HTML> diff --git a/desktop/test/deployment/update/publisher/update/pub1.oxt b/desktop/test/deployment/update/publisher/update/pub1.oxt Binary files differnew file mode 100644 index 0000000000..cd04a58d55 --- /dev/null +++ b/desktop/test/deployment/update/publisher/update/pub1.oxt diff --git a/desktop/test/deployment/update/publisher/update/pub1.update.xml b/desktop/test/deployment/update/publisher/update/pub1.update.xml new file mode 100644 index 0000000000..14a35d7982 --- /dev/null +++ b/desktop/test/deployment/update/publisher/update/pub1.update.xml @@ -0,0 +1,53 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + * 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 . +--> +<description xmlns="http://openoffice.org/extensions/update/2006" + xmlns:xlink="http://www.w3.org/1999/xlink"> + <version value="2.0" /> + + <identifier value="org.openoffice/framework/desktop/test/deployment/update/publisher/pub1"/> + + <publisher> + <name xlink:href="http://extensions.openoffice.org/testarea/desktop/publisher/publisher_de.html" lang="de">My OpenOffice de</name> + <name xlink:href="http://extensions.openoffice.org/testarea/desktop/publisher/publisher_en.html" lang="en">My OpenOffice en</name> + <name xlink:href="http://extensions.openoffice.org/testarea/desktop/publisher/publisher_de-DE.html" lang="de-DE">My OpenOffice de-DE</name> + <name xlink:href="http://extensions.openoffice.org/testarea/desktop/publisher/publisher_de-DE-altmark.html" lang="de-DE-altmark">My OpenOffice de-DE-altmark</name> + <name xlink:href="http://extensions.openoffice.org/testarea/desktop/publisher/publisher_en-GB.html" lang="en-GB">My OpenOffice en-GB</name> + <name xlink:href="http://extensions.openoffice.org/testarea/desktop/publisher/publisher_en-US.html" lang="en-US">My OpenOffice en-US</name> + <name xlink:href="http://extensions.openoffice.org/testarea/desktop/publisher/publisher_en-US-region1.html" lang="en-US-region1">My OpenOffice en-US-region1</name> + <name xlink:href="http://extensions.openoffice.org/testarea/desktop/publisher/publisher_en-US-region2.html" lang="en-US-region2">My OpenOffice en-US-region2</name> + <name xlink:href="http://extensions.openoffice.org/testarea/desktop/publisher/publisher_en-region3.html" lang="en-region3">My OpenOffice en-region3</name> + </publisher> + + <release-notes> + <src xlink:href="http://extensions.openoffice.org/testarea/desktop/publisher/release-notes_de.html" lang="de" /> + <src xlink:href="http://extensions.openoffice.org/testarea/desktop/publisher/release-notes_en.html" lang="en" /> + <src xlink:href="http://extensions.openoffice.org/testarea/desktop/publisher/release-notes_de-DE.html" lang="de-DE" /> + <src xlink:href="http://extensions.openoffice.org/testarea/desktop/publisher/release-notes_de-DE-altmark.html" lang="de-DE-altmark" /> + <src xlink:href="http://extensions.openoffice.org/testarea/desktop/publisher/release-notes_en-GB.html" lang="en-GB" /> + <src xlink:href="http://extensions.openoffice.org/testarea/desktop/publisher/release-notes_en-US.html" lang="en-US" /> + <src xlink:href="http://extensions.openoffice.org/testarea/desktop/publisher/release-notes_en-US-region1.html" lang="en-US-region1" /> + <src xlink:href="http://extensions.openoffice.org/testarea/desktop/publisher/release-notes_en-US-region2.html" lang="en-US-region2" /> + <src xlink:href="http://extensions.openoffice.org/testarea/desktop/publisher/release-notes_en-region3.html" lang="en-region3" /> + </release-notes> + + <update-download> + <src xlink:href="http://extensions.openoffice.org/testarea/desktop/publisher/update/pub1.oxt" /> + </update-download> +</description> + diff --git a/desktop/test/deployment/update/publisher/update/pub10.oxt b/desktop/test/deployment/update/publisher/update/pub10.oxt Binary files differnew file mode 100644 index 0000000000..501a843381 --- /dev/null +++ b/desktop/test/deployment/update/publisher/update/pub10.oxt diff --git a/desktop/test/deployment/update/publisher/update/pub11.oxt b/desktop/test/deployment/update/publisher/update/pub11.oxt Binary files differnew file mode 100644 index 0000000000..692c0401f4 --- /dev/null +++ b/desktop/test/deployment/update/publisher/update/pub11.oxt diff --git a/desktop/test/deployment/update/publisher/update/pub2.oxt b/desktop/test/deployment/update/publisher/update/pub2.oxt Binary files differnew file mode 100644 index 0000000000..2a0bd6c21f --- /dev/null +++ b/desktop/test/deployment/update/publisher/update/pub2.oxt diff --git a/desktop/test/deployment/update/publisher/update/pub2.update.xml b/desktop/test/deployment/update/publisher/update/pub2.update.xml new file mode 100644 index 0000000000..675ce484d4 --- /dev/null +++ b/desktop/test/deployment/update/publisher/update/pub2.update.xml @@ -0,0 +1,51 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + * 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 . +--> +<description xmlns="http://openoffice.org/extensions/update/2006" + xmlns:xlink="http://www.w3.org/1999/xlink"> + <version value="2.0" /> + + <identifier value="org.openoffice/framework/desktop/test/deployment/update/publisher/pub2"/> + + <publisher> + <name xlink:href="http://extensions.openoffice.org/testarea/desktop/publisher/publisher_de.html" lang="de">My OpenOffice de</name> + <name xlink:href="http://extensions.openoffice.org/testarea/desktop/publisher/publisher_en.html" lang="en">My OpenOffice en</name> + <name xlink:href="http://extensions.openoffice.org/testarea/desktop/publisher/publisher_de-DE.html" lang="de-DE">My OpenOffice de-DE</name> + <name xlink:href="http://extensions.openoffice.org/testarea/desktop/publisher/publisher_de-DE-altmark.html" lang="de-DE-altmark">My OpenOffice de-DE-altmark</name> + <name xlink:href="http://extensions.openoffice.org/testarea/desktop/publisher/publisher_en-GB.html" lang="en-GB">My OpenOffice en-GB</name> + <name xlink:href="http://extensions.openoffice.org/testarea/desktop/publisher/publisher_en-US-region1.html" lang="en-US-region1">My OpenOffice en-US-region1</name> + <name xlink:href="http://extensions.openoffice.org/testarea/desktop/publisher/publisher_en-US-region2.html" lang="en-US-region2">My OpenOffice en-US-region2</name> + <name xlink:href="http://extensions.openoffice.org/testarea/desktop/publisher/publisher_en-region3.html" lang="en-region3">My OpenOffice en-region3</name> + </publisher> + + <release-notes> + <src xlink:href="http://extensions.openoffice.org/testarea/desktop/publisher/release-notes_de.html" lang="de" /> + <src xlink:href="http://extensions.openoffice.org/testarea/desktop/publisher/release-notes_en.html" lang="en" /> + <src xlink:href="http://extensions.openoffice.org/testarea/desktop/publisher/release-notes_de-DE.html" lang="de-DE" /> + <src xlink:href="http://extensions.openoffice.org/testarea/desktop/publisher/release-notes_de-DE-altmark.html" lang="de-DE-altmark" /> + <src xlink:href="http://extensions.openoffice.org/testarea/desktop/publisher/release-notes_en-GB.html" lang="en-GB" /> + <src xlink:href="http://extensions.openoffice.org/testarea/desktop/publisher/release-notes_en-US-region1.html" lang="en-US-region1" /> + <src xlink:href="http://extensions.openoffice.org/testarea/desktop/publisher/release-notes_en-US-region2.html" lang="en-US-region2" /> + <src xlink:href="http://extensions.openoffice.org/testarea/desktop/publisher/release-notes_en-region3.html" lang="en-region3" /> + </release-notes> + + <update-download> + <src xlink:href="http://extensions.openoffice.org/testarea/desktop/publisher/update/pub2.oxt" /> + </update-download> +</description> + diff --git a/desktop/test/deployment/update/publisher/update/pub3.oxt b/desktop/test/deployment/update/publisher/update/pub3.oxt Binary files differnew file mode 100644 index 0000000000..60675fc4d2 --- /dev/null +++ b/desktop/test/deployment/update/publisher/update/pub3.oxt diff --git a/desktop/test/deployment/update/publisher/update/pub3.update.xml b/desktop/test/deployment/update/publisher/update/pub3.update.xml new file mode 100644 index 0000000000..13199e3758 --- /dev/null +++ b/desktop/test/deployment/update/publisher/update/pub3.update.xml @@ -0,0 +1,47 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + * 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 . +--> +<description xmlns="http://openoffice.org/extensions/update/2006" + xmlns:xlink="http://www.w3.org/1999/xlink"> + <version value="2.0" /> + + <identifier value="org.openoffice/framework/desktop/test/deployment/update/publisher/pub3"/> + + <publisher> + <name xlink:href="http://extensions.openoffice.org/testarea/desktop/publisher/publisher_de.html" lang="de">My OpenOffice de</name> + <name xlink:href="http://extensions.openoffice.org/testarea/desktop/publisher/publisher_en.html" lang="en">My OpenOffice en</name> + <name xlink:href="http://extensions.openoffice.org/testarea/desktop/publisher/publisher_de-DE.html" lang="de-DE">My OpenOffice de-DE</name> + <name xlink:href="http://extensions.openoffice.org/testarea/desktop/publisher/publisher_de-DE-altmark.html" lang="de-DE-altmark">My OpenOffice de-DE-altmark</name> + <name xlink:href="http://extensions.openoffice.org/testarea/desktop/publisher/publisher_en-GB.html" lang="en-GB">My OpenOffice en-GB</name> + <name xlink:href="http://extensions.openoffice.org/testarea/desktop/publisher/publisher_en-region3.html" lang="en-region3">My OpenOffice en-region3</name> + </publisher> + + <release-notes> + <src xlink:href="http://extensions.openoffice.org/testarea/desktop/publisher/release-notes_de.html" lang="de" /> + <src xlink:href="http://extensions.openoffice.org/testarea/desktop/publisher/release-notes_en.html" lang="en" /> + <src xlink:href="http://extensions.openoffice.org/testarea/desktop/publisher/release-notes_de-DE.html" lang="de-DE" /> + <src xlink:href="http://extensions.openoffice.org/testarea/desktop/publisher/release-notes_de-DE-altmark.html" lang="de-DE-altmark" /> + <src xlink:href="http://extensions.openoffice.org/testarea/desktop/publisher/release-notes_en-GB.html" lang="en-GB" /> + <src xlink:href="http://extensions.openoffice.org/testarea/desktop/publisher/release-notes_en-region3.html" lang="en-region3" /> + </release-notes> + + <update-download> + <src xlink:href="http://extensions.openoffice.org/testarea/desktop/publisher/update/pub3.oxt" /> + </update-download> +</description> + diff --git a/desktop/test/deployment/update/publisher/update/pub4.oxt b/desktop/test/deployment/update/publisher/update/pub4.oxt Binary files differnew file mode 100644 index 0000000000..19f7b7991b --- /dev/null +++ b/desktop/test/deployment/update/publisher/update/pub4.oxt diff --git a/desktop/test/deployment/update/publisher/update/pub4.update.xml b/desktop/test/deployment/update/publisher/update/pub4.update.xml new file mode 100644 index 0000000000..800b688218 --- /dev/null +++ b/desktop/test/deployment/update/publisher/update/pub4.update.xml @@ -0,0 +1,45 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + * 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 . +--> +<description xmlns="http://openoffice.org/extensions/update/2006" + xmlns:xlink="http://www.w3.org/1999/xlink"> + <version value="2.0" /> + + <identifier value="org.openoffice/framework/desktop/test/deployment/update/publisher/pub4"/> + + <publisher> + <name xlink:href="http://extensions.openoffice.org/testarea/desktop/publisher/publisher_de.html" lang="de">My OpenOffice de</name> + <name xlink:href="http://extensions.openoffice.org/testarea/desktop/publisher/publisher_de-DE.html" lang="de-DE">My OpenOffice de-DE</name> + <name xlink:href="http://extensions.openoffice.org/testarea/desktop/publisher/publisher_de-DE-altmark.html" lang="de-DE-altmark">My OpenOffice de-DE-altmark</name> + <name xlink:href="http://extensions.openoffice.org/testarea/desktop/publisher/publisher_en-GB.html" lang="en-GB">My OpenOffice en-GB</name> + <name xlink:href="http://extensions.openoffice.org/testarea/desktop/publisher/publisher_en-region3.html" lang="en-region3">My OpenOffice en-region3</name> + </publisher> + + <release-notes> + <src xlink:href="http://extensions.openoffice.org/testarea/desktop/publisher/release-notes_de.html" lang="de" /> + <src xlink:href="http://extensions.openoffice.org/testarea/desktop/publisher/release-notes_de-DE.html" lang="de-DE" /> + <src xlink:href="http://extensions.openoffice.org/testarea/desktop/publisher/release-notes_de-DE-altmark.html" lang="de-DE-altmark" /> + <src xlink:href="http://extensions.openoffice.org/testarea/desktop/publisher/release-notes_en-GB.html" lang="en-GB" /> + <src xlink:href="http://extensions.openoffice.org/testarea/desktop/publisher/release-notes_en-region3.html" lang="en-region3" /> + </release-notes> + + <update-download> + <src xlink:href="http://extensions.openoffice.org/testarea/desktop/publisher/update/pub4.oxt" /> + </update-download> +</description> + diff --git a/desktop/test/deployment/update/publisher/update/pub5.oxt b/desktop/test/deployment/update/publisher/update/pub5.oxt Binary files differnew file mode 100644 index 0000000000..afc632d570 --- /dev/null +++ b/desktop/test/deployment/update/publisher/update/pub5.oxt diff --git a/desktop/test/deployment/update/publisher/update/pub5.update.xml b/desktop/test/deployment/update/publisher/update/pub5.update.xml new file mode 100644 index 0000000000..70bc7194f1 --- /dev/null +++ b/desktop/test/deployment/update/publisher/update/pub5.update.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + * 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 . +--> +<description xmlns="http://openoffice.org/extensions/update/2006" + xmlns:xlink="http://www.w3.org/1999/xlink"> + <version value="2.0" /> + + <identifier value="org.openoffice/framework/desktop/test/deployment/update/publisher/pub5"/> + + <publisher> + <name xlink:href="http://extensions.openoffice.org/testarea/desktop/publisher/publisher_de.html" lang="de">My OpenOffice de</name> + <name xlink:href="http://extensions.openoffice.org/testarea/desktop/publisher/publisher_de-DE.html" lang="de-DE">My OpenOffice de-DE</name> + <name xlink:href="http://extensions.openoffice.org/testarea/desktop/publisher/publisher_de-DE-altmark.html" lang="de-DE-altmark">My OpenOffice de-DE-altmark</name> + </publisher> + + <release-notes> + <src xlink:href="http://extensions.openoffice.org/testarea/desktop/publisher/release-notes_de.html" lang="de" /> + <src xlink:href="http://extensions.openoffice.org/testarea/desktop/publisher/release-notes_de-DE.html" lang="de-DE" /> + <src xlink:href="http://extensions.openoffice.org/testarea/desktop/publisher/release-notes_de-DE-altmark.html" lang="de-DE-altmark" /> + </release-notes> + + <update-download> + <src xlink:href="http://extensions.openoffice.org/testarea/desktop/publisher/update/pub5.oxt" /> + </update-download> +</description> + diff --git a/desktop/test/deployment/update/publisher/update/pub6.oxt b/desktop/test/deployment/update/publisher/update/pub6.oxt Binary files differnew file mode 100644 index 0000000000..a68b445b8a --- /dev/null +++ b/desktop/test/deployment/update/publisher/update/pub6.oxt diff --git a/desktop/test/deployment/update/publisher/update/pub6.update.xml b/desktop/test/deployment/update/publisher/update/pub6.update.xml new file mode 100644 index 0000000000..69ac58b289 --- /dev/null +++ b/desktop/test/deployment/update/publisher/update/pub6.update.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + * 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 . +--> +<description xmlns="http://openoffice.org/extensions/update/2006" + xmlns:xlink="http://www.w3.org/1999/xlink"> + <version value="2.0" /> + + <identifier value="org.openoffice/framework/desktop/test/deployment/update/publisher/pub6"/> + + <publisher> + <name xlink:href="http://extensions.openoffice.org/testarea/desktop/publisher/publisher_de.html" lang="de">My OpenOffice de</name> + <name xlink:href="http://extensions.openoffice.org/testarea/desktop/publisher/publisher_en.html" lang="en">My OpenOffice en</name> + <name xlink:href="http://extensions.openoffice.org/testarea/desktop/publisher/publisher_de-DE.html" lang="de-DE">My OpenOffice de-DE</name> + <name xlink:href="http://extensions.openoffice.org/testarea/desktop/publisher/publisher_de-DE-altmark.html" lang="de-DE-altmark">My OpenOffice de-DE-altmark</name> + <name xlink:href="http://extensions.openoffice.org/testarea/desktop/publisher/publisher_en-GB.html" lang="en-GB">My OpenOffice en-GB</name> + <name xlink:href="http://extensions.openoffice.org/testarea/desktop/publisher/publisher_en-US.html" lang="en-US">My OpenOffice en-US</name> + <name xlink:href="http://extensions.openoffice.org/testarea/desktop/publisher/publisher_en-US-region1.html" lang="en-US-region1">My OpenOffice en-US-region1</name> + <name xlink:href="http://extensions.openoffice.org/testarea/desktop/publisher/publisher_en-US-region2.html" lang="en-US-region2">My OpenOffice en-US-region2</name> + <name xlink:href="http://extensions.openoffice.org/testarea/desktop/publisher/publisher_en-region3.html" lang="en-region3">My OpenOffice en-region3</name> + </publisher> + + <update-download> + <src xlink:href="http://extensions.openoffice.org/testarea/desktop/publisher/update/pub6.oxt" /> + </update-download> +</description> + diff --git a/desktop/test/deployment/update/publisher/update/pub7.oxt b/desktop/test/deployment/update/publisher/update/pub7.oxt Binary files differnew file mode 100644 index 0000000000..1b4bee0442 --- /dev/null +++ b/desktop/test/deployment/update/publisher/update/pub7.oxt diff --git a/desktop/test/deployment/update/publisher/update/pub7.update.xml b/desktop/test/deployment/update/publisher/update/pub7.update.xml new file mode 100644 index 0000000000..7ca456e954 --- /dev/null +++ b/desktop/test/deployment/update/publisher/update/pub7.update.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + * 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 . +--> +<description xmlns="http://openoffice.org/extensions/update/2006" + xmlns:xlink="http://www.w3.org/1999/xlink"> + <version value="2.0" /> + + <identifier value="org.openoffice/framework/desktop/test/deployment/update/publisher/pub7"/> + + <release-notes> + <src xlink:href="http://extensions.openoffice.org/testarea/desktop/publisher/release-notes_de.html" lang="de" /> + <src xlink:href="http://extensions.openoffice.org/testarea/desktop/publisher/release-notes_en.html" lang="en" /> + <src xlink:href="http://extensions.openoffice.org/testarea/desktop/publisher/release-notes_de-DE.html" lang="de-DE" /> + <src xlink:href="http://extensions.openoffice.org/testarea/desktop/publisher/release-notes_de-DE-altmark.html" lang="de-DE-altmark" /> + <src xlink:href="http://extensions.openoffice.org/testarea/desktop/publisher/release-notes_en-GB.html" lang="en-GB" /> + <src xlink:href="http://extensions.openoffice.org/testarea/desktop/publisher/release-notes_en-US.html" lang="en-US" /> + <src xlink:href="http://extensions.openoffice.org/testarea/desktop/publisher/release-notes_en-US-region1.html" lang="en-US-region1" /> + <src xlink:href="http://extensions.openoffice.org/testarea/desktop/publisher/release-notes_en-US-region2.html" lang="en-US-region2" /> + <src xlink:href="http://extensions.openoffice.org/testarea/desktop/publisher/release-notes_en-region3.html" lang="en-region3" /> + </release-notes> + + <update-download> + <src xlink:href="http://extensions.openoffice.org/testarea/desktop/publisher/update/pub7.oxt" /> + </update-download> +</description> + diff --git a/desktop/test/deployment/update/publisher/update/pub8.oxt b/desktop/test/deployment/update/publisher/update/pub8.oxt Binary files differnew file mode 100644 index 0000000000..5688ab9d24 --- /dev/null +++ b/desktop/test/deployment/update/publisher/update/pub8.oxt diff --git a/desktop/test/deployment/update/publisher/update/pub9.oxt b/desktop/test/deployment/update/publisher/update/pub9.oxt Binary files differnew file mode 100644 index 0000000000..752cfbbcf0 --- /dev/null +++ b/desktop/test/deployment/update/publisher/update/pub9.oxt diff --git a/desktop/test/deployment/update/readme.txt b/desktop/test/deployment/update/readme.txt new file mode 100644 index 0000000000..fa17783089 --- /dev/null +++ b/desktop/test/deployment/update/readme.txt @@ -0,0 +1,58 @@ +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This file incorporates work covered by the following license notice: +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed +# with this work for additional information regarding copyright +# ownership. The ASF licenses this file to you under the Apache +# License, Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a copy of +# the License at http://www.apache.org/licenses/LICENSE-2.0 . +# + +The extensions in the subdirectories of the update folder are used for +testing the online update feature of extensions. The folder such as +simple, +license, +defect, etc. contain extensions which can be installed in OOo. The +corresponding update information file and the update are located on +the extensions.openoffice.org website (cvs: extensions/www/testarea). For example: + +desktop/test/deployment/update/simple/plain1.oxt + +is version 1 of this extension and it references + +http://extensions.openoffice.org/testarea/desktop/simple/update/plain1.update.xml + +which in turn references version 2 at + +http://extensions.openoffice.org/testarea/desktop/simple/update/plain1.oxt + + +To have all in one place the update information file and the update are also contained +in the desktop project. They are in the update subfolder of the different test folders. +For example + +.../update/simple/update +.../update/license/update +.../update/updatefeed/update + + +The different test folders for the update are also committed in project extensions/www +so that the files can be obtain via a URL. The structure and the contents is about the +same as the content +of desktop/test/deployment/update +For example in + +extensions/www/testarea/desktop + +are the subfolder defect, simple, updatefeed, wrong_url, etc. +they contain the extensions which are installed directly by the Extension Manager. +These folders contain also the update subfolder which contains the update information +and the actual updates. diff --git a/desktop/test/deployment/update/simple/plain1.oxt b/desktop/test/deployment/update/simple/plain1.oxt Binary files differnew file mode 100644 index 0000000000..6256f99d5e --- /dev/null +++ b/desktop/test/deployment/update/simple/plain1.oxt diff --git a/desktop/test/deployment/update/simple/plain2.oxt b/desktop/test/deployment/update/simple/plain2.oxt Binary files differnew file mode 100644 index 0000000000..03249c2777 --- /dev/null +++ b/desktop/test/deployment/update/simple/plain2.oxt diff --git a/desktop/test/deployment/update/simple/plain3.oxt b/desktop/test/deployment/update/simple/plain3.oxt Binary files differnew file mode 100644 index 0000000000..64838932d1 --- /dev/null +++ b/desktop/test/deployment/update/simple/plain3.oxt diff --git a/desktop/test/deployment/update/simple/readme.txt b/desktop/test/deployment/update/simple/readme.txt new file mode 100644 index 0000000000..34ad6bedab --- /dev/null +++ b/desktop/test/deployment/update/simple/readme.txt @@ -0,0 +1,31 @@ +The folder contains only simple extension. That is, they only contain +- META-INF +-t.rdb +-description.xml + +The description.xml contains a version, a display name, and one URL to the update data + +For example: + + +<?xml version="1.0" encoding="UTF-8"?> +<description xmlns="http://openoffice.org/extensions/description/2006" + xmlns:xlink="http://www.w3.org/1999/xlink"> + <version value="1.0" /> + + <display-name> + <name lang="de">plain1 de</name> + </display-name> + + <update-information> + <src xlink:href="http://extensions.openoffice.org/testarea/desktop/simple/plain1.update.xml" /> + </update-information> +</description> + +There is only one language as display name available, which will therefore always be displayed. + +The update information which is referenced in the update-information and the update is committed in the extensions/www project. To modify them get the project: + +cvs co extensions/wwww + +the files can be found under extensions/www/testarea/desktop diff --git a/desktop/test/deployment/update/simple/update/plain1.oxt b/desktop/test/deployment/update/simple/update/plain1.oxt Binary files differnew file mode 100644 index 0000000000..d73362e873 --- /dev/null +++ b/desktop/test/deployment/update/simple/update/plain1.oxt diff --git a/desktop/test/deployment/update/simple/update/plain1.update.xml b/desktop/test/deployment/update/simple/update/plain1.update.xml new file mode 100644 index 0000000000..741fcf7746 --- /dev/null +++ b/desktop/test/deployment/update/simple/update/plain1.update.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + * 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 . +--> +<description xmlns="http://openoffice.org/extensions/update/2006" + xmlns:xlink="http://www.w3.org/1999/xlink"> + <identifier value="org.openoffice.legacy.plain1.oxt"/> + <version value="2.0" /> + <update-download> + <src xlink:href="http://extensions.openoffice.org/testarea/desktop/simple/update/plain1.oxt" /> + </update-download> +</description> + diff --git a/desktop/test/deployment/update/simple/update/plain2.oxt b/desktop/test/deployment/update/simple/update/plain2.oxt Binary files differnew file mode 100644 index 0000000000..3dc02aa97a --- /dev/null +++ b/desktop/test/deployment/update/simple/update/plain2.oxt diff --git a/desktop/test/deployment/update/simple/update/plain2.update.xml b/desktop/test/deployment/update/simple/update/plain2.update.xml new file mode 100644 index 0000000000..883a82e624 --- /dev/null +++ b/desktop/test/deployment/update/simple/update/plain2.update.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + * 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 . +--> +<description xmlns="http://openoffice.org/extensions/update/2006" + xmlns:xlink="http://www.w3.org/1999/xlink"> + <identifier value="org.openoffice.legacy.plain2.oxt"/> + <version value="2.0" /> + <update-download> + <src xlink:href="http://extensions.openoffice.org/testarea/desktop/simple/update/plain2.oxt" /> + </update-download> +</description> + diff --git a/desktop/test/deployment/update/simple/update/plain3.oxt b/desktop/test/deployment/update/simple/update/plain3.oxt Binary files differnew file mode 100644 index 0000000000..575152403b --- /dev/null +++ b/desktop/test/deployment/update/simple/update/plain3.oxt diff --git a/desktop/test/deployment/update/simple/update/plain3.update.xml b/desktop/test/deployment/update/simple/update/plain3.update.xml new file mode 100644 index 0000000000..e6b3f383ae --- /dev/null +++ b/desktop/test/deployment/update/simple/update/plain3.update.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + * 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 . +--> +<description xmlns="http://openoffice.org/extensions/update/2006" + xmlns:xlink="http://www.w3.org/1999/xlink"> + <identifier value="org.openoffice.legacy.plain3.oxt"/> + <version value="2.0" /> + <update-download> + <src xlink:href="http://extensions.openoffice.org/testarea/desktop/simple/update/plain3.oxt" /> + </update-download> +</description> + diff --git a/desktop/test/deployment/update/updatefeed/feed1.oxt b/desktop/test/deployment/update/updatefeed/feed1.oxt Binary files differnew file mode 100644 index 0000000000..b1b11eccea --- /dev/null +++ b/desktop/test/deployment/update/updatefeed/feed1.oxt diff --git a/desktop/test/deployment/update/updatefeed/feed2.oxt b/desktop/test/deployment/update/updatefeed/feed2.oxt Binary files differnew file mode 100644 index 0000000000..47dca1676c --- /dev/null +++ b/desktop/test/deployment/update/updatefeed/feed2.oxt diff --git a/desktop/test/deployment/update/updatefeed/update/feed1.oxt b/desktop/test/deployment/update/updatefeed/update/feed1.oxt Binary files differnew file mode 100644 index 0000000000..82bb9665ae --- /dev/null +++ b/desktop/test/deployment/update/updatefeed/update/feed1.oxt diff --git a/desktop/test/deployment/update/updatefeed/update/feed1.update.xml b/desktop/test/deployment/update/updatefeed/update/feed1.update.xml new file mode 100644 index 0000000000..90a7c23c95 --- /dev/null +++ b/desktop/test/deployment/update/updatefeed/update/feed1.update.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + * 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 . +--> +<description xmlns="http://openoffice.org/extensions/update/2006" + xmlns:xlink="http://www.w3.org/1999/xlink"> + <identifier value="org.openoffice.legacy.feed1.oxt"/> + <version value="2.0" /> + <update-download> + <src xlink:href="http://extensions.openoffice.org/testarea/desktop/updatefeed/update/feed1.oxt" /> + </update-download> +</description> + diff --git a/desktop/test/deployment/update/updatefeed/update/feed1.xml b/desktop/test/deployment/update/updatefeed/update/feed1.xml new file mode 100644 index 0000000000..ed53012a4b --- /dev/null +++ b/desktop/test/deployment/update/updatefeed/update/feed1.xml @@ -0,0 +1,50 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + * 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 . +--> +<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en-US"> + + <title>OpenOffice.org Update Feed</title> + <link rel="alternate" type="text/html" href="http://update.services.openoffice.org/ooo/snapshot.html"/> + <updated>2006-11-06T18:30:02Z</updated> + <author> + <name>The OpenOffice.org Project</name> + <uri>http://openoffice.org</uri> + <email>updatefeed@openoffice.org</email> + </author> + <id>urn:uuid:a4ccd383-1dd1-11b2-a95c-0003ba566e9d</id> + <entry> + <title>feed1.oxt version 2.0 available</title> + <link rel="alternate" type="text/html" + href="http://extensions.openoffice.org"/> + <id>urn:uuid:a4ccd383-1dd1-11b2-a95c-0003ba566e9f</id> + <category term="org.openoffice.legacy.feed1.oxt" label="feed1.oxt" /> + <updated>2006-11-06T18:30:02Z</updated> + <summary>Click here to go to the download page.</summary> + <content type="application/xml" src="http://extensions.openoffice.org/testarea/desktop/updatefeed/update/feed1.update.xml" /> + </entry> + <entry> + <title>feed2.oxt version 2.0 available</title> + <link rel="alternate" type="text/html" + href="http://extensions.openoffice.org"/> + <id>urn:uuid:a4ccd383-1dd1-11b2-a95c-0003ba566eaf</id> + <category term="org.openoffice.legacy.feed2.oxt" label="feed2.oxt" /> + <updated>2006-11-06T18:30:02Z</updated> + <summary>Click here to go to the download page.</summary> + <content type="application/xml" src="http://extensions.openoffice.org/testarea/desktop/updatefeed/update/feed2.update.xml" /> + </entry> +</feed> diff --git a/desktop/test/deployment/update/updatefeed/update/feed2.oxt b/desktop/test/deployment/update/updatefeed/update/feed2.oxt Binary files differnew file mode 100644 index 0000000000..9c867ae4a8 --- /dev/null +++ b/desktop/test/deployment/update/updatefeed/update/feed2.oxt diff --git a/desktop/test/deployment/update/updatefeed/update/feed2.update.xml b/desktop/test/deployment/update/updatefeed/update/feed2.update.xml new file mode 100644 index 0000000000..e37c692b0b --- /dev/null +++ b/desktop/test/deployment/update/updatefeed/update/feed2.update.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + * 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 . +--> +<description xmlns="http://openoffice.org/extensions/update/2006" + xmlns:xlink="http://www.w3.org/1999/xlink"> + <identifier value="org.openoffice.legacy.feed2.oxt"/> + <version value="2.0" /> + <update-download> + <src xlink:href="http://extensions.openoffice.org/testarea/desktop/updatefeed/update/feed2.oxt" /> + </update-download> +</description> + diff --git a/desktop/test/deployment/update/updateinfocreation/build/MANIFEST.MF b/desktop/test/deployment/update/updateinfocreation/build/MANIFEST.MF new file mode 100644 index 0000000000..09e2f42ca0 --- /dev/null +++ b/desktop/test/deployment/update/updateinfocreation/build/MANIFEST.MF @@ -0,0 +1,2 @@ +RegistrationClassName: com.sun.star.comp.smoketest.TestExtension + diff --git a/desktop/test/deployment/update/updateinfocreation/build/TestExtension.idl b/desktop/test/deployment/update/updateinfocreation/build/TestExtension.idl new file mode 100644 index 0000000000..bd5a1ac5f8 --- /dev/null +++ b/desktop/test/deployment/update/updateinfocreation/build/TestExtension.idl @@ -0,0 +1,27 @@ +/* -*- 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 . + */ + + +module com { module sun { module star { module comp { module smoketest { + // example service, XServiceInfo is implemented here for demonstration + // issues. XServiceInfo must be implemented by all components. + service TestExtension: css::lang::XServiceInfo; +};};};};}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/test/deployment/update/updateinfocreation/build/TestExtension.java b/desktop/test/deployment/update/updateinfocreation/build/TestExtension.java new file mode 100644 index 0000000000..8d48a30c81 --- /dev/null +++ b/desktop/test/deployment/update/updateinfocreation/build/TestExtension.java @@ -0,0 +1,156 @@ +/* + * 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 . + */ +package com.sun.star.comp.smoketest; + +import com.sun.star.lib.uno.helper.Factory; +import com.sun.star.lang.XMultiComponentFactory; +import com.sun.star.lang.XSingleComponentFactory; +import com.sun.star.lib.uno.helper.WeakBase; +import com.sun.star.uno.UnoRuntime; +import com.sun.star.uno.XComponentContext; +import com.sun.star.registry.XRegistryKey; +import com.sun.star.lang.XInitialization; +import com.sun.star.lang.XTypeProvider; +import com.sun.star.lang.XServiceInfo; +import com.sun.star.uno.Type; + +/** This class capsulates the class, that implements the minimal component, a + * factory for creating the service (<CODE>__getComponentFactory</CODE>) and a + * method, that writes the information into the given registry key + * (<CODE>__writeRegistryServiceInfo</CODE>). + */ +public class TestExtension { + /** This class implements the component. At least the interfaces XServiceInfo, + * XTypeProvider, and XInitialization should be provided by the service. + */ + public static class _TestExtension extends WeakBase + implements XServiceInfo { + /** The service name, that must be used to get an instance of this service. + */ + private static final String __serviceName = + "com.sun.star.comp.smoketest.TestExtension"; + + /** The initial component contextr, that gives access to + * the service manager, supported singletons, ... + * It's often later used + */ + private XComponentContext m_cmpCtx; + + /** The service manager, that gives access to all registered services. + * It's often later used + */ + private XMultiComponentFactory m_xMCF; + + /** The constructor of the inner class has a XMultiServiceFactory parameter. + * @param xmultiservicefactoryInitialization A special service factory + * could be introduced while initializing. + */ + public _TestExtension(XComponentContext xCompContext) { + try { + m_cmpCtx = xCompContext; + m_xMCF = m_cmpCtx.getServiceManager(); + } + catch( Exception e ) { + e.printStackTrace(); + } + } + + /** This method returns an array of all supported service names. + * @return Array of supported service names. + */ + public String[] getSupportedServiceNames() { + return getServiceNames(); + } + + /** This method is a simple helper function to used in the + * static component initialisation functions as well as in + * getSupportedServiceNames. + */ + public static String[] getServiceNames() { + String[] sSupportedServiceNames = { __serviceName }; + return sSupportedServiceNames; + } + + /** This method returns true, if the given service will be + * supported by the component. + * @param sServiceName Service name. + * @return True, if the given service name will be supported. + */ + public boolean supportsService( String sServiceName ) { + return sServiceName.equals( __serviceName ); + } + + /** Return the class name of the component. + * @return Class name of the component. + */ + public String getImplementationName() { + return _TestExtension.class.getName(); + } + } + + + /** + * Gives a factory for creating the service. + * This method is called by the <code>JavaLoader</code> + * <p> + * @return returns a <code>XSingleComponentFactory</code> for creating + * the component + * @param sImplName the name of the implementation for which a + * service is desired + * @see com.sun.star.comp.loader.JavaLoader + */ + public static XSingleComponentFactory __getComponentFactory(String sImplName) + { + XSingleComponentFactory xFactory = null; + + if ( sImplName.equals( _TestExtension.class.getName() ) ) + xFactory = Factory.createComponentFactory(_TestExtension.class, + _TestExtension.getServiceNames()); + + return xFactory; + } + + /** + * Writes the service information into the given registry key. + * This method is called by the <code>JavaLoader</code> + * <p> + * @return returns true if the operation succeeded + * @param regKey the registryKey + * @see com.sun.star.comp.loader.JavaLoader + */ + public static boolean __writeRegistryServiceInfo(XRegistryKey regKey) { + return Factory.writeRegistryServiceInfo(_TestExtension.class.getName(), + _TestExtension.getServiceNames(), + regKey); + } + /** This method is a member of the interface for initializing an object + * directly after its creation. + * @param object This array of arbitrary objects will be passed to the + * component after its creation. + * @throws Exception Every exception will not be handled, but will be + * passed to the caller. + */ + public void initialize( Object[] object ) + throws com.sun.star.uno.Exception { + /* The component describes what arguments are expected and in which + * order! At this point you can read the objects and initialize + * your component using these objects. + */ + } + +} diff --git a/desktop/test/deployment/update/updateinfocreation/build/description.xml b/desktop/test/deployment/update/updateinfocreation/build/description.xml new file mode 100644 index 0000000000..283fb1cca8 --- /dev/null +++ b/desktop/test/deployment/update/updateinfocreation/build/description.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + * 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 . +--> +<description xmlns="http://openoffice.org/extensions/description/2006" + xmlns:xlink="http://www.w3.org/1999/xlink" + xmlns:d="http://openoffice.org/extensions/description/2006" > + <identifier value="org.openoffice.extensions.testarea.desktop.updateinfo"/> + <version value="2.0" /> + <dependencies > + <OpenOffice.org-minimal-version value="2.1" d:name="OpenOffice.org 2.1"/> + </dependencies> + <update-information> + <src xlink:href="http://update.services.openoffice.org/ProductUpdateService/check.Update?product=extension&extensionid=org.openoffice.extensions.testarea.desktop.updateinfo&refresh=true"/> + </update-information> +</description> diff --git a/desktop/test/deployment/update/updateinfocreation/build/makefile.mk b/desktop/test/deployment/update/updateinfocreation/build/makefile.mk new file mode 100644 index 0000000000..1cb59985e8 --- /dev/null +++ b/desktop/test/deployment/update/updateinfocreation/build/makefile.mk @@ -0,0 +1,80 @@ +# +# 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 . +# + +PRJ = ..$/..$/..$/..$/.. +PRJNAME = updateinfo +PACKAGE = com$/sun$/star$/comp$/smoketest +TARGET = com_sun_star_comp_smoketest + +# --- Settings ----------------------------------------------------- + +.INCLUDE : settings.mk + +JARFILES = ridl.jar jurt.jar unoil.jar juh.jar + + +JARTARGET = TestExtension.jar +JARCOMPRESS = TRUE +CUSTOMMANIFESTFILE = MANIFEST.MF + +ZIP1TARGET=updateinfo +ZIP1LIST=* +ZIPFLAGS=-r +ZIP1DIR=$(MISC)$/$(TARGET) +ZIP1EXT=.oxt + +EXTUPDATEINFO_NAME=org.openoffice.extensions.testarea.desktop.updateinfo.update.xml +EXTUPDATEINFO_SOURCE=description.xml +EXTUPDATEINFO_URLS = http://extensions.openoffice.org/testarea/desktop/updateinfocreation/update/updateinfo.oxt +# --- Files -------------------------------------------------------- + +COPY_OXT_MANIFEST:= $(MISC)$/$(TARGET)$/META-INF$/manifest.xml +JAVAFILES = TestExtension.java + +# --- Targets ------------------------------------------------------ + +.INCLUDE : target.mk + +$(JARTARGETN) : $(MISC)$/$(TARGET).javamaker.done + +$(JAVACLASSFILES) : $(MISC)$/$(TARGET).javamaker.done + +$(MISC)$/$(TARGET).javamaker.done: $(BIN)$/TestExtension.rdb + $(JAVAMAKER) -O$(CLASSDIR) -BUCR -nD -X$(SOLARBINDIR)/types.rdb $< + $(TOUCH) $@ + +$(BIN)$/TestExtension.rdb: TestExtension.idl + $(IDLC) -O$(MISC) -I$(SOLARIDLDIR) -cid -we $< + +-$(RM) $@ + $(REGMERGE) $@ /UCR $(MISC)$/TestExtension.urd + +$(MISC)$/$(ZIP1TARGET).createdir : + +$(MKDIRHIER) $(MISC)$/$(TARGET)$/META-INF >& $(NULLDEV) && $(TOUCH) $@ + +$(MISC)$/$(TARGET)_resort : manifest.xml $(JARTARGETN) $(MISC)$/$(ZIP1TARGET).createdir $(BIN)$/TestExtension.rdb description.xml + $(GNUCOPY) -u manifest.xml $(MISC)$/$(TARGET)$/META-INF$/manifest.xml + $(GNUCOPY) -u $(JARTARGETN) $(MISC)$/$(TARGET)$/$(JARTARGET) + $(GNUCOPY) -u $(BIN)$/TestExtension.rdb $(MISC)$/$(TARGET)$/TestExtension.rdb + $(GNUCOPY) -u description.xml $(MISC)$/$(TARGET)$/description.xml + $(TOUCH) $@ + +.IF "$(ZIP1TARGETN)"!="" +$(ZIP1TARGETN) : $(MISC)$/$(TARGET)_resort $(MISC)$/$(ZIP1TARGET).createdir + +.ENDIF # "$(ZIP1TARGETN)"!="" + diff --git a/desktop/test/deployment/update/updateinfocreation/build/manifest.xml b/desktop/test/deployment/update/updateinfocreation/build/manifest.xml new file mode 100644 index 0000000000..fcedabf96b --- /dev/null +++ b/desktop/test/deployment/update/updateinfocreation/build/manifest.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="UTF-8"?> +<manifest:manifest xmlns:manifest="http://openoffice.org/2001/manifest"> + <manifest:file-entry manifest:full-path="TestExtension.jar" manifest:media-type="application/vnd.sun.star.uno-component;type=Java"/> + <manifest:file-entry manifest:full-path="TestExtension.rdb" manifest:media-type="application/vnd.sun.star.uno-typelibrary;type=RDB"/> +</manifest:manifest> diff --git a/desktop/test/deployment/update/updateinfocreation/readme.txt b/desktop/test/deployment/update/updateinfocreation/readme.txt new file mode 100644 index 0000000000..20819bb70a --- /dev/null +++ b/desktop/test/deployment/update/updateinfocreation/readme.txt @@ -0,0 +1,38 @@ +The extension build in this test uses an update information which is obtained +through a http get request. That is the URL does not reference an update +information file. Instead it invokes code on a webserver which returns the +update information. The URL used in this example is: + +http://update.services.openoffice.org/ProductUpdateService/check.Update?product=extension&extensionid=org.openoffice.extensions.testarea.desktop.updateinfo&refresh=true + +The updateinfo.oxt in this directory has the version 1.0 and in the sub-directory "update is the version 2 of this extension. Version 1.0 is also available here +/extensions/www/testarea/desktop/updateinfocreation/updateinfo.oxt +and version 2.0 here +/extensions/www/testarea/desktop/updateinfocreation/update/updateinfo.oxt + +Therefore they can be accessed through + +http://extensions.openoffice.org/testarea/desktop/updateinfocreation/updateinfo.oxt +and +http://extensions.openoffice.org/testarea/desktop/updateinfocreation/update/updateinfo.oxt + +The latter location (version 2.0) will also be referenced by the update information +which are returned by the webserver. + +The build sub-directory contains the code of the extension (version 2.0) and can +be build by calling dmake in this directory. The makefile uses the special macros: + +EXTUPDATEINFO_NAME=org.openoffice.extensions.testarea.desktop.updateinfo.update.xml +EXTUPDATEINFO_SOURCE=description.xml +EXTUPDATEINFO_URLS = http://extensions.openoffice.org/testarea/desktop/updateinfocreation/update/updateinfo.oxt + +This causes the generation of the update information file. This file could be +directly references by the URL in the <update-information> of the description.xml. +See also the Wiki entry at: +http://wiki.openoffice.org/wiki/Creating_update_information_for_extensions +This generated update information file can then be used by the webserver, when it +sends back the requested update information. The update information file will be +generated in the misc directory of the output directory. + +The update information file needs to be copied into common.pro/pus.mxyz directory. +The project mwsfinish will process the files in the pus directory. diff --git a/desktop/test/deployment/update/updateinfocreation/update/updateinfo.oxt b/desktop/test/deployment/update/updateinfocreation/update/updateinfo.oxt Binary files differnew file mode 100644 index 0000000000..52ddd3158e --- /dev/null +++ b/desktop/test/deployment/update/updateinfocreation/update/updateinfo.oxt diff --git a/desktop/test/deployment/update/updateinfocreation/updateinfo.oxt b/desktop/test/deployment/update/updateinfocreation/updateinfo.oxt Binary files differnew file mode 100644 index 0000000000..43ac7003bc --- /dev/null +++ b/desktop/test/deployment/update/updateinfocreation/updateinfo.oxt diff --git a/desktop/test/deployment/update/website_update/readme.txt b/desktop/test/deployment/update/website_update/readme.txt new file mode 100644 index 0000000000..b888bba703 --- /dev/null +++ b/desktop/test/deployment/update/website_update/readme.txt @@ -0,0 +1,133 @@ +The folder contains extensions which need to be updated through a web site. +The "Updates dialog" of the Extension Manager will mark the updates for these +extensions as "browser based update". The Extension Manager will open a browser +for each of the extensions and navigate to the respective website. + +================================================================================ +web1.oxt - web5.oxt: +================================================================================ +They contain <update-information>. That is they reference directly the respective +webX.update.xml (for example, web1.update.xml) files which are available at +http://extensions.openoffice.org/testarea/desktop/website_update/update/... +For example: +http://extensions.openoffice.org/testarea/desktop/website_update/update/web2.update.xml + +The update information contain multiple URLs to "localized" web sites. Each URL is +assigned to a particular local. For example: + +<src xlink:href="http://extensions.openoffice.org/testarea/desktop/website_update/update/web1_de.html" lang="de"/> + +The Extension Manager will choose the URLs where the lang attribute matches most +closely the locale of the office. + +The following table shows what localized web site is used, when the office uses the locale +en-US. The web page will display the locale used. See update/web1_de.html, etc. + + +Localization: + +Installed office en-US + | publisher | release notes +============================================= +web1.oxt | en-US | en-US +--------------------------------------------- +web2.oxt | en-US-region1 | en-US-region1 +--------------------------------------------- +web3.oxt | en | en +--------------------------------------------- +web4.oxt | en-GB | en-GB +--------------------------------------------- +web5.oxt | de | de + + +================================================================================ +web6.oxt +================================================================================ +Need not be committed in extensions/www/testarea/desktop. + +web6.oxt is intended for tests with extensions.services.openoffice.org. +It does not contain <update-information>. That is, the Extension Manager will obtain +the update information from the repository as feed: + +http://updateext.services.openoffice.org/ProductUpdateService/check.Update + +Test +---- +Repository: + +-Create the new extension in the repository. +-Provide a company name and a URL to the company website. In our case this should be + http://extensions.openoffice.org/testarea/desktop/website_update/update/web1_en-US.html + +-Create a new release for the extension. Upload the description.xml of version 2.0 +(update/web6/description.xml). Provide a download URL for the web site (field + "Download from page / Open follow up page URL", which should be + http://extensions.openoffice.org/testarea/desktop/website_update/update/web1_en-US.html + Provide release notes. + + +Office: +-Install version 1.0 of the extension: + desktop/test/deployment/update/website_update/web6.oxt + +-Run the update in the Extension Manager + + +Result: +The Update Dialog should show the publisher name as provided in "Provider URL" field +of the extension edit page (not release). + +A release notes link is displayed with a URL to the release notes as provided in +the "Provider Title" field of the extension release edit page. + +When running the update then the web browser should navigate to +http://extensions.openoffice.org/testarea/desktop/website_update/update/web1_en-US.html + + +================================================================================ +web7.oxt +================================================================================ +Need not be committed in extensions/www/testarea/desktop + +web7.oxt is intended for tests with extensions.services.openoffice.org. +It does not contain <update-information>. That is, the Extension Manager will obtain +the update information from the repository as feed: + +http://updateext.services.openoffice.org/ProductUpdateService/check.Update + +The description.xml which will be uploaded contains URLs for release notes and publisher +names/ URLs. That is, this information is not generated from the information of the +repository web site. + +Test +----------- +Repository: + +-Create the new extension in the repository. +-Provide a company name and a URL to the company website. In our case these should be different + to those provided in the description.xml. These should NOT go into the update feed. + Choose for example as "Provider Title": FOO and as "Provider URL" some valid URL but NOT + http://extensions.openoffice.org/testarea/desktop/website_update/update/web1_en-US.html + +-Create a new release for the extension. Upload the description.xml of version 2.0 +(update/web7/description.xml). Provide a download URL for the web site (field + "Download from page / Open follow up page URL", which should be + http://extensions.openoffice.org/testarea/desktop/website_update/update/web1_en-US.html + Provide release notes. + +Office: +-Install version 1.0 of the extension: + desktop/test/deployment/update/website_update/web7.oxt + +-Run the update in the Extension Manager + +Result: +The Update Dialog should show the publisher name as provided in the description.xml. +That is: My OpenOffice en-US and NOT "FOO". + +A release notes link is displayed with a URL to the release notes as provided in +the description.xml. That is: +http://extensions.openoffice.org/testarea/desktop/publisher/release-notes_xxx.html + +When running the update then the web browser should navigate to +http://extensions.openoffice.org/testarea/desktop/website_update/update/web1_en-US.html diff --git a/desktop/test/deployment/update/website_update/update/web1.oxt b/desktop/test/deployment/update/website_update/update/web1.oxt Binary files differnew file mode 100644 index 0000000000..157d5d952c --- /dev/null +++ b/desktop/test/deployment/update/website_update/update/web1.oxt diff --git a/desktop/test/deployment/update/website_update/update/web1.update.xml b/desktop/test/deployment/update/website_update/update/web1.update.xml new file mode 100644 index 0000000000..860c8ebc93 --- /dev/null +++ b/desktop/test/deployment/update/website_update/update/web1.update.xml @@ -0,0 +1,37 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + * 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 . +--> +<description xmlns="http://openoffice.org/extensions/update/2006" + xmlns:xlink="http://www.w3.org/1999/xlink"> + <version value="2.0" /> + + <identifier value="org.openoffice/framework/desktop/test/deployment/update/website_update/web1"/> + + <update-website> + <src xlink:href="http://extensions.openoffice.org/testarea/desktop/website_update/update/web1_de.html" lang="de" /> + <src xlink:href="http://extensions.openoffice.org/testarea/desktop/website_update/update/web1_en.html" lang="en" /> + <src xlink:href="http://extensions.openoffice.org/testarea/desktop/website_update/update/web1_de-DE.html" lang="de-DE" /> + <src xlink:href="http://extensions.openoffice.org/testarea/desktop/website_update/update/web1_de-DE-altmark.html" lang="de-DE-altmark" /> + <src xlink:href="http://extensions.openoffice.org/testarea/desktop/website_update/update/web1_en-GB.html" lang="en-GB" /> + <src xlink:href="http://extensions.openoffice.org/testarea/desktop/website_update/update/web1_en-US.html" lang="en-US" /> + <src xlink:href="http://extensions.openoffice.org/testarea/desktop/website_update/update/web1_en-US-region1.html" lang="en-US-region1" /> + <src xlink:href="http://extensions.openoffice.org/testarea/desktop/website_update/update/web1_en-US-region2.html" lang="en-US-region2" /> + <src xlink:href="http://extensions.openoffice.org/testarea/desktop/website_update/update/web1_en-region3.html" lang="en-region3" /> + </update-website> +</description> + diff --git a/desktop/test/deployment/update/website_update/update/web1_de-DE-altmark.html b/desktop/test/deployment/update/website_update/update/web1_de-DE-altmark.html new file mode 100644 index 0000000000..ffed5a52e8 --- /dev/null +++ b/desktop/test/deployment/update/website_update/update/web1_de-DE-altmark.html @@ -0,0 +1,18 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> +<HTML> +<HEAD> +</HEAD> +<BODY> +<H1>Update Extensions</H1> +<H1>de-DE-altmark</H1> +<p><a href="web1.oxt">web1.oxt</a></p> +<p><a href="web2.oxt">web2.oxt</a></p> +<p><a href="web3.oxt">web3.oxt</a></p> +<p><a href="web4.oxt">web4.oxt</a></p> +<p><a href="web5.oxt">web5.oxt</a></p> +<p><a href="web6.oxt">web6.oxt</a></p> +<p><a href="web7.oxt">web7.oxt</a></p> + +</BODY> +</HTML> + diff --git a/desktop/test/deployment/update/website_update/update/web1_de-DE.html b/desktop/test/deployment/update/website_update/update/web1_de-DE.html new file mode 100644 index 0000000000..33fb7f2ec8 --- /dev/null +++ b/desktop/test/deployment/update/website_update/update/web1_de-DE.html @@ -0,0 +1,18 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> +<HTML> +<HEAD> +</HEAD> +<BODY> +<H1>Update Extensions</H1> +<H1>de-DE</H1> +<p><a href="web1.oxt">web1.oxt</a></p> +<p><a href="web2.oxt">web2.oxt</a></p> +<p><a href="web3.oxt">web3.oxt</a></p> +<p><a href="web4.oxt">web4.oxt</a></p> +<p><a href="web5.oxt">web5.oxt</a></p> +<p><a href="web6.oxt">web6.oxt</a></p> +<p><a href="web7.oxt">web7.oxt</a></p> + +</BODY> +</HTML> + diff --git a/desktop/test/deployment/update/website_update/update/web1_de.html b/desktop/test/deployment/update/website_update/update/web1_de.html new file mode 100644 index 0000000000..31a53b91db --- /dev/null +++ b/desktop/test/deployment/update/website_update/update/web1_de.html @@ -0,0 +1,19 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> +<HTML> +<HEAD> +</HEAD> +<BODY> +<H1>Update Extensions</H1> +<H1>de</H1> +<p><a href="web1.oxt">web1.oxt</a></p> +<p><a href="web2.oxt">web2.oxt</a></p> +<p><a href="web3.oxt">web3.oxt</a></p> +<p><a href="web4.oxt">web4.oxt</a></p> +<p><a href="web5.oxt">web5.oxt</a></p> +<p><a href="web6.oxt">web6.oxt</a></p> +<p><a href="web7.oxt">web7.oxt</a></p> + + +</BODY> +</HTML> + diff --git a/desktop/test/deployment/update/website_update/update/web1_en-GB.html b/desktop/test/deployment/update/website_update/update/web1_en-GB.html new file mode 100644 index 0000000000..c46328a821 --- /dev/null +++ b/desktop/test/deployment/update/website_update/update/web1_en-GB.html @@ -0,0 +1,19 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> +<HTML> +<HEAD> +</HEAD> +<BODY> +<H1>Update Extensions</H1> +<H1>en-GB</H1> +<p><a href="web1.oxt">web1.oxt</a></p> +<p><a href="web2.oxt">web2.oxt</a></p> +<p><a href="web3.oxt">web3.oxt</a></p> +<p><a href="web4.oxt">web4.oxt</a></p> +<p><a href="web5.oxt">web5.oxt</a></p> +<p><a href="web6.oxt">web6.oxt</a></p> +<p><a href="web7.oxt">web7.oxt</a></p> + + +</BODY> +</HTML> + diff --git a/desktop/test/deployment/update/website_update/update/web1_en-US-region1.html b/desktop/test/deployment/update/website_update/update/web1_en-US-region1.html new file mode 100644 index 0000000000..80b41823b7 --- /dev/null +++ b/desktop/test/deployment/update/website_update/update/web1_en-US-region1.html @@ -0,0 +1,19 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
+<HTML>
+<HEAD>
+</HEAD>
+<BODY>
+<H1>Update Extensions</H1>
+<H1>en-US-region1</H1>
+<p><a href="web1.oxt">web1.oxt</a></p>
+<p><a href="web2.oxt">web2.oxt</a></p>
+<p><a href="web3.oxt">web3.oxt</a></p>
+<p><a href="web4.oxt">web4.oxt</a></p>
+<p><a href="web5.oxt">web5.oxt</a></p>
+<p><a href="web6.oxt">web6.oxt</a></p>
+<p><a href="web7.oxt">web7.oxt</a></p>
+
+
+</BODY>
+</HTML>
+
diff --git a/desktop/test/deployment/update/website_update/update/web1_en-US-region2.html b/desktop/test/deployment/update/website_update/update/web1_en-US-region2.html new file mode 100644 index 0000000000..1a501f520d --- /dev/null +++ b/desktop/test/deployment/update/website_update/update/web1_en-US-region2.html @@ -0,0 +1,19 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> +<HTML> +<HEAD> +</HEAD> +<BODY> +<H1>Update Extensions</H1> +<H1>en-US-region2</H1> +<p><a href="web1.oxt">web1.oxt</a></p> +<p><a href="web2.oxt">web2.oxt</a></p> +<p><a href="web3.oxt">web3.oxt</a></p> +<p><a href="web4.oxt">web4.oxt</a></p> +<p><a href="web5.oxt">web5.oxt</a></p> +<p><a href="web6.oxt">web6.oxt</a></p> +<p><a href="web7.oxt">web7.oxt</a></p> + + +</BODY> +</HTML> + diff --git a/desktop/test/deployment/update/website_update/update/web1_en-US.html b/desktop/test/deployment/update/website_update/update/web1_en-US.html new file mode 100644 index 0000000000..f861b09c01 --- /dev/null +++ b/desktop/test/deployment/update/website_update/update/web1_en-US.html @@ -0,0 +1,20 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
+<HTML>
+<HEAD>
+</HEAD>
+<BODY>
+<H1>Update Extensions</H1>
+<H1>en-US</H1>
+<p><a href="web1.oxt">web1.oxt</a></p>
+<p><a href="web2.oxt">web2.oxt</a></p>
+<p><a href="web3.oxt">web3.oxt</a></p>
+<p><a href="web4.oxt">web4.oxt</a></p>
+<p><a href="web5.oxt">web5.oxt</a></p>
+<p><a href="web6.oxt">web6.oxt</a></p>
+<p><a href="web7.oxt">web7.oxt</a></p>
+
+
+
+</BODY>
+</HTML>
+
diff --git a/desktop/test/deployment/update/website_update/update/web1_en-region3.html b/desktop/test/deployment/update/website_update/update/web1_en-region3.html new file mode 100644 index 0000000000..f55bcbe381 --- /dev/null +++ b/desktop/test/deployment/update/website_update/update/web1_en-region3.html @@ -0,0 +1,19 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> +<HTML> +<HEAD> +</HEAD> +<BODY> +<H1>Update Extensions</H1> +<H1>en-region3</H1> +<p><a href="web1.oxt">web1.oxt</a></p> +<p><a href="web2.oxt">web2.oxt</a></p> +<p><a href="web3.oxt">web3.oxt</a></p> +<p><a href="web4.oxt">web4.oxt</a></p> +<p><a href="web5.oxt">web5.oxt</a></p> +<p><a href="web6.oxt">web6.oxt</a></p> +<p><a href="web7.oxt">web7.oxt</a></p> + + +</BODY> +</HTML> + diff --git a/desktop/test/deployment/update/website_update/update/web1_en.html b/desktop/test/deployment/update/website_update/update/web1_en.html new file mode 100644 index 0000000000..a0b422ebf2 --- /dev/null +++ b/desktop/test/deployment/update/website_update/update/web1_en.html @@ -0,0 +1,19 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> +<HTML> +<HEAD> +</HEAD> +<BODY> +<H1>Update Extensions</H1> +<H1>en</H1> +<p><a href="web1.oxt">web1.oxt</a></p> +<p><a href="web2.oxt">web2.oxt</a></p> +<p><a href="web3.oxt">web3.oxt</a></p> +<p><a href="web4.oxt">web4.oxt</a></p> +<p><a href="web5.oxt">web5.oxt</a></p> +<p><a href="web6.oxt">web6.oxt</a></p> +<p><a href="web7.oxt">web7.oxt</a></p> + + +</BODY> +</HTML> + diff --git a/desktop/test/deployment/update/website_update/update/web2.oxt b/desktop/test/deployment/update/website_update/update/web2.oxt Binary files differnew file mode 100644 index 0000000000..3a13e81143 --- /dev/null +++ b/desktop/test/deployment/update/website_update/update/web2.oxt diff --git a/desktop/test/deployment/update/website_update/update/web2.update.xml b/desktop/test/deployment/update/website_update/update/web2.update.xml new file mode 100644 index 0000000000..18c871eab7 --- /dev/null +++ b/desktop/test/deployment/update/website_update/update/web2.update.xml @@ -0,0 +1,36 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + * 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 . +--> +<description xmlns="http://openoffice.org/extensions/update/2006" + xmlns:xlink="http://www.w3.org/1999/xlink"> + <version value="2.0" /> + + <identifier value="org.openoffice/framework/desktop/test/deployment/update/website_update/web2"/> + + <update-website> + <src xlink:href="http://extensions.openoffice.org/testarea/desktop/website_update/update/web1_de.html" lang="de" /> + <src xlink:href="http://extensions.openoffice.org/testarea/desktop/website_update/update/web1_en.html" lang="en" /> + <src xlink:href="http://extensions.openoffice.org/testarea/desktop/website_update/update/web1_de-DE.html" lang="de-DE" /> + <src xlink:href="http://extensions.openoffice.org/testarea/desktop/website_update/update/web1_de-DE-altmark.html" lang="de-DE-altmark" /> + <src xlink:href="http://extensions.openoffice.org/testarea/desktop/website_update/update/web1_en-GB.html" lang="en-GB" /> + <src xlink:href="http://extensions.openoffice.org/testarea/desktop/website_update/update/web1_en-US-region1.html" lang="en-US-region1" /> + <src xlink:href="http://extensions.openoffice.org/testarea/desktop/website_update/update/web1_en-US-region2.html" lang="en-US-region2" /> + <src xlink:href="http://extensions.openoffice.org/testarea/desktop/website_update/update/web1_en-region3.html" lang="en-region3" /> + </update-website> +</description> + diff --git a/desktop/test/deployment/update/website_update/update/web3.oxt b/desktop/test/deployment/update/website_update/update/web3.oxt Binary files differnew file mode 100644 index 0000000000..b3214a4e69 --- /dev/null +++ b/desktop/test/deployment/update/website_update/update/web3.oxt diff --git a/desktop/test/deployment/update/website_update/update/web3.update.xml b/desktop/test/deployment/update/website_update/update/web3.update.xml new file mode 100644 index 0000000000..5d7711def6 --- /dev/null +++ b/desktop/test/deployment/update/website_update/update/web3.update.xml @@ -0,0 +1,34 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + * 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 . +--> +<description xmlns="http://openoffice.org/extensions/update/2006" + xmlns:xlink="http://www.w3.org/1999/xlink"> + <version value="2.0" /> + + <identifier value="org.openoffice/framework/desktop/test/deployment/update/website_update/web3"/> + + <update-website> + <src xlink:href="http://extensions.openoffice.org/testarea/desktop/website_update/update/web1_de.html" lang="de" /> + <src xlink:href="http://extensions.openoffice.org/testarea/desktop/website_update/update/web1_en.html" lang="en" /> + <src xlink:href="http://extensions.openoffice.org/testarea/desktop/website_update/update/web1_de-DE.html" lang="de-DE" /> + <src xlink:href="http://extensions.openoffice.org/testarea/desktop/website_update/update/web1_de-DE-altmark.html" lang="de-DE-altmark" /> + <src xlink:href="http://extensions.openoffice.org/testarea/desktop/website_update/update/web1_en-GB.html" lang="en-GB" /> + <src xlink:href="http://extensions.openoffice.org/testarea/desktop/website_update/update/web1_en-region3.html" lang="en-region3" /> + </update-website> +</description> + diff --git a/desktop/test/deployment/update/website_update/update/web4.oxt b/desktop/test/deployment/update/website_update/update/web4.oxt Binary files differnew file mode 100644 index 0000000000..93766fd44f --- /dev/null +++ b/desktop/test/deployment/update/website_update/update/web4.oxt diff --git a/desktop/test/deployment/update/website_update/update/web4.update.xml b/desktop/test/deployment/update/website_update/update/web4.update.xml new file mode 100644 index 0000000000..e016ae3cbf --- /dev/null +++ b/desktop/test/deployment/update/website_update/update/web4.update.xml @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + * 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 . +--> +<description xmlns="http://openoffice.org/extensions/update/2006" + xmlns:xlink="http://www.w3.org/1999/xlink"> + <version value="2.0" /> + + <identifier value="org.openoffice/framework/desktop/test/deployment/update/website_update/web4"/> + + <update-website> + <src xlink:href="http://extensions.openoffice.org/testarea/desktop/website_update/update/web1_de.html" lang="de" /> + <src xlink:href="http://extensions.openoffice.org/testarea/desktop/website_update/update/web1_de-DE.html" lang="de-DE" /> + <src xlink:href="http://extensions.openoffice.org/testarea/desktop/website_update/update/web1_de-DE-altmark.html" lang="de-DE-altmark" /> + <src xlink:href="http://extensions.openoffice.org/testarea/desktop/website_update/update/web1_en-GB.html" lang="en-GB" /> + <src xlink:href="http://extensions.openoffice.org/testarea/desktop/website_update/update/web1_en-region3.html" lang="en-region3" /> + </update-website> +</description> + diff --git a/desktop/test/deployment/update/website_update/update/web5.oxt b/desktop/test/deployment/update/website_update/update/web5.oxt Binary files differnew file mode 100644 index 0000000000..1ae8f01b19 --- /dev/null +++ b/desktop/test/deployment/update/website_update/update/web5.oxt diff --git a/desktop/test/deployment/update/website_update/update/web5.update.xml b/desktop/test/deployment/update/website_update/update/web5.update.xml new file mode 100644 index 0000000000..951ef95a9a --- /dev/null +++ b/desktop/test/deployment/update/website_update/update/web5.update.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + * 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 . +--> +<description xmlns="http://openoffice.org/extensions/update/2006" + xmlns:xlink="http://www.w3.org/1999/xlink"> + <version value="2.0" /> + + <identifier value="org.openoffice/framework/desktop/test/deployment/update/website_update/web5"/> + + <update-website> + <src xlink:href="http://extensions.openoffice.org/testarea/desktop/website_update/update/web1_de.html" lang="de" /> + <src xlink:href="http://extensions.openoffice.org/testarea/desktop/website_update/update/web1_de-DE.html" lang="de-DE" /> + <src xlink:href="http://extensions.openoffice.org/testarea/desktop/website_update/update/web1_de-DE-altmark.html" lang="de-DE-altmark" /> + </update-website> +</description> + diff --git a/desktop/test/deployment/update/website_update/update/web6.oxt b/desktop/test/deployment/update/website_update/update/web6.oxt Binary files differnew file mode 100644 index 0000000000..8bc16fb2c7 --- /dev/null +++ b/desktop/test/deployment/update/website_update/update/web6.oxt diff --git a/desktop/test/deployment/update/website_update/update/web6/description.xml b/desktop/test/deployment/update/website_update/update/web6/description.xml new file mode 100644 index 0000000000..47a32a287a --- /dev/null +++ b/desktop/test/deployment/update/website_update/update/web6/description.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + * 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 . +--> +<description xmlns="http://openoffice.org/extensions/description/2006" + xmlns:xlink="http://www.w3.org/1999/xlink"> + <version value="2.0" /> + <identifier value="org.openoffice/framework/desktop/test/deployment/update/website_update/web6"/> + <display-name> + <name>web-based update test 1</name> + </display-name> + +</description> + diff --git a/desktop/test/deployment/update/website_update/update/web6/readme.txt b/desktop/test/deployment/update/website_update/update/web6/readme.txt new file mode 100644 index 0000000000..6756b8ef91 --- /dev/null +++ b/desktop/test/deployment/update/website_update/update/web6/readme.txt @@ -0,0 +1,5 @@ +This folder contains the description.xml from update/web6.oxt
+When creating the release 2.0 on the repository then this description.xml can be uploaded.
+
+
+This folder is not needed on extensions.openoffice.org/testarea/desktop/...
diff --git a/desktop/test/deployment/update/website_update/update/web7.oxt b/desktop/test/deployment/update/website_update/update/web7.oxt Binary files differnew file mode 100644 index 0000000000..4d6220a48a --- /dev/null +++ b/desktop/test/deployment/update/website_update/update/web7.oxt diff --git a/desktop/test/deployment/update/website_update/update/web7/description.xml b/desktop/test/deployment/update/website_update/update/web7/description.xml new file mode 100644 index 0000000000..9d5e4569d4 --- /dev/null +++ b/desktop/test/deployment/update/website_update/update/web7/description.xml @@ -0,0 +1,53 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + * 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 . +--> +<description xmlns="http://openoffice.org/extensions/description/2006" + xmlns:xlink="http://www.w3.org/1999/xlink"> + <version value="2.0" /> + <identifier value="org.openoffice/framework/desktop/test/deployment/update/website_update/web7"/> + <display-name> + <name>web-based update test 1</name> + </display-name> + + <publisher> + <name xlink:href="http://extensions.openoffice.org/testarea/desktop/publisher/publisher_de.html" lang="de">My OpenOffice de</name> + <name xlink:href="http://extensions.openoffice.org/testarea/desktop/publisher/publisher_en.html" lang="en">My OpenOffice en</name> + <name xlink:href="http://extensions.openoffice.org/testarea/desktop/publisher/publisher_de-DE.html" lang="de-DE">My OpenOffice de-DE</name> + <name xlink:href="http://extensions.openoffice.org/testarea/desktop/publisher/publisher_de-DE-altmark.html" lang="de-DE-altmark">My OpenOffice de-DE-altmark</name> + <name xlink:href="http://extensions.openoffice.org/testarea/desktop/publisher/publisher_en-GB.html" lang="en-GB">My OpenOffice en-GB</name> + <name xlink:href="http://extensions.openoffice.org/testarea/desktop/publisher/publisher_en-US.html" lang="en-US">My OpenOffice en-US</name> + <name xlink:href="http://extensions.openoffice.org/testarea/desktop/publisher/publisher_en-US-region1.html" lang="en-US-region1">My OpenOffice en-US-region1</name> + <name xlink:href="http://extensions.openoffice.org/testarea/desktop/publisher/publisher_en-US-region2.html" lang="en-US-region2">My OpenOffice en-US-region2</name> + <name xlink:href="http://extensions.openoffice.org/testarea/desktop/publisher/publisher_en-region3.html" lang="en-region3">My OpenOffice en-region3</name> + </publisher> + + <release-notes> + <src xlink:href="http://extensions.openoffice.org/testarea/desktop/publisher/release-notes_de.html" lang="de" /> + <src xlink:href="http://extensions.openoffice.org/testarea/desktop/publisher/release-notes_en.html" lang="en" /> + <src xlink:href="http://extensions.openoffice.org/testarea/desktop/publisher/release-notes_de-DE.html" lang="de-DE" /> + <src xlink:href="http://extensions.openoffice.org/testarea/desktop/publisher/release-notes_de-DE-altmark.html" lang="de-DE-altmark" /> + <src xlink:href="http://extensions.openoffice.org/testarea/desktop/publisher/release-notes_en-GB.html" lang="en-GB" /> + <src xlink:href="http://extensions.openoffice.org/testarea/desktop/publisher/release-notes_en-US.html" lang="en-US" /> + <src xlink:href="http://extensions.openoffice.org/testarea/desktop/publisher/release-notes_en-US-region1.html" lang="en-US-region1" /> + <src xlink:href="http://extensions.openoffice.org/testarea/desktop/publisher/release-notes_en-US-region2.html" lang="en-US-region2" /> + <src xlink:href="http://extensions.openoffice.org/testarea/desktop/publisher/release-notes_en-region3.html" lang="en-region3" /> + </release-notes> + + +</description> + diff --git a/desktop/test/deployment/update/website_update/update/web7/readme.txt b/desktop/test/deployment/update/website_update/update/web7/readme.txt new file mode 100644 index 0000000000..45b2e23740 --- /dev/null +++ b/desktop/test/deployment/update/website_update/update/web7/readme.txt @@ -0,0 +1,5 @@ +This folder contains the description.xml from update/web7.oxt
+When creating the release 2.0 on the repository then this description.xml can be uploaded.
+
+
+This folder is not needed on extensions.openoffice.org/testarea/desktop/...
diff --git a/desktop/test/deployment/update/website_update/web1.oxt b/desktop/test/deployment/update/website_update/web1.oxt Binary files differnew file mode 100644 index 0000000000..7c17586e04 --- /dev/null +++ b/desktop/test/deployment/update/website_update/web1.oxt diff --git a/desktop/test/deployment/update/website_update/web2.oxt b/desktop/test/deployment/update/website_update/web2.oxt Binary files differnew file mode 100644 index 0000000000..705e70a753 --- /dev/null +++ b/desktop/test/deployment/update/website_update/web2.oxt diff --git a/desktop/test/deployment/update/website_update/web3.oxt b/desktop/test/deployment/update/website_update/web3.oxt Binary files differnew file mode 100644 index 0000000000..4e63a75f0c --- /dev/null +++ b/desktop/test/deployment/update/website_update/web3.oxt diff --git a/desktop/test/deployment/update/website_update/web4.oxt b/desktop/test/deployment/update/website_update/web4.oxt Binary files differnew file mode 100644 index 0000000000..e66513e683 --- /dev/null +++ b/desktop/test/deployment/update/website_update/web4.oxt diff --git a/desktop/test/deployment/update/website_update/web5.oxt b/desktop/test/deployment/update/website_update/web5.oxt Binary files differnew file mode 100644 index 0000000000..65b02db934 --- /dev/null +++ b/desktop/test/deployment/update/website_update/web5.oxt diff --git a/desktop/test/deployment/update/website_update/web6.oxt b/desktop/test/deployment/update/website_update/web6.oxt Binary files differnew file mode 100644 index 0000000000..98416edfa5 --- /dev/null +++ b/desktop/test/deployment/update/website_update/web6.oxt diff --git a/desktop/test/deployment/update/website_update/web7.oxt b/desktop/test/deployment/update/website_update/web7.oxt Binary files differnew file mode 100644 index 0000000000..31ba45f032 --- /dev/null +++ b/desktop/test/deployment/update/website_update/web7.oxt diff --git a/desktop/test/deployment/update/wrong_url/readme.txt b/desktop/test/deployment/update/wrong_url/readme.txt new file mode 100644 index 0000000000..9e3bf8b969 --- /dev/null +++ b/desktop/test/deployment/update/wrong_url/readme.txt @@ -0,0 +1,18 @@ +The extensions use either multiple urls to the update information file, or the update information file contains multiple urls. Some of those URLs point to locations which do not exist. + +url1.oxt: The corresponding url1.update.xml contains two download urls. The first Url points to a location that is not available. The second points to the new version. + +url2.oxt: Contains to URLs to update information files. The first URL in url2.oxt is wrong and the second is +correct. + +url3.oxt: contains to URLs to update information files which point to locations which do not exist. + +wrongdownload1.oxt: The corresponding wrongdownload1.update.xml contains two download URLs which point to locations which are not available. + +wrongdownload2.oxt: same as wrongdownload1.oxt + +wrongdownload3.oxt: same as wrongdownload1.oxt + + +Use the wrongdownload extensions to check the automatic scrolling of the text area that contains the results. + diff --git a/desktop/test/deployment/update/wrong_url/update/url1.oxt b/desktop/test/deployment/update/wrong_url/update/url1.oxt Binary files differnew file mode 100644 index 0000000000..479b546c84 --- /dev/null +++ b/desktop/test/deployment/update/wrong_url/update/url1.oxt diff --git a/desktop/test/deployment/update/wrong_url/update/url1.update.xml b/desktop/test/deployment/update/wrong_url/update/url1.update.xml new file mode 100644 index 0000000000..e6980d5586 --- /dev/null +++ b/desktop/test/deployment/update/wrong_url/update/url1.update.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + * 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 . +--> +<description xmlns="http://openoffice.org/extensions/update/2006" + xmlns:xlink="http://www.w3.org/1999/xlink"> + <identifier value="org.openoffice.legacy.url1.oxt"/> + <version value="2.0" /> + <update-download> + <src xlink:href="http://extensions.openoffice.org/testarea/desktop/wrong_url/notavailable/url1.oxt" /> + <src xlink:href="http://extensions.openoffice.org/testarea/desktop/wrong_url/update/url1.oxt" /> + </update-download> +</description> + diff --git a/desktop/test/deployment/update/wrong_url/update/url2.oxt b/desktop/test/deployment/update/wrong_url/update/url2.oxt Binary files differnew file mode 100644 index 0000000000..ec2c5c6528 --- /dev/null +++ b/desktop/test/deployment/update/wrong_url/update/url2.oxt diff --git a/desktop/test/deployment/update/wrong_url/update/url2.update.xml b/desktop/test/deployment/update/wrong_url/update/url2.update.xml new file mode 100644 index 0000000000..3ced2f9d2c --- /dev/null +++ b/desktop/test/deployment/update/wrong_url/update/url2.update.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + * 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 . +--> +<description xmlns="http://openoffice.org/extensions/update/2006" + xmlns:xlink="http://www.w3.org/1999/xlink"> + <identifier value="org.openoffice.legacy.url2.oxt"/> + <version value="2.0" /> + <update-download> + <src xlink:href="http://extensions.openoffice.org/testarea/desktop/wrong_url/update/url2.oxt" /> + </update-download> +</description> + diff --git a/desktop/test/deployment/update/wrong_url/update/wrongdownload1.update.xml b/desktop/test/deployment/update/wrong_url/update/wrongdownload1.update.xml new file mode 100644 index 0000000000..916036e038 --- /dev/null +++ b/desktop/test/deployment/update/wrong_url/update/wrongdownload1.update.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + * 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 . +--> +<description xmlns="http://openoffice.org/extensions/update/2006" + xmlns:xlink="http://www.w3.org/1999/xlink"> + <identifier value="org.openoffice.legacy.wrongdownload1.oxt"/> + <version value="2.0" /> + <update-download> + <src xlink:href="http://extensions.openoffice.org/testarea/desktop/wrong_url/notavailable/wrongdownload1.oxt" /> + <src xlink:href="http://extensions.openoffice.org/notavailable/" /> + </update-download> +</description> + diff --git a/desktop/test/deployment/update/wrong_url/update/wrongdownload2.update.xml b/desktop/test/deployment/update/wrong_url/update/wrongdownload2.update.xml new file mode 100644 index 0000000000..36b24ed195 --- /dev/null +++ b/desktop/test/deployment/update/wrong_url/update/wrongdownload2.update.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + * 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 . +--> +<description xmlns="http://openoffice.org/extensions/update/2006" + xmlns:xlink="http://www.w3.org/1999/xlink"> + <identifier value="org.openoffice.legacy.wrongdownload2.oxt"/> + <version value="2.0" /> + <update-download> + <src xlink:href="http://extensions.openoffice.org/testarea/desktop/wrong_url/notavailable/wrongdownload2.oxt" /> + <src xlink:href="http://extensions.openoffice.org/notavailable/" /> + </update-download> +</description> + diff --git a/desktop/test/deployment/update/wrong_url/update/wrongdownload3.update.xml b/desktop/test/deployment/update/wrong_url/update/wrongdownload3.update.xml new file mode 100644 index 0000000000..f5a2bb8b1b --- /dev/null +++ b/desktop/test/deployment/update/wrong_url/update/wrongdownload3.update.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + * 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 . +--> +<description xmlns="http://openoffice.org/extensions/update/2006" + xmlns:xlink="http://www.w3.org/1999/xlink"> + <identifier value="org.openoffice.legacy.wrongdownload3.oxt"/> + <version value="2.0" /> + <update-download> + <src xlink:href="http://extensions.openoffice.org/testarea/desktop/wrong_url/notavailable/wrongdownload3.oxt" /> + <src xlink:href="http://extensions.openoffice.org/notavailable/" /> + </update-download> +</description> + diff --git a/desktop/test/deployment/update/wrong_url/url1.oxt b/desktop/test/deployment/update/wrong_url/url1.oxt Binary files differnew file mode 100644 index 0000000000..41d8522fbb --- /dev/null +++ b/desktop/test/deployment/update/wrong_url/url1.oxt diff --git a/desktop/test/deployment/update/wrong_url/url2.oxt b/desktop/test/deployment/update/wrong_url/url2.oxt Binary files differnew file mode 100644 index 0000000000..d68e45e5e5 --- /dev/null +++ b/desktop/test/deployment/update/wrong_url/url2.oxt diff --git a/desktop/test/deployment/update/wrong_url/url3.oxt b/desktop/test/deployment/update/wrong_url/url3.oxt Binary files differnew file mode 100644 index 0000000000..80f93b74d1 --- /dev/null +++ b/desktop/test/deployment/update/wrong_url/url3.oxt diff --git a/desktop/test/deployment/update/wrong_url/wrongdownload1.oxt b/desktop/test/deployment/update/wrong_url/wrongdownload1.oxt Binary files differnew file mode 100644 index 0000000000..535ae331a9 --- /dev/null +++ b/desktop/test/deployment/update/wrong_url/wrongdownload1.oxt diff --git a/desktop/test/deployment/update/wrong_url/wrongdownload2.oxt b/desktop/test/deployment/update/wrong_url/wrongdownload2.oxt Binary files differnew file mode 100644 index 0000000000..aafe2c2467 --- /dev/null +++ b/desktop/test/deployment/update/wrong_url/wrongdownload2.oxt diff --git a/desktop/test/deployment/update/wrong_url/wrongdownload3.oxt b/desktop/test/deployment/update/wrong_url/wrongdownload3.oxt Binary files differnew file mode 100644 index 0000000000..fbdac925a2 --- /dev/null +++ b/desktop/test/deployment/update/wrong_url/wrongdownload3.oxt diff --git a/desktop/test/deployment/version/readme.txt b/desktop/test/deployment/version/readme.txt new file mode 100644 index 0000000000..6135d08a26 --- /dev/null +++ b/desktop/test/deployment/version/readme.txt @@ -0,0 +1,76 @@ +# +# 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 . +# + +There are three extensions in various versions: + +1 version_XXX/plain.oxt has no dependencies and no license. +2a version_XXX/dependency.oxt has an unsatisfied dependency and no license. +2b version_nodependencies_XXX/dependency.oxt is identical to 2a but without the + dependency. +3 version_XXX/license.oxt has no dependencies and a license. + +The different versions are: + +A version_none contains no version element (treated as version "0"). +B version_badelement contains a bad <version val="1"/> (not allowed by the + specification, but treated by the current implementation as version "0"). +C version_badvalue contains a bad <version value="1.a"/> (not allowed by the + specification, but treated by the current implementation as version "1"). +D version_0.0 contains <version value="0.0"/> (same as version "0"). +E version_1.2.3 contains <version value="1.2.3"/>. +F version_1.2.4.7 contains <version value="1.2.4.7"/>. +G version_1.02.4.7.0 contains <version value="1.02.4.7.0"/> (same as version + "1.2.4.7"). +H version_1.2.15.3 contains <version value="1.2.15.3"/>. + +The total order among the various versions is thus + + A = B = D < C < E < F = G < H. + +Things to watch for: + +- If version y of extension e is to be installed and version x < y of + extension e is already installed, then + unopkg add e + will replace x with y. + +- If version y of extension e is to be installed and version x >= y of + extension e is already installed, then + unopkg add e + will fail with an error message. + +- If version y of extension e is to be installed and any version x of + extension e is already installed, then + unopkg add -f e + will replace x with y. + +- If version y of extension e is to be installed and any version x of + extension e is already installed, then + unopkg gui "Add..." + and + soffice "Tools - Package Manager... - Add..." + will query with a dialog whether to replace x with y. The dialog will have + "OK" (replace) preselected if x < y, and "Cancel" otherwise. + +- If replacing an installed version x of an extension e with a version y fails + because y has unsatisfied dependencies, or because y has a license to which the + user does not agree, version x is left installed afterwards. + +- Checking for already installed versions of an extension is only done within a + single layer (unopkg versus unopkg --shared; "My Packages" versus + "OpenOffice Packages" in unopkg gui/soffice), not across layers. diff --git a/desktop/test/deployment/version/version_0.0/dependency.oxt b/desktop/test/deployment/version/version_0.0/dependency.oxt Binary files differnew file mode 100644 index 0000000000..30c8432251 --- /dev/null +++ b/desktop/test/deployment/version/version_0.0/dependency.oxt diff --git a/desktop/test/deployment/version/version_0.0/license.oxt b/desktop/test/deployment/version/version_0.0/license.oxt Binary files differnew file mode 100644 index 0000000000..b994ff71b7 --- /dev/null +++ b/desktop/test/deployment/version/version_0.0/license.oxt diff --git a/desktop/test/deployment/version/version_0.0/plain.oxt b/desktop/test/deployment/version/version_0.0/plain.oxt Binary files differnew file mode 100644 index 0000000000..f156014eb8 --- /dev/null +++ b/desktop/test/deployment/version/version_0.0/plain.oxt diff --git a/desktop/test/deployment/version/version_1.02.4.7.0/dependency.oxt b/desktop/test/deployment/version/version_1.02.4.7.0/dependency.oxt Binary files differnew file mode 100644 index 0000000000..4d75f70762 --- /dev/null +++ b/desktop/test/deployment/version/version_1.02.4.7.0/dependency.oxt diff --git a/desktop/test/deployment/version/version_1.02.4.7.0/license.oxt b/desktop/test/deployment/version/version_1.02.4.7.0/license.oxt Binary files differnew file mode 100644 index 0000000000..40938b7543 --- /dev/null +++ b/desktop/test/deployment/version/version_1.02.4.7.0/license.oxt diff --git a/desktop/test/deployment/version/version_1.02.4.7.0/plain.oxt b/desktop/test/deployment/version/version_1.02.4.7.0/plain.oxt Binary files differnew file mode 100644 index 0000000000..521a2b6c77 --- /dev/null +++ b/desktop/test/deployment/version/version_1.02.4.7.0/plain.oxt diff --git a/desktop/test/deployment/version/version_1.2.15.3/dependency.oxt b/desktop/test/deployment/version/version_1.2.15.3/dependency.oxt Binary files differnew file mode 100644 index 0000000000..6f2a301f3b --- /dev/null +++ b/desktop/test/deployment/version/version_1.2.15.3/dependency.oxt diff --git a/desktop/test/deployment/version/version_1.2.15.3/license.oxt b/desktop/test/deployment/version/version_1.2.15.3/license.oxt Binary files differnew file mode 100644 index 0000000000..2e2a875750 --- /dev/null +++ b/desktop/test/deployment/version/version_1.2.15.3/license.oxt diff --git a/desktop/test/deployment/version/version_1.2.15.3/plain.oxt b/desktop/test/deployment/version/version_1.2.15.3/plain.oxt Binary files differnew file mode 100644 index 0000000000..000f3a144f --- /dev/null +++ b/desktop/test/deployment/version/version_1.2.15.3/plain.oxt diff --git a/desktop/test/deployment/version/version_1.2.3/dependency.oxt b/desktop/test/deployment/version/version_1.2.3/dependency.oxt Binary files differnew file mode 100644 index 0000000000..c296634587 --- /dev/null +++ b/desktop/test/deployment/version/version_1.2.3/dependency.oxt diff --git a/desktop/test/deployment/version/version_1.2.3/license.oxt b/desktop/test/deployment/version/version_1.2.3/license.oxt Binary files differnew file mode 100644 index 0000000000..9cd80e9911 --- /dev/null +++ b/desktop/test/deployment/version/version_1.2.3/license.oxt diff --git a/desktop/test/deployment/version/version_1.2.3/plain.oxt b/desktop/test/deployment/version/version_1.2.3/plain.oxt Binary files differnew file mode 100644 index 0000000000..e34264591c --- /dev/null +++ b/desktop/test/deployment/version/version_1.2.3/plain.oxt diff --git a/desktop/test/deployment/version/version_1.2.4.7/dependency.oxt b/desktop/test/deployment/version/version_1.2.4.7/dependency.oxt Binary files differnew file mode 100644 index 0000000000..53089e76b0 --- /dev/null +++ b/desktop/test/deployment/version/version_1.2.4.7/dependency.oxt diff --git a/desktop/test/deployment/version/version_1.2.4.7/license.oxt b/desktop/test/deployment/version/version_1.2.4.7/license.oxt Binary files differnew file mode 100644 index 0000000000..e283508d34 --- /dev/null +++ b/desktop/test/deployment/version/version_1.2.4.7/license.oxt diff --git a/desktop/test/deployment/version/version_1.2.4.7/plain.oxt b/desktop/test/deployment/version/version_1.2.4.7/plain.oxt Binary files differnew file mode 100644 index 0000000000..d63c79a734 --- /dev/null +++ b/desktop/test/deployment/version/version_1.2.4.7/plain.oxt diff --git a/desktop/test/deployment/version/version_badelement/dependency.oxt b/desktop/test/deployment/version/version_badelement/dependency.oxt Binary files differnew file mode 100644 index 0000000000..3cb8faa2e7 --- /dev/null +++ b/desktop/test/deployment/version/version_badelement/dependency.oxt diff --git a/desktop/test/deployment/version/version_badelement/license.oxt b/desktop/test/deployment/version/version_badelement/license.oxt Binary files differnew file mode 100644 index 0000000000..7b2b7730ec --- /dev/null +++ b/desktop/test/deployment/version/version_badelement/license.oxt diff --git a/desktop/test/deployment/version/version_badelement/plain.oxt b/desktop/test/deployment/version/version_badelement/plain.oxt Binary files differnew file mode 100644 index 0000000000..62267c212f --- /dev/null +++ b/desktop/test/deployment/version/version_badelement/plain.oxt diff --git a/desktop/test/deployment/version/version_badvalue/dependency.oxt b/desktop/test/deployment/version/version_badvalue/dependency.oxt Binary files differnew file mode 100644 index 0000000000..7d81033654 --- /dev/null +++ b/desktop/test/deployment/version/version_badvalue/dependency.oxt diff --git a/desktop/test/deployment/version/version_badvalue/license.oxt b/desktop/test/deployment/version/version_badvalue/license.oxt Binary files differnew file mode 100644 index 0000000000..b97723ebb0 --- /dev/null +++ b/desktop/test/deployment/version/version_badvalue/license.oxt diff --git a/desktop/test/deployment/version/version_badvalue/plain.oxt b/desktop/test/deployment/version/version_badvalue/plain.oxt Binary files differnew file mode 100644 index 0000000000..f9964ed8f0 --- /dev/null +++ b/desktop/test/deployment/version/version_badvalue/plain.oxt diff --git a/desktop/test/deployment/version/version_nodependencies_0.0/dependency.oxt b/desktop/test/deployment/version/version_nodependencies_0.0/dependency.oxt Binary files differnew file mode 100644 index 0000000000..f156014eb8 --- /dev/null +++ b/desktop/test/deployment/version/version_nodependencies_0.0/dependency.oxt diff --git a/desktop/test/deployment/version/version_nodependencies_1.02.4.7.0/dependency.oxt b/desktop/test/deployment/version/version_nodependencies_1.02.4.7.0/dependency.oxt Binary files differnew file mode 100644 index 0000000000..521a2b6c77 --- /dev/null +++ b/desktop/test/deployment/version/version_nodependencies_1.02.4.7.0/dependency.oxt diff --git a/desktop/test/deployment/version/version_nodependencies_1.2.15.3/dependency.oxt b/desktop/test/deployment/version/version_nodependencies_1.2.15.3/dependency.oxt Binary files differnew file mode 100644 index 0000000000..000f3a144f --- /dev/null +++ b/desktop/test/deployment/version/version_nodependencies_1.2.15.3/dependency.oxt diff --git a/desktop/test/deployment/version/version_nodependencies_1.2.3/dependency.oxt b/desktop/test/deployment/version/version_nodependencies_1.2.3/dependency.oxt Binary files differnew file mode 100644 index 0000000000..e34264591c --- /dev/null +++ b/desktop/test/deployment/version/version_nodependencies_1.2.3/dependency.oxt diff --git a/desktop/test/deployment/version/version_nodependencies_1.2.4.7/dependency.oxt b/desktop/test/deployment/version/version_nodependencies_1.2.4.7/dependency.oxt Binary files differnew file mode 100644 index 0000000000..d63c79a734 --- /dev/null +++ b/desktop/test/deployment/version/version_nodependencies_1.2.4.7/dependency.oxt diff --git a/desktop/test/deployment/version/version_nodependencies_badelement/dependency.oxt b/desktop/test/deployment/version/version_nodependencies_badelement/dependency.oxt Binary files differnew file mode 100644 index 0000000000..62267c212f --- /dev/null +++ b/desktop/test/deployment/version/version_nodependencies_badelement/dependency.oxt diff --git a/desktop/test/deployment/version/version_nodependencies_badvalue/dependency.oxt b/desktop/test/deployment/version/version_nodependencies_badvalue/dependency.oxt Binary files differnew file mode 100644 index 0000000000..f9964ed8f0 --- /dev/null +++ b/desktop/test/deployment/version/version_nodependencies_badvalue/dependency.oxt diff --git a/desktop/test/deployment/version/version_nodependencies_none/dependency.oxt b/desktop/test/deployment/version/version_nodependencies_none/dependency.oxt Binary files differnew file mode 100644 index 0000000000..fc227b099e --- /dev/null +++ b/desktop/test/deployment/version/version_nodependencies_none/dependency.oxt diff --git a/desktop/test/deployment/version/version_none/dependency.oxt b/desktop/test/deployment/version/version_none/dependency.oxt Binary files differnew file mode 100644 index 0000000000..36a1854bf5 --- /dev/null +++ b/desktop/test/deployment/version/version_none/dependency.oxt diff --git a/desktop/test/deployment/version/version_none/license.oxt b/desktop/test/deployment/version/version_none/license.oxt Binary files differnew file mode 100644 index 0000000000..1564c089b0 --- /dev/null +++ b/desktop/test/deployment/version/version_none/license.oxt diff --git a/desktop/test/deployment/version/version_none/plain.oxt b/desktop/test/deployment/version/version_none/plain.oxt Binary files differnew file mode 100644 index 0000000000..fc227b099e --- /dev/null +++ b/desktop/test/deployment/version/version_none/plain.oxt diff --git a/desktop/uiconfig/ui/dependenciesdialog.ui b/desktop/uiconfig/ui/dependenciesdialog.ui new file mode 100644 index 0000000000..d4f2fd6419 --- /dev/null +++ b/desktop/uiconfig/ui/dependenciesdialog.ui @@ -0,0 +1,145 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Generated with glade 3.22.1 --> +<interface domain="dkt"> + <requires lib="gtk+" version="3.20"/> + <object class="GtkTreeStore" id="liststore1"> + <columns> + <!-- column-name text --> + <column type="gchararray"/> + <!-- column-name id --> + <column type="gchararray"/> + </columns> + </object> + <object class="GtkDialog" id="Dependencies"> + <property name="can_focus">False</property> + <property name="hexpand">True</property> + <property name="vexpand">True</property> + <property name="border_width">6</property> + <property name="title" translatable="yes" context="dependenciesdialog|Dependencies">System dependencies check</property> + <property name="modal">True</property> + <property name="default_width">0</property> + <property name="default_height">0</property> + <property name="type_hint">dialog</property> + <child> + <placeholder/> + </child> + <child internal-child="vbox"> + <object class="GtkBox" id="dialog-vbox1"> + <property name="can_focus">False</property> + <property name="hexpand">True</property> + <property name="vexpand">True</property> + <property name="orientation">vertical</property> + <property name="spacing">12</property> + <child internal-child="action_area"> + <object class="GtkButtonBox" id="dialog-action_area1"> + <property name="can_focus">False</property> + <property name="layout_style">center</property> + <child> + <object class="GtkButton" id="ok"> + <property name="label" translatable="yes" context="stock">_OK</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="can_default">True</property> + <property name="has_default">True</property> + <property name="receives_default">True</property> + <property name="use-underline">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="pack_type">end</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkBox" id="box1"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="hexpand">True</property> + <property name="vexpand">True</property> + <property name="orientation">vertical</property> + <property name="spacing">12</property> + <child> + <object class="GtkLabel" id="label1"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label" translatable="yes" context="dependenciesdialog|label1">The extension cannot be installed as the following system dependencies are not fulfilled:</property> + <property name="wrap">True</property> + <property name="width-chars">50</property> + <property name="max_width_chars">50</property> + <property name="xalign">0</property> + <accessibility> + <relation type="label-for" target="depListTreeview"/> + </accessibility> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkScrolledWindow"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="hexpand">True</property> + <property name="vexpand">True</property> + <property name="shadow_type">in</property> + <child> + <object class="GtkTreeView" id="depListTreeview"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="hexpand">True</property> + <property name="vexpand">True</property> + <property name="model">liststore1</property> + <property name="headers_visible">False</property> + <property name="search_column">0</property> + <property name="show_expanders">False</property> + <accessibility> + <relation type="labelled-by" target="label1"/> + </accessibility> + <child internal-child="selection"> + <object class="GtkTreeSelection" id="Macro Library List-selection2"/> + </child> + <child> + <object class="GtkTreeViewColumn" id="treeviewcolumn5"> + <property name="resizable">True</property> + <property name="spacing">6</property> + <child> + <object class="GtkCellRendererText" id="cellrenderer4"/> + <attributes> + <attribute name="text">0</attribute> + </attributes> + </child> + </object> + </child> + </object> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + </child> + <action-widgets> + <action-widget response="-5">ok</action-widget> + </action-widgets> + </object> +</interface> diff --git a/desktop/uiconfig/ui/extensionmanager.ui b/desktop/uiconfig/ui/extensionmanager.ui new file mode 100644 index 0000000000..215536e179 --- /dev/null +++ b/desktop/uiconfig/ui/extensionmanager.ui @@ -0,0 +1,403 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Generated with glade 3.38.2 --> +<interface domain="dkt"> + <requires lib="gtk+" version="3.20"/> + <object class="GtkDialog" id="ExtensionManagerDialog"> + <property name="can-focus">False</property> + <property name="border-width">6</property> + <property name="title" translatable="yes" context="extensionmanager|ExtensionManagerDialog">Extensions</property> + <property name="default-width">0</property> + <property name="default-height">0</property> + <property name="type-hint">dialog</property> + <child internal-child="vbox"> + <object class="GtkBox" id="dialog-vbox1"> + <property name="can-focus">False</property> + <property name="orientation">vertical</property> + <property name="spacing">12</property> + <child internal-child="action_area"> + <object class="GtkButtonBox" id="dialog-action_area1"> + <property name="can-focus">False</property> + <property name="layout-style">end</property> + <child> + <object class="GtkButton" id="help"> + <property name="label" translatable="yes" context="stock">_Help</property> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="receives-default">True</property> + <property name="use-underline">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + <property name="secondary">True</property> + </packing> + </child> + <child> + <object class="GtkButton" id="close"> + <property name="label" translatable="yes" context="stock">_Close</property> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="receives-default">True</property> + <property name="use-underline">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="pack-type">end</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkBox" id="box1"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="orientation">vertical</property> + <property name="spacing">6</property> + <child> + <object class="GtkFrame" id="frame1"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="label-xalign">0</property> + <property name="shadow-type">none</property> + <child> + <!-- n-columns=3 n-rows=1 --> + <object class="GtkGrid" id="grid1"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="margin-start">12</property> + <property name="margin-top">6</property> + <property name="column-spacing">12</property> + <child> + <object class="GtkCheckButton" id="shared"> + <property name="label" translatable="yes" context="extensionmanager|shared">Installed for all users</property> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="receives-default">False</property> + <property name="use-underline">True</property> + <property name="active">True</property> + <property name="draw-indicator">True</property> + <child internal-child="accessible"> + <object class="AtkObject" id="shared-atkobject"> + <property name="AtkObject::accessible-description" translatable="yes" context="extensionmanager|extended_tip|shared">Filter extensions available for all users of this computer.</property> + </object> + </child> + </object> + <packing> + <property name="left-attach">1</property> + <property name="top-attach">0</property> + </packing> + </child> + <child> + <object class="GtkCheckButton" id="user"> + <property name="label" translatable="yes" context="extensionmanager|user">Installed for current user</property> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="receives-default">False</property> + <property name="use-underline">True</property> + <property name="active">True</property> + <property name="draw-indicator">True</property> + <child internal-child="accessible"> + <object class="AtkObject" id="user-atkobject"> + <property name="AtkObject::accessible-description" translatable="yes" context="extensionmanager|extended_tip|user">Filter extensions only available for the currently logged in user.</property> + </object> + </child> + </object> + <packing> + <property name="left-attach">2</property> + <property name="top-attach">0</property> + </packing> + </child> + <child> + <object class="GtkCheckButton" id="bundled"> + <property name="label" translatable="yes" context="extensionmanager|bundled">Bundled with %PRODUCTNAME</property> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="receives-default">False</property> + <property name="use-underline">True</property> + <property name="active">True</property> + <property name="draw-indicator">True</property> + <child internal-child="accessible"> + <object class="AtkObject" id="bundled-atkobject"> + <property name="AtkObject::accessible-description" translatable="yes" context="extensionmanager|extended_tip|bundled">Bundled extensions are installed by the system administrator using the operating system specific installer packages. These can not be installed, updated or removed here.</property> + </object> + </child> + </object> + <packing> + <property name="left-attach">0</property> + <property name="top-attach">0</property> + </packing> + </child> + </object> + </child> + <child type="label"> + <object class="GtkLabel" id="label1"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="label" translatable="yes" context="extensionmanager|label1">Display Extensions</property> + <attributes> + <attribute name="weight" value="bold"/> + </attributes> + </object> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkEntry" id="search"> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="truncate-multiline">True</property> + <property name="placeholder-text" translatable="yes" context="extensionmanager|search">Search...</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkScrolledWindow" id="scroll"> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="hexpand">True</property> + <property name="vexpand">True</property> + <property name="hscrollbar-policy">never</property> + <property name="shadow-type">in</property> + <child> + <object class="GtkViewport"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <child> + <object class="GtkDrawingArea" id="extensions"> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="events">GDK_BUTTON_MOTION_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_STRUCTURE_MASK</property> + <property name="hexpand">True</property> + <property name="vexpand">True</property> + <child internal-child="accessible"> + <object class="AtkObject" id="extensions-atkobject"> + <property name="AtkObject::accessible-description" translatable="yes" context="extensionmanager|extended_tip|extensions">Select the extension that you want to remove, enable, or disable. For some extensions, you can also open an Options dialog.</property> + </object> + </child> + </object> + </child> + </object> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">2</property> + </packing> + </child> + <child> + <object class="GtkButtonBox"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="spacing">6</property> + <property name="layout-style">start</property> + <child> + <object class="GtkButton" id="optionsbtn"> + <property name="label" translatable="yes" context="extensionmanager|optionsbtn">_Options</property> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="receives-default">True</property> + <property name="use-underline">True</property> + <child internal-child="accessible"> + <object class="AtkObject" id="optionsbtn-atkobject"> + <property name="AtkObject::accessible-description" translatable="yes" context="extensionmanager|extended_tip|optionsbtn">Select an installed extension, then click to open the Options dialog for the extension.</property> + </object> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkButton" id="updatebtn"> + <property name="label" translatable="yes" context="extensionmanager|updatebtn">Check for _Updates</property> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="receives-default">True</property> + <property name="use-underline">True</property> + <child internal-child="accessible"> + <object class="AtkObject" id="updatebtn-atkobject"> + <property name="AtkObject::accessible-description" translatable="yes" context="extensionmanager|extended_tip|updatebtn">Click to check for online updates of all installed extensions. To check for updates of the selected extension only, choose the Update command from the context menu. The check for availability of updates starts immediately.</property> + </object> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + <property name="secondary">True</property> + </packing> + </child> + <child> + <object class="GtkButton" id="addbtn"> + <property name="label" translatable="yes" context="extensionmanager|addbtn">_Add</property> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="can-default">True</property> + <property name="has-default">True</property> + <property name="receives-default">True</property> + <property name="use-underline">True</property> + <child internal-child="accessible"> + <object class="AtkObject" id="addbtn-atkobject"> + <property name="AtkObject::accessible-description" translatable="yes" context="extensionmanager|extended_tip|addbtn">Click Add to add an extension.</property> + </object> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">2</property> + <property name="secondary">True</property> + </packing> + </child> + <child> + <object class="GtkButton" id="removebtn"> + <property name="label" translatable="yes" context="extensionmanager|removebtn">_Remove</property> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="receives-default">True</property> + <property name="use-underline">True</property> + <child internal-child="accessible"> + <object class="AtkObject" id="removebtn-atkobject"> + <property name="AtkObject::accessible-description" translatable="yes" context="extensionmanager|extended_tip|removebtn">Select the extension that you want to remove, and then click Remove.</property> + </object> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">3</property> + <property name="secondary">True</property> + </packing> + </child> + <child> + <object class="GtkButton" id="enablebtn"> + <property name="label" translatable="yes" context="extensionmanager|enablebtn">_Enable</property> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="receives-default">True</property> + <property name="use-underline">True</property> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">4</property> + <property name="secondary">True</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">3</property> + </packing> + </child> + <child> + <!-- n-columns=3 n-rows=2 --> + <object class="GtkGrid" id="grid3"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="column-spacing">6</property> + <child> + <object class="GtkLabel" id="progressft"> + <property name="can-focus">False</property> + <property name="no-show-all">True</property> + <property name="label" translatable="yes" context="extensionmanager|progressft">Adding %EXTENSION_NAME</property> + <property name="justify">right</property> + </object> + <packing> + <property name="left-attach">0</property> + <property name="top-attach">0</property> + </packing> + </child> + <child> + <object class="GtkButton" id="cancel"> + <property name="label" translatable="yes" context="stock">_Cancel</property> + <property name="can-focus">True</property> + <property name="receives-default">True</property> + <property name="no-show-all">True</property> + <property name="use-underline">True</property> + </object> + <packing> + <property name="left-attach">2</property> + <property name="top-attach">0</property> + </packing> + </child> + <child> + <object class="GtkProgressBar" id="progressbar"> + <property name="can-focus">False</property> + <property name="no-show-all">True</property> + <property name="valign">center</property> + <property name="hexpand">True</property> + </object> + <packing> + <property name="left-attach">1</property> + <property name="top-attach">0</property> + </packing> + </child> + <child> + <object class="GtkLinkButton" id="getextensions"> + <property name="label" translatable="yes" context="extensionmanager|getextensions">Get more extensions online...</property> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="receives-default">True</property> + <property name="relief">none</property> + <property name="xalign">0</property> + <child internal-child="accessible"> + <object class="AtkObject" id="getextensions-atkobject"> + <property name="AtkObject::accessible-description" translatable="yes" context="extensionmanager|extended_tip|getextensions">You can find a collection of extensions on the Web.</property> + </object> + </child> + </object> + <packing> + <property name="left-attach">0</property> + <property name="top-attach">1</property> + <property name="width">3</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">4</property> + </packing> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + </child> + <action-widgets> + <action-widget response="-11">help</action-widget> + <action-widget response="-6">close</action-widget> + </action-widgets> + <child internal-child="accessible"> + <object class="AtkObject" id="ExtensionManagerDialog-atkobject"> + <property name="AtkObject::accessible-description" translatable="yes" context="extensionmanager|extended_tip|ExtensionManagerDialog">The Extension Manager adds, removes, disables, enables, and updates extensions.</property> + </object> + </child> + </object> +</interface> diff --git a/desktop/uiconfig/ui/extensionmenu.ui b/desktop/uiconfig/ui/extensionmenu.ui new file mode 100644 index 0000000000..ff889b46c1 --- /dev/null +++ b/desktop/uiconfig/ui/extensionmenu.ui @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Generated with glade 3.22.1 --> +<interface domain="dkt"> + <requires lib="gtk+" version="3.20"/> + <object class="GtkMenu" id="menu"> + <property name="visible">True</property> + <property name="can_focus">False</property> + </object> +</interface> diff --git a/desktop/uiconfig/ui/installforalldialog.ui b/desktop/uiconfig/ui/installforalldialog.ui new file mode 100644 index 0000000000..d2f645220f --- /dev/null +++ b/desktop/uiconfig/ui/installforalldialog.ui @@ -0,0 +1,82 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Generated with glade 3.16.1 --> +<interface domain="dkt"> + <requires lib="gtk+" version="3.20"/> + <object class="GtkMessageDialog" id="InstallForAllDialog"> + <property name="can_focus">False</property> + <property name="resizable">False</property> + <property name="modal">True</property> + <property name="type_hint">dialog</property> + <property name="skip_taskbar_hint">True</property> + <property name="message_type">question</property> + <property name="text" translatable="yes" context="installforalldialog|InstallForAllDialog">For whom do you want to install the extension?</property> + <property name="secondary_text" translatable="yes" context="installforalldialog|InstallForAllDialog">Make sure that no further users are working with the same %PRODUCTNAME, when installing an extension for all users in a multi user environment.</property> + <child internal-child="vbox"> + <object class="GtkBox" id="messagedialog-vbox"> + <property name="can_focus">False</property> + <property name="orientation">vertical</property> + <property name="spacing">24</property> + <child internal-child="action_area"> + <object class="GtkButtonBox" id="messagedialog-action_area"> + <property name="can_focus">False</property> + <child> + <object class="GtkButton" id="no"> + <property name="label" translatable="yes" context="installforalldialog|no">_For all users</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="use_underline">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkButton" id="yes"> + <property name="label" translatable="yes" context="installforalldialog|yes">_Only for me</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="can_default">True</property> + <property name="has_default">True</property> + <property name="receives_default">True</property> + <property name="use_underline">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkButton" id="cancel"> + <property name="label" translatable="yes" context="stock">_Cancel</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="use-underline">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">2</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="pack_type">end</property> + <property name="position">0</property> + </packing> + </child> + </object> + </child> + <action-widgets> + <action-widget response="-9">no</action-widget> + <action-widget response="-8">yes</action-widget> + <action-widget response="-6">cancel</action-widget> + </action-widgets> + </object> +</interface> diff --git a/desktop/uiconfig/ui/licensedialog.ui b/desktop/uiconfig/ui/licensedialog.ui new file mode 100644 index 0000000000..5ebbf1fa7e --- /dev/null +++ b/desktop/uiconfig/ui/licensedialog.ui @@ -0,0 +1,229 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Generated with glade 3.22.1 --> +<interface domain="dkt"> + <requires lib="gtk+" version="3.20"/> + <object class="GtkDialog" id="LicenseDialog"> + <property name="can_focus">False</property> + <property name="border_width">6</property> + <property name="title" translatable="yes" context="licensedialog|LicenseDialog">Extension Software License Agreement</property> + <property name="type_hint">dialog</property> + <child> + <placeholder/> + </child> + <child internal-child="vbox"> + <object class="GtkBox" id="dialog-vbox1"> + <property name="can_focus">False</property> + <property name="orientation">vertical</property> + <property name="spacing">12</property> + <child internal-child="action_area"> + <object class="GtkButtonBox" id="dialog-action_area1"> + <property name="can_focus">False</property> + <property name="layout_style">end</property> + <child> + <object class="GtkButton" id="ok"> + <property name="label" translatable="yes" context="licensedialog|accept">Accept</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="can_default">True</property> + <property name="has_default">True</property> + <property name="receives_default">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkButton" id="cancel"> + <property name="label" translatable="yes" context="licensedialog|decline">Decline</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="pack_type">end</property> + <property name="position">0</property> + </packing> + </child> + <child> + <!-- n-columns=1 n-rows=1 --> + <object class="GtkGrid" id="grid1"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="hexpand">True</property> + <property name="vexpand">True</property> + <property name="row_spacing">6</property> + <child> + <object class="GtkLabel" id="head"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label" translatable="yes" context="licensedialog|head">Please follow these steps to proceed with the installation of the extension:</property> + <property name="xalign">0</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">1</property> + </packing> + </child> + <child> + <!-- n-columns=1 n-rows=1 --> + <object class="GtkGrid" id="grid2"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="margin_top">12</property> + <property name="hexpand">True</property> + <property name="row_spacing">6</property> + <property name="column_spacing">12</property> + <child> + <object class="GtkLabel" id="label2"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="valign">start</property> + <property name="label" translatable="yes" context="licensedialog|label2">1.</property> + <property name="xalign">0</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">0</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="label3"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="valign">start</property> + <property name="label" translatable="yes" context="licensedialog|label3">2.</property> + <property name="xalign">0</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">1</property> + </packing> + </child> + <child> + <object class="GtkImage" id="arrow1"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="valign">start</property> + <property name="icon_name">res/sc06300.png</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">0</property> + </packing> + </child> + <child> + <object class="GtkImage" id="arrow2"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="valign">start</property> + <property name="icon_name">res/sc06300.png</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="label4"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="valign">start</property> + <property name="hexpand">True</property> + <property name="label" translatable="yes" context="licensedialog|label4">Read the complete License Agreement. Use the scroll bar or the 'Scroll Down' button in this dialog to view the entire license text.</property> + <property name="wrap">True</property> + <property name="width_chars">55</property> + <property name="max_width_chars">55</property> + <property name="xalign">0</property> + </object> + <packing> + <property name="left_attach">2</property> + <property name="top_attach">0</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="label5"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="valign">start</property> + <property name="hexpand">True</property> + <property name="label" translatable="yes" context="licensedialog|label5">Accept the License Agreement for the extension by pressing the 'Accept' button.</property> + <property name="wrap">True</property> + <property name="width_chars">55</property> + <property name="max_width_chars">55</property> + <property name="xalign">0</property> + </object> + <packing> + <property name="left_attach">2</property> + <property name="top_attach">1</property> + </packing> + </child> + <child> + <object class="GtkButton" id="down"> + <property name="label" translatable="yes" context="licensedialog|down">_Scroll Down</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="events">GDK_BUTTON_MOTION_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK | GDK_STRUCTURE_MASK</property> + <property name="valign">start</property> + <property name="use_underline">True</property> + </object> + <packing> + <property name="left_attach">3</property> + <property name="top_attach">0</property> + <property name="height">2</property> + </packing> + </child> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">2</property> + </packing> + </child> + <child> + <object class="GtkScrolledWindow"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="hexpand">True</property> + <property name="vexpand">True</property> + <property name="shadow_type">in</property> + <child> + <object class="GtkTextView" id="textview"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="hexpand">True</property> + <property name="vexpand">True</property> + <property name="editable">False</property> + </object> + </child> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">0</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + </child> + <action-widgets> + <action-widget response="-5">ok</action-widget> + <action-widget response="-6">cancel</action-widget> + </action-widgets> + </object> +</interface> diff --git a/desktop/uiconfig/ui/showlicensedialog.ui b/desktop/uiconfig/ui/showlicensedialog.ui new file mode 100644 index 0000000000..b53c5d0045 --- /dev/null +++ b/desktop/uiconfig/ui/showlicensedialog.ui @@ -0,0 +1,83 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Generated with glade 3.22.1 --> +<interface domain="dkt"> + <requires lib="gtk+" version="3.20"/> + <object class="GtkDialog" id="ShowLicenseDialog"> + <child internal-child="accessible"> + <object class="AtkObject" id="ShowLicenseDialog-atkobject"> + <property name="AtkObject::accessible-description" translatable="yes" context="showlicensedialog|extended_tip|ShowLicenseDialog">Read the license. Click the Scroll Down button to scroll down if necessary. Click Accept to continue the installation of the extension.</property> + </object> + </child> + <property name="can_focus">False</property> + <property name="border_width">6</property> + <property name="title" translatable="yes" context="showlicensedialog|ShowLicenseDialog">Extension Software License Agreement</property> + <property name="modal">True</property> + <property name="default_width">0</property> + <property name="default_height">0</property> + <property name="type_hint">dialog</property> + <child> + <placeholder/> + </child> + <child internal-child="vbox"> + <object class="GtkBox" id="dialog-vbox1"> + <property name="can_focus">False</property> + <property name="orientation">vertical</property> + <property name="spacing">12</property> + <child internal-child="action_area"> + <object class="GtkButtonBox" id="dialog-action_area1"> + <property name="can_focus">False</property> + <property name="layout_style">end</property> + <child> + <object class="GtkButton" id="close"> + <property name="label" translatable="yes" context="stock">_Close</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="can_default">True</property> + <property name="has_default">True</property> + <property name="receives_default">True</property> + <property name="use-underline">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="pack_type">end</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkScrolledWindow" id="scrolledwindow1"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="hexpand">True</property> + <property name="vexpand">True</property> + <property name="shadow_type">in</property> + <child> + <object class="GtkTextView" id="textview"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="hexpand">True</property> + <property name="vexpand">True</property> + <property name="editable">False</property> + </object> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + </child> + <action-widgets> + <action-widget response="-7">close</action-widget> + </action-widgets> + </object> +</interface> diff --git a/desktop/uiconfig/ui/updatedialog.ui b/desktop/uiconfig/ui/updatedialog.ui new file mode 100644 index 0000000000..976fbfd108 --- /dev/null +++ b/desktop/uiconfig/ui/updatedialog.ui @@ -0,0 +1,393 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Generated with glade 3.36.0 --> +<interface domain="dkt"> + <requires lib="gtk+" version="3.20"/> + <object class="GtkTreeStore" id="liststore1"> + <columns> + <!-- column-name check1 --> + <column type="gboolean"/> + <!-- column-name text --> + <column type="gchararray"/> + <!-- column-name id --> + <column type="gchararray"/> + <!-- column-name checkvis1 --> + <column type="gboolean"/> + <!-- column-name checktri1 --> + <column type="gboolean"/> + </columns> + </object> + <object class="GtkDialog" id="UpdateDialog"> + <property name="can_focus">False</property> + <property name="hexpand">True</property> + <property name="vexpand">True</property> + <property name="border_width">6</property> + <property name="title" translatable="yes" context="updatedialog|UpdateDialog">Extension Update</property> + <property name="modal">True</property> + <property name="default_width">0</property> + <property name="default_height">0</property> + <property name="type_hint">dialog</property> + <child internal-child="vbox"> + <object class="GtkBox" id="dialog-vbox1"> + <property name="can_focus">False</property> + <property name="orientation">vertical</property> + <property name="spacing">12</property> + <child internal-child="action_area"> + <object class="GtkButtonBox" id="dialog-action_area1"> + <property name="can_focus">False</property> + <property name="layout_style">end</property> + <child> + <object class="GtkButton" id="help"> + <property name="label" translatable="yes" context="stock">_Help</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="use-underline">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + <property name="secondary">True</property> + </packing> + </child> + <child> + <object class="GtkButton" id="ok"> + <property name="label" translatable="yes" context="updatedialog|INSTALL">_Install</property> + <property name="visible">True</property> + <property name="sensitive">False</property> + <property name="can_focus">True</property> + <property name="can_default">True</property> + <property name="has_default">True</property> + <property name="receives_default">True</property> + <property name="use_underline">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkButton" id="close"> + <property name="label" translatable="yes" context="stock">_Close</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="use-underline">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">2</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="pack_type">end</property> + <property name="position">0</property> + </packing> + </child> + <child> + <!-- n-columns=1 n-rows=1 --> + <object class="GtkGrid"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="hexpand">True</property> + <property name="vexpand">True</property> + <property name="row_spacing">12</property> + <child> + <object class="GtkBox" id="box2"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="hexpand">True</property> + <property name="vexpand">True</property> + <property name="orientation">vertical</property> + <property name="spacing">6</property> + <child> + <!-- n-columns=1 n-rows=1 --> + <object class="GtkGrid" id="grid1"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="hexpand">True</property> + <property name="column_spacing">6</property> + <child> + <object class="GtkLabel" id="UPDATE_LABEL"> + <property name="visible">True</property> + <property name="sensitive">False</property> + <property name="can_focus">False</property> + <property name="halign">start</property> + <property name="hexpand">True</property> + <property name="label" translatable="yes" context="updatedialog|UPDATE_LABEL">_Available extension updates</property> + <property name="use_underline">True</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">0</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="UPDATE_CHECKING"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="halign">end</property> + <property name="label" translatable="yes" context="updatedialog|UPDATE_CHECKING">Checking...</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">0</property> + </packing> + </child> + <child> + <object class="GtkSpinner" id="THROBBER"> + <property name="visible">True</property> + <property name="can_focus">False</property> + </object> + <packing> + <property name="left_attach">2</property> + <property name="top_attach">0</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkScrolledWindow" id="checklistwin"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="hexpand">True</property> + <property name="vexpand">True</property> + <property name="hscrollbar_policy">never</property> + <property name="vscrollbar_policy">never</property> + <property name="shadow_type">in</property> + <child> + <object class="GtkTreeView" id="checklist"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="hexpand">True</property> + <property name="vexpand">True</property> + <property name="model">liststore1</property> + <property name="headers_visible">False</property> + <property name="search_column">0</property> + <property name="show_expanders">False</property> + <child internal-child="selection"> + <object class="GtkTreeSelection"/> + </child> + <child> + <object class="GtkTreeViewColumn" id="treeviewcolumn4"> + <property name="resizable">True</property> + <property name="spacing">6</property> + <property name="alignment">0.5</property> + <child> + <object class="GtkCellRendererToggle" id="cellrenderer5"/> + <attributes> + <attribute name="visible">3</attribute> + <attribute name="active">0</attribute> + </attributes> + </child> + </object> + </child> + <child> + <object class="GtkTreeViewColumn" id="treeviewcolumn5"> + <property name="resizable">True</property> + <property name="spacing">6</property> + <child> + <object class="GtkCellRendererText" id="cellrenderer4"/> + <attributes> + <attribute name="text">1</attribute> + </attributes> + </child> + </object> + </child> + </object> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkCheckButton" id="UPDATE_ALL"> + <property name="label" translatable="yes" context="updatedialog|UPDATE_ALL">_Show all updates</property> + <property name="visible">True</property> + <property name="sensitive">False</property> + <property name="can_focus">True</property> + <property name="receives_default">False</property> + <property name="use_underline">True</property> + <property name="draw_indicator">True</property> + <child internal-child="accessible"> + <object class="AtkObject" id="UPDATE_ALL-atkobject"> + <property name="AtkObject::accessible-description" translatable="yes" context="updatedialog|extended_tip|UPDATE_ALL">By default, only the downloadable extensions are shown in the dialog. Mark Show all Updates to see also other extensions and error messages.</property> + </object> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">2</property> + </packing> + </child> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">0</property> + </packing> + </child> + <child> + <object class="GtkBox" id="box3"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="hexpand">True</property> + <property name="vexpand">True</property> + <property name="orientation">vertical</property> + <property name="spacing">6</property> + <child> + <object class="GtkLabel" id="DESCRIPTION_LABEL"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="halign">start</property> + <property name="label" translatable="yes" context="updatedialog|DESCRIPTION_LABEL">Description</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <!-- n-columns=1 n-rows=1 --> + <object class="GtkGrid" id="grid2"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="column_spacing">6</property> + <child> + <object class="GtkLabel" id="PUBLISHER_LABEL"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="halign">start</property> + <property name="label" translatable="yes" context="updatedialog|PUBLISHER_LABEL">Publisher:</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">PUBLISHER_LINK</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">0</property> + </packing> + </child> + <child> + <object class="GtkLinkButton" id="PUBLISHER_LINK"> + <property name="label" translatable="yes" context="updatedialog|PUBLISHER_LINK">button</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="relief">none</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">0</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="RELEASE_NOTES_LABEL"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="halign">start</property> + <property name="label" translatable="yes" context="updatedialog|RELEASE_NOTES_LABEL">What is new:</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">RELEASE_NOTES_LINK</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">1</property> + </packing> + </child> + <child> + <object class="GtkLinkButton" id="RELEASE_NOTES_LINK"> + <property name="label" translatable="yes" context="updatedialog|RELEASE_NOTES_LINK">Release notes</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="relief">none</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkScrolledWindow" id="DESCRIPTIONSWIN"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="hexpand">True</property> + <property name="vexpand">True</property> + <property name="shadow_type">in</property> + <child> + <object class="GtkTextView" id="DESCRIPTIONS"> + <property name="visible">True</property> + <property name="sensitive">False</property> + <property name="can_focus">True</property> + <property name="hexpand">True</property> + <property name="vexpand">True</property> + <property name="editable">False</property> + <property name="wrap_mode">word</property> + <property name="cursor_visible">False</property> + <property name="accepts_tab">False</property> + <child internal-child="accessible"> + <object class="AtkObject" id="DESCRIPTIONS-atkobject"> + <property name="AtkObject::accessible-description" translatable="yes" context="updatedialog|extended_tip|DESCRIPTIONS">While checking for updates, you see a progress indicator. Wait for some messages to show up in the dialog, or click Cancel to abort the update check.</property> + </object> + </child> + </object> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">2</property> + </packing> + </child> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + </child> + <action-widgets> + <action-widget response="-11">help</action-widget> + <action-widget response="-5">ok</action-widget> + <action-widget response="-7">close</action-widget> + </action-widgets> + <child type="titlebar"> + <placeholder/> + </child> + <child internal-child="accessible"> + <object class="AtkObject" id="UpdateDialog-atkobject"> + <property name="AtkObject::accessible-description" translatable="yes" context="updatedialog|extended_tip|UpdateDialog">Click the Check for Updates button in the Extension Manager to check for online updates for all installed extensions. To check for online updates for only the selected extension, right-click to open the context menu, then choose Update.</property> + </object> + </child> + </object> +</interface> diff --git a/desktop/uiconfig/ui/updateinstalldialog.ui b/desktop/uiconfig/ui/updateinstalldialog.ui new file mode 100644 index 0000000000..15e7bcc398 --- /dev/null +++ b/desktop/uiconfig/ui/updateinstalldialog.ui @@ -0,0 +1,182 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Generated with glade 3.36.0 --> +<interface domain="dkt"> + <requires lib="gtk+" version="3.20"/> + <object class="GtkDialog" id="UpdateInstallDialog"> + <property name="can_focus">False</property> + <property name="border_width">6</property> + <property name="title" translatable="yes" context="updateinstalldialog|UpdateInstallDialog">Download and Installation</property> + <property name="modal">True</property> + <property name="default_width">0</property> + <property name="default_height">0</property> + <property name="type_hint">dialog</property> + <child internal-child="vbox"> + <object class="GtkBox" id="dialog-vbox1"> + <property name="can_focus">False</property> + <property name="orientation">vertical</property> + <property name="spacing">12</property> + <child internal-child="action_area"> + <object class="GtkButtonBox" id="dialog-action_area1"> + <property name="can_focus">False</property> + <property name="layout_style">end</property> + <child> + <object class="GtkButton" id="help"> + <property name="label" translatable="yes" context="stock">_Help</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="use-underline">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + <property name="secondary">True</property> + </packing> + </child> + <child> + <object class="GtkButton" id="ok"> + <property name="label" translatable="yes" context="stock">_OK</property> + <property name="visible">True</property> + <property name="sensitive">False</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="use-underline">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkButton" id="cancel"> + <property name="label" translatable="yes" context="stock">_Cancel</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="use-underline">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">2</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="pack_type">end</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkBox" id="box1"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="hexpand">True</property> + <property name="vexpand">True</property> + <property name="orientation">vertical</property> + <property name="spacing">6</property> + <child> + <object class="GtkLabel" id="DOWNLOADING"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="halign">start</property> + <property name="label" translatable="yes" context="updateinstalldialog|DOWNLOADING">Downloading extensions...</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">STATUSBAR</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkProgressBar" id="STATUSBAR"> + <property name="visible">True</property> + <property name="can_focus">False</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="EXTENSION_NAME"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="halign">start</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">2</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="RESULTS"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="halign">start</property> + <property name="label" translatable="yes" context="updateinstalldialog|RESULTS">Result</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">INFO</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">3</property> + </packing> + </child> + <child> + <object class="GtkScrolledWindow"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="hexpand">True</property> + <property name="vexpand">True</property> + <property name="shadow_type">in</property> + <child> + <object class="GtkTextView" id="INFO"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="hexpand">True</property> + <property name="vexpand">True</property> + <property name="editable">False</property> + <property name="cursor_visible">False</property> + </object> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">4</property> + </packing> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + </child> + <action-widgets> + <action-widget response="-11">help</action-widget> + <action-widget response="-5">ok</action-widget> + <action-widget response="-6">cancel</action-widget> + </action-widgets> + <child type="titlebar"> + <placeholder/> + </child> + <child internal-child="accessible"> + <object class="AtkObject" id="UpdateInstallDialog-atkobject"> + <property name="AtkObject::accessible-description" translatable="yes" context="updateinstalldialog|extended_tip|UpdateInstallDialog">Click the Check for Updates button in the Extensions dialog to check for online updates for all installed extensions. To check for online updates for only the selected extension, right-click to open the context menu, then choose Update.</property> + </object> + </child> + </object> +</interface> diff --git a/desktop/uiconfig/ui/updaterequireddialog.ui b/desktop/uiconfig/ui/updaterequireddialog.ui new file mode 100644 index 0000000000..12c85e95a0 --- /dev/null +++ b/desktop/uiconfig/ui/updaterequireddialog.ui @@ -0,0 +1,194 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Generated with glade 3.22.1 --> +<interface domain="dkt"> + <requires lib="gtk+" version="3.20"/> + <object class="GtkDialog" id="UpdateRequiredDialog"> + <property name="can_focus">False</property> + <property name="border_width">5</property> + <property name="title" translatable="yes" context="updaterequireddialog|UpdateRequiredDialog">Extension Update Required</property> + <property name="modal">True</property> + <property name="default_width">0</property> + <property name="default_height">0</property> + <property name="type_hint">dialog</property> + <child> + <placeholder/> + </child> + <child internal-child="vbox"> + <object class="GtkBox" id="dialog-vbox1"> + <property name="can_focus">False</property> + <property name="orientation">vertical</property> + <property name="spacing">12</property> + <child internal-child="action_area"> + <object class="GtkButtonBox" id="dialog-action_area1"> + <property name="can_focus">False</property> + <property name="layout_style">end</property> + <child> + <object class="GtkButton" id="ok"> + <property name="label" translatable="yes" context="updaterequireddialog|check">Check for _Updates...</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="can_default">True</property> + <property name="has_default">True</property> + <property name="receives_default">True</property> + <property name="use_underline">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkButton" id="disable"> + <property name="label" translatable="yes" context="updaterequireddialog|disable">Disable all</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="use_underline">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkButton" id="cancel"> + <property name="label" translatable="yes" context="stock">_Cancel</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="no_show_all">True</property> + <property name="use-underline">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">2</property> + </packing> + </child> + <child> + <object class="GtkButton" id="help"> + <property name="label" translatable="yes" context="stock">_Help</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="use-underline">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">3</property> + <property name="secondary">True</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="pack_type">end</property> + <property name="position">1</property> + </packing> + </child> + <child> + <!-- n-columns=1 n-rows=1 --> + <object class="GtkGrid" id="grid1"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="row_spacing">12</property> + <child> + <object class="GtkLabel" id="updatelabel"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label" translatable="yes" context="updaterequireddialog|updatelabel">%PRODUCTNAME has been updated to a new version. Some installed %PRODUCTNAME extensions are not compatible with this version and need to be updated before they can be used.</property> + <property name="wrap">True</property> + <property name="width_chars">95</property> + <property name="max_width_chars">95</property> + <property name="xalign">0</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">0</property> + </packing> + </child> + <child> + <object class="GtkScrolledWindow" id="scroll"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="hexpand">True</property> + <property name="vexpand">True</property> + <property name="hscrollbar_policy">never</property> + <property name="shadow_type">in</property> + <child> + <object class="GtkViewport"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <child> + <object class="GtkDrawingArea" id="extensions"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="events">GDK_BUTTON_MOTION_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_STRUCTURE_MASK</property> + <property name="hexpand">True</property> + <property name="vexpand">True</property> + </object> + </child> + </object> + </child> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">1</property> + </packing> + </child> + <child> + <!-- n-columns=1 n-rows=1 --> + <object class="GtkGrid" id="grid2"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="hexpand">True</property> + <property name="row_spacing">6</property> + <child> + <object class="GtkLabel" id="progresslabel"> + <property name="can_focus">False</property> + <property name="no_show_all">True</property> + <property name="label" translatable="yes" context="updaterequireddialog|progresslabel">Adding %EXTENSION_NAME</property> + <property name="use_underline">True</property> + <property name="xalign">0</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">0</property> + </packing> + </child> + <child> + <object class="GtkProgressBar" id="progress"> + <property name="can_focus">False</property> + <property name="no_show_all">True</property> + <property name="hexpand">True</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">1</property> + </packing> + </child> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">2</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + </object> + </child> + <action-widgets> + <action-widget response="101">ok</action-widget> + <action-widget response="102">disable</action-widget> + <action-widget response="-11">help</action-widget> + </action-widgets> + </object> +</interface> diff --git a/desktop/unx/source/args.c b/desktop/unx/source/args.c new file mode 100644 index 0000000000..199b58a8e5 --- /dev/null +++ b/desktop/unx/source/args.c @@ -0,0 +1,138 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +#include <stdlib.h> +#include <string.h> +#include <osl/process.h> + +#include "args.h" + +/* do we start -env: */ +static int +is_env_arg (rtl_uString const *str) +{ + return !rtl_ustr_ascii_compare_WithLength (str->buffer, 5, "-env:"); +} + +static const struct { + const char *name; + unsigned int bInhibitSplash : 1; + unsigned int bInhibitPagein : 1; + unsigned int bInhibitJavaLdx : 1; + unsigned int bInhibitPipe : 1; + const char *pPageinType; +} pArgDescr[] = { + /* have a trailing argument */ + { "pt", 1, 0, 0, 0, NULL }, + { "p", 1, 0, 0, 0, NULL }, + { "display", 0, 0, 0, 0, NULL }, + + /* no splash */ + { "nologo", 1, 0, 0, 0, NULL }, + { "headless", 1, 0, 0, 0, NULL }, + { "invisible", 1, 0, 0, 0, NULL }, + { "quickstart", 1, 0, 0, 0, NULL }, + { "minimized", 1, 0, 0, 0, NULL }, + { "convert-to", 1, 0, 0, 0, NULL }, + { "cat", 1, 0, 0, 0, NULL }, + + /* pagein bits */ + { "writer", 0, 0, 0, 0, "pagein-writer" }, + { "calc", 0, 0, 0, 0, "pagein-calc" }, + { "draw", 0, 0, 0, 0, "pagein-draw" }, + { "impress", 0, 0, 0, 0, "pagein-impress" }, + + /* Do not send --help/--version over the pipe, as their output shall go to + the calling process's stdout (ideally, this would also happen in the + presence of unknown options); also prevent splash/pagein/javaldx overhead + (as these options will be processed early in soffice_main): */ + { "version", 1, 1, 1, 1, NULL }, + { "help", 1, 1, 1, 1, NULL }, + { "h", 1, 1, 1, 1, NULL }, + { "?", 1, 1, 1, 1, NULL }, +}; + +Args *args_parse (void) +{ + Args *args; + sal_uInt32 nArgs, i, j; + + nArgs = osl_getCommandArgCount(); + i = sizeof (Args) + sizeof (rtl_uString *) * nArgs; + args = malloc (i); + memset (args, 0, i); + args->nArgsTotal = nArgs; + + j = 0; + + /* sort the -env: args to the front */ + for ( i = 0; i < nArgs; ++i ) + { + rtl_uString *pTmp = NULL; + osl_getCommandArg( i, &pTmp ); + if (is_env_arg (pTmp)) + args->ppArgs[j++] = pTmp; + else + rtl_uString_release (pTmp); + } + args->nArgsEnv = j; + + /* Then the other args */ + for ( i = 0; i < nArgs; ++i ) + { + rtl_uString *pTmp = NULL; + + osl_getCommandArg( i, &pTmp ); + if (!is_env_arg (pTmp)) + args->ppArgs[j++] = pTmp; + else + rtl_uString_release (pTmp); + } + + for ( i = args->nArgsEnv; i < args->nArgsTotal; i++ ) + { + const sal_Unicode *arg = args->ppArgs[i]->buffer; + sal_Int32 length = args->ppArgs[i]->length; + + /* grok only parameters */ + if (arg[0] != '-') + continue; + + while (length > 1 && arg[0] == '-') { + arg++; + length--; + } + + for ( j = 0; j < SAL_N_ELEMENTS (pArgDescr); ++j ) { + if (rtl_ustr_ascii_compare_WithLength( + arg, length, pArgDescr[j].name) + == 0) + { + args->bInhibitSplash |= pArgDescr[j].bInhibitSplash; + args->bInhibitPagein |= pArgDescr[j].bInhibitPagein; + args->bInhibitJavaLdx |= pArgDescr[j].bInhibitJavaLdx; + args->bInhibitPipe |= pArgDescr[j].bInhibitPipe; + if (pArgDescr[j].pPageinType) + args->pPageinType = pArgDescr[j].pPageinType; + break; + } + } + } + + return args; +} + +void +args_free (Args *args) +{ + /* FIXME: free ppArgs */ + rtl_uString_release( args->pAppPath ); + free (args); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/unx/source/args.h b/desktop/unx/source/args.h new file mode 100644 index 0000000000..f0fe7ce39b --- /dev/null +++ b/desktop/unx/source/args.h @@ -0,0 +1,28 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +#include <sal/types.h> +#include <rtl/ustring.h> + +typedef struct { + rtl_uString *pAppPath; + const char *pPageinType; // @pagein-writer for - writer etc. else NULL + sal_Bool bInhibitSplash; // should we show a splash screen + sal_Bool bInhibitPagein; // should we run pagein ? + sal_Bool bInhibitJavaLdx; // should we run javaldx ? + sal_Bool bInhibitPipe; // for --help and --version + + sal_uInt32 nArgsEnv; // number of -env: style args + sal_uInt32 nArgsTotal; // number of -env: as well as -writer style args + rtl_uString *ppArgs[1]; // sorted argument array +} Args; + +Args *args_parse (void); +void args_free (Args *args); + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/unx/source/file_image.h b/desktop/unx/source/file_image.h new file mode 100644 index 0000000000..6f0194d17e --- /dev/null +++ b/desktop/unx/source/file_image.h @@ -0,0 +1,69 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#pragma once + +#ifndef INCLUDED_STDDEF_H +#include <stddef.h> +#define INCLUDED_STDDEF_H +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/** file_image. + */ +struct file_image_st +{ + void * m_base; + size_t m_size; +}; + +typedef struct file_image_st file_image; + +#define FILE_IMAGE_INITIALIZER { NULL, 0 } + + +/** file_image_open. + */ +int file_image_open ( + file_image * image, + const char * filename); + + +/** file_image_pagein. + */ +int file_image_pagein ( + file_image * image); + + +/** file_image_close. + */ +int file_image_close ( + file_image * image); + + +/** Epilog. + */ +#ifdef __cplusplus +} +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/unx/source/file_image_unx.c b/desktop/unx/source/file_image_unx.c new file mode 100644 index 0000000000..4294a57611 --- /dev/null +++ b/desktop/unx/source/file_image_unx.c @@ -0,0 +1,121 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include "file_image.h" + +#include <unistd.h> + +#include <errno.h> +#include <fcntl.h> + +#include <sys/mman.h> +#include <sys/stat.h> + +#include <string.h> + +/* + * file_image_open + */ +int file_image_open (file_image * image, const char * filename) +{ + int result = 0; + int fd; + struct stat st; + void * p; + + if (image == NULL) + return EINVAL; + + image->m_base = MAP_FAILED; + image->m_size = 0; + + if ((fd = open (filename, O_RDONLY)) == -1) + return errno; + + if (fstat (fd, &st) == -1) + { + result = errno; + goto cleanup_and_leave; + } + + p = mmap (NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0); + if (p == MAP_FAILED) + { + result = errno; + goto cleanup_and_leave; + } + + image->m_base = p; + image->m_size = st.st_size; + +cleanup_and_leave: + close (fd); + return result; +} + +/* + * file_image_pagein. + */ +int file_image_pagein (file_image * image) +{ + long s = -1; + volatile char c =0; + size_t idx; + + if (image == NULL) + return EINVAL; + if (image->m_base == NULL) + return EINVAL; + if (image->m_size == 0) + return 0; + + if (madvise (image->m_base, image->m_size, MADV_WILLNEED) == -1) + return errno; + + s = sysconf (_SC_PAGESIZE); + if (s == -1) + s = 0x1000; + // force touching of each page despite the optimizer + for(idx = 0; idx < image->m_size; idx += (size_t)s) + { + c ^= ((volatile const char*)(image->m_base))[idx]; + } + c ^= ((volatile const char*)(image->m_base))[image->m_size-1]; + (void)c; // silence Clang 13 trunk -Wunused-but-set-variable + + return 0; +} + +/* + * file_image_close + */ +int file_image_close (file_image * image) +{ + if (image == NULL) + return EINVAL; + + if (munmap (image->m_base, image->m_size) == -1) + return errno; + + image->m_base = NULL; + image->m_size = 0; + return 0; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/unx/source/pagein.c b/desktop/unx/source/pagein.c new file mode 100644 index 0000000000..87bbb699f8 --- /dev/null +++ b/desktop/unx/source/pagein.c @@ -0,0 +1,102 @@ +/* -*- 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 "file_image.h" +#include "pagein.h" + +#include <errno.h> +#include <stdio.h> +#include <string.h> +#include <sys/stat.h> +#ifdef LINUX +#include <sys/sysmacros.h> +#endif + +/* do_pagein */ +static void do_pagein (const char * filename) +{ + int result; + file_image image = FILE_IMAGE_INITIALIZER; + + if (file_image_open (&image, filename) != 0) + return; + + if ((result = file_image_pagein (&image)) != 0) + { + fprintf (stderr, "file_image_pagein %s: %s\n", filename, strerror(result)); + } + + file_image_close (&image); +} + +static int isRotational(char const * path) +{ + int ret = 1; +#ifdef LINUX + FILE * fp = NULL; + char fullpath[4096]; + struct stat out; + int major, minor; + char type; + if (stat(path , &out) == -1) + return ret; + major = major(out.st_dev); + minor = 0; /* minor(out.st_dev); only the device itself has a queue */ + sprintf(fullpath,"/sys/dev/block/%d:%d/queue/rotational",major,minor); + if ((fp = fopen(fullpath, "r")) == NULL) + return ret; + if (fgets(&type, 1, fp)) + ret = type == '1'; + fclose(fp); +#endif + return ret; +} + +void pagein_execute(char const * path, char const * file) +{ + char fullpath[4096]; + char *p = NULL; + FILE * fp = NULL; + if(!isRotational(path)) + return; + memset(fullpath, 0, sizeof(fullpath)); + strncpy (fullpath, path, 3000); + if (!(p = strrchr (fullpath, '/'))) + p = fullpath; + else + p++; + strncpy(p, file, 1024); + p[strlen(p)] = '\0'; + if ((fp = fopen (fullpath, "r")) == NULL) + { + + fprintf (stderr, "fopen %s: %s\n", fullpath, strerror(errno)); + return; + } + while (fgets (p, 1024, fp) != NULL) + { + p[strlen(p) - 1] = '\0'; + + /* paths relative to the location of the pagein file */ + do_pagein (fullpath); + } + fclose (fp); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/unx/source/pagein.h b/desktop/unx/source/pagein.h new file mode 100644 index 0000000000..7fb8b6a918 --- /dev/null +++ b/desktop/unx/source/pagein.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 <config_features.h> + +#if HAVE_FEATURE_PAGEIN +void pagein_execute(char const* path, char const* file); +#else +inline void pagein_execute(char const* path, char const* file) +{ + (void)path; + (void)file; +} +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/unx/source/splashx.c b/desktop/unx/source/splashx.c new file mode 100644 index 0000000000..4dc50a7406 --- /dev/null +++ b/desktop/unx/source/splashx.c @@ -0,0 +1,811 @@ +/* -*- 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 <config_features.h> +#include "splashx.h" + +#if defined(ENABLE_QUICKSTART_LIBPNG) && HAVE_FEATURE_UI + +#include <X11/Xlib.h> +#include <X11/Xatom.h> +#include <X11/Xutil.h> + +#include <X11/extensions/Xinerama.h> + +#include <osl/endian.h> +#include <fcntl.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include <png.h> + +#include <osl/process.h> +#include <osl/thread.h> +#include <rtl/bootstrap.h> +#include <rtl/ustrbuf.h> + +typedef struct { + unsigned char b, g, r; +} color_t; + +struct splash +{ + Display* display; + int screen; + int depth; + int display_width; + int display_height; + int display_x_pos; + int display_y_pos; + Visual* visual; + + int width; + int height; + + Colormap color_map; + Window win; + GC gc; + //true when intro-highres loaded successfully + sal_Bool bHasHiDpiImage; + +// Progress bar values +// taken from desktop/source/splash/splash.cxx + int tlx; + int tly; + int barwidth; + int barheight; + int barspace; + color_t barcol; + color_t framecol; + + XColor barcolor; + XColor framecolor; + + unsigned char** bitmap_rows; + png_structp png_ptr; + png_infop info_ptr; + +}; + +#define WINDOW_WIDTH 440 +#define WINDOW_HEIGHT 299 + +#define PROGRESS_XOFFSET 12 +#define PROGRESS_YOFFSET 18 +#define PROGRESS_BARSPACE 2 + +/* libpng-1.2.41 */ +#ifndef PNG_TRANSFORM_GRAY_TO_RGB +# define PNG_TRANSFORM_GRAY_TO_RGB 0x2000 +#endif + +static int splash_load_bmp( struct splash* splash, const char *filename ) +{ + FILE *file; + + if ( !(file = fopen( filename, "r" ) ) ) + return 0; + + splash->png_ptr = png_create_read_struct( PNG_LIBPNG_VER_STRING, NULL, NULL, NULL ); + splash->info_ptr = png_create_info_struct(splash->png_ptr); + png_init_io( splash->png_ptr, file ); + + if( setjmp( png_jmpbuf( splash->png_ptr ) ) ) + { + png_destroy_read_struct( &(splash->png_ptr), &(splash->info_ptr), NULL ); + fclose( file ); + return 0; + } + + png_read_png( splash->png_ptr, splash->info_ptr, + PNG_TRANSFORM_EXPAND | PNG_TRANSFORM_STRIP_ALPHA | + PNG_TRANSFORM_GRAY_TO_RGB | PNG_TRANSFORM_BGR, NULL); + + splash->bitmap_rows = png_get_rows( splash->png_ptr, splash->info_ptr ); + splash->width = png_get_image_width( splash->png_ptr, splash->info_ptr ); + splash->height = png_get_image_height( splash->png_ptr, splash->info_ptr ); + + fclose( file ); + return 1; +} + +static void setup_color( int const val[3], color_t *col ) +{ + if ( val[0] < 0 || val[1] < 0 || val[2] < 0 ) + return; + +#define CONVERT_COLOR( from,to ) if ( from < 0 ) to = 0; else if ( from > 255 ) to = 255; else to = from; + CONVERT_COLOR( val[0], col->r ); + CONVERT_COLOR( val[1], col->g ); + CONVERT_COLOR( val[2], col->b ); +#undef CONVERT_COLOR +} + +/* Fill 'array' with values of the key 'name'. + Its value is a comma delimited list of integers */ +static void get_bootstrap_value( int *array, int size, rtlBootstrapHandle handle, const char *name ) +{ + rtl_uString *pKey = NULL, *pValue = NULL; + + /* get the value from the ini file */ + rtl_uString_newFromAscii( &pKey, name ); + rtl_bootstrap_get_from_handle( handle, pKey, &pValue, NULL ); + + /* the value is several numbers delimited by ',' - parse it */ + if ( rtl_uString_getLength( pValue ) > 0 ) + { + rtl_uString *pToken = NULL; + int i = 0; + sal_Int32 nIndex = 0; + for ( ; ( nIndex >= 0 ) && ( i < size ); ++i ) + { + nIndex = rtl_uString_getToken( &pToken, pValue, 0, ',', nIndex ); + array[i] = rtl_ustr_toInt32( rtl_uString_getStr( pToken ), 10 ); + } + + rtl_uString_release( pToken ); + } + + /* cleanup */ + rtl_uString_release( pKey ); + rtl_uString_release( pValue ); +} + +// setup +static void splash_setup( struct splash* splash, int const barc[3], int const framec[3], int posx, int posy, int w, int h ) +{ + if ( splash->width <= 500 ) + { + splash->barwidth = splash->width - ( 2 * PROGRESS_XOFFSET ); + splash->barheight = 6; + splash->tlx = PROGRESS_XOFFSET; + splash->tly = splash->height - PROGRESS_YOFFSET; + + splash->barcol.r = 0; + splash->barcol.g = 0; + splash->barcol.b = 128; + } + + if ( posx >= 0 ) + splash->tlx = posx; + if ( posy >= 0 ) + splash->tly = posy; + if ( w >= 0 ) + splash->barwidth = w; + if ( h >= 0 ) + splash->barheight = h; + + setup_color( barc, &(splash->barcol) ); + setup_color( framec, &(splash->framecol) ); +} + +// Universal shift: bits >= 0 - left, otherwise right +#define SHIFT( x, bits ) ( ( (bits) >= 0 )? ( (x) << (bits) ): ( (x) >> -(bits) ) ) + +// Position of the highest bit (more or less integer log2) +static int HIGHEST_BIT( unsigned long x ) +{ + int i = 0; + for ( ; x; ++i ) + x >>= 1; + + return i; +} + +// Number of bits set to 1 +static int BITS( unsigned long x ) +{ + int i = 0; + for ( ; x; x >>= 1 ) + if ( x & 1UL ) + ++i; + + return i; +} + +// Set 'bitmap' as the background of our 'win' window +static void create_pixmap(struct splash* splash) +{ + Pixmap pixmap; + GC pixmap_gc; + XGCValues values; + + if ( !splash->bitmap_rows ) + { + return; + } + pixmap = XCreatePixmap( splash->display, splash->win, splash->width, splash->height, splash->depth ); + + pixmap_gc = XCreateGC( splash->display, pixmap, 0/*value_mask*/, &values ); + + if ( splash->visual->class == TrueColor ) + { + const unsigned long red_mask = splash->visual->red_mask; + const unsigned long green_mask = splash->visual->green_mask; + const unsigned long blue_mask = splash->visual->blue_mask; + + const unsigned long red_delta_mask = ( 1UL << ( 8 - BITS( red_mask ) ) ) - 1; + const unsigned long green_delta_mask = ( 1UL << ( 8 - BITS( green_mask ) ) ) - 1; + const unsigned long blue_delta_mask = ( 1UL << ( 8 - BITS( blue_mask ) ) ) - 1; + + const int red_shift = HIGHEST_BIT( red_mask ) - 8; + const int green_shift = HIGHEST_BIT( green_mask ) - 8; + const int blue_shift = HIGHEST_BIT( blue_mask ) - 8; + + XImage* image = XCreateImage( splash->display, splash->visual, splash->depth, ZPixmap, + 0, NULL, splash->width, splash->height, 32, 0 ); + + const int bytes_per_line = image->bytes_per_line; + const int bpp = image->bits_per_pixel; + const int byte_order = image->byte_order; +#if defined OSL_LITENDIAN + const int machine_byte_order = LSBFirst; +#else /* OSL_BIGENDIAN */ + const int machine_byte_order = MSBFirst; +#endif + + char *data = malloc( splash->height * bytes_per_line ); + char *out = data; + image->data = data; + + // The following dithers & converts the color_t color to one + // acceptable for the visual +#define COPY_IN_OUT( pix_size, code ) \ + { \ + int x, y; \ + for ( y = 0; y < splash->height; ++y ) \ + { \ + unsigned long red_delta = 0, green_delta = 0, blue_delta = 0; \ + color_t *in = (color_t *)(splash->bitmap_rows[y]); \ + out = data + y * bytes_per_line; \ + for ( x = 0; x < splash->width; ++x, ++in ) \ + { \ + unsigned long red = in->r + red_delta; \ + unsigned long green = in->g + green_delta; \ + unsigned long blue = in->b + blue_delta; \ + unsigned long pixel = 0; \ + uint32_t tmp = 0; \ + (void) tmp; \ + red_delta = red & red_delta_mask; \ + green_delta = green & green_delta_mask; \ + blue_delta = blue & blue_delta_mask; \ + if ( red > 255 ) \ + red = 255; \ + if ( green > 255 ) \ + green = 255; \ + if ( blue > 255 ) \ + blue = 255; \ + pixel = \ + ( SHIFT( red, red_shift ) & red_mask ) | \ + ( SHIFT( green, green_shift ) & green_mask ) | \ + ( SHIFT( blue, blue_shift ) & blue_mask ); \ + code \ + } \ + } \ + } + + if ( bpp == 32 ) + { + if ( machine_byte_order == byte_order ) + COPY_IN_OUT( 4, *( (uint32_t *)out ) = (uint32_t)pixel; out += 4; ) + else + COPY_IN_OUT( 4, tmp = pixel; + *( (uint8_t *)out ) = *( (uint8_t *)(&tmp) + 3 ); + *( (uint8_t *)out + 1 ) = *( (uint8_t *)(&tmp) + 2 ); + *( (uint8_t *)out + 2 ) = *( (uint8_t *)(&tmp) + 1 ); + *( (uint8_t *)out + 3 ) = *( (uint8_t *)(&tmp) ); + out += 4; ) + } + else if ( bpp == 24 ) + { + if (machine_byte_order == byte_order) + { +#if defined OSL_LITENDIAN + COPY_IN_OUT( 3, memcpy(out, &pixel, sizeof (color_t)); out += 3; ) +#else /* OSL_BIGENDIAN */ + COPY_IN_OUT( 3, tmp = pixel; + *( (uint8_t *)out ) = *( (uint8_t *)(&tmp) + 1 ); + *( (uint8_t *)out + 1 ) = *( (uint8_t *)(&tmp) + 2 ); + *( (uint8_t *)out + 2 ) = *( (uint8_t *)(&tmp) + 3 ); + out += 3; ) +#endif + } + else + COPY_IN_OUT( 3, tmp = pixel; + *( (uint8_t *)out ) = *( (uint8_t *)(&tmp) + 3 ); + *( (uint8_t *)out + 1 ) = *( (uint8_t *)(&tmp) + 2 ); + *( (uint8_t *)out + 2 ) = *( (uint8_t *)(&tmp) + 1 ); + out += 3; ) + } + else if ( bpp == 16 ) + { + if ( machine_byte_order == byte_order ) + COPY_IN_OUT( 2, *( (uint16_t *)out ) = (uint16_t)pixel; out += 2; ) + else + COPY_IN_OUT( 2, tmp = pixel; + *( (uint8_t *)out ) = *( (uint8_t *)(&tmp) + 1 ); + *( (uint8_t *)out + 1 ) = *( (uint8_t *)(&tmp) ); + out += 2; ); + } + else if ( bpp == 8 ) + { + COPY_IN_OUT( 1, *( (uint8_t *)out ) = (uint8_t)pixel; ++out; ) + } + else + { + fprintf( stderr, "Unsupported depth: %d bits per pixel.\n", bpp ); + XFreeGC( splash->display, pixmap_gc ); + XFreePixmap( splash->display, pixmap ); + XDestroyImage( image ); + return; + } + +#undef COPY_IN_OUT + + XPutImage( splash->display, pixmap, pixmap_gc, image, 0, 0, 0, 0, splash->width, splash->height ); + XDestroyImage( image ); + } + + XSetWindowBackgroundPixmap( splash->display, splash->win, pixmap ); + + XFreeGC( splash->display, pixmap_gc ); + XFreePixmap( splash->display, pixmap ); +} + +// The old method of hiding the window decorations +static void suppress_decorations_motif(struct splash* splash) +{ + struct + { + unsigned long flags, functions, decorations; + long input_mode; + } mwmhints; + + Atom a = XInternAtom( splash->display, "_MOTIF_WM_HINTS", False ); + + mwmhints.flags = 15; // functions, decorations, input_mode, status + mwmhints.functions = 2; // ? + mwmhints.decorations = 0; + mwmhints.input_mode = 0; + + XChangeProperty( splash->display, splash->win, a, a, 32, + PropModeReplace, (unsigned char*)&mwmhints, 5 ); +} + +// This is a splash, set it as such. +// If it fails, just hide the decorations... +static void suppress_decorations(struct splash* splash) +{ + Atom atom_type = XInternAtom( splash->display, "_NET_WM_WINDOW_TYPE", True ); + Atom atom_splash = XInternAtom( splash->display, "_NET_WM_WINDOW_TYPE_SPLASH", True ); + + if ( atom_type != None && atom_splash != None ) + XChangeProperty( splash->display, splash->win, atom_type, XA_ATOM, 32, + PropModeReplace, (unsigned char*)&atom_splash, 1 ); + //else + suppress_decorations_motif(splash); // FIXME: Unconditional until Metacity/compiz's SPLASH handling is fixed +} + +/** + * Connects to the display and initializes splash with the screen details + * + * @return Success: 1; Failure: 0 + */ +static int splash_init_display( struct splash* splash, int argc, char** argv ) +{ + char *display_name = NULL; + int i; + int n_xinerama_screens = 1; + XineramaScreenInfo* p_screens = NULL; + + for ( i = 0; i < argc; i++ ) + { + if ( !strcmp( argv[i], "-display" ) || !strcmp( argv[i], "--display" ) ) + { + display_name = ( i + 1 < argc )? argv[i+1]: NULL; + } + } + + if ( !display_name ) + { + display_name = getenv( "DISPLAY" ); + } + // init display + splash->display = XOpenDisplay( display_name ); + if ( !splash->display ) + { + fprintf( stderr, "Failed to open display\n" ); + return 0; + } + + // create the window + splash->screen = DefaultScreen( splash->display ); + splash->depth = DefaultDepth( splash->display, splash->screen ); + splash->color_map = DefaultColormap( splash->display, splash->screen ); + splash->visual = DefaultVisual( splash->display, splash->screen ); + + splash->display_width = DisplayWidth( splash->display, splash->screen ); + splash->display_height = DisplayHeight( splash->display, splash->screen ); + splash->display_x_pos = 0; + splash->display_y_pos = 0; + + p_screens = XineramaQueryScreens( splash->display, &n_xinerama_screens ); + if( p_screens ) + { + for( i=0; i < n_xinerama_screens; i++ ) + { + if ( p_screens[i].screen_number == splash->screen ) + { + splash->display_width = p_screens[i].width; + splash->display_height = p_screens[i].height; + splash->display_x_pos = p_screens[i].x_org; + splash->display_y_pos = p_screens[i].y_org; + break; + } + } + XFree( p_screens ); + } + return 1; +} + +/** + * Create the window for the splash screen + * + * @return Success: 1; Failure: 0 + */ +static int splash_create_window(struct splash* splash) +{ + Window root_win; + XGCValues values; + const char* name = "LibreOffice"; + const char* icon = "icon"; // FIXME + XSizeHints size_hints; + + root_win = RootWindow( splash->display, splash->screen ); + + splash->win = XCreateSimpleWindow( splash->display, root_win, + (splash->display_x_pos + (splash->display_width - splash->width)/2), + (splash->display_y_pos + (splash->display_height - splash->height)/2), + splash->width, splash->height, 0, + BlackPixel( splash->display, splash->screen ), BlackPixel( splash->display, splash->screen ) ); + + XSetWindowColormap( splash->display, splash->win, splash->color_map ); + + // setup colors +#define FILL_COLOR( xcol,col ) xcol.red = 256*col.r; xcol.green = 256*col.g; xcol.blue = 256*col.b; + FILL_COLOR( splash->barcolor, splash->barcol ); + FILL_COLOR( splash->framecolor, splash->framecol ); +#undef FILL_COLOR + + XAllocColor( splash->display, splash->color_map, &(splash->barcolor) ); + XAllocColor( splash->display, splash->color_map, &(splash->framecolor) ); + + // not resizable, no decorations, etc. + splash->gc = XCreateGC( splash->display, splash->win, 0/*value_mask*/, &values ); + + size_hints.flags = PPosition | PSize | PMinSize | PMaxSize; + size_hints.x = splash->display_x_pos; + size_hints.y = splash->display_y_pos; + size_hints.width = splash->width; + size_hints.height = splash->height; + size_hints.min_width = splash->width; + size_hints.max_width = splash->width; + size_hints.min_height = splash->height; + size_hints.max_height = splash->height; + + XSetStandardProperties( splash->display, splash->win, name, icon, None, + NULL, 0, &size_hints ); + + // the actual work + suppress_decorations(splash); + create_pixmap(splash); + + // show it + XSelectInput( splash->display, splash->win, 0 ); + XMapWindow( splash->display, splash->win ); + + return 1; +} + +// Re-draw & process the events +// Just throwing them away - we do not need anything more... +static void process_events(struct splash* splash) +{ + XEvent xev; + int num_events; + + XFlush( splash->display ); + num_events = XPending( splash->display ); + while ( num_events > 0 ) + { + num_events--; + XNextEvent( splash->display, &xev ); + } +} + + +static rtl_String* ustr_to_str( rtl_uString* pStr ) +{ + rtl_String *pOut = NULL; + + rtl_uString2String( &pOut, rtl_uString_getStr( pStr ), + rtl_uString_getLength( pStr ), osl_getThreadTextEncoding(), OUSTRING_TO_OSTRING_CVTFLAGS ); + + return pOut; +} + +static sal_Bool isHiDPI(struct splash* splash) +{ + const char* pValStr; + double nDPI; + + /* + * GNOME currently enables HiDPI support when the screen resolution is at least 192 dpi + * and the screen height (in device pixels) is at least 1200. + */ + + if (splash->display_height < 1200) + return sal_False; + + pValStr = XGetDefault(splash->display, "Xft", "dpi"); + /* if it's too old to have this, assume it's not hidpi */ + if (!pValStr) + return sal_False; + + nDPI = strtod(pValStr, NULL); + if (nDPI < 192) + return sal_False; + + return sal_True; +} + +#define IMG_SUFFIX ".png" + +static void splash_load_image( struct splash* splash, rtl_uString* pUAppPath ) +{ + /* FIXME-BCP47: if we wanted to support language tags here that would get + * complicated, this is C-source not C++ so LanguageTag can't be used. For + * now the splash screen will have to get along with language-territory. */ + + char *pBuffer, *pSuffix, *pLocale; + int nLocSize; + rtl_Locale *pLoc = NULL; + rtl_String *pLang, *pCountry, *pAppPath; + + osl_getProcessLocale (&pLoc); + pLang = ustr_to_str (pLoc->Language); + pCountry = ustr_to_str (pLoc->Country); + + nLocSize = strlen (pLang->buffer) + strlen (pCountry->buffer) + 3; + pLocale = malloc (nLocSize); + pLocale[0] = '-'; + strcpy (pLocale + 1, pLang->buffer); + strcat (pLocale, "_"); + strcat (pLocale, pCountry->buffer); + + rtl_string_release( pCountry ); + rtl_string_release( pLang ); + + pAppPath = ustr_to_str (pUAppPath); + pBuffer = malloc (pAppPath->length + nLocSize + 256); + strcpy (pBuffer, pAppPath->buffer); + pSuffix = pBuffer + pAppPath->length; + rtl_string_release( pAppPath ); + + strcpy (pSuffix, "intro"); + strcat (pSuffix, pLocale); + strcat (pSuffix, IMG_SUFFIX); + if ( splash_load_bmp( splash, pBuffer ) ) + goto cleanup; /* success */ + + /* load high resolution splash image */ + splash->bHasHiDpiImage = sal_False; + if (isHiDPI(splash)) + { + strcpy (pSuffix, "intro-highres" IMG_SUFFIX); + if ( splash_load_bmp( splash, pBuffer ) ) + { + splash->bHasHiDpiImage = sal_True; + goto cleanup; /* success */ + } + } + /* load standard resolution splash image */ + strcpy (pSuffix, "intro" IMG_SUFFIX); + if ( splash_load_bmp( splash, pBuffer ) ) + goto cleanup; /* success */ + + fprintf (stderr, "Failed to find intro image\n"); + + cleanup: + free (pLocale); + free (pBuffer); +} + +/* Load the colors and size of the splash. */ +static void splash_load_defaults( struct splash* splash, rtl_uString* pAppPath, sal_Bool* bNoDefaults ) +{ + rtl_uString *pSettings = NULL, *pTmp = NULL; + rtlBootstrapHandle handle; + int logo[1] = { -1 }, + bar[3] = { -1, -1, -1 }, + frame[3] = { -1, -1, -1 }, + pos[2] = { -1, -1 }, + size[2] = { -1, -1 }; + + /* construct the sofficerc file location */ + rtl_uString_newFromAscii( &pSettings, "file://" ); + rtl_uString_newConcat( &pSettings, pSettings, pAppPath ); + rtl_uString_newConcat( &pSettings, pSettings, pTmp ); + rtl_uString_newFromAscii( &pTmp, SAL_CONFIGFILE( "soffice" ) ); + rtl_uString_newConcat( &pSettings, pSettings, pTmp ); + + /* use it as the bootstrap file */ + handle = rtl_bootstrap_args_open( pSettings ); + + /* get the values */ + get_bootstrap_value( logo, 1, handle, "Logo" ); + get_bootstrap_value( bar, 3, handle, "ProgressBarColor" ); + get_bootstrap_value( frame, 3, handle, "ProgressFrameColor" ); + if (isHiDPI(splash) && splash->bHasHiDpiImage) + { + get_bootstrap_value( pos, 2, handle, "ProgressPositionHigh" ); + get_bootstrap_value( size, 2, handle, "ProgressSizeHigh" ); + } + else + { + get_bootstrap_value( pos, 2, handle, "ProgressPosition" ); + get_bootstrap_value( size, 2, handle, "ProgressSize" ); + } + + if ( logo[0] == 0 ) + { + *bNoDefaults = sal_True; + } + + splash_setup( splash, bar, frame, pos[0], pos[1], size[0], size[1] ); + + /* cleanup */ + rtl_bootstrap_args_close( handle ); + rtl_uString_release( pSettings ); + rtl_uString_release( pTmp ); +} + + +// Draw the progress +void splash_draw_progress( struct splash* splash, int progress ) +{ + int length = 0; + + if (!splash) + { + return; + } + // sanity + if ( progress < 0 ) + { + progress = 0; + } + if ( progress > 100 ) + { + progress = 100; + } + // draw progress... + length = ( progress * splash->barwidth / 100 ) - ( 2 * splash->barspace ); + if ( length < 0 ) + { + length = 0; + } + // border + XSetForeground( splash->display, splash->gc, splash->framecolor.pixel ); + XDrawRectangle( splash->display, splash->win, splash->gc, splash->tlx, splash->tly, + splash->barwidth, splash->barheight ); + + // progress bar + XSetForeground( splash->display, splash->gc, splash->barcolor.pixel ); + XFillRectangle( splash->display, splash->win, splash->gc, + splash->tlx + splash->barspace, splash->tly + splash->barspace, + length + 1, splash->barheight - 2 * splash->barspace + 1 ); + + // pending events + process_events(splash); +} + +void splash_destroy(struct splash* splash) +{ + if(!splash) + return; + + if(splash->display) + { + if(splash->gc) + { + XFreeGC(splash->display, splash->gc); + splash->gc = NULL; + } + + XCloseDisplay( splash->display ); + splash->display = NULL; + png_destroy_read_struct( &(splash->png_ptr), &(splash->info_ptr), NULL ); + } + free(splash); +} + +struct splash* splash_create(rtl_uString* pAppPath, int argc, char** argv) +{ + struct splash* splash; + sal_Bool bNoDefaults = sal_False; + + splash = calloc(1, sizeof(struct splash)); + if (splash && !splash_init_display(splash, argc, argv)) + { + splash_destroy(splash); + splash = NULL; + } + + if (!splash) + return NULL; + + splash->width = WINDOW_WIDTH; + splash->height = WINDOW_HEIGHT; + + splash->tlx = 212; + splash->tly = 216; + splash->barwidth = 263; + splash->barheight = 8; + splash->barspace = PROGRESS_BARSPACE; + splash->barcol.b = 18; + splash->barcol.g = 202; + splash->barcol.r = 157; + splash->framecol.b = 0xD3; + splash->framecol.g = 0xD3; + splash->framecol.r = 0xD3; + + splash_load_image( splash, pAppPath ); + splash_load_defaults( splash, pAppPath, &bNoDefaults ); + + if (!bNoDefaults && splash_create_window(splash)) + { + splash_draw_progress( splash, 0 ); + return splash; + } + + splash_destroy(splash); + return NULL; +} + +#else /* not ENABLE_QUICKSTART_LIBPNG */ + +struct splash +{ +}; + +/* Stubs that will never be called in this case */ +void splash_draw_progress( struct splash* splash, int progress ) +{ + (void)splash; (void)progress; +} + +void splash_destroy(struct splash* splash) +{ + (void)splash; +} + +struct splash* splash_create(rtl_uString* pAppPath, int argc, char** argv) +{ + (void)pAppPath; (void)argc; (void)argv; + return NULL; +} + + +#endif // defined(ENABLE_QUICKSTART_LIBPNG) && HAVE_FEATURE_UI + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/unx/source/splashx.h b/desktop/unx/source/splashx.h new file mode 100644 index 0000000000..0ca1ba6559 --- /dev/null +++ b/desktop/unx/source/splashx.h @@ -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/. + */ +#pragma once + +#include <rtl/ustring.h> + +#ifdef __cplusplus +extern "C" { +#endif + +struct splash; + +struct splash* splash_create(rtl_uString* pAppPath, int argc, char** argv); + +void splash_destroy(struct splash* splash); + +void splash_draw_progress(struct splash* splash, int progress); + +#ifdef __cplusplus +} // extern "C" +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/unx/source/start.c b/desktop/unx/source/start.c new file mode 100644 index 0000000000..0bb009d9c5 --- /dev/null +++ b/desktop/unx/source/start.c @@ -0,0 +1,887 @@ +/* -*- 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 <config_java.h> + +#include <signal.h> +#include <unistd.h> +#include <limits.h> +#include <stdlib.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/socket.h> +#include <arpa/inet.h> +#include <sys/un.h> +#include <poll.h> +#include <fcntl.h> +#include <stdio.h> +#include <libgen.h> +#include <string.h> +#include <errno.h> + +#include <desktop/exithelper.h> +#include <osl/process.h> +#include <osl/thread.h> +#include <rtl/bootstrap.h> +#include <rtl/digest.h> +#include <rtl/process.h> +#include <rtl/ustrbuf.h> +#include <sal/main.h> + +#include "args.h" +#include "pagein.h" +#include "splashx.h" + +#define PIPEDEFAULTPATH "/tmp" +#define PIPEALTERNATEPATH "/var/tmp" + +/* Easier conversions: rtl_uString to rtl_String */ +static rtl_String *ustr_to_str(rtl_uString *pStr) +{ + rtl_String *pOut = NULL; + + rtl_uString2String(&pOut, rtl_uString_getStr(pStr), + rtl_uString_getLength(pStr), osl_getThreadTextEncoding(), OUSTRING_TO_OSTRING_CVTFLAGS); + + return pOut; +} + +/* Easier conversions: char * to rtl_uString */ +static rtl_uString *charp_to_ustr(const char *pStr) +{ + rtl_uString *pOut = NULL; + + rtl_string2UString(&pOut, pStr, strlen(pStr), osl_getThreadTextEncoding(), OSTRING_TO_OUSTRING_CVTFLAGS); + + return pOut; +} + +typedef struct { + int status_fd; + oslProcess child; +} ChildInfo; + +static int +child_info_get_status_fd(ChildInfo const *info) +{ + return info->status_fd; +} + +static void +child_info_destroy(ChildInfo *info) +{ + close (info->status_fd); + osl_freeProcessHandle (info->child); + free (info); +} + +static ChildInfo * child_spawn(Args *args, sal_Bool bAllArgs, sal_Bool bWithStatus) +{ + rtl_uString *pApp = NULL, *pTmp = NULL; + rtl_uString **ppArgs; + sal_uInt32 nArgs, i; + ChildInfo *info; + int status_pipe[2]; + oslProcessError nError; + + info = calloc (1, sizeof (ChildInfo)); + + /* create pipe */ + if (pipe(status_pipe) < 0) + { + fprintf(stderr, "ERROR: no file handles\n"); + exit(1); + } + info->status_fd = status_pipe[0]; + + /* application name */ + rtl_uString_newFromAscii(&pApp, "file://"); + rtl_uString_newConcat(&pApp, pApp, args->pAppPath); + rtl_uString_newFromAscii(&pTmp, "soffice.bin"); + rtl_uString_newConcat(&pApp, pApp, pTmp); + rtl_uString_release(pTmp); + pTmp = NULL; + + /* copy args */ + nArgs = bAllArgs ? args->nArgsTotal : args->nArgsEnv; + ppArgs = (rtl_uString **)calloc(nArgs + 1, sizeof(rtl_uString*)); + for (i = 0; i < nArgs; ++i) + ppArgs[i] = args->ppArgs[i]; + + if(bWithStatus) + { + char buffer[64]; + + /* add the pipe arg */ + snprintf(buffer, 63, "--splash-pipe=%d", status_pipe[1]); + rtl_uString_newFromAscii( &pTmp, buffer ); + ppArgs[nArgs] = pTmp; + ++nArgs; + } + + /* start the main process */ + nError = osl_executeProcess(pApp, ppArgs, nArgs, + osl_Process_NORMAL, + NULL, + NULL, + NULL, 0, + &info->child ); + + if (pTmp) + rtl_uString_release(pTmp); + free (ppArgs); + + if (nError != osl_Process_E_None) + { + fprintf(stderr, "ERROR %d forking process\n", nError); + rtl_uString_release(pApp); + _exit (1); + } + + rtl_uString_release(pApp); + close( status_pipe[1] ); + + return info; +} + +static sal_Bool child_exited_wait(ChildInfo *info, sal_Bool bShortWait) +{ + TimeValue t = { 0, 250 /* ms */ * 1000 * 1000 }; + if (!bShortWait) + t.Seconds = 1024; + + return osl_joinProcessWithTimeout(info->child, &t) != osl_Process_E_TimedOut; +} + +static int child_get_exit_code(ChildInfo *info) +{ + oslProcessInfo inf; + + inf.Code = -1; + inf.Size = sizeof(inf); + + if (osl_getProcessInfo(info->child, osl_Process_EXITCODE, &inf) != osl_Process_E_None) + { + fprintf(stderr, "Warning: failed to fetch libreoffice exit status\n"); + return -1; + } + + return inf.Code; +} + +typedef enum { ProgressContinue, ProgressRestart, ProgressExit } ProgressStatus; + +/* Path of the application, with trailing slash. */ +static rtl_uString *get_app_path(const char *pAppExec) +{ + char pRealPath[PATH_MAX]; + rtl_uString *pResult; + sal_Int32 len; + char* dummy; + + char *pOrigPath = strdup(pAppExec); + char *pPath = dirname(pOrigPath); + + dummy = realpath(pPath, pRealPath); + (void)dummy; + pResult = charp_to_ustr(pRealPath); + free(pOrigPath); + + len = rtl_uString_getLength(pResult); + if (len > 0 && rtl_uString_getStr(pResult)[len - 1] != '/') + { + rtl_uString *pSlash = NULL; + rtl_uString_newFromAscii(&pSlash, "/"); + rtl_uString_newConcat(&pResult, pResult, pSlash); + rtl_uString_release(pSlash); + } + + return pResult; +} + +/* Compute the OOo md5 hash from 'pText' */ +static rtl_uString *get_md5hash(rtl_uString *pText) +{ + rtl_uString *pResult = NULL; + sal_Int32 nCapacity = 100; + unsigned char *pData = NULL; + sal_uInt32 nSize = 0; + rtlDigest digest; + sal_uInt32 md5_key_len = 0; + sal_uInt8* md5_buf = NULL; + sal_uInt32 i = 0; + + if ( !pText ) + return NULL; + + pData = (unsigned char *)rtl_uString_getStr(pText); + nSize = rtl_uString_getLength(pText) * sizeof(sal_Unicode); + if (!pData) + return NULL; + + digest = rtl_digest_create(rtl_Digest_AlgorithmMD5); + if (!digest) + return NULL; + + md5_key_len = rtl_digest_queryLength(digest); + md5_buf = (sal_uInt8 *)calloc(md5_key_len, sizeof(sal_uInt8)); + + rtl_digest_init(digest, pData , nSize); + rtl_digest_update(digest, pData, nSize); + rtl_digest_get(digest, md5_buf, md5_key_len); + rtl_digest_destroy(digest); + + /* create hex-value string from the MD5 value to keep + the string size minimal */ + rtl_uString_new_WithLength(&pResult, nCapacity); + for (; i < md5_key_len; ++i) + { + char val[3]; + snprintf(val, 3, "%x", md5_buf[i]); /* sic! we ignore some of the 0's */ + + rtl_uStringbuffer_insert_ascii(&pResult, &nCapacity, rtl_uString_getLength(pResult), + val, strlen(val)); + } + + /* cleanup */ + free(md5_buf); + + return pResult; +} + +/* Construct the pipe name */ +static rtl_uString *get_pipe_path(rtl_uString *pAppPath) +{ + rtl_uString *pPath = NULL, *pTmp = NULL, *pUserInstallation = NULL; + rtl_uString *pResult = NULL, *pBasePath = NULL, *pAbsUserInstallation = NULL; + rtlBootstrapHandle handle; + rtl_uString *pMd5hash = NULL; + sal_Unicode pUnicode[RTL_USTR_MAX_VALUEOFINT32]; + + /* setup bootstrap filename */ + rtl_uString_newFromAscii(&pPath, "file://"); + rtl_uString_newConcat(&pPath, pPath, pAppPath); + rtl_uString_newConcat(&pPath, pPath, pTmp); + rtl_uString_newFromAscii(&pTmp, SAL_CONFIGFILE("bootstrap")); + rtl_uString_newConcat(&pPath, pPath, pTmp); + + /* read userinstallation value */ + handle = rtl_bootstrap_args_open(pPath); + + rtl_uString_newFromAscii(&pTmp, "UserInstallation"); + rtl_bootstrap_get_from_handle(handle, pTmp, &pUserInstallation, NULL); + + rtl_bootstrap_args_close(handle); + + /* turn it into an absolute path - unwinding symlinks etc. */ + if (osl_getProcessWorkingDir(&pBasePath) || + osl_getAbsoluteFileURL(pBasePath, pUserInstallation, &pAbsUserInstallation)) + rtl_uString_newFromString(&pAbsUserInstallation, pUserInstallation); + + /* create the pipe name */ + pMd5hash = get_md5hash(pAbsUserInstallation); + if (!pMd5hash) + rtl_uString_new(&pMd5hash); + + if (access(PIPEDEFAULTPATH, W_OK) == 0) + { + rtl_uString_newFromAscii(&pResult, PIPEDEFAULTPATH); + } + else if (access(PIPEALTERNATEPATH, W_OK) == 0) + { + rtl_uString_newFromAscii(&pResult, PIPEALTERNATEPATH); + } + else + { + fprintf(stderr, "ERROR: no valid pipe path found.\n"); + exit(1); + } + + rtl_uString_newFromAscii(&pTmp, "/OSL_PIPE_"); + rtl_uString_newConcat(&pResult, pResult, pTmp); + + rtl_ustr_valueOfInt32(pUnicode, (int)getuid(), 10); + rtl_uString_newFromStr(&pTmp, pUnicode); + rtl_uString_newConcat(&pResult, pResult, pTmp); + + rtl_uString_newFromAscii(&pTmp, "_SingleOfficeIPC_"); + rtl_uString_newConcat(&pResult, pResult, pTmp); + + rtl_uString_newConcat(&pResult, pResult, pMd5hash); + + /* cleanup */ + rtl_uString_release(pMd5hash); + rtl_uString_release(pPath); + rtl_uString_release(pTmp); + + if (pBasePath) + rtl_uString_release(pBasePath); + + rtl_uString_release(pUserInstallation); + rtl_uString_release(pAbsUserInstallation); + + return pResult; +} + +/* Get fd of the pipe of the already running OOo. */ +static int connect_pipe(rtl_uString *pPipePath) +{ + int fd; + size_t len; + struct sockaddr_un addr; + + rtl_String *pPipeStr = ustr_to_str(pPipePath); + + memset(&addr, 0, sizeof(addr)); + + if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) + return fd; + + (void)fcntl(fd, F_SETFD, FD_CLOEXEC); + + addr.sun_family = AF_UNIX; + strncpy(addr.sun_path, rtl_string_getStr(pPipeStr), sizeof(addr.sun_path) - 1); + rtl_string_release(pPipeStr); + +/* cut / paste from osl's pipe.c */ +#if defined(FREEBSD) + len = SUN_LEN(&addr); +#else + len = sizeof(addr); +#endif + + if (connect(fd, (struct sockaddr *)&addr, len) < 0) + { + close(fd); + fd = -1; + } + return fd; +} + +/* Escape: "," -> "\\,", "\0" -> "\\0", "\\" -> "\\\\" */ +static rtl_uString *escape_path(rtl_uString const *pToEscape) +{ + rtl_uString *pBuffer = NULL; + sal_Int32 nCapacity = 1000; + sal_Int32 i = 0; + sal_Int32 nEscapeLength = rtl_uString_getLength(pToEscape); + + rtl_uString_new_WithLength(&pBuffer, nCapacity); + + for (; i < nEscapeLength; ++i) + { + sal_Unicode c = pToEscape->buffer[i]; + switch (c) + { + case '\0': + rtl_uStringbuffer_insert_ascii(&pBuffer, &nCapacity, + rtl_uString_getLength(pBuffer), + RTL_CONSTASCII_STRINGPARAM("\\0")); + break; + case ',': + rtl_uStringbuffer_insert_ascii(&pBuffer, &nCapacity, + rtl_uString_getLength(pBuffer), + RTL_CONSTASCII_STRINGPARAM("\\,")); + break; + case '\\': + rtl_uStringbuffer_insert_ascii(&pBuffer, &nCapacity, + rtl_uString_getLength(pBuffer), + RTL_CONSTASCII_STRINGPARAM("\\\\")); + break; + default: + rtl_uStringbuffer_insert(&pBuffer, &nCapacity, + rtl_uString_getLength(pBuffer), + &c, 1); + } + } + + return pBuffer; +} + +/* Send args to the LO instance (using the 'fd' file descriptor) */ +static sal_Bool send_args(int fd, rtl_uString const *pCwdPath) +{ + rtl_uString *pBuffer = NULL, *pTmp = NULL; + sal_Int32 nCapacity = 1000; + rtl_String *pOut = NULL; + sal_Bool bResult; + size_t nLen; + rtl_uString *pEscapedCwdPath = escape_path(pCwdPath); + sal_uInt32 nArg = 0; + sal_uInt32 nArgCount = rtl_getAppCommandArgCount(); + + rtl_uString_new_WithLength(&pBuffer, nCapacity); + rtl_uString_new(&pTmp); + + rtl_uStringbuffer_insert_ascii(&pBuffer, &nCapacity, + rtl_uString_getLength(pBuffer), + RTL_CONSTASCII_STRINGPARAM("InternalIPC::Arguments")); + + if (rtl_uString_getLength(pEscapedCwdPath)) + { + rtl_uStringbuffer_insert_ascii(&pBuffer, &nCapacity, + rtl_uString_getLength(pBuffer), + RTL_CONSTASCII_STRINGPARAM("1")); + + rtl_uStringbuffer_insert(&pBuffer, &nCapacity, + rtl_uString_getLength(pBuffer), + rtl_uString_getStr(pEscapedCwdPath), + rtl_uString_getLength(pEscapedCwdPath)); + } + else + { + rtl_uStringbuffer_insert_ascii(&pBuffer, &nCapacity, + rtl_uString_getLength(pBuffer), + RTL_CONSTASCII_STRINGPARAM("0")); + } + + for (nArg = 0; nArg < nArgCount; ++nArg) + { + rtl_uString *pEscapedTmp = NULL; + rtl_uStringbuffer_insert_ascii(&pBuffer, &nCapacity, + rtl_uString_getLength(pBuffer), + ",", 1); + + rtl_getAppCommandArg(nArg, &pTmp); + + pEscapedTmp = escape_path(pTmp); + + rtl_uStringbuffer_insert(&pBuffer, &nCapacity, + rtl_uString_getLength(pBuffer), + rtl_uString_getStr(pEscapedTmp), + rtl_uString_getLength(pEscapedTmp)); + + rtl_uString_release(pEscapedTmp); + } + + if (!rtl_convertUStringToString( + &pOut, rtl_uString_getStr(pBuffer), + rtl_uString_getLength(pBuffer), RTL_TEXTENCODING_UTF8, + (RTL_UNICODETOTEXT_FLAGS_UNDEFINED_ERROR + | RTL_UNICODETOTEXT_FLAGS_INVALID_ERROR))) + { + fprintf(stderr, "ERROR: cannot convert arguments to UTF-8\n"); + exit(1); + } + + nLen = rtl_string_getLength(pOut) + 1; + ssize_t n = write(fd, rtl_string_getStr(pOut), nLen); + bResult = (n >= 0 && (size_t) n == nLen); + + if ( bResult ) + { + char resp[SAL_N_ELEMENTS("InternalIPC::ProcessingDone")]; + n = read(fd, resp, SAL_N_ELEMENTS(resp)); + bResult = n == SAL_N_ELEMENTS(resp) + && (memcmp( + resp, "InternalIPC::ProcessingDone", + SAL_N_ELEMENTS(resp)) + == 0); + } + + /* cleanup */ + rtl_uString_release(pEscapedCwdPath); + rtl_uString_release(pBuffer); + rtl_uString_release(pTmp); + rtl_string_release(pOut); + + return bResult; +} + + +#define BUFFER_LEN 255 + +/* Read the percent to show in splash. */ +static ProgressStatus read_percent(ChildInfo const *info, int *pPercent) +{ + static char pBuffer[BUFFER_LEN + 1]; + static char *pNext = pBuffer; + static ssize_t nRead = 0; + + char *pBegin; + char *pIter; + char c; + + /* from the last call */ + int nNotProcessed = nRead - (pNext - pBuffer); + if (nNotProcessed >= BUFFER_LEN) + return ProgressContinue; + + memmove(pBuffer, pNext, nNotProcessed); + + /* read data */ + nRead = read(child_info_get_status_fd(info), + pBuffer + nNotProcessed, BUFFER_LEN - nNotProcessed); + + if (nRead < 0) + { + if (errno == EINTR) + return ProgressContinue; + + return ProgressExit; + } + + nRead += nNotProcessed; + pBuffer[nRead] = '\0'; + + /* skip old data */ + pBegin = pBuffer; + pNext = pBuffer; + for (pIter = pBuffer; *pIter; ++pIter) + { + if (*pIter == '\n') + { + pBegin = pNext; + pNext = pIter + 1; + } + } + + if (!strncasecmp(pBegin, "end", 3)) + return ProgressExit; + else if (!strncasecmp(pBegin, "restart", 7)) + return ProgressRestart; + else if (sscanf(pBegin, "%d%c", pPercent, &c) == 2 && c == '%') + return ProgressContinue; + + /* unexpected - let's exit the splash to be safe */ + return ProgressExit; +} + +/* Simple system check. */ +static void system_checks(void) +{ +#ifdef LINUX + struct stat buf; + + /* check proc is mounted - lots of things fail otherwise */ + if (stat("/proc/version", &buf) != 0) + { + fprintf(stderr, "ERROR: /proc not mounted - LibreOffice is unlikely to work well if at all\n"); + exit(1); + } +#endif +} + +static void exec_pagein (Args *args) +{ + rtl_String * path = ustr_to_str(args->pAppPath); + pagein_execute(rtl_string_getStr(path), "pagein-common"); + + if (args->pPageinType) + pagein_execute(rtl_string_getStr(path), args->pPageinType); + + rtl_string_release(path); +} + +#if HAVE_FEATURE_JAVA + +static void extend_library_path(const char *new_element) +{ + rtl_uString *pEnvName=NULL, *pOrigEnvVar=NULL, *pNewEnvVar=NULL; + + rtl_uString_newFromAscii(&pEnvName, "LD_LIBRARY_PATH"); + rtl_uString_newFromAscii(&pNewEnvVar, new_element); + + osl_getEnvironment(pEnvName, &pOrigEnvVar); + if (pOrigEnvVar && pOrigEnvVar->length) + { + rtl_uString *pDelim = NULL; + rtl_uString_newFromAscii(&pDelim, ":"); + rtl_uString_newConcat(&pNewEnvVar, pNewEnvVar, pDelim); + rtl_uString_newConcat(&pNewEnvVar, pNewEnvVar, pOrigEnvVar); + rtl_uString_release(pDelim); + } + + osl_setEnvironment(pEnvName, pNewEnvVar); + + if (pOrigEnvVar) + rtl_uString_release(pOrigEnvVar); + + rtl_uString_release(pNewEnvVar); + rtl_uString_release(pEnvName); +} + +static void exec_javaldx(Args *args) +{ + char newpath[4096]; + sal_uInt32 nArgs; + rtl_uString *pApp; + rtl_uString **ppArgs; + rtl_uString *pTmp, *pTmp2; + + oslProcess javaldx = NULL; + oslFileHandle fileOut = NULL; + oslProcessError err; + + ppArgs = (rtl_uString **)calloc(args->nArgsEnv + 2, sizeof(rtl_uString*)); + + for (nArgs = 0; nArgs < args->nArgsEnv; ++nArgs) + ppArgs[nArgs] = args->ppArgs[nArgs]; + + /* Use absolute path to redirectrc */ + pTmp = NULL; + rtl_uString_newFromAscii(&pTmp, "-env:INIFILENAME=vnd.sun.star.pathname:"); + rtl_uString_newConcat(&pTmp, pTmp, args->pAppPath); + pTmp2 = NULL; + rtl_uString_newFromAscii(&pTmp2, "redirectrc"); + rtl_uString_newConcat(&pTmp, pTmp, pTmp2); + ppArgs[nArgs] = pTmp; + rtl_uString_release (pTmp2); + nArgs++; + + /* And also to javaldx */ + pApp = NULL; + rtl_uString_newFromAscii(&pApp, "file://"); + rtl_uString_newConcat(&pApp, pApp, args->pAppPath); + pTmp = NULL; + rtl_uString_newFromAscii(&pTmp, "javaldx"); + rtl_uString_newConcat(&pApp, pApp, pTmp); + rtl_uString_release(pTmp); + + err = osl_executeProcess_WithRedirectedIO(pApp, ppArgs, nArgs, + osl_Process_NORMAL, + NULL, // security + NULL, // work dir + NULL, 0, + &javaldx, // process handle + NULL, + &fileOut, + NULL); + + rtl_uString_release(ppArgs[nArgs-1]); + rtl_uString_release(pApp); + free(ppArgs); + + if(err != osl_Process_E_None) + { + fprintf (stderr, "Warning: failed to launch javaldx - java may not function correctly\n"); + + if (javaldx) + osl_freeProcessHandle(javaldx); + if (fileOut) + osl_closeFile(fileOut); + return; + } + else + { + char *chomp; + sal_uInt64 bytes_read; + + /* Magically osl_readLine doesn't work with pipes with E_SPIPE - so be this lame instead: */ + while (osl_readFile (fileOut, newpath, SAL_N_ELEMENTS (newpath), &bytes_read) == osl_File_E_INTR); + + if (bytes_read <= 0) + { + fprintf (stderr, "Warning: failed to read path from javaldx\n"); + + if (javaldx) + osl_freeProcessHandle(javaldx); + + if (fileOut) + osl_closeFile(fileOut); + + return; + } + + newpath[bytes_read] = '\0'; + + if ((chomp = strstr (newpath, "\n"))) + *chomp = '\0'; + } + + if (newpath[0] != '\0') { + extend_library_path(newpath); + } + + if (javaldx) + osl_freeProcessHandle(javaldx); + + if (fileOut) + osl_closeFile(fileOut); +} + +#endif + +// has to be a global :( +static oslProcess * volatile g_pProcess = NULL; + +static void sigterm_handler(int ignored) +{ + (void) ignored; + + if (g_pProcess) { + int SigTermSucceded = 0; + oslProcessInfo info; + info.Size = sizeof(oslProcessInfo); + + // forward SIGTERM to soffice.bin and give it a chance to semi-gracefully exit + // enough to remove named pipe + if (osl_getProcessInfo(g_pProcess, osl_Process_IDENTIFIER, &info) == osl_Process_E_None) { + TimeValue delay = { 1, 0 }; // 1 sec + SigTermSucceded = kill(info.Ident, SIGTERM) == 0 && + osl_joinProcessWithTimeout(g_pProcess, &delay) == osl_Process_E_None; + } + + // didn't work, SIGKILL instead + if (!SigTermSucceded) { + osl_terminateProcess(g_pProcess); // uses SIGKILL to terminate soffice.bin + osl_joinProcess(g_pProcess); + } + } + + _exit(255); +} + + +SAL_IMPLEMENT_MAIN_WITH_ARGS(argc, argv) +{ + sal_Bool bSentArgs = sal_False; + const char* pUsePlugin; + rtl_uString *pPipePath = NULL; + Args *args; + int status = 0; + struct splash* splash = NULL; + struct sigaction sigpipe_action; + struct sigaction sigterm_action; + + /* turn SIGPIPE into an error */ + memset(&sigpipe_action, 0, sizeof(struct sigaction)); + sigpipe_action.sa_handler = SIG_IGN; + sigemptyset(&sigpipe_action.sa_mask); + sigaction(SIGPIPE, &sigpipe_action, NULL); + memset(&sigterm_action, 0, sizeof(struct sigaction)); + sigterm_action.sa_handler = &sigterm_handler; + sigemptyset(&sigterm_action.sa_mask); + sigaction(SIGTERM, &sigterm_action, NULL); + + args = args_parse(); + args->pAppPath = get_app_path(argv[0]); + if (!args->pAppPath) + { + fprintf(stderr, "ERROR: Can't read app link\n"); + exit(1); + } + +#ifndef ENABLE_QUICKSTART_LIBPNG + /* we can't load and render it anyway */ + args->bInhibitSplash = sal_True; +#endif + + pUsePlugin = getenv("SAL_USE_VCLPLUGIN"); + if (pUsePlugin && !strcmp(pUsePlugin, "svp")) + args->bInhibitSplash = sal_True; + + if (!args->bInhibitPipe && !getenv("LIBO_FLATPAK")) + { + int fd = 0; + pPipePath = get_pipe_path(args->pAppPath); + + if ((fd=connect_pipe(pPipePath)) >= 0) + { + // Wait for answer + char resp[strlen("InternalIPC::SendArguments") + 1]; + ssize_t n = read(fd, resp, SAL_N_ELEMENTS(resp)); + if (n == (ssize_t) SAL_N_ELEMENTS(resp) && + (memcmp(resp, "InternalIPC::SendArguments", + SAL_N_ELEMENTS(resp) - 1) == 0)) + { + rtl_uString *pCwdPath = NULL; + osl_getProcessWorkingDir(&pCwdPath); + + // Then send args + bSentArgs = send_args(fd, pCwdPath); + } + + close(fd); + } + } + + if (!bSentArgs) + { + /* we have to prepare for, and exec the binary */ + int nPercent = 0; + ChildInfo *info; + sal_Bool bAllArgs = sal_True; + sal_Bool bShortWait, bRestart; + + /* sanity check pieces */ + system_checks(); + + /* load splash image and create window */ + if (!args->bInhibitSplash) + splash = splash_create(args->pAppPath, argc, argv); + + /* pagein */ + if (!args->bInhibitPagein) + exec_pagein(args); + + /* javaldx */ +#if HAVE_FEATURE_JAVA + if (!args->bInhibitJavaLdx) + exec_javaldx (args); +#endif + + do + { + bRestart = sal_False; + + /* fast updates if we have somewhere to update it to */ + bShortWait = splash ? sal_True : sal_False; + + /* Periodically update the splash & the percent according + to what status_fd says, poll quickly only while starting */ + info = child_spawn (args, bAllArgs, bShortWait); + g_pProcess = info->child; + + while (!child_exited_wait(info, bShortWait)) + { + ProgressStatus eResult; + + splash_draw_progress(splash, nPercent); + eResult = read_percent(info, &nPercent); + + if (eResult != ProgressContinue) + { + splash_destroy(splash); + splash = NULL; + bShortWait = sal_False; + } + } + + status = child_get_exit_code(info); + g_pProcess = NULL; // reset + + switch (status) + { + case EXITHELPER_CRASH_WITH_RESTART: // re-start with just -env: parameters + bRestart = sal_True; + bAllArgs = sal_False; + break; + case EXITHELPER_NORMAL_RESTART: // re-start with all arguments + bRestart = sal_True; + bAllArgs = sal_True; + break; + default: + break; + } + + child_info_destroy(info); + } while (bRestart); + } + + /* cleanup */ + if (pPipePath) + rtl_uString_release(pPipePath); + + args_free(args); + + return status; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/util/officeloader.rc b/desktop/util/officeloader.rc new file mode 100644 index 0000000000..0b1d4f1045 --- /dev/null +++ b/desktop/util/officeloader.rc @@ -0,0 +1,36 @@ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#define WIN32_LEAN_AND_MEAN +#include <windows.h> +#include "version.hrc" + +1 ICON PPS(RES_APP_ICON) +2 ICON "icons/oasis-text.ico" +3 ICON "icons/oasis-text-template.ico" +4 ICON "icons/oasis-spreadsheet.ico" +5 ICON "icons/oasis-spreadsheet-template.ico" +6 ICON "icons/oasis-drawing.ico" +7 ICON "icons/oasis-drawing-template.ico" +8 ICON "icons/oasis-presentation.ico" +9 ICON "icons/oasis-presentation-template.ico" +10 ICON "icons/oasis-master-document.ico" +11 ICON "icons/oasis-web-template.ico" +12 ICON "icons/oasis-database.ico" +13 ICON "icons/oasis-formula.ico" +14 ICON "icons/oxt-extension.ico" diff --git a/desktop/win32/source/QuickStart/QuickStart.cxx b/desktop/win32/source/QuickStart/QuickStart.cxx new file mode 100644 index 0000000000..3277a6abfe --- /dev/null +++ b/desktop/win32/source/QuickStart/QuickStart.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 . + */ + +// QuickStart.cpp : Defines the entry point for the application. + +#include <sal/config.h> + +#define WIN32_LEAN_AND_MEAN +#include <windows.h> +#include <shellapi.h> + +#include "resource.h" +#include <systools/win32/uwinapi.h> +#include <systools/win32/qswin32.h> + +#include <stdio.h> +#include <stdlib.h> +#include <malloc.h> +#include <memory.h> + +static bool SofficeRuns() +{ + // check for soffice by searching the communication window + return FindWindowExW( nullptr, nullptr, QUICKSTART_CLASSNAME, nullptr ) != nullptr; +} + +static bool launchSoffice( ) +{ + if ( !SofficeRuns() ) + { + wchar_t filename[_MAX_PATH + 1]; + + filename[_MAX_PATH] = 0; + GetModuleFileNameW( nullptr, filename, _MAX_PATH ); // soffice resides in the same dir + wchar_t *p = wcsrchr( filename, L'\\' ); + if ( !p ) + return false; + + wcsncpy( p+1, L"soffice.exe", _MAX_PATH - (p+1 - filename) ); + + wchar_t imagename[_MAX_PATH + 1]; + + imagename[_MAX_PATH] = 0; + _snwprintf(imagename, _MAX_PATH, L"\"%s\" --quickstart", filename ); + + STARTUPINFOW aStartupInfo; + ZeroMemory(&aStartupInfo, sizeof(aStartupInfo)); + aStartupInfo.cb = sizeof(aStartupInfo); + aStartupInfo.wShowWindow = SW_SHOW; + PROCESS_INFORMATION aProcessInfo; + bool bSuccess = CreateProcessW(filename, imagename, nullptr, nullptr, TRUE, 0, nullptr, nullptr, &aStartupInfo, &aProcessInfo); + if ( !bSuccess ) + return false; + + return true; + } + else + return true; +} + +int APIENTRY wWinMain(HINSTANCE /*hInstance*/, + HINSTANCE /*hPrevInstance*/, + LPWSTR /*lpCmdLine*/, + int /*nCmdShow*/) +{ + // Look for --killtray argument + + for ( int i = 1; i < __argc; i++ ) + { + if ( 0 == wcscmp( __wargv[i], L"--killtray" ) ) + { + HWND hwndTray = FindWindowW( QUICKSTART_CLASSNAME, nullptr ); + + if ( hwndTray ) + { + UINT uMsgKillTray = RegisterWindowMessageW( SHUTDOWN_QUICKSTART_MESSAGE ); + SendMessageW( hwndTray, uMsgKillTray, 0, 0 ); + } + + return 0; + } + } + + launchSoffice(); + return 0; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/win32/source/QuickStart/QuickStart.rc b/desktop/win32/source/QuickStart/QuickStart.rc new file mode 100644 index 0000000000..cf91314794 --- /dev/null +++ b/desktop/win32/source/QuickStart/QuickStart.rc @@ -0,0 +1,3 @@ +#include "resource.h" + +ICON_ACTIVE ICON DISCARDABLE "icons/soffice.ico" diff --git a/desktop/win32/source/QuickStart/resource.h b/desktop/win32/source/QuickStart/resource.h new file mode 100644 index 0000000000..3dfbabcd89 --- /dev/null +++ b/desktop/win32/source/QuickStart/resource.h @@ -0,0 +1,5 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +#define ICON_ACTIVE 1 + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/win32/source/applauncher/launcher.cxx b/desktop/win32/source/applauncher/launcher.cxx new file mode 100644 index 0000000000..bf80dba2cc --- /dev/null +++ b/desktop/win32/source/applauncher/launcher.cxx @@ -0,0 +1,105 @@ +/* -*- 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 "launcher.hxx" + +#include <stdlib.h> +#include <malloc.h> + +extern "C" int APIENTRY wWinMain( HINSTANCE, HINSTANCE, LPWSTR, int ) +{ + // Retrieve startup info + + STARTUPINFOW aStartupInfo; + + ZeroMemory( &aStartupInfo, sizeof(aStartupInfo) ); + aStartupInfo.cb = sizeof( aStartupInfo ); + GetStartupInfoW( &aStartupInfo ); + + // Retrieve command line + + LPWSTR lpCommandLine = static_cast<LPWSTR>(_alloca( sizeof(WCHAR) * (wcslen(GetCommandLineW()) + wcslen(APPLICATION_SWITCH) + 2) )); + + wcscpy( lpCommandLine, GetCommandLineW() ); + wcscat( lpCommandLine, L" " ); + wcscat( lpCommandLine, APPLICATION_SWITCH ); + + // Calculate application name + + WCHAR szApplicationName[MAX_PATH]; + WCHAR szDrive[MAX_PATH]; + WCHAR szDir[MAX_PATH]; + WCHAR szFileName[MAX_PATH]; + WCHAR szExt[MAX_PATH]; + + GetModuleFileNameW( nullptr, szApplicationName, MAX_PATH ); + _wsplitpath( szApplicationName, szDrive, szDir, szFileName, szExt ); + _wmakepath( szApplicationName, szDrive, szDir, L"soffice", L".exe" ); + + PROCESS_INFORMATION aProcessInfo; + + bool fSuccess = CreateProcessW( + szApplicationName, + lpCommandLine, + nullptr, + nullptr, + TRUE, + 0, + nullptr, + nullptr, + &aStartupInfo, + &aProcessInfo ); + + if ( fSuccess ) + { + // Wait for soffice process to be terminated to allow other applications + // to wait for termination of started process + + WaitForSingleObject( aProcessInfo.hProcess, INFINITE ); + + CloseHandle( aProcessInfo.hProcess ); + CloseHandle( aProcessInfo.hThread ); + + return 0; + } + + DWORD dwError = GetLastError(); + + LPWSTR lpMsgBuf = nullptr; + + FormatMessageW( + FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + nullptr, + dwError, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language + reinterpret_cast<LPWSTR>(&lpMsgBuf), + 0, + nullptr + ); + + // Display the string. + MessageBoxW( nullptr, lpMsgBuf, nullptr, MB_OK | MB_ICONERROR ); + + // Free the buffer. + HeapFree( GetProcessHeap(), 0, lpMsgBuf ); + + return dwError; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/win32/source/applauncher/launcher.hxx b/desktop/win32/source/applauncher/launcher.hxx new file mode 100644 index 0000000000..e28cc7466e --- /dev/null +++ b/desktop/win32/source/applauncher/launcher.hxx @@ -0,0 +1,27 @@ +/* -*- 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 + +#define WIN32_LEAN_AND_MEAN +#include <windows.h> + +extern WCHAR APPLICATION_SWITCH[]; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/win32/source/applauncher/launcher.rc b/desktop/win32/source/applauncher/launcher.rc new file mode 100644 index 0000000000..8e56ef7549 --- /dev/null +++ b/desktop/win32/source/applauncher/launcher.rc @@ -0,0 +1,23 @@ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#define WIN32_LEAN_AND_MEAN +#include <windows.h> +#include "version.hrc" + +1 ICON PPS(RES_APP_ICON) diff --git a/desktop/win32/source/applauncher/sbase.cxx b/desktop/win32/source/applauncher/sbase.cxx new file mode 100644 index 0000000000..a8e832a5b1 --- /dev/null +++ b/desktop/win32/source/applauncher/sbase.cxx @@ -0,0 +1,24 @@ +/* -*- 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 "launcher.hxx" + +WCHAR APPLICATION_SWITCH[] = L"--base"; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/win32/source/applauncher/scalc.cxx b/desktop/win32/source/applauncher/scalc.cxx new file mode 100644 index 0000000000..27df421193 --- /dev/null +++ b/desktop/win32/source/applauncher/scalc.cxx @@ -0,0 +1,24 @@ +/* -*- 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 "launcher.hxx" + +WCHAR APPLICATION_SWITCH[] = L"--calc"; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/win32/source/applauncher/sdraw.cxx b/desktop/win32/source/applauncher/sdraw.cxx new file mode 100644 index 0000000000..3cfb7e425c --- /dev/null +++ b/desktop/win32/source/applauncher/sdraw.cxx @@ -0,0 +1,24 @@ +/* -*- 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 "launcher.hxx" + +WCHAR APPLICATION_SWITCH[] = L"--draw"; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/win32/source/applauncher/simpress.cxx b/desktop/win32/source/applauncher/simpress.cxx new file mode 100644 index 0000000000..c3b73bf408 --- /dev/null +++ b/desktop/win32/source/applauncher/simpress.cxx @@ -0,0 +1,24 @@ +/* -*- 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 "launcher.hxx" + +WCHAR APPLICATION_SWITCH[] = L"--impress"; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/win32/source/applauncher/smath.cxx b/desktop/win32/source/applauncher/smath.cxx new file mode 100644 index 0000000000..ae441b2e4f --- /dev/null +++ b/desktop/win32/source/applauncher/smath.cxx @@ -0,0 +1,24 @@ +/* -*- 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 "launcher.hxx" + +WCHAR APPLICATION_SWITCH[] = L"--math"; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/win32/source/applauncher/soffice_safe.cxx b/desktop/win32/source/applauncher/soffice_safe.cxx new file mode 100644 index 0000000000..241a03b764 --- /dev/null +++ b/desktop/win32/source/applauncher/soffice_safe.cxx @@ -0,0 +1,14 @@ +/* -*- 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 "launcher.hxx" + +WCHAR APPLICATION_SWITCH[] = L"--safe-mode"; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/win32/source/applauncher/sweb.cxx b/desktop/win32/source/applauncher/sweb.cxx new file mode 100644 index 0000000000..69aeda135e --- /dev/null +++ b/desktop/win32/source/applauncher/sweb.cxx @@ -0,0 +1,24 @@ +/* -*- 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 "launcher.hxx" + +WCHAR APPLICATION_SWITCH[] = L"--web"; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/win32/source/applauncher/swriter.cxx b/desktop/win32/source/applauncher/swriter.cxx new file mode 100644 index 0000000000..fc470d3ac1 --- /dev/null +++ b/desktop/win32/source/applauncher/swriter.cxx @@ -0,0 +1,24 @@ +/* -*- 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 "launcher.hxx" + +WCHAR APPLICATION_SWITCH[] = L"--writer"; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/win32/source/loader.cxx b/desktop/win32/source/loader.cxx new file mode 100644 index 0000000000..d30f0ef908 --- /dev/null +++ b/desktop/win32/source/loader.cxx @@ -0,0 +1,480 @@ +/* -*- 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 "loader.hxx" +#include <cassert> +#include <systools/win32/uwinapi.h> +#include <stdlib.h> +#include <string> +#include <vector> +#include <desktop/exithelper.h> +#include <tools/pathutils.hxx> + +#include <fstream> +#include <boost/property_tree/ptree.hpp> +#include <boost/property_tree/ini_parser.hpp> + +namespace { + +void fail() +{ + LPWSTR buf = nullptr; + FormatMessageW( + FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, nullptr, + GetLastError(), 0, reinterpret_cast< LPWSTR >(&buf), 0, nullptr); + MessageBoxW(nullptr, buf, nullptr, MB_OK | MB_ICONERROR); + HeapFree(GetProcessHeap(), 0, buf); + TerminateProcess(GetCurrentProcess(), 255); +} + +LPWSTR* GetCommandArgs(int* pArgc) { return CommandLineToArgvW(GetCommandLineW(), pArgc); } + +// tdf#120249: quotes in arguments need to be escaped; backslashes before quotes need doubling. See +// https://docs.microsoft.com/en-us/windows/desktop/api/shellapi/nf-shellapi-commandlinetoargvw +std::wstring EscapeArg(LPCWSTR sArg) +{ + const size_t nOrigSize = wcslen(sArg); + LPCWSTR const end = sArg + nOrigSize; + std::wstring sResult(L"\""); + + LPCWSTR lastPosQuote = sArg; + LPCWSTR posQuote; + while ((posQuote = std::find(lastPosQuote, end, L'"')) != end) + { + LPCWSTR posBackslash = posQuote; + while (posBackslash != lastPosQuote && *(posBackslash - 1) == L'\\') + --posBackslash; + + sResult.append(lastPosQuote, posBackslash); + sResult.append((posQuote - posBackslash) * 2 + 1, L'\\'); // 2n+1 '\' to escape the '"' + sResult.append(1, L'"'); + lastPosQuote = posQuote + 1; + } + + LPCWSTR posTrailingBackslashSeq = end; + while (posTrailingBackslashSeq != lastPosQuote && *(posTrailingBackslashSeq - 1) == L'\\') + --posTrailingBackslashSeq; + sResult.append(lastPosQuote, posTrailingBackslashSeq); + sResult.append((end - posTrailingBackslashSeq) * 2, L'\\'); // 2n '\' before closing '"' + sResult.append(1, L'"'); + + return sResult; +} + +void AddEscapedArg(LPCWSTR sArg, std::vector<std::wstring>& aEscapedArgs, + std::size_t& iLengthAccumulator) +{ + std::wstring sEscapedArg = EscapeArg(sArg); + aEscapedArgs.push_back(sEscapedArg); + iLengthAccumulator += sEscapedArg.length() + 1; // a space between args +} + +bool HasWildCard(LPCWSTR sArg) +{ + while (*sArg != L'\0') + { + if (*sArg == L'*' || *sArg == L'?') + return true; + sArg++; + } + return false; +} + +} + +namespace desktop_win32 { + +void extendLoaderEnvironment(WCHAR * binPath, WCHAR * iniDirectory) { + if (!GetModuleFileNameW(nullptr, iniDirectory, MAX_PATH)) { + fail(); + } + WCHAR * iniDirEnd = tools::filename(iniDirectory); + WCHAR name[MAX_PATH + MY_LENGTH(L".bin")]; + // hopefully std::size_t is large enough to not overflow + WCHAR * nameEnd = name; + for (WCHAR * p = iniDirEnd; *p != L'\0'; ++p) { + *nameEnd++ = *p; + } + if (!(nameEnd - name >= 4 && nameEnd[-4] == L'.' && + (((nameEnd[-3] == L'E' || nameEnd[-3] == L'e') && + (nameEnd[-2] == L'X' || nameEnd[-2] == L'x') && + (nameEnd[-1] == L'E' || nameEnd[-1] == L'e')) || + ((nameEnd[-3] == L'C' || nameEnd[-3] == L'c') && + (nameEnd[-2] == L'O' || nameEnd[-2] == L'o') && + (nameEnd[-1] == L'M' || nameEnd[-1] == L'm'))))) + { + *nameEnd = L'.'; + nameEnd += 4; + } + nameEnd[-3] = 'b'; + nameEnd[-2] = 'i'; + nameEnd[-1] = 'n'; + tools::buildPath(binPath, iniDirectory, iniDirEnd, name, nameEnd - name); + *iniDirEnd = L'\0'; + std::size_t const maxEnv = 32767; + WCHAR env[maxEnv]; + DWORD n = GetEnvironmentVariableW(L"PATH", env, maxEnv); + if ((n >= maxEnv || n == 0) && GetLastError() != ERROR_ENVVAR_NOT_FOUND) { + fail(); + } + // must be first in PATH to override other entries + assert(*(iniDirEnd - 1) == L'\\'); // hence -1 below + if (wcsncmp(env, iniDirectory, iniDirEnd - iniDirectory - 1) != 0 + || env[iniDirEnd - iniDirectory - 1] != L';') + { + WCHAR pad[MAX_PATH + maxEnv]; + // hopefully std::size_t is large enough to not overflow + WCHAR * p = commandLineAppend(pad, iniDirectory, iniDirEnd - iniDirectory - 1); + if (n != 0) { + *p++ = L';'; + for (DWORD i = 0; i <= n; ++i) { + *p++ = env[i]; + } + } else { + *p++ = L'\0'; + } + if (!SetEnvironmentVariableW(L"PATH", pad)) { + fail(); + } + } +} + +int officeloader_impl(bool bAllowConsole) +{ + WCHAR szTargetFileName[MAX_PATH] = {}; + WCHAR szIniDirectory[MAX_PATH]; + STARTUPINFOW aStartupInfo; + + desktop_win32::extendLoaderEnvironment(szTargetFileName, szIniDirectory); + + ZeroMemory(&aStartupInfo, sizeof(aStartupInfo)); + aStartupInfo.cb = sizeof(aStartupInfo); + + // Create process with same command line, environment and stdio handles which + // are directed to the created pipes + GetStartupInfoW(&aStartupInfo); + + DWORD dwExitCode = DWORD(-1); + + bool fSuccess = false; + LPWSTR lpCommandLine = nullptr; + bool bFirst = true; + WCHAR cwd[MAX_PATH]; + DWORD cwdLen = GetCurrentDirectoryW(MAX_PATH, cwd); + if (cwdLen >= MAX_PATH) + { + cwdLen = 0; + } + std::vector<std::wstring> aEscapedArgs; + + // read limit values from bootstrap.ini + unsigned int nMaxMemoryInMB = 0; + bool bExcludeChildProcesses = true; + + const WCHAR* szIniFile = L"\\bootstrap.ini"; + const size_t nDirLen = wcslen(szIniDirectory); + if (wcslen(szIniFile) + nDirLen < MAX_PATH) + { + WCHAR szBootstrapIni[MAX_PATH]; + wcscpy(szBootstrapIni, szIniDirectory); + wcscpy(&szBootstrapIni[nDirLen], szIniFile); + + try + { + boost::property_tree::ptree pt; + std::ifstream aFile(szBootstrapIni); + boost::property_tree::ini_parser::read_ini(aFile, pt); + nMaxMemoryInMB = pt.get("Win32.LimitMaximumMemoryInMB", nMaxMemoryInMB); + bExcludeChildProcesses = pt.get("Win32.ExcludeChildProcessesFromLimit", bExcludeChildProcesses); + } + catch (...) + { + nMaxMemoryInMB = 0; + } + } + + // create a Windows JobObject with a memory limit + HANDLE hJobObject = nullptr; + if (nMaxMemoryInMB > 0) + { + JOBOBJECT_EXTENDED_LIMIT_INFORMATION aJobLimit; + aJobLimit.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_JOB_MEMORY; + if (bExcludeChildProcesses) + aJobLimit.BasicLimitInformation.LimitFlags |= JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK; + aJobLimit.JobMemoryLimit = nMaxMemoryInMB * 1024 * 1024; + hJobObject = CreateJobObjectW(nullptr, nullptr); + if (hJobObject != nullptr) + SetInformationJobObject(hJobObject, JobObjectExtendedLimitInformation, &aJobLimit, + sizeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION)); + } + + do + { + if (bFirst) + { + int argc = 0; + LPWSTR* argv = GetCommandArgs(&argc); + std::size_t n = 0; + for (int i = 0; i < argc; ++i) + { + // check for wildCards in arguments- windows does not expand automatically + if (HasWildCard(argv[i])) + { + WIN32_FIND_DATAW aFindData; + HANDLE h = FindFirstFileW(argv[i], &aFindData); + if (h == INVALID_HANDLE_VALUE) + { + AddEscapedArg(argv[i], aEscapedArgs, n); + } + else + { + const int nPathSize = 32 * 1024; + wchar_t drive[nPathSize]; + wchar_t dir[nPathSize]; + wchar_t path[nPathSize]; + _wsplitpath_s(argv[i], drive, nPathSize, dir, nPathSize, nullptr, 0, + nullptr, 0); + _wmakepath_s(path, nPathSize, drive, dir, aFindData.cFileName, nullptr); + AddEscapedArg(path, aEscapedArgs, n); + + while (FindNextFileW(h, &aFindData)) + { + _wmakepath_s(path, nPathSize, drive, dir, aFindData.cFileName, nullptr); + AddEscapedArg(path, aEscapedArgs, n); + } + FindClose(h); + } + } + else + { + AddEscapedArg(argv[i], aEscapedArgs, n); + } + } + LocalFree(argv); + n += MY_LENGTH(L" \"-env:OOO_CWD=2") + 4 * cwdLen + MY_LENGTH(L"\"") + 1; + // 4 * cwdLen: each char preceded by backslash, each trailing + // backslash doubled + lpCommandLine = new WCHAR[n]; + } + WCHAR* p = desktop_win32::commandLineAppend(lpCommandLine, aEscapedArgs[0].c_str(), + aEscapedArgs[0].length()); + for (size_t i = 1; i < aEscapedArgs.size(); ++i) + { + const std::wstring& rArg = aEscapedArgs[i]; + if (bFirst || EXITHELPER_NORMAL_RESTART == dwExitCode + || wcsncmp(rArg.c_str(), MY_STRING(L"\"-env:")) == 0) + { + p = desktop_win32::commandLineAppend(p, MY_STRING(L" ")); + p = desktop_win32::commandLineAppend(p, rArg.c_str(), rArg.length()); + } + } + + p = desktop_win32::commandLineAppend(p, MY_STRING(L" \"-env:OOO_CWD=")); + if (cwdLen == 0) + { + p = desktop_win32::commandLineAppend(p, MY_STRING(L"0")); + } + else + { + p = desktop_win32::commandLineAppend(p, MY_STRING(L"2")); + p = desktop_win32::commandLineAppendEncoded(p, cwd); + } + desktop_win32::commandLineAppend(p, MY_STRING(L"\"")); + bFirst = false; + + WCHAR szParentProcessId[64]; // This is more than large enough for a 128 bit decimal value + bool bHeadlessMode(false); + + { + // Check command line arguments for "--headless" parameter. We only + // set the environment variable "ATTACHED_PARENT_PROCESSID" for the headless + // mode as self-destruction of the soffice.bin process can lead to + // certain side-effects (log-off can result in data-loss, ".lock" is not deleted. + // See 138244 for more information. + int argc2; + LPWSTR* argv2 = GetCommandArgs(&argc2); + + if (argc2 > 1) + { + int n; + + for (n = 1; n < argc2; n++) + { + if (0 == wcsnicmp(argv2[n], L"-headless", 9) + || 0 == wcsnicmp(argv2[n], L"--headless", 10)) + { + bHeadlessMode = true; + } + } + } + + LocalFree(argv2); + } + + if (_ltow(static_cast<long>(GetCurrentProcessId()), szParentProcessId, 10) && bHeadlessMode) + SetEnvironmentVariableW(L"ATTACHED_PARENT_PROCESSID", szParentProcessId); + + PROCESS_INFORMATION aProcessInfo; + + fSuccess = CreateProcessW(szTargetFileName, lpCommandLine, nullptr, nullptr, TRUE, + bAllowConsole ? 0 : DETACHED_PROCESS, nullptr, szIniDirectory, + &aStartupInfo, &aProcessInfo); + + if (fSuccess) + { + DWORD dwWaitResult; + + if (hJobObject) + AssignProcessToJobObject(hJobObject, aProcessInfo.hProcess); + + do + { + // On Windows XP it seems as the desktop calls WaitForInputIdle after "OpenWith" so + // we have to do so as if we were processing any messages + + dwWaitResult = MsgWaitForMultipleObjects(1, &aProcessInfo.hProcess, FALSE, INFINITE, + QS_ALLEVENTS); + + if (WAIT_OBJECT_0 + 1 == dwWaitResult) + { + MSG msg; + + PeekMessageW(&msg, nullptr, 0, 0, PM_REMOVE); + } + } while (WAIT_OBJECT_0 + 1 == dwWaitResult); + + dwExitCode = 0; + GetExitCodeProcess(aProcessInfo.hProcess, &dwExitCode); + + CloseHandle(aProcessInfo.hProcess); + CloseHandle(aProcessInfo.hThread); + } + } while (fSuccess + && (EXITHELPER_CRASH_WITH_RESTART == dwExitCode + || EXITHELPER_NORMAL_RESTART == dwExitCode)); + + if (hJobObject) + CloseHandle(hJobObject); + + delete[] lpCommandLine; + + return fSuccess ? dwExitCode : -1; +} + +int unopkgloader_impl(bool bAllowConsole) +{ + WCHAR szTargetFileName[MAX_PATH]; + WCHAR szIniDirectory[MAX_PATH]; + desktop_win32::extendLoaderEnvironment(szTargetFileName, szIniDirectory); + + STARTUPINFOW aStartupInfo{}; + aStartupInfo.cb = sizeof(aStartupInfo); + GetStartupInfoW(&aStartupInfo); + + DWORD dwExitCode = DWORD(-1); + + size_t iniDirLen = wcslen(szIniDirectory); + WCHAR cwd[MAX_PATH]; + DWORD cwdLen = GetCurrentDirectoryW(MAX_PATH, cwd); + if (cwdLen >= MAX_PATH) { + cwdLen = 0; + } + WCHAR redirect[MAX_PATH]; + DWORD dummy; + bool hasRedirect = + tools::buildPath( + redirect, szIniDirectory, szIniDirectory + iniDirLen, + MY_STRING(L"redirect.ini")) != nullptr && + (GetBinaryTypeW(redirect, &dummy) || // cheaper check for file existence? + GetLastError() != ERROR_FILE_NOT_FOUND); + LPWSTR cl1 = GetCommandLineW(); + WCHAR* cl2 = new WCHAR[ + wcslen(cl1) + + (hasRedirect + ? (MY_LENGTH(L" \"-env:INIFILENAME=vnd.sun.star.pathname:") + + iniDirLen + MY_LENGTH(L"redirect.ini\"")) + : 0) + + MY_LENGTH(L" \"-env:OOO_CWD=2") + 4 * cwdLen + MY_LENGTH(L"\"") + 1]; + // 4 * cwdLen: each char preceded by backslash, each trailing backslash + // doubled + WCHAR* p = desktop_win32::commandLineAppend(cl2, cl1); + if (hasRedirect) { + p = desktop_win32::commandLineAppend( + p, MY_STRING(L" \"-env:INIFILENAME=vnd.sun.star.pathname:")); + p = desktop_win32::commandLineAppend(p, szIniDirectory); + p = desktop_win32::commandLineAppend(p, MY_STRING(L"redirect.ini\"")); + } + p = desktop_win32::commandLineAppend(p, MY_STRING(L" \"-env:OOO_CWD=")); + if (cwdLen == 0) { + p = desktop_win32::commandLineAppend(p, MY_STRING(L"0")); + } + else { + p = desktop_win32::commandLineAppend(p, MY_STRING(L"2")); + p = desktop_win32::commandLineAppendEncoded(p, cwd); + } + desktop_win32::commandLineAppend(p, MY_STRING(L"\"")); + + PROCESS_INFORMATION aProcessInfo; + + bool fSuccess = CreateProcessW( + szTargetFileName, + cl2, + nullptr, + nullptr, + TRUE, + bAllowConsole ? 0 : DETACHED_PROCESS, + nullptr, + szIniDirectory, + &aStartupInfo, + &aProcessInfo); + + delete[] cl2; + + if (fSuccess) + { + DWORD dwWaitResult; + + do + { + // On Windows XP it seems as the desktop calls WaitForInputIdle after "OpenWidth" so we have to do so + // as if we were processing any messages + + dwWaitResult = MsgWaitForMultipleObjects(1, &aProcessInfo.hProcess, FALSE, INFINITE, QS_ALLEVENTS); + + if (WAIT_OBJECT_0 + 1 == dwWaitResult) + { + MSG msg; + + PeekMessageW(&msg, nullptr, 0, 0, PM_REMOVE); + } + } while (WAIT_OBJECT_0 + 1 == dwWaitResult); + + dwExitCode = 0; + GetExitCodeProcess(aProcessInfo.hProcess, &dwExitCode); + + CloseHandle(aProcessInfo.hProcess); + CloseHandle(aProcessInfo.hThread); + } + + return dwExitCode; +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/win32/source/loader.hxx b/desktop/win32/source/loader.hxx new file mode 100644 index 0000000000..66b0ed8e53 --- /dev/null +++ b/desktop/win32/source/loader.hxx @@ -0,0 +1,89 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#pragma once + +#include <cstddef> +#define WIN32_LEAN_AND_MEAN +#include <windows.h> +#include <string.h> + +#define MY_LENGTH(s) (sizeof (s) / sizeof *(s) - 1) +#define MY_STRING(s) (s), MY_LENGTH(s) + +namespace desktop_win32 { + +inline WCHAR * commandLineAppend( + WCHAR * buffer, WCHAR const * text, std::size_t length) +{ + wcsncpy(buffer, text, length + 1); // trailing null + return buffer + length; +} + +inline WCHAR * commandLineAppend(WCHAR * buffer, WCHAR const * text) { + return commandLineAppend(buffer, text, wcslen(text)); +} + +inline WCHAR * commandLineAppendEncoded(WCHAR * buffer, WCHAR const * text) { + std::size_t n = 0; + for (;;) { + WCHAR c = *text++; + if (c == L'\0') { + break; + } else if (c == L'$') { + buffer = commandLineAppend(buffer, MY_STRING(L"\\$")); + n = 0; + } else if (c == L'\\') { + buffer = commandLineAppend(buffer, MY_STRING(L"\\\\")); + n += 2; + } else { + *buffer++ = c; + n = 0; + } + } + // The command line will continue with a double quote, so double any + // preceding backslashes as required by Windows: + for (std::size_t i = 0; i < n; ++i) { + *buffer++ = L'\\'; + } + *buffer = L'\0'; + return buffer; +} + +// Set the PATH environment variable in the current (loader) process, so that a +// following CreateProcess has the necessary environment: +// @param binPath +// Must point to an array of size at least MAX_PATH. Is filled with the null +// terminated full path to the "bin" file corresponding to the current +// executable. +// @param iniDirectory +// Must point to an array of size at least MAX_PATH. Is filled with the null +// terminated full directory path (ending in "\") to the "ini" file +// corresponding to the current executable. +void extendLoaderEnvironment(WCHAR * binPath, WCHAR * iniDirectory); + +// Implementation of the process guarding soffice.bin +int officeloader_impl(bool bAllowConsole); + +// Implementation of the process guarding unopkg.bin +int unopkgloader_impl(bool bAllowConsole); + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/win32/source/officeloader/soffice_com.cxx b/desktop/win32/source/officeloader/soffice_com.cxx new file mode 100644 index 0000000000..5c6974e66a --- /dev/null +++ b/desktop/win32/source/officeloader/soffice_com.cxx @@ -0,0 +1,19 @@ +/* -*- 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 "../loader.hxx" + +int main(int /*argc*/, char** /*argv*/) +{ + // let soffice.bin use soffice.com's console + return desktop_win32::officeloader_impl(true); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/win32/source/officeloader/soffice_exe.cxx b/desktop/win32/source/officeloader/soffice_exe.cxx new file mode 100644 index 0000000000..03ff0a546b --- /dev/null +++ b/desktop/win32/source/officeloader/soffice_exe.cxx @@ -0,0 +1,19 @@ +/* -*- 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 "../loader.hxx" + +int WINAPI wWinMain(HINSTANCE, HINSTANCE, LPWSTR, int) +{ + // no console for soffice.bin when started by soffice.exe + return desktop_win32::officeloader_impl(false); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/win32/source/officeloader/unopkg_com.cxx b/desktop/win32/source/officeloader/unopkg_com.cxx new file mode 100644 index 0000000000..a93ac60365 --- /dev/null +++ b/desktop/win32/source/officeloader/unopkg_com.cxx @@ -0,0 +1,19 @@ +/* -*- 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 "../loader.hxx" + +int main(int /*argc*/, char** /*argv*/) +{ + // let unopkg.bin use unopkg.com's console + return desktop_win32::unopkgloader_impl(true); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/win32/source/officeloader/unopkg_exe.cxx b/desktop/win32/source/officeloader/unopkg_exe.cxx new file mode 100644 index 0000000000..40b1afa092 --- /dev/null +++ b/desktop/win32/source/officeloader/unopkg_exe.cxx @@ -0,0 +1,19 @@ +/* -*- 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 "../loader.hxx" + +int WINAPI wWinMain(HINSTANCE, HINSTANCE, LPWSTR, int) +{ + // no console for unopkg.bin when started by unopkg.exe + return desktop_win32::unopkgloader_impl(false); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/win32/source/unoinfo.cxx b/desktop/win32/source/unoinfo.cxx new file mode 100644 index 0000000000..14cee8819d --- /dev/null +++ b/desktop/win32/source/unoinfo.cxx @@ -0,0 +1,87 @@ +/* -*- 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 <cstddef> +#include <stdio.h> +#include <stdlib.h> +#include <wchar.h> + +#define WIN32_LEAN_AND_MEAN +#include <windows.h> + +#include <tools/pathutils.hxx> + +#define MY_LENGTH(s) (sizeof (s) / sizeof *(s) - 1) +#define MY_STRING(s) (s), MY_LENGTH(s) + +namespace { + +wchar_t * getBrandPath(wchar_t * path) { + DWORD n = GetModuleFileNameW(nullptr, path, MAX_PATH); + if (n == 0 || n >= MAX_PATH) { + exit(EXIT_FAILURE); + } + return tools::filename(path); +} + +void writeNull() { + if (fwrite("\0\0", 1, 2, stdout) != 2) { + exit(EXIT_FAILURE); + } +} + +void writePath( + wchar_t const * frontBegin, wchar_t const * frontEnd, + wchar_t const * backBegin, std::size_t backLength) +{ + wchar_t path[MAX_PATH]; + wchar_t * end = tools::buildPath( + path, frontBegin, frontEnd, backBegin, backLength); + if (end == nullptr) { + exit(EXIT_FAILURE); + } + std::size_t n = (end - path) * sizeof (wchar_t); + if (fwrite(path, 1, n, stdout) != n) { + exit(EXIT_FAILURE); + } +} + +} + +int wmain(int argc, wchar_t ** argv, wchar_t **) { + if (argc == 2 && wcscmp(argv[1], L"c++") == 0) { + wchar_t path[MAX_PATH]; + wchar_t * pathEnd = getBrandPath(path); + writePath(path, pathEnd, MY_STRING(L"")); + } else if (argc == 2 && wcscmp(argv[1], L"java") == 0) { + if (fwrite("1", 1, 1, stdout) != 1) { + exit(EXIT_FAILURE); + } + wchar_t path[MAX_PATH]; + wchar_t * pathEnd = getBrandPath(path); + writePath(path, pathEnd, MY_STRING(L"classes\\libreoffice.jar")); + writeNull(); + writePath(path, pathEnd, MY_STRING(L"")); + } else { + exit(EXIT_FAILURE); + } + exit(EXIT_SUCCESS); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |