1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <sal/config.h>
22 #include <config_dbus.h>
23 #include <config_features.h>
24 #include <config_feature_desktop.h>
27 #include "officeipcthread.hxx"
28 #include "cmdlineargs.hxx"
29 #include "dispatchwatcher.hxx"
30 #include <com/sun/star/frame/TerminationVetoException.hpp>
31 #include <salhelper/thread.hxx>
32 #include <sal/log.hxx>
33 #include <unotools/bootstrap.hxx>
34 #include <vcl/svapp.hxx>
35 #include <unotools/configmgr.hxx>
36 #include <osl/pipe.hxx>
37 #include <rtl/digest.h>
38 #include <rtl/ustrbuf.hxx>
39 #include <rtl/instance.hxx>
40 #include <osl/conditn.hxx>
41 #include <unotools/moduleoptions.hxx>
42 #include <rtl/strbuf.hxx>
43 #include <cppuhelper/supportsservice.hxx>
44 #include <osl/file.hxx>
45 #include <rtl/process.h>
52 #include <dbus/dbus.h>
53 #include <sys/socket.h>
56 using namespace desktop
;
57 using namespace ::com::sun::star::uno
;
58 using namespace ::com::sun::star::lang
;
59 using namespace ::com::sun::star::frame
;
63 char const ARGUMENT_PREFIX
[] = "InternalIPC::Arguments";
64 char const SEND_ARGUMENTS
[] = "InternalIPC::SendArguments";
65 char const PROCESSING_DONE
[] = "InternalIPC::ProcessingDone";
67 // Receives packets from the pipe until a packet ends in a NUL character (that
68 // will not be included in the returned string) or it cannot read anything (due
69 // to error or closed pipe, in which case an empty string will be returned to
71 OString
readStringFromPipe(osl::StreamPipe
const & pipe
) {
72 for (OStringBuffer str
;;) {
74 sal_Int32 n
= pipe
.recv(buf
, SAL_N_ELEMENTS(buf
));
76 SAL_INFO("desktop.app", "read empty string");
80 if (buf
[n
- 1] == '\0') {
85 //TODO: how does OStringBuffer.append handle overflow?
87 auto s
= str
.makeStringAndClear();
88 SAL_INFO("desktop.app", "read <" << s
<< ">");
101 class Parser
: public CommandLineArgs::Supplier
{
103 explicit Parser(OString
const & input
): m_input(input
) {
104 if (!m_input
.match(ARGUMENT_PREFIX
) ||
105 m_input
.getLength() == RTL_CONSTASCII_LENGTH(ARGUMENT_PREFIX
))
107 throw CommandLineArgs::Supplier::Exception();
109 m_index
= RTL_CONSTASCII_LENGTH(ARGUMENT_PREFIX
);
110 switch (m_input
[m_index
++]) {
116 if (!next(&url
, false)) {
117 throw CommandLineArgs::Supplier::Exception();
125 if (!next(&path
, false)) {
126 throw CommandLineArgs::Supplier::Exception();
129 if (osl::FileBase::getFileURLFromSystemPath(path
, url
) ==
130 osl::FileBase::E_None
)
137 throw CommandLineArgs::Supplier::Exception();
141 virtual std::optional
< OUString
> getCwdUrl() override
{ return m_cwdUrl
; }
143 virtual bool next(OUString
* argument
) override
{ return next(argument
, true); }
146 bool next(OUString
* argument
, bool prefix
) {
147 OSL_ASSERT(argument
!= nullptr);
148 if (m_index
< m_input
.getLength()) {
150 if (m_input
[m_index
] != ',') {
151 throw CommandLineArgs::Supplier::Exception();
156 while (m_index
< m_input
.getLength()) {
157 char c
= m_input
[m_index
];
163 if (m_index
>= m_input
.getLength())
164 throw CommandLineArgs::Supplier::Exception();
165 c
= m_input
[m_index
++];
174 throw CommandLineArgs::Supplier::Exception();
179 OString
b2(b
.makeStringAndClear());
180 if (!rtl_convertStringToUString(
181 &argument
->pData
, b2
.getStr(), b2
.getLength(),
182 RTL_TEXTENCODING_UTF8
,
183 (RTL_TEXTTOUNICODE_FLAGS_UNDEFINED_ERROR
|
184 RTL_TEXTTOUNICODE_FLAGS_MBUNDEFINED_ERROR
|
185 RTL_TEXTTOUNICODE_FLAGS_INVALID_ERROR
)))
187 throw CommandLineArgs::Supplier::Exception();
195 std::optional
< OUString
> m_cwdUrl
;
200 bool addArgument(OStringBuffer
&rArguments
, char prefix
,
201 const OUString
&rArgument
)
204 if (!rArgument
.convertToString(
205 &utf8
, RTL_TEXTENCODING_UTF8
,
206 (RTL_UNICODETOTEXT_FLAGS_UNDEFINED_ERROR
|
207 RTL_UNICODETOTEXT_FLAGS_INVALID_ERROR
)))
211 rArguments
.append(prefix
);
212 for (sal_Int32 i
= 0; i
< utf8
.getLength(); ++i
) {
216 rArguments
.append("\\0");
219 rArguments
.append("\\,");
222 rArguments
.append("\\\\");
225 rArguments
.append(c
);
234 rtl::Reference
< RequestHandler
> RequestHandler::pGlobal
;
236 // Turns a string in aMsg such as file:///home/foo/.libreoffice/3
237 // Into a hex string of well known length ff132a86...
238 static OUString
CreateMD5FromString( const OUString
& aMsg
)
240 SAL_INFO("desktop.app", "create md5 from '" << aMsg
<< "'");
242 rtlDigest handle
= rtl_digest_create( rtl_Digest_AlgorithmMD5
);
245 const sal_uInt8
* pData
= reinterpret_cast<const sal_uInt8
*>(aMsg
.getStr());
246 sal_uInt32 nSize
= aMsg
.getLength() * sizeof( sal_Unicode
);
247 sal_uInt32 nMD5KeyLen
= rtl_digest_queryLength( handle
);
248 std::unique_ptr
<sal_uInt8
[]> pMD5KeyBuffer(new sal_uInt8
[ nMD5KeyLen
]);
250 rtl_digest_init( handle
, pData
, nSize
);
251 rtl_digest_update( handle
, pData
, nSize
);
252 rtl_digest_get( handle
, pMD5KeyBuffer
.get(), nMD5KeyLen
);
253 rtl_digest_destroy( handle
);
255 // Create hex-value string from the MD5 value to keep the string size minimal
256 OUStringBuffer
aBuffer( nMD5KeyLen
* 2 + 1 );
257 for ( sal_uInt32 i
= 0; i
< nMD5KeyLen
; i
++ )
258 aBuffer
.append( static_cast<sal_Int32
>(pMD5KeyBuffer
[i
]), 16 );
260 return aBuffer
.makeStringAndClear();
268 class ProcessEventsClass_Impl
271 DECL_STATIC_LINK( ProcessEventsClass_Impl
, CallEvent
, void*, void );
272 DECL_STATIC_LINK( ProcessEventsClass_Impl
, ProcessDocumentsEvent
, void*, void );
277 IMPL_STATIC_LINK( ProcessEventsClass_Impl
, CallEvent
, void*, pEvent
, void )
279 // Application events are processed by the Desktop::HandleAppEvent implementation.
280 Desktop::HandleAppEvent( *static_cast<ApplicationEvent
*>(pEvent
) );
281 delete static_cast<ApplicationEvent
*>(pEvent
);
284 IMPL_STATIC_LINK( ProcessEventsClass_Impl
, ProcessDocumentsEvent
, void*, pEvent
, void )
286 // Documents requests are processed by the RequestHandler implementation
287 ProcessDocumentsRequest
* pDocsRequest
= static_cast<ProcessDocumentsRequest
*>(pEvent
);
288 RequestHandler::ExecuteCmdLineRequests(*pDocsRequest
, false);
292 static void ImplPostForeignAppEvent( ApplicationEvent
* pEvent
)
294 Application::PostUserEvent( LINK( nullptr, ProcessEventsClass_Impl
, CallEvent
), pEvent
);
297 static void ImplPostProcessDocumentsEvent( std::unique_ptr
<ProcessDocumentsRequest
> pEvent
)
299 Application::PostUserEvent( LINK( nullptr, ProcessEventsClass_Impl
, ProcessDocumentsEvent
), pEvent
.release() );
302 oslSignalAction
SalMainPipeExchangeSignal_impl(SAL_UNUSED_PARAMETER
void* /*pData*/, oslSignalInfo
* pInfo
)
304 if( pInfo
->Signal
== osl_Signal_Terminate
)
305 RequestHandler::SetDowning();
306 return osl_Signal_ActCallNextHdl
;
310 // The RequestHandlerController implementation is a bookkeeper for all pending requests
311 // that were created by the RequestHandler. The requests are waiting to be processed by
312 // our framework loadComponentFromURL function (e.g. open/print request).
313 // During shutdown the framework is asking RequestHandlerController about pending requests.
314 // If there are pending requests framework has to stop the shutdown process. It is waiting
315 // for these requests because framework is not able to handle shutdown and open a document
320 OUString SAL_CALL
RequestHandlerController::getImplementationName()
322 return "com.sun.star.comp.RequestHandlerController";
325 sal_Bool
RequestHandlerController::supportsService(
326 OUString
const & ServiceName
)
328 return cppu::supportsService(this, ServiceName
);
331 Sequence
< OUString
> SAL_CALL
RequestHandlerController::getSupportedServiceNames()
337 void SAL_CALL
RequestHandlerController::disposing( const EventObject
& )
341 // XTerminateListener
342 void SAL_CALL
RequestHandlerController::queryTermination( const EventObject
& )
344 // Desktop ask about pending request through our office ipc pipe. We have to
345 // be sure that no pending request is waiting because framework is not able to
346 // handle shutdown and open a document concurrently.
348 if ( RequestHandler::AreRequestsPending() )
349 throw TerminationVetoException();
350 RequestHandler::SetDowning();
353 void SAL_CALL
RequestHandlerController::notifyTermination( const EventObject
& )
357 class IpcThread
: public salhelper::Thread
{
359 void start(RequestHandler
* handler
) {
364 virtual void close() = 0;
367 explicit IpcThread(char const * name
): Thread(name
), m_handler(nullptr) {}
369 virtual ~IpcThread() override
{}
371 bool process(OString
const & arguments
, bool * waitProcessed
);
373 RequestHandler
* m_handler
;
376 class PipeIpcThread
: public IpcThread
{
378 static RequestHandler::Status
enable(rtl::Reference
<IpcThread
> * thread
);
381 explicit PipeIpcThread(osl::Pipe
const & pipe
):
382 IpcThread("PipeIPC"), pipe_(pipe
)
385 virtual ~PipeIpcThread() override
{}
387 void execute() override
;
389 void close() override
{ pipe_
.close(); }
398 struct DbusConnectionHolder
{
399 explicit DbusConnectionHolder(DBusConnection
* theConnection
):
400 connection(theConnection
)
403 DbusConnectionHolder(DbusConnectionHolder
&& other
): connection(nullptr)
404 { std::swap(connection
, other
.connection
); }
406 ~DbusConnectionHolder() {
407 if (connection
!= nullptr) {
408 dbus_connection_close(connection
);
409 dbus_connection_unref(connection
);
413 DBusConnection
* connection
;
416 struct DbusMessageHolder
{
417 explicit DbusMessageHolder(DBusMessage
* theMessage
): message(theMessage
) {}
419 ~DbusMessageHolder() { clear(); }
422 if (message
!= nullptr) {
423 dbus_message_unref(message
);
428 DBusMessage
* message
;
431 DbusMessageHolder(DbusMessageHolder
const &) = delete;
432 DbusMessageHolder
& operator =(DbusMessageHolder
const &) = delete;
437 class DbusIpcThread
: public IpcThread
{
439 static RequestHandler::Status
enable(rtl::Reference
<IpcThread
> * thread
);
442 explicit DbusIpcThread(DbusConnectionHolder
&& connection
):
443 IpcThread("DbusIPC"), connection_(std::move(connection
))
446 virtual ~DbusIpcThread() override
{}
448 void execute() override
;
450 void close() override
;
452 DbusConnectionHolder connection_
;
455 RequestHandler::Status
DbusIpcThread::enable(rtl::Reference
<IpcThread
> * thread
)
457 assert(thread
!= nullptr);
458 if (!dbus_threads_init_default()) {
459 SAL_WARN("desktop.app", "dbus_threads_init_default failed");
460 return RequestHandler::IPC_STATUS_BOOTSTRAP_ERROR
;
464 DbusConnectionHolder
con(dbus_bus_get_private(DBUS_BUS_SESSION
, &e
));
465 assert((con
.connection
== nullptr) == bool(dbus_error_is_set(&e
)));
466 if (con
.connection
== nullptr) {
469 "dbus_bus_get_private failed with: " << e
.name
<< ": "
472 return RequestHandler::IPC_STATUS_BOOTSTRAP_ERROR
;
475 int n
= dbus_bus_request_name(
476 con
.connection
, "org.libreoffice.LibreOfficeIpc0",
477 DBUS_NAME_FLAG_DO_NOT_QUEUE
, &e
);
478 assert((n
== -1) == bool(dbus_error_is_set(&e
)));
483 "dbus_bus_request_name failed with: " << e
.name
<< ": "
486 return RequestHandler::IPC_STATUS_BOOTSTRAP_ERROR
;
487 case DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER
:
488 *thread
= new DbusIpcThread(std::move(con
));
489 return RequestHandler::IPC_STATUS_OK
;
490 case DBUS_REQUEST_NAME_REPLY_EXISTS
:
492 OStringBuffer
buf(ARGUMENT_PREFIX
);
494 if (!(utl::Bootstrap::getProcessWorkingDir(arg
)
495 && addArgument(buf
, '1', arg
)))
499 sal_uInt32 narg
= rtl_getAppCommandArgCount();
500 for (sal_uInt32 i
= 0; i
!= narg
; ++i
) {
501 rtl_getAppCommandArg(i
, &arg
.pData
);
502 if (!addArgument(buf
, ',', arg
)) {
503 return RequestHandler::IPC_STATUS_BOOTSTRAP_ERROR
;
506 char const * argstr
= buf
.getStr();
507 DbusMessageHolder
msg(
508 dbus_message_new_method_call(
509 "org.libreoffice.LibreOfficeIpc0",
510 "/org/libreoffice/LibreOfficeIpc0",
511 "org.libreoffice.LibreOfficeIpcIfc0", "Execute"));
512 if (msg
.message
== nullptr) {
514 "desktop.app", "dbus_message_new_method_call failed");
515 return RequestHandler::IPC_STATUS_BOOTSTRAP_ERROR
;
518 dbus_message_iter_init_append(msg
.message
, &it
);
519 if (!dbus_message_iter_append_basic(
520 &it
, DBUS_TYPE_STRING
, &argstr
))
523 "desktop.app", "dbus_message_iter_append_basic failed");
524 return RequestHandler::IPC_STATUS_BOOTSTRAP_ERROR
;
526 DbusMessageHolder
repl(
527 dbus_connection_send_with_reply_and_block(
528 con
.connection
, msg
.message
, 0x7FFFFFFF, &e
));
530 (repl
.message
== nullptr) == bool(dbus_error_is_set(&e
)));
531 if (repl
.message
== nullptr) {
534 "dbus_connection_send_with_reply_and_block failed"
535 " with: " << e
.name
<< ": " << e
.message
);
539 return RequestHandler::IPC_STATUS_2ND_OFFICE
;
541 case DBUS_REQUEST_NAME_REPLY_IN_QUEUE
:
542 case DBUS_REQUEST_NAME_REPLY_ALREADY_OWNER
:
545 "dbus_bus_request_name failed with unexpected " << +n
);
546 return RequestHandler::IPC_STATUS_BOOTSTRAP_ERROR
;
548 for (;;) std::abort();
553 void DbusIpcThread::execute()
555 assert(m_handler
!= nullptr);
556 m_handler
->cReady
.wait();
559 osl::MutexGuard
g(RequestHandler::GetMutex());
560 if (m_handler
->mState
== RequestHandler::State::Downing
) {
564 if (!dbus_connection_read_write(connection_
.connection
, -1)) {
568 DbusMessageHolder
msg(
569 dbus_connection_pop_message(connection_
.connection
));
570 if (msg
.message
== nullptr) {
573 if (!dbus_message_is_method_call(
574 msg
.message
, "org.libreoffice.LibreOfficeIpcIfc0",
577 SAL_INFO("desktop.app", "unknown DBus message ignored");
581 if (!dbus_message_iter_init(msg
.message
, &it
)) {
583 "desktop.app", "DBus message without argument ignored");
586 if (dbus_message_iter_get_arg_type(&it
) != DBUS_TYPE_STRING
) {
589 "DBus message with non-string argument ignored");
593 dbus_message_iter_get_basic(&it
, &argstr
);
594 bool waitProcessed
= false;
596 osl::MutexGuard
g(RequestHandler::GetMutex());
597 if (!process(argstr
, &waitProcessed
)) {
602 m_handler
->cProcessed
.wait();
604 DbusMessageHolder
repl(dbus_message_new_method_return(msg
.message
));
605 if (repl
.message
== nullptr) {
607 "desktop.app", "dbus_message_new_method_return failed");
610 dbus_uint32_t serial
= 0;
611 if (!dbus_connection_send(
612 connection_
.connection
, repl
.message
, &serial
)) {
613 SAL_WARN("desktop.app", "dbus_connection_send failed");
616 dbus_connection_flush(connection_
.connection
);
621 void DbusIpcThread::close() {
622 assert(connection_
.connection
!= nullptr);
623 // Make dbus_connection_read_write fall out of internal poll call blocking
626 if (!dbus_connection_get_socket(connection_
.connection
, &fd
)) {
627 SAL_WARN("desktop.app", "dbus_connection_get_socket failed");
630 if (shutdown(fd
, SHUT_RD
) == -1) {
631 auto const e
= errno
;
632 SAL_WARN("desktop.app", "shutdown failed with errno " << e
);
640 class theRequestHandlerMutex
641 : public rtl::Static
<osl::Mutex
, theRequestHandlerMutex
> {};
644 ::osl::Mutex
& RequestHandler::GetMutex()
646 return theRequestHandlerMutex::get();
649 void RequestHandler::SetDowning()
651 // We have the order to block all incoming requests. Framework
652 // wants to shutdown and we have to make sure that no loading/printing
653 // requests are executed anymore.
654 ::osl::MutexGuard
aGuard( GetMutex() );
657 pGlobal
->mState
= State::Downing
;
660 void RequestHandler::EnableRequests()
662 // switch between just queueing the requests and executing them
663 ::osl::MutexGuard
aGuard( GetMutex() );
667 if (pGlobal
->mState
!= State::Downing
) {
668 pGlobal
->mState
= State::RequestsEnabled
;
670 // hit the compiler over the head - this avoids GCC -Werror=maybe-uninitialized
671 std::optional
<OUString
> tmp
;
672 ProcessDocumentsRequest
aEmptyReq(tmp
);
673 // trigger already queued requests
674 RequestHandler::ExecuteCmdLineRequests(aEmptyReq
, true);
678 bool RequestHandler::AreRequestsPending()
680 // Give info about pending requests
681 ::osl::MutexGuard
aGuard( GetMutex() );
683 return ( pGlobal
->mnPendingRequests
> 0 );
688 void RequestHandler::RequestsCompleted()
690 // Remove nCount pending requests from our internal counter
691 ::osl::MutexGuard
aGuard( GetMutex() );
694 if ( pGlobal
->mnPendingRequests
> 0 )
695 pGlobal
->mnPendingRequests
--;
699 RequestHandler::Status
RequestHandler::Enable(bool ipc
)
701 ::osl::MutexGuard
aGuard( GetMutex() );
704 return IPC_STATUS_OK
;
706 #if !HAVE_FEATURE_DESKTOP || HAVE_FEATURE_MACOSX_SANDBOX
711 pGlobal
= new RequestHandler
;
712 return IPC_STATUS_OK
;
715 enum class Kind
{ Pipe
, Dbus
};
718 kind
= std::getenv("LIBO_FLATPAK") != nullptr ? Kind::Dbus
: Kind::Pipe
;
722 rtl::Reference
<IpcThread
> thread
;
723 Status stat
= Status(); // silence bogus potentially-uninitialized warnings
726 stat
= PipeIpcThread::enable(&thread
);
730 stat
= DbusIpcThread::enable(&thread
);
736 assert(thread
.is() == (stat
== IPC_STATUS_OK
));
737 if (stat
== IPC_STATUS_OK
) {
738 pGlobal
= new RequestHandler
;
739 pGlobal
->mIpcThread
= thread
;
740 pGlobal
->mIpcThread
->start(pGlobal
.get());
745 RequestHandler::Status
PipeIpcThread::enable(rtl::Reference
<IpcThread
> * thread
)
747 assert(thread
!= nullptr);
749 // The name of the named pipe is created with the hashcode of the user installation directory (without /user). We have to retrieve
750 // this information from a unotools implementation.
751 OUString aUserInstallPath
;
752 ::utl::Bootstrap::PathStatus aLocateResult
= ::utl::Bootstrap::locateUserInstallation( aUserInstallPath
);
753 if (aLocateResult
!= utl::Bootstrap::PATH_EXISTS
754 && aLocateResult
!= utl::Bootstrap::PATH_VALID
)
756 return RequestHandler::IPC_STATUS_BOOTSTRAP_ERROR
;
759 // Try to determine if we are the first office or not! This should prevent multiple
760 // access to the user directory !
761 // First we try to create our pipe if this fails we try to connect. We have to do this
762 // in a loop because the other office can crash or shutdown between createPipe
764 auto aUserInstallPathHashCode
= CreateMD5FromString(aUserInstallPath
);
766 // Check result to create a hash code from the user install path
767 if ( aUserInstallPathHashCode
.isEmpty() )
768 return RequestHandler::IPC_STATUS_BOOTSTRAP_ERROR
; // Something completely broken, we cannot create a valid hash code!
777 PipeMode nPipeMode
= PIPEMODE_DONTKNOW
;
779 OUString
aPipeIdent( "SingleOfficeIPC_" + aUserInstallPathHashCode
);
782 osl::Security security
;
784 // Try to create pipe
785 if ( pipe
.create( aPipeIdent
, osl_Pipe_CREATE
, security
))
788 nPipeMode
= PIPEMODE_CREATED
;
790 else if( pipe
.create( aPipeIdent
, osl_Pipe_OPEN
, security
)) // Creation not successful, now we try to connect
792 osl::StreamPipe
aStreamPipe(pipe
.getHandle());
793 if (readStringFromPipe(aStreamPipe
) == SEND_ARGUMENTS
)
795 // Pipe connected to first office
796 nPipeMode
= PIPEMODE_CONNECTED
;
800 // Pipe connection failed (other office exited or crashed)
803 tval
.Nanosec
= 500000000;
804 salhelper::Thread::wait( tval
);
809 oslPipeError eReason
= pipe
.getError();
810 if ((eReason
== osl_Pipe_E_ConnectionRefused
) || (eReason
== osl_Pipe_E_invalidError
))
811 return RequestHandler::IPC_STATUS_PIPE_ERROR
;
813 // Wait for second office to be ready
814 TimeValue aTimeValue
;
815 aTimeValue
.Seconds
= 0;
816 aTimeValue
.Nanosec
= 10000000; // 10ms
817 salhelper::Thread::wait( aTimeValue
);
820 } while ( nPipeMode
== PIPEMODE_DONTKNOW
);
822 if ( nPipeMode
== PIPEMODE_CREATED
)
824 // Seems we are the one and only, so create listening thread
825 *thread
= new PipeIpcThread(pipe
);
826 return RequestHandler::IPC_STATUS_OK
;
830 // Seems another office is running. Pipe arguments to it and self terminate
831 osl::StreamPipe
aStreamPipe(pipe
.getHandle());
833 OStringBuffer
aArguments(ARGUMENT_PREFIX
);
835 if (!(utl::Bootstrap::getProcessWorkingDir(cwdUrl
) &&
836 addArgument(aArguments
, '1', cwdUrl
)))
838 aArguments
.append('0');
840 sal_uInt32 nCount
= rtl_getAppCommandArgCount();
841 for( sal_uInt32 i
=0; i
< nCount
; i
++ )
843 rtl_getAppCommandArg( i
, &aUserInstallPath
.pData
);
844 if (!addArgument(aArguments
, ',', aUserInstallPath
)) {
845 return RequestHandler::IPC_STATUS_BOOTSTRAP_ERROR
;
848 aArguments
.append('\0');
849 // finally, write the string onto the pipe
850 SAL_INFO("desktop.app", "writing <" << aArguments
.getStr() << ">");
851 sal_Int32 n
= aStreamPipe
.write(
852 aArguments
.getStr(), aArguments
.getLength());
853 if (n
!= aArguments
.getLength()) {
854 SAL_INFO("desktop.app", "short write: " << n
);
855 return RequestHandler::IPC_STATUS_BOOTSTRAP_ERROR
;
858 if (readStringFromPipe(aStreamPipe
) != PROCESSING_DONE
)
860 // something went wrong
861 return RequestHandler::IPC_STATUS_BOOTSTRAP_ERROR
;
864 return RequestHandler::IPC_STATUS_2ND_OFFICE
;
868 void RequestHandler::Disable()
870 osl::ClearableMutexGuard
aMutex( GetMutex() );
875 rtl::Reference
< RequestHandler
> handler(pGlobal
);
878 handler
->mState
= State::Downing
;
879 if (handler
->mIpcThread
.is()) {
880 handler
->mIpcThread
->close();
883 // release mutex to avoid deadlocks
886 handler
->cReady
.set();
888 // exit gracefully and join
889 if (handler
->mIpcThread
.is())
891 handler
->mIpcThread
->join();
892 handler
->mIpcThread
.clear();
895 handler
->cReady
.reset();
898 RequestHandler::RequestHandler() :
899 mState( State::Starting
),
900 mnPendingRequests( 0 )
904 RequestHandler::~RequestHandler()
906 assert(!mIpcThread
.is());
909 void RequestHandler::SetReady(bool bIsReady
)
911 osl::MutexGuard
g(GetMutex());
915 pGlobal
->cReady
.set();
917 pGlobal
->cReady
.reset();
921 void RequestHandler::WaitForReady()
923 rtl::Reference
<RequestHandler
> t
;
925 osl::MutexGuard
g(GetMutex());
934 bool IpcThread::process(OString
const & arguments
, bool * waitProcessed
) {
935 assert(waitProcessed
!= nullptr);
937 std::unique_ptr
< CommandLineArgs
> aCmdLineArgs
;
941 aCmdLineArgs
.reset( new CommandLineArgs( p
) );
943 catch ( const CommandLineArgs::Supplier::Exception
& )
945 SAL_WARN("desktop.app", "Error in received command line arguments");
949 bool bDocRequestSent
= false;
951 OUString
aUnknown( aCmdLineArgs
->GetUnknown() );
952 if (aUnknown
.isEmpty() && !aCmdLineArgs
->IsHelp() && !aCmdLineArgs
->IsVersion())
954 const CommandLineArgs
&rCurrentCmdLineArgs
= Desktop::GetCommandLineArgs();
956 if ( aCmdLineArgs
->IsQuickstart() )
958 // we have to use application event, because we have to start quickstart service in main thread!!
959 ApplicationEvent
* pAppEvent
=
960 new ApplicationEvent(ApplicationEvent::Type::QuickStart
);
961 ImplPostForeignAppEvent( pAppEvent
);
964 // handle request for acceptor
965 std::vector
< OUString
> const & accept
= aCmdLineArgs
->GetAccept();
966 for (auto const& elem
: accept
)
968 ApplicationEvent
* pAppEvent
= new ApplicationEvent(
969 ApplicationEvent::Type::Accept
, elem
);
970 ImplPostForeignAppEvent( pAppEvent
);
972 // handle acceptor removal
973 std::vector
< OUString
> const & unaccept
= aCmdLineArgs
->GetUnaccept();
974 for (auto const& elem
: unaccept
)
976 ApplicationEvent
* pAppEvent
= new ApplicationEvent(
977 ApplicationEvent::Type::Unaccept
, elem
);
978 ImplPostForeignAppEvent( pAppEvent
);
981 std::unique_ptr
<ProcessDocumentsRequest
> pRequest(new ProcessDocumentsRequest(
982 aCmdLineArgs
->getCwdUrl()));
983 m_handler
->cProcessed
.reset();
984 pRequest
->pcProcessed
= &m_handler
->cProcessed
;
985 m_handler
->mbSuccess
= false;
986 pRequest
->mpbSuccess
= &m_handler
->mbSuccess
;
988 // Print requests are not dependent on the --invisible cmdline argument as they are
989 // loaded with the "hidden" flag! So they are always checked.
990 pRequest
->aPrintList
= aCmdLineArgs
->GetPrintList();
991 bDocRequestSent
|= !pRequest
->aPrintList
.empty();
992 pRequest
->aPrintToList
= aCmdLineArgs
->GetPrintToList();
993 pRequest
->aPrinterName
= aCmdLineArgs
->GetPrinterName();
994 bDocRequestSent
|= !( pRequest
->aPrintToList
.empty() || pRequest
->aPrinterName
.isEmpty() );
995 pRequest
->aConversionList
= aCmdLineArgs
->GetConversionList();
996 pRequest
->aConversionParams
= aCmdLineArgs
->GetConversionParams();
997 pRequest
->aConversionOut
= aCmdLineArgs
->GetConversionOut();
998 pRequest
->aImageConversionType
= aCmdLineArgs
->GetImageConversionType();
999 pRequest
->aInFilter
= aCmdLineArgs
->GetInFilter();
1000 pRequest
->bTextCat
= aCmdLineArgs
->IsTextCat();
1001 pRequest
->bScriptCat
= aCmdLineArgs
->IsScriptCat();
1002 bDocRequestSent
|= !pRequest
->aConversionList
.empty();
1004 if ( !rCurrentCmdLineArgs
.IsInvisible() )
1006 // Read cmdline args that can open/create documents. As they would open a window
1007 // they are only allowed if the "--invisible" is currently not used!
1008 pRequest
->aOpenList
= aCmdLineArgs
->GetOpenList();
1009 bDocRequestSent
|= !pRequest
->aOpenList
.empty();
1010 pRequest
->aViewList
= aCmdLineArgs
->GetViewList();
1011 bDocRequestSent
|= !pRequest
->aViewList
.empty();
1012 pRequest
->aStartList
= aCmdLineArgs
->GetStartList();
1013 bDocRequestSent
|= !pRequest
->aStartList
.empty();
1014 pRequest
->aForceOpenList
= aCmdLineArgs
->GetForceOpenList();
1015 bDocRequestSent
|= !pRequest
->aForceOpenList
.empty();
1016 pRequest
->aForceNewList
= aCmdLineArgs
->GetForceNewList();
1017 bDocRequestSent
|= !pRequest
->aForceNewList
.empty();
1019 // Special command line args to create an empty document for a given module
1022 // we only do this if no document was specified on the command line,
1023 // since this would be inconsistent with the behaviour of
1024 // the first process, see OpenClients() (call to OpenDefault()) in app.cxx
1025 if ( aCmdLineArgs
->HasModuleParam() && !bDocRequestSent
)
1027 SvtModuleOptions aOpt
;
1028 SvtModuleOptions::EFactory eFactory
= SvtModuleOptions::EFactory::WRITER
;
1029 if ( aCmdLineArgs
->IsWriter() )
1030 eFactory
= SvtModuleOptions::EFactory::WRITER
;
1031 else if ( aCmdLineArgs
->IsCalc() )
1032 eFactory
= SvtModuleOptions::EFactory::CALC
;
1033 else if ( aCmdLineArgs
->IsDraw() )
1034 eFactory
= SvtModuleOptions::EFactory::DRAW
;
1035 else if ( aCmdLineArgs
->IsImpress() )
1036 eFactory
= SvtModuleOptions::EFactory::IMPRESS
;
1037 else if ( aCmdLineArgs
->IsBase() )
1038 eFactory
= SvtModuleOptions::EFactory::DATABASE
;
1039 else if ( aCmdLineArgs
->IsMath() )
1040 eFactory
= SvtModuleOptions::EFactory::MATH
;
1041 else if ( aCmdLineArgs
->IsGlobal() )
1042 eFactory
= SvtModuleOptions::EFactory::WRITERGLOBAL
;
1043 else if ( aCmdLineArgs
->IsWeb() )
1044 eFactory
= SvtModuleOptions::EFactory::WRITERWEB
;
1046 if ( !pRequest
->aOpenList
.empty() )
1047 pRequest
->aModule
= aOpt
.GetFactoryName( eFactory
);
1049 pRequest
->aOpenList
.push_back( aOpt
.GetFactoryEmptyDocumentURL( eFactory
) );
1050 bDocRequestSent
= true;
1054 if ( !aCmdLineArgs
->IsQuickstart() ) {
1055 bool bShowHelp
= false;
1056 OUStringBuffer aHelpURLBuffer
;
1057 if (aCmdLineArgs
->IsHelpWriter()) {
1059 aHelpURLBuffer
.append("vnd.sun.star.help://swriter/start");
1060 } else if (aCmdLineArgs
->IsHelpCalc()) {
1062 aHelpURLBuffer
.append("vnd.sun.star.help://scalc/start");
1063 } else if (aCmdLineArgs
->IsHelpDraw()) {
1065 aHelpURLBuffer
.append("vnd.sun.star.help://sdraw/start");
1066 } else if (aCmdLineArgs
->IsHelpImpress()) {
1068 aHelpURLBuffer
.append("vnd.sun.star.help://simpress/start");
1069 } else if (aCmdLineArgs
->IsHelpBase()) {
1071 aHelpURLBuffer
.append("vnd.sun.star.help://sdatabase/start");
1072 } else if (aCmdLineArgs
->IsHelpBasic()) {
1074 aHelpURLBuffer
.append("vnd.sun.star.help://sbasic/start");
1075 } else if (aCmdLineArgs
->IsHelpMath()) {
1077 aHelpURLBuffer
.append("vnd.sun.star.help://smath/start");
1080 aHelpURLBuffer
.append("?Language=");
1081 aHelpURLBuffer
.append(utl::ConfigManager::getUILocale());
1083 aHelpURLBuffer
.append("&System=UNX");
1084 #elif defined _WIN32
1085 aHelpURLBuffer
.append("&System=WIN");
1087 ApplicationEvent
* pAppEvent
= new ApplicationEvent(
1088 ApplicationEvent::Type::OpenHelpUrl
,
1089 aHelpURLBuffer
.makeStringAndClear());
1090 ImplPostForeignAppEvent( pAppEvent
);
1094 if ( bDocRequestSent
)
1096 // Send requests to dispatch watcher if we have at least one. The receiver
1097 // is responsible to delete the request after processing it.
1098 if ( aCmdLineArgs
->HasModuleParam() )
1100 SvtModuleOptions aOpt
;
1102 // Support command line parameters to start a module (as preselection)
1103 if ( aCmdLineArgs
->IsWriter() && aOpt
.IsModuleInstalled( SvtModuleOptions::EModule::WRITER
) )
1104 pRequest
->aModule
= aOpt
.GetFactoryName( SvtModuleOptions::EFactory::WRITER
);
1105 else if ( aCmdLineArgs
->IsCalc() && aOpt
.IsModuleInstalled( SvtModuleOptions::EModule::CALC
) )
1106 pRequest
->aModule
= aOpt
.GetFactoryName( SvtModuleOptions::EFactory::CALC
);
1107 else if ( aCmdLineArgs
->IsImpress() && aOpt
.IsModuleInstalled( SvtModuleOptions::EModule::IMPRESS
) )
1108 pRequest
->aModule
= aOpt
.GetFactoryName( SvtModuleOptions::EFactory::IMPRESS
);
1109 else if ( aCmdLineArgs
->IsDraw() && aOpt
.IsModuleInstalled( SvtModuleOptions::EModule::DRAW
) )
1110 pRequest
->aModule
= aOpt
.GetFactoryName( SvtModuleOptions::EFactory::DRAW
);
1113 ImplPostProcessDocumentsEvent( std::move(pRequest
) );
1117 // delete not used request again
1120 if (aCmdLineArgs
->IsEmpty())
1122 // no document was sent, just bring Office to front
1123 ApplicationEvent
* pAppEvent
=
1124 new ApplicationEvent(ApplicationEvent::Type::Appear
);
1125 ImplPostForeignAppEvent( pAppEvent
);
1128 *waitProcessed
= bDocRequestSent
;
1132 void PipeIpcThread::execute()
1134 assert(m_handler
!= nullptr);
1137 osl::StreamPipe aStreamPipe
;
1138 oslPipeError nError
= pipe_
.accept( aStreamPipe
);
1141 if( nError
== osl_Pipe_E_None
)
1143 // if we receive a request while the office is displaying some dialog or error during
1144 // bootstrap, that dialogs event loop might get events that are dispatched by this thread
1145 // we have to wait for cReady to be set by the real main loop.
1146 // only requests that don't dispatch events may be processed before cReady is set.
1147 m_handler
->cReady
.wait();
1149 // we might have decided to shutdown while we were sleeping
1150 if (!RequestHandler::pGlobal
.is()) return;
1152 // only lock the mutex when processing starts, otherwise we deadlock when the office goes
1154 osl::ClearableMutexGuard
aGuard( RequestHandler::GetMutex() );
1156 if (m_handler
->mState
== RequestHandler::State::Downing
)
1161 // notify client we're ready to process its args:
1162 SAL_INFO("desktop.app", "writing <" << SEND_ARGUMENTS
<< ">");
1163 sal_Int32 n
= aStreamPipe
.write(
1164 SEND_ARGUMENTS
, SAL_N_ELEMENTS(SEND_ARGUMENTS
));
1165 // incl. terminating NUL
1166 if (n
!= SAL_N_ELEMENTS(SEND_ARGUMENTS
)) {
1167 SAL_WARN("desktop.app", "short write: " << n
);
1171 OString aArguments
= readStringFromPipe(aStreamPipe
);
1173 // Is this a lookup message from another application? if so, ignore
1174 if (aArguments
.isEmpty())
1177 bool waitProcessed
= false;
1178 if (!process(aArguments
, &waitProcessed
)) {
1182 // we don't need the mutex any longer...
1184 bool bSuccess
= true;
1185 // wait for processing to finish
1188 m_handler
->cProcessed
.wait();
1189 bSuccess
= m_handler
->mbSuccess
;
1193 // processing finished, inform the requesting end:
1194 SAL_INFO("desktop.app", "writing <" << PROCESSING_DONE
<< ">");
1195 n
= aStreamPipe
.write(PROCESSING_DONE
, SAL_N_ELEMENTS(PROCESSING_DONE
));
1196 // incl. terminating NUL
1197 if (n
!= SAL_N_ELEMENTS(PROCESSING_DONE
))
1199 SAL_WARN("desktop.app", "short write: " << n
);
1207 osl::MutexGuard
aGuard( RequestHandler::GetMutex() );
1208 if (m_handler
->mState
== RequestHandler::State::Downing
)
1214 SAL_WARN( "desktop.app", "Error on accept: " << static_cast<int>(nError
));
1218 salhelper::Thread::wait( tval
);
1220 } while( schedule() );
1223 static void AddToDispatchList(
1224 std::vector
<DispatchWatcher::DispatchRequest
>& rDispatchList
,
1225 std::optional
< OUString
> const & cwdUrl
,
1226 std::vector
< OUString
> const & aRequestList
,
1227 DispatchWatcher::RequestType nType
,
1228 const OUString
& aParam
,
1229 const OUString
& aFactory
)
1231 for (auto const& request
: aRequestList
)
1233 rDispatchList
.push_back({nType
, request
, cwdUrl
, aParam
, aFactory
});
1237 static void AddConversionsToDispatchList(
1238 std::vector
<DispatchWatcher::DispatchRequest
>& rDispatchList
,
1239 std::optional
< OUString
> const & cwdUrl
,
1240 std::vector
< OUString
> const & rRequestList
,
1241 const OUString
& rParam
,
1242 const OUString
& rPrinterName
,
1243 const OUString
& rFactory
,
1244 const OUString
& rParamOut
,
1245 const OUString
& rImgOut
,
1246 const bool isTextCat
,
1247 const bool isScriptCat
)
1249 DispatchWatcher::RequestType nType
;
1250 OUString
aParam( rParam
);
1252 if( !rParam
.isEmpty() )
1255 nType
= DispatchWatcher::REQUEST_CAT
;
1257 nType
= DispatchWatcher::REQUEST_CONVERSION
;
1263 nType
= DispatchWatcher::REQUEST_SCRIPT_CAT
;
1266 nType
= DispatchWatcher::REQUEST_BATCHPRINT
;
1267 aParam
= rPrinterName
;
1271 OUString
aOutDir( rParamOut
.trim() );
1272 OUString
aImgOut( rImgOut
.trim() );
1280 utl::Bootstrap::getProcessWorkingDir( aPWD
);
1283 if( !::osl::FileBase::getAbsoluteFileURL( aPWD
, rParamOut
, aOutDir
) )
1284 ::osl::FileBase::getSystemPathFromFileURL( aOutDir
, aOutDir
);
1286 if( !rParamOut
.trim().isEmpty() )
1288 aParam
+= ";" + aOutDir
;
1292 ::osl::FileBase::getSystemPathFromFileURL( aPWD
, aPWD
);
1293 aParam
+= ";" + aPWD
;
1296 if( !rImgOut
.trim().isEmpty() )
1297 aParam
+= "|" + aImgOut
;
1299 for (auto const& request
: rRequestList
)
1301 rDispatchList
.push_back({nType
, request
, cwdUrl
, aParam
, rFactory
});
1307 struct ConditionSetGuard
1309 osl::Condition
* m_pCondition
;
1310 ConditionSetGuard(osl::Condition
* pCondition
) : m_pCondition(pCondition
) {}
1311 ~ConditionSetGuard() { if (m_pCondition
) m_pCondition
->set(); }
1316 bool RequestHandler::ExecuteCmdLineRequests(
1317 ProcessDocumentsRequest
& aRequest
, bool noTerminate
)
1319 // protect the dispatch list
1320 osl::ClearableMutexGuard
aGuard( GetMutex() );
1322 // ensure that Processed flag (if exists) is signaled in any outcome
1323 ConditionSetGuard
aSetGuard(aRequest
.pcProcessed
);
1325 static std::vector
<DispatchWatcher::DispatchRequest
> aDispatchList
;
1327 // Create dispatch list for dispatch watcher
1328 AddToDispatchList( aDispatchList
, aRequest
.aCwdUrl
, aRequest
.aInFilter
, DispatchWatcher::REQUEST_INFILTER
, "", aRequest
.aModule
);
1329 AddToDispatchList( aDispatchList
, aRequest
.aCwdUrl
, aRequest
.aOpenList
, DispatchWatcher::REQUEST_OPEN
, "", aRequest
.aModule
);
1330 AddToDispatchList( aDispatchList
, aRequest
.aCwdUrl
, aRequest
.aViewList
, DispatchWatcher::REQUEST_VIEW
, "", aRequest
.aModule
);
1331 AddToDispatchList( aDispatchList
, aRequest
.aCwdUrl
, aRequest
.aStartList
, DispatchWatcher::REQUEST_START
, "", aRequest
.aModule
);
1332 AddToDispatchList( aDispatchList
, aRequest
.aCwdUrl
, aRequest
.aPrintList
, DispatchWatcher::REQUEST_PRINT
, "", aRequest
.aModule
);
1333 AddToDispatchList( aDispatchList
, aRequest
.aCwdUrl
, aRequest
.aPrintToList
, DispatchWatcher::REQUEST_PRINTTO
, aRequest
.aPrinterName
, aRequest
.aModule
);
1334 AddToDispatchList( aDispatchList
, aRequest
.aCwdUrl
, aRequest
.aForceOpenList
, DispatchWatcher::REQUEST_FORCEOPEN
, "", aRequest
.aModule
);
1335 AddToDispatchList( aDispatchList
, aRequest
.aCwdUrl
, aRequest
.aForceNewList
, DispatchWatcher::REQUEST_FORCENEW
, "", aRequest
.aModule
);
1336 AddConversionsToDispatchList( aDispatchList
, aRequest
.aCwdUrl
, aRequest
.aConversionList
, aRequest
.aConversionParams
, aRequest
.aPrinterName
, aRequest
.aModule
, aRequest
.aConversionOut
, aRequest
.aImageConversionType
, aRequest
.bTextCat
, aRequest
.bScriptCat
);
1337 bool bShutdown( false );
1341 if( ! pGlobal
->AreRequestsEnabled() )
1343 // Either starting, or downing - do not process the request, just try to bring Office to front
1344 ApplicationEvent
* pAppEvent
=
1345 new ApplicationEvent(ApplicationEvent::Type::Appear
);
1346 ImplPostForeignAppEvent(pAppEvent
);
1350 pGlobal
->mnPendingRequests
+= aDispatchList
.size();
1351 if ( !pGlobal
->mpDispatchWatcher
.is() )
1353 pGlobal
->mpDispatchWatcher
= new DispatchWatcher
;
1355 rtl::Reference
<DispatchWatcher
> dispatchWatcher(
1356 pGlobal
->mpDispatchWatcher
);
1359 std::vector
<DispatchWatcher::DispatchRequest
> aTempList
;
1360 aTempList
.swap( aDispatchList
);
1364 // Execute dispatch requests
1365 bShutdown
= dispatchWatcher
->executeDispatchRequests( aTempList
, noTerminate
);
1366 if (aRequest
.mpbSuccess
)
1367 *aRequest
.mpbSuccess
= true; // signal that we have actually succeeded
1375 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */