/* -*- 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 <dispatch/closedispatcher.hxx>
#include <pattern/frame.hxx>
#include <framework/framelistanalyzer.hxx>
#include <services.h>
#include <com/sun/star/bridge/BridgeFactory.hpp>
#include <com/sun/star/bridge/XBridgeFactory2.hpp>
#include <com/sun/star/frame/Desktop.hpp>
#include <com/sun/star/frame/DispatchResultState.hpp>
#include <com/sun/star/frame/XController.hpp>
#include <com/sun/star/frame/CommandGroup.hpp>
#include <com/sun/star/frame/StartModule.hpp>
#include <com/sun/star/lang/DisposedException.hpp>
#include <com/sun/star/awt/XTopWindow.hpp>
#include <com/sun/star/document/XActionLockable.hpp>
#include <com/sun/star/beans/XFastPropertySet.hpp>
#include <toolkit/helper/vclunohelper.hxx>
#include <osl/diagnose.h>
#include <utility>
#include <vcl/window.hxx>
#include <vcl/svapp.hxx>
#include <vcl/syswin.hxx>
#include <unotools/moduleoptions.hxx>
#include <o3tl/string_view.hxx>
using namespace com::sun::star;
namespace framework{
#ifdef fpf
#error "Who uses \"fpf\" as define. It will overwrite my namespace alias ..."
#endif
namespace fpf = ::framework::pattern::frame;
constexpr OUString URL_CLOSEDOC = u".uno:CloseDoc"_ustr;
constexpr OUString URL_CLOSEWIN = u".uno:CloseWin"_ustr;
const char URL_CLOSEFRAME[] = ".uno:CloseFrame";
CloseDispatcher::CloseDispatcher(css::uno::Reference< css::uno::XComponentContext > xContext ,
const css::uno::Reference< css::frame::XFrame >& xFrame ,
std::u16string_view sTarget)
: m_xContext(std::move(xContext))
, m_aAsyncCallback(
new vcl::EventPoster(LINK(this, CloseDispatcher, impl_asyncCallback)))
, m_eOperation(E_CLOSE_DOC)
, m_pSysWindow(nullptr)
{
uno::Reference<frame::XFrame> xTarget = static_impl_searchRightTargetFrame(xFrame, sTarget);
m_xCloseFrame = xTarget;
// Try to retrieve the system window instance of the closing frame.
uno::Reference<awt::XWindow> xWindow = xTarget->getContainerWindow();
if (xWindow.is())
{
VclPtr<vcl::Window> pWindow = VCLUnoHelper::GetWindow(xWindow);
if (pWindow->IsSystemWindow())
m_pSysWindow = dynamic_cast<SystemWindow*>(pWindow.get());
}
}
CloseDispatcher::~CloseDispatcher()
{
SolarMutexGuard g;
m_aAsyncCallback.reset();
m_pSysWindow.reset();
}
void SAL_CALL CloseDispatcher::dispatch(const css::util::URL& aURL ,
const css::uno::Sequence< css::beans::PropertyValue >& lArguments)
{
dispatchWithNotification(aURL, lArguments, css::uno::Reference< css::frame::XDispatchResultListener >());
}
css::uno::Sequence< sal_Int16 > SAL_CALL CloseDispatcher::getSupportedCommandGroups()
{
return css::uno::Sequence< sal_Int16 >{css::frame::CommandGroup::VIEW, css::frame::CommandGroup::DOCUMENT};
}
css::uno::Sequence< css::frame::DispatchInformation > SAL_CALL CloseDispatcher::getConfigurableDispatchInformation(sal_Int16 nCommandGroup)
{
if (nCommandGroup == css::frame::CommandGroup::VIEW)
{
/* Attention: Don't add .uno:CloseFrame here. Because it's not really
a configurable feature ... and further it does not have
a valid UIName entry inside the GenericCommands.xcu ... */
css::uno::Sequence< css::frame::DispatchInformation > lViewInfos{
{ URL_CLOSEWIN, css::frame::CommandGroup::VIEW }
};
return lViewInfos;
}
else if (nCommandGroup == css::frame::CommandGroup::DOCUMENT)
{
css::uno::Sequence< css::frame::DispatchInformation > lDocInfos{
{ URL_CLOSEDOC, css::frame::CommandGroup::DOCUMENT }
};
return lDocInfos;
}
return css::uno::Sequence< css::frame::DispatchInformation >();
}
void SAL_CALL CloseDispatcher::addStatusListener(const css::uno::Reference< css::frame::XStatusListener >& /*xListener*/,
const css::util::URL& /*aURL*/ )
{
}
void SAL_CALL CloseDispatcher::removeStatusListener(const css::uno::Reference< css::frame::XStatusListener >& /*xListener*/,
const css::util::URL& /*aURL*/ )
{
}
void SAL_CALL CloseDispatcher::dispatchWithNotification(const css::util::URL& aURL ,
const css::uno::Sequence< css::beans::PropertyValue >& lArguments,
const css::uno::Reference< css::frame::XDispatchResultListener >& xListener )
{
// SAFE -> ----------------------------------
SolarMutexClearableGuard aWriteLock;
// This reference indicates, that we were already called before and
// our asynchronous process was not finished yet.
// We have to reject double calls. Otherwise we risk,
// that we try to close an already closed resource...
// And it is no problem to do nothing then. The UI user will try it again, if
// non of these jobs was successful.
if (m_xSelfHold.is())
{
aWriteLock.clear();
// <- SAFE ------------------------------
implts_notifyResultListener(
xListener,
css::frame::DispatchResultState::DONTKNOW,
css::uno::Any());
return;
}
// First we have to check, if this dispatcher is used right. Means if valid URLs are used.
// If not - we have to break this operation. But an optional listener must be informed.
// BTW: We save the information about the requested operation. Because
// we need it later.
if ( aURL.Complete == URL_CLOSEDOC )
m_eOperation = E_CLOSE_DOC;
else if ( aURL.Complete == URL_CLOSEWIN )
m_eOperation = E_CLOSE_WIN;
else if ( aURL.Complete == URL_CLOSEFRAME )
m_eOperation = E_CLOSE_FRAME;
else
{
aWriteLock.clear();
// <- SAFE ------------------------------
implts_notifyResultListener(
xListener,
css::frame::DispatchResultState::FAILURE,
css::uno::Any());
return;
}
if (m_pSysWindow && m_pSysWindow->GetCloseHdl().IsSet())
{
// The closing frame has its own close handler. Call it instead.
m_pSysWindow->GetCloseHdl().Call(*m_pSysWindow);
aWriteLock.clear();
// <- SAFE ------------------------------
implts_notifyResultListener(
xListener,
css::frame::DispatchResultState::SUCCESS,
css::uno::Any());
return;
}
// OK - URLs are the right ones.
// But we can't execute synchronously :-)
// May we are called from a generic key-input handler,
// which isn't aware that this call kill its own environment...
// Do it asynchronous everytimes!
// But don't forget to hold ourselves alive.
// We are called back from an environment, which doesn't know a uno reference.
// They call us back by using our c++ interface.
m_xResultListener = xListener;
m_xSelfHold.set(static_cast< ::cppu::OWeakObject* >(this), css::uno::UNO_QUERY);
aWriteLock.clear();
// <- SAFE ----------------------------------
bool bIsSynchron = false;
for (const css::beans::PropertyValue& rArg : lArguments )
{
if ( rArg.Name == "SynchronMode" )
{
rArg.Value >>= bIsSynchron;
break;
}
}
if ( bIsSynchron )
impl_asyncCallback(nullptr);
else
{
SolarMutexGuard g;
m_aAsyncCallback->Post();
}
}
/**
@short asynchronous callback
@descr We start all actions inside this object asynchronous
(see comments there).
Now we do the following:
- close all views to the same document, if needed and possible
- make the current frame empty
! This step is necessary to handle errors during closing the
document inside the frame. May the document shows a dialog and
the user ignore it. Then the state of the office can be changed
during we try to close frame and document.
- check the environment (means count open frames - excluding our
current one)
- decide then, if we must close this frame only, establish the backing mode
or shutdown the whole application.
*/
IMPL_LINK_NOARG(CloseDispatcher, impl_asyncCallback, LinkParamNone*, void)
{
try
{
// Allow calling of XController->suspend() everytimes.
// Dispatch is an UI functionality. We implement such dispatch object here.
// And further XController->suspend() was designed to bring an UI ...
bool bControllerSuspended = false;
bool bCloseAllViewsToo;
EOperation eOperation;
css::uno::Reference< css::uno::XComponentContext > xContext;
css::uno::Reference< css::frame::XFrame > xCloseFrame;
css::uno::Reference< css::frame::XDispatchResultListener > xListener;
{
SolarMutexGuard g;
// Closing of all views, related to the same document, is allowed
// only if the dispatched URL was ".uno:CloseDoc"!
bCloseAllViewsToo = (m_eOperation == E_CLOSE_DOC);
eOperation = m_eOperation;
xContext = m_xContext;
xCloseFrame.set(m_xCloseFrame.get(), css::uno::UNO_QUERY);
xListener = m_xResultListener;
}
// frame already dead ?!
// Nothing to do !
if (! xCloseFrame.is())
return;
bool bCloseFrame = false;
bool bEstablishBackingMode = false;
bool bTerminateApp = false;
// Analyze the environment a first time.
// If we found some special cases, we can
// make some decisions earlier!
css::uno::Reference< css::frame::XFramesSupplier > xDesktop( css::frame::Desktop::create(xContext), css::uno::UNO_QUERY_THROW);
FrameListAnalyzer aCheck1(xDesktop, xCloseFrame, FrameAnalyzerFlags::Help | FrameAnalyzerFlags::BackingComponent);
// Check for existing UNO connections.
// NOTE: There is a race between checking this and connections being created/destroyed before
// we close the frame / terminate the app.
css::uno::Reference<css::bridge::XBridgeFactory2> bridgeFac( css::bridge::BridgeFactory::create(xContext) );
bool bHasActiveConnections = bridgeFac->getExistingBridges().hasElements();
// a) If the current frame (where the close dispatch was requested for) does not have
// any parent frame ... it will close this frame only. Such frame isn't part of the
// global desktop tree ... and such frames are used as "implementation details" only.
// E.g. the live previews of our wizards doing such things. And then the owner of the frame
// is responsible for closing the application or accepting closing of the application
// by others.
if ( ! xCloseFrame->getCreator().is())
bCloseFrame = true;
// b) The help window can't disagree with any request.
// Because it doesn't implement a controller - it uses a window only.
// Further it can't be the last open frame - if we do all other things
// right inside this CloseDispatcher implementation.
// => close it!
else if (aCheck1.m_bReferenceIsHelp)
bCloseFrame = true;
// c) If we are already in "backing mode", we terminate the application, if no active UNO connections are found.
// If there is an active UNO connection, we only close the frame and leave the application alive.
// It doesn't matter, how many other frames (can be the help or hidden frames only) are open then.
else if (aCheck1.m_bReferenceIsBacking) {
if (bHasActiveConnections)
bCloseFrame = true;
else
bTerminateApp = true;
}
// d) Otherwise we have to: close all views to the same document, close the
// document inside our own frame and decide then again, what has to be done!
else
{
if (implts_prepareFrameForClosing(m_xCloseFrame, bCloseAllViewsToo, bControllerSuspended))
{
// OK; this frame is empty now.
// Check the environment again to decide, what is the next step.
FrameListAnalyzer aCheck2(xDesktop, xCloseFrame, FrameAnalyzerFlags::All);
// c1) there is as minimum 1 frame open, which is visible and contains a document
// different from our one. And it's not the help!
// (tdf#30920 consider that closing a frame which is not the backing window (start center) while there is
// another frame that is the backing window open only closes the frame, and not terminate the app, so
// closing the license frame doesn't terminate the app if launched from the start center)
// => close our frame only - nothing else.
if (!aCheck2.m_lOtherVisibleFrames.empty() || (!aCheck2.m_bReferenceIsBacking && aCheck2.m_xBackingComponent.is()))
bCloseFrame = true;
// c2) if we close the current view ... but not all other views
// to the same document, we must close the current frame only!
// Because implts_closeView() suspended this view only - does not
// close the frame.
if (
(!bCloseAllViewsToo ) &&
(!aCheck2.m_lModelFrames.empty())
)
bCloseFrame = true;
else
// c3) there is no other (visible) frame open ...
// The help module will be ignored everytimes!
// But we have to decide if we must terminate the
// application or establish the backing mode now.
// And that depends from the dispatched URL ...
{
if (eOperation == E_CLOSE_FRAME)
{
if (bHasActiveConnections)
bCloseFrame = true;
else
bTerminateApp = true;
}
else if( SvtModuleOptions().IsModuleInstalled(SvtModuleOptions::EModule::STARTMODULE) )
bEstablishBackingMode = true;
else if (bHasActiveConnections)
bCloseFrame = true;
else
bTerminateApp = true;
}
}
}
// Do it now ...
bool bSuccess = false;
if (bCloseFrame)
bSuccess = implts_closeFrame();
else if (bEstablishBackingMode)
#if defined MACOSX
{
// on mac close down, quickstarter keeps the process alive
// however if someone has shut down the quickstarter
// behave as any other platform
bool bQuickstarterRunning = false;
// get quickstart service
try
{
css::uno::Reference< css::beans::XFastPropertySet > xSet( xContext->getServiceManager()->createInstanceWithContext(IMPLEMENTATIONNAME_QUICKLAUNCHER, xContext), css::uno::UNO_QUERY_THROW );
css::uno::Any aVal( xSet->getFastPropertyValue( 0 ) );
bool bState = false;
if( aVal >>= bState )
bQuickstarterRunning = bState;
}
catch( const css::uno::Exception& )
{
}
bSuccess = bQuickstarterRunning ? implts_terminateApplication() : implts_establishBackingMode();
}
#else
bSuccess = implts_establishBackingMode();
#endif
else if (bTerminateApp)
bSuccess = implts_terminateApplication();
if ( ! bSuccess && bControllerSuspended )
{
css::uno::Reference< css::frame::XController > xController = xCloseFrame->getController();
if (xController.is())
xController->suspend(false);
}
// inform listener
sal_Int16 nState = css::frame::DispatchResultState::FAILURE;
if (bSuccess)
nState = css::frame::DispatchResultState::SUCCESS;
implts_notifyResultListener(xListener, nState, css::uno::Any());
SolarMutexGuard g;
// This method was called asynchronous from our main thread by using a pointer.
// We reached this method only, by using a reference to ourself :-)
// Further this member is used to detect still running and not yet finished
// asynchronous operations. So it's time now to release this reference.
// But hold it temp alive. Otherwise we die before we can finish this method really :-))
css::uno::Reference< css::uno::XInterface > xTempHold = m_xSelfHold;
m_xSelfHold.clear();
m_xResultListener.clear();
}
catch(const css::lang::DisposedException&)
{
}
}
bool CloseDispatcher::implts_prepareFrameForClosing(const css::uno::Reference< css::frame::XFrame >& xFrame,
bool bCloseAllOtherViewsToo,
bool& bControllerSuspended )
{
// Frame already dead ... so this view is closed ... is closed ... is ... .-)
if (! xFrame.is())
return true;
// Close all views to the same document ... if forced to do so.
// But don't touch our own frame here!
// We must do so ... because the may be following controller->suspend()
// will show the "save/discard/cancel" dialog for the last view only!
if (bCloseAllOtherViewsToo)
{
css::uno::Reference< css::uno::XComponentContext > xContext;
{
SolarMutexGuard g;
xContext = m_xContext;
}
css::uno::Reference< css::frame::XFramesSupplier > xDesktop( css::frame::Desktop::create( xContext ), css::uno::UNO_QUERY_THROW);
FrameListAnalyzer aCheck(xDesktop, xFrame, FrameAnalyzerFlags::All);
size_t c = aCheck.m_lModelFrames.size();
size_t i = 0;
for (i=0; i<c; ++i)
{
if (!fpf::closeIt(aCheck.m_lModelFrames[i]))
return false;
}
}
// Inform user about modified documents or still running jobs (e.g. printing).
{
css::uno::Reference< css::frame::XController > xController = xFrame->getController();
if (xController.is()) // some views don't uses a controller .-( (e.g. the help window)
{
bControllerSuspended = xController->suspend(true);
if (! bControllerSuspended)
return false;
}
}
// don't remove the component really by e.g. calling setComponent(null, null).
// It's enough to suspend the controller.
// If we close the frame later this controller doesn't show the same dialog again.
return true;
}
bool CloseDispatcher::implts_closeFrame()
{
css::uno::Reference< css::frame::XFrame > xFrame;
{
SolarMutexGuard g;
xFrame.set(m_xCloseFrame.get(), css::uno::UNO_QUERY);
}
// frame already dead ? => so it's closed ... it's closed ...
if ( ! xFrame.is() )
return true;
// don't deliver ownership; our "UI user" will try it again if it failed.
// OK - he will get an empty frame then. But normally an empty frame
// should be closeable always :-)
if (!fpf::closeIt(xFrame))
return false;
{
SolarMutexGuard g;
m_xCloseFrame.clear();
}
return true;
}
bool CloseDispatcher::implts_establishBackingMode()
{
css::uno::Reference< css::uno::XComponentContext > xContext;
css::uno::Reference< css::frame::XFrame > xFrame;
{
SolarMutexGuard g;
xContext = m_xContext;
xFrame.set(m_xCloseFrame.get(), css::uno::UNO_QUERY);
}
if (!xFrame.is())
return false;
css::uno::Reference < css::document::XActionLockable > xLock( xFrame, css::uno::UNO_QUERY );
if ( xLock.is() && xLock->isActionLocked() )
return false;
css::uno::Reference< css::awt::XWindow > xContainerWindow = xFrame->getContainerWindow();
css::uno::Reference< css::frame::XController > xStartModule = css::frame::StartModule::createWithParentWindow(
xContext, xContainerWindow);
// Attention: You MUST(!) call setComponent() before you call attachFrame().
css::uno::Reference< css::awt::XWindow > xBackingWin(xStartModule, css::uno::UNO_QUERY);
xFrame->setComponent(xBackingWin, xStartModule);
xStartModule->attachFrame(xFrame);
xContainerWindow->setVisible(true);
return true;
}
bool CloseDispatcher::implts_terminateApplication()
{
css::uno::Reference< css::uno::XComponentContext > xContext;
{
SolarMutexGuard g;
xContext = m_xContext;
}
css::uno::Reference< css::frame::XDesktop2 > xDesktop = css::frame::Desktop::create( xContext );
return xDesktop->terminate();
}
void CloseDispatcher::implts_notifyResultListener(const css::uno::Reference< css::frame::XDispatchResultListener >& xListener,
sal_Int16 nState ,
const css::uno::Any& aResult )
{
if (!xListener.is())
return;
css::frame::DispatchResultEvent aEvent(
css::uno::Reference< css::uno::XInterface >(static_cast< ::cppu::OWeakObject* >(this), css::uno::UNO_QUERY),
nState,
aResult);
xListener->dispatchFinished(aEvent);
}
css::uno::Reference< css::frame::XFrame > CloseDispatcher::static_impl_searchRightTargetFrame(const css::uno::Reference< css::frame::XFrame >& xFrame ,
std::u16string_view sTarget)
{
if (o3tl::equalsIgnoreAsciiCase(sTarget, u"_self"))
return xFrame;
OSL_ENSURE(sTarget.empty(), "CloseDispatch used for unexpected target. Magic things will happen now .-)");
css::uno::Reference< css::frame::XFrame > xTarget = xFrame;
while(true)
{
// a) top frames will be closed
if (xTarget->isTop())
return xTarget;
// b) even child frame containing top level windows (e.g. query designer of database) will be closed
css::uno::Reference< css::awt::XWindow > xWindow = xTarget->getContainerWindow();
css::uno::Reference< css::awt::XTopWindow > xTopWindowCheck(xWindow, css::uno::UNO_QUERY);
if (xTopWindowCheck.is())
{
// b1) Note: Toolkit interface XTopWindow sometimes is used by real VCL-child-windows also .-)
// Be sure that these window is really a "top system window".
// Attention ! Checking Window->GetParent() isn't the right approach here.
// Because sometimes VCL create "implicit border windows" as parents even we created
// a simple XWindow using the toolkit only .-(
SolarMutexGuard aSolarLock;
VclPtr<vcl::Window> pWindow = VCLUnoHelper::GetWindow( xWindow );
if ( pWindow && pWindow->IsSystemWindow() )
return xTarget;
}
// c) try to find better results on parent frame
// If no parent frame exists (because this frame is used outside the desktop tree)
// the given frame must be used directly.
css::uno::Reference< css::frame::XFrame > xParent = xTarget->getCreator();
if ( ! xParent.is())
return xTarget;
// c1) check parent frame inside next loop ...
xTarget = std::move(xParent);
}
}
} // namespace framework
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
↑ V547 Expression 'bIsSynchron' is always false.