/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * This file incorporates work covered by the following license notice: * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed * with this work for additional information regarding copyright * ownership. The ASF licenses this file to you under the Apache * License, Version 2.0 (the "License"); you may not use this 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 #include #include "shutdownicon.hxx" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace ::com::sun::star; using namespace ::com::sun::star::uno; using namespace ::com::sun::star::frame; using namespace ::com::sun::star::container; using namespace ::com::sun::star::lang; using namespace ::com::sun::star::beans; using namespace ::com::sun::star::util; using namespace ::com::sun::star::ui::dialogs; using namespace ::sfx2; class SfxNotificationListener_Impl : public cppu::WeakImplHelper< XDispatchResultListener > { public: virtual void SAL_CALL dispatchFinished( const DispatchResultEvent& aEvent ) override; virtual void SAL_CALL disposing( const EventObject& aEvent ) override; }; void SAL_CALL SfxNotificationListener_Impl::dispatchFinished( const DispatchResultEvent& ) { ShutdownIcon::LeaveModalMode(); } void SAL_CALL SfxNotificationListener_Impl::disposing( const EventObject& ) { } OUString SAL_CALL ShutdownIcon::getImplementationName() { return u"com.sun.star.comp.desktop.QuickstartWrapper"_ustr; } sal_Bool SAL_CALL ShutdownIcon::supportsService(OUString const & ServiceName) { return cppu::supportsService(this, ServiceName); } css::uno::Sequence SAL_CALL ShutdownIcon::getSupportedServiceNames() { return { u"com.sun.star.office.Quickstart"_ustr }; } bool ShutdownIcon::bModalMode = false; rtl::Reference ShutdownIcon::pShutdownIcon; void ShutdownIcon::initSystray() { if (m_bInitialized) return; m_bInitialized = true; #ifdef ENABLE_QUICKSTART_APPLET # ifdef _WIN32 win32_init_sys_tray(); # elif defined MACOSX aqua_init_systray(); # endif // MACOSX #endif // ENABLE_QUICKSTART_APPLET } void ShutdownIcon::deInitSystray() { if (!m_bInitialized) return; #ifdef ENABLE_QUICKSTART_APPLET # ifdef _WIN32 win32_shutdown_sys_tray(); # elif defined MACOSX aqua_shutdown_systray(); # endif // MACOSX #endif // ENABLE_QUICKSTART_APPLET m_bVeto = false; m_pFileDlg.reset(); m_bInitialized = false; } static bool UseSystemFileDialog() { return !Application::IsHeadlessModeEnabled() && officecfg::Office::Common::Misc::UseSystemFileDialog::get(); } ShutdownIcon::ShutdownIcon( css::uno::Reference< XComponentContext > xContext ) : m_bVeto ( false ), m_bListenForTermination ( false ), m_bSystemDialogs(UseSystemFileDialog()), m_xContext(std::move( xContext )), m_bInitialized( false ) { } ShutdownIcon::~ShutdownIcon() { deInitSystray(); } void ShutdownIcon::OpenURL( const OUString& aURL, const OUString& rTarget, const Sequence< PropertyValue >& aArgs ) { if ( !getInstance() || !getInstance()->m_xDesktop.is() ) return; css::uno::Reference < XDispatchProvider > xDispatchProvider = getInstance()->m_xDesktop; if ( !xDispatchProvider.is() ) return; css::util::URL aDispatchURL; aDispatchURL.Complete = aURL; css::uno::Reference< util::XURLTransformer > xURLTransformer( util::URLTransformer::create( ::comphelper::getProcessComponentContext() ) ); try { css::uno::Reference< css::frame::XDispatch > xDispatch; xURLTransformer->parseStrict( aDispatchURL ); xDispatch = xDispatchProvider->queryDispatch( aDispatchURL, rTarget, 0 ); if ( xDispatch.is() ) xDispatch->dispatch( aDispatchURL, aArgs ); } catch ( css::uno::RuntimeException& ) { throw; } catch ( css::uno::Exception& ) { } } void ShutdownIcon::FileOpen() { if ( getInstance() && getInstance()->m_xDesktop.is() ) { ::SolarMutexGuard aGuard; EnterModalMode(); getInstance()->StartFileDialog(); } } void ShutdownIcon::FromTemplate() { if ( !getInstance() || !getInstance()->m_xDesktop.is() ) return; css::uno::Reference < css::frame::XFramesSupplier > xDesktop = getInstance()->m_xDesktop; css::uno::Reference < css::frame::XFrame > xFrame( xDesktop->getActiveFrame() ); if ( !xFrame.is() ) xFrame = xDesktop; URL aTargetURL; aTargetURL.Complete = ".uno:NewDoc"; css::uno::Reference< util::XURLTransformer > xTrans( util::URLTransformer::create( ::comphelper::getProcessComponentContext() ) ); xTrans->parseStrict( aTargetURL ); css::uno::Reference < css::frame::XDispatchProvider > xProv( xFrame, UNO_QUERY ); css::uno::Reference < css::frame::XDispatch > xDisp; if ( xProv.is() ) { xDisp = xProv->queryDispatch( aTargetURL, u"_self"_ustr, 0 ); } if ( !xDisp.is() ) return; Sequence aArgs { comphelper::makePropertyValue(u"Referer"_ustr, u"private:user"_ustr) }; css::uno::Reference< css::frame::XNotifyingDispatch > xNotifier(xDisp, UNO_QUERY); if (xNotifier.is()) { EnterModalMode(); xNotifier->dispatchWithNotification(aTargetURL, aArgs, new SfxNotificationListener_Impl); } else xDisp->dispatch( aTargetURL, aArgs ); } OUString ShutdownIcon::GetUrlDescription( std::u16string_view aUrl ) { return SvFileInformationManager::GetDescription( INetURLObject( aUrl ) ); } void ShutdownIcon::StartFileDialog() { ::SolarMutexGuard aGuard; bool bDirty = m_bSystemDialogs != UseSystemFileDialog(); if ( m_pFileDlg && bDirty ) { // Destroy instance as changing the system file dialog setting // forces us to create a new FileDialogHelper instance! m_pFileDlg.reset(); } if ( !m_pFileDlg ) m_pFileDlg.reset( new FileDialogHelper( ui::dialogs::TemplateDescription::FILEOPEN_READONLY_VERSION, FileDialogFlags::MultiSelection, OUString(), SfxFilterFlags::NONE, SfxFilterFlags::NONE, nullptr ) ); m_pFileDlg->StartExecuteModal( LINK( this, ShutdownIcon, DialogClosedHdl_Impl ) ); } IMPL_LINK( ShutdownIcon, DialogClosedHdl_Impl, FileDialogHelper*, /*unused*/, void ) { DBG_ASSERT( m_pFileDlg, "ShutdownIcon, DialogClosedHdl_Impl(): no file dialog" ); // use constructor for filling up filters automatically! if ( ERRCODE_NONE == m_pFileDlg->GetError() ) { css::uno::Reference< XFilePicker3 > xPicker = m_pFileDlg->GetFilePicker(); try { if ( xPicker.is() ) { css::uno::Reference < XFilePickerControlAccess > xPickerControls ( xPicker, UNO_QUERY ); Sequence< OUString > sFiles = xPicker->getSelectedFiles(); int nFiles = sFiles.getLength(); css::uno::Reference < css::task::XInteractionHandler2 > xInteraction( task::InteractionHandler::createWithParent(::comphelper::getProcessComponentContext(), nullptr) ); int nArgs=3; Sequence< PropertyValue > aArgs{ comphelper::makePropertyValue(u"InteractionHandler"_ustr, xInteraction), comphelper::makePropertyValue(u"MacroExecutionMode"_ustr, sal_Int16(css::document::MacroExecMode::USE_CONFIG)), comphelper::makePropertyValue(u"UpdateDocMode"_ustr, sal_Int16(css::document::UpdateDocMode::ACCORDING_TO_CONFIG)) }; // use the filedlghelper to get the current filter name, // because it removes the extensions before you get the filter name. OUString aFilterName( m_pFileDlg->GetCurrentFilter() ); if ( xPickerControls.is() ) { // Set readonly flag bool bReadOnly = false; xPickerControls->getValue( ExtendedFilePickerElementIds::CHECKBOX_READONLY, 0 ) >>= bReadOnly; // Only set property if readonly is set to TRUE if ( bReadOnly ) { aArgs.realloc( ++nArgs ); auto pArgs = aArgs.getArray(); pArgs[nArgs-1].Name = "ReadOnly"; pArgs[nArgs-1].Value <<= bReadOnly; } // Get version string sal_Int32 iVersion = -1; xPickerControls->getValue( ExtendedFilePickerElementIds::LISTBOX_VERSION, ControlActions::GET_SELECTED_ITEM_INDEX ) >>= iVersion; if ( iVersion >= 0 ) { sal_Int16 uVersion = static_cast(iVersion); aArgs.realloc( ++nArgs ); auto pArgs = aArgs.getArray(); pArgs[nArgs-1].Name = "Version"; pArgs[nArgs-1].Value <<= uVersion; } // Retrieve the current filter if ( aFilterName.isEmpty() ) xPickerControls->getValue( CommonFilePickerElementIds::LISTBOX_FILTER, ControlActions::GET_SELECTED_ITEM ) >>= aFilterName; } // Convert UI filter name to internal filter name if ( !aFilterName.isEmpty() ) { std::shared_ptr pFilter = SfxGetpApp()->GetFilterMatcher().GetFilter4UIName( aFilterName, SfxFilterFlags::NONE, SfxFilterFlags::NOTINFILEDLG ); if ( pFilter ) { aFilterName = pFilter->GetFilterName(); if ( !aFilterName.isEmpty() ) { aArgs.realloc( ++nArgs ); auto pArgs = aArgs.getArray(); pArgs[nArgs-1].Name = "FilterName"; pArgs[nArgs-1].Value <<= aFilterName; } } } if ( 1 == nFiles ) OpenURL( sFiles[0], u"_default"_ustr, aArgs ); else { OUString aBaseDirURL = sFiles[0]; if ( !aBaseDirURL.isEmpty() && !aBaseDirURL.endsWith("/") ) aBaseDirURL += "/"; int iFiles; for ( iFiles = 1; iFiles < nFiles; iFiles++ ) { OUString aURL = aBaseDirURL + sFiles[iFiles]; OpenURL( aURL, u"_default"_ustr, aArgs ); } } } } catch ( ... ) { } } #ifdef _WIN32 // Destroy dialog to prevent problems with custom controls // This fix is dependent on the dialog settings. Destroying the dialog here will // crash the non-native dialog implementation! Therefore make this dependent on // the settings. if (UseSystemFileDialog()) { m_pFileDlg.reset(); } #endif LeaveModalMode(); } void ShutdownIcon::addTerminateListener() { ShutdownIcon* pInst = getInstance(); if ( ! pInst) return; if (pInst->m_bListenForTermination) return; css::uno::Reference< XDesktop2 > xDesktop = pInst->m_xDesktop; if ( ! xDesktop.is()) return; xDesktop->addTerminateListener( pInst ); pInst->m_bListenForTermination = true; } void ShutdownIcon::terminateDesktop() { ShutdownIcon* pInst = getInstance(); if ( ! pInst) return; css::uno::Reference< XDesktop2 > xDesktop = pInst->m_xDesktop; if ( ! xDesktop.is()) return; // always remove ourselves as listener pInst->m_bListenForTermination = true; xDesktop->removeTerminateListener( pInst ); // terminate desktop only if no tasks exist css::uno::Reference< XIndexAccess > xTasks = xDesktop->getFrames(); if( xTasks.is() && xTasks->getCount() < 1 ) Application::Quit(); // remove the instance pointer ShutdownIcon::pShutdownIcon = nullptr; } ShutdownIcon* ShutdownIcon::getInstance() { OSL_ASSERT( pShutdownIcon ); return pShutdownIcon.get(); } ShutdownIcon* ShutdownIcon::createInstance() { if (pShutdownIcon) return pShutdownIcon.get(); try { rtl::Reference pIcon(new ShutdownIcon( comphelper::getProcessComponentContext() )); pIcon->init (); pShutdownIcon = std::move(pIcon); } catch (...) { } return pShutdownIcon.get(); } void ShutdownIcon::init() { css::uno::Reference < XDesktop2 > xDesktop = Desktop::create( m_xContext ); std::unique_lock aGuard(m_aMutex); m_xDesktop = std::move(xDesktop); } void ShutdownIcon::disposing(std::unique_lock&) { m_xContext.clear(); m_xDesktop.clear(); deInitSystray(); } // XEventListener void SAL_CALL ShutdownIcon::disposing( const css::lang::EventObject& ) { } // XTerminateListener void SAL_CALL ShutdownIcon::queryTermination( const css::lang::EventObject& ) { SAL_INFO("sfx.appl", "ShutdownIcon::queryTermination: veto is " << m_bVeto); std::unique_lock aGuard( m_aMutex ); if ( m_bVeto ) throw css::frame::TerminationVetoException(); } void SAL_CALL ShutdownIcon::notifyTermination( const css::lang::EventObject& ) { } void SAL_CALL ShutdownIcon::initialize( const css::uno::Sequence< css::uno::Any>& aArguments ) { std::unique_lock aGuard( m_aMutex ); // third argument only sets veto, everything else will be ignored! if (aArguments.getLength() > 2) { bool bVeto = ::cppu::any2bool(aArguments[2]); m_bVeto = bVeto; return; } if ( aArguments.getLength() > 0 ) { if ( !ShutdownIcon::pShutdownIcon ) { try { bool bQuickstart = ::cppu::any2bool( aArguments[0] ); if( !bQuickstart && !GetAutostart() ) return; aGuard.unlock(); init (); aGuard.lock(); if ( !m_xDesktop.is() ) return; /* Create a sub-classed instance - foo */ ShutdownIcon::pShutdownIcon = this; initSystray(); } catch(const css::lang::IllegalArgumentException&) { } } } if ( aArguments.getLength() > 1 ) { bool bAutostart = ::cppu::any2bool( aArguments[1] ); if (bAutostart && !GetAutostart()) SetAutostart( true ); if (!bAutostart && GetAutostart()) SetAutostart( false ); } } void ShutdownIcon::EnterModalMode() { bModalMode = true; } void ShutdownIcon::LeaveModalMode() { bModalMode = false; } #ifdef _WIN32 // defined in shutdowniconw32.cxx #elif defined MACOSX // defined in shutdowniconaqua.cxx #else bool ShutdownIcon::IsQuickstarterInstalled() { return false; } #endif #ifdef ENABLE_QUICKSTART_APPLET #ifdef _WIN32 OUString ShutdownIcon::getShortcutName() { return GetAutostartFolderNameW32() + "\\" + SfxResId(STR_QUICKSTART_LNKNAME) + ".lnk"; } #endif // _WIN32 #endif bool ShutdownIcon::GetAutostart( ) { #if defined MACOSX return true; #elif defined ENABLE_QUICKSTART_APPLET bool bRet = false; OUString aShortcut( getShortcutName() ); OUString aShortcutUrl; osl::File::getFileURLFromSystemPath( aShortcut, aShortcutUrl ); osl::File f( aShortcutUrl ); osl::File::RC error = f.open( osl_File_OpenFlag_Read ); if( error == osl::File::E_None ) { f.close(); bRet = true; } return bRet; #else // ENABLE_QUICKSTART_APPLET return false; #endif } void ShutdownIcon::SetAutostart( bool bActivate ) { #ifdef ENABLE_QUICKSTART_APPLET #ifndef MACOSX OUString aShortcut( getShortcutName() ); #endif if( bActivate && IsQuickstarterInstalled() ) { #ifdef _WIN32 EnableAutostartW32( aShortcut ); #endif } else { #ifndef MACOSX OUString aShortcutUrl; ::osl::File::getFileURLFromSystemPath( aShortcut, aShortcutUrl ); ::osl::File::remove( aShortcutUrl ); #endif } #else (void)bActivate; // unused variable #endif // ENABLE_QUICKSTART_APPLET } const ::sal_Int32 PROPHANDLE_TERMINATEVETOSTATE = 0; // XFastPropertySet void SAL_CALL ShutdownIcon::setFastPropertyValue( ::sal_Int32 nHandle, const css::uno::Any& aValue ) { switch(nHandle) { case PROPHANDLE_TERMINATEVETOSTATE : { // use new value in case it's a valid information only bool bState( false ); if (! (aValue >>= bState)) return; m_bVeto = bState; if (m_bVeto && ! m_bListenForTermination) addTerminateListener(); } break; default : throw css::beans::UnknownPropertyException(OUString::number(nHandle)); } } // XFastPropertySet css::uno::Any SAL_CALL ShutdownIcon::getFastPropertyValue( ::sal_Int32 nHandle ) { css::uno::Any aValue; switch(nHandle) { case PROPHANDLE_TERMINATEVETOSTATE : { bool bState = (m_bListenForTermination && m_bVeto); aValue <<= bState; } break; default : throw css::beans::UnknownPropertyException(OUString::number(nHandle)); } return aValue; } extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface * com_sun_star_comp_desktop_QuickstartWrapper_get_implementation( css::uno::XComponentContext *context, css::uno::Sequence const &) { return cppu::acquire(new ShutdownIcon(context)); } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */