1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "nsNativeAppSupportBase.h"
8 #include "nsComponentManagerUtils.h"
11 #include "nsISupportsPrimitives.h"
12 #include "nsIObserverService.h"
13 #include "nsIAppStartup.h"
14 #include "nsServiceManagerUtils.h"
16 #include "nsXREDirProvider.h"
17 #include "nsReadableUtils.h"
20 #include "nsDirectoryServiceDefs.h"
21 #include "nsPIDOMWindow.h"
22 #include "nsIWidget.h"
23 #include "mozilla/Logging.h"
24 #include "mozilla/Services.h"
25 #include "mozilla/XREAppData.h"
29 #include <glib-object.h>
33 # include <gdk/gdkx.h>
34 # include <X11/ICE/ICElib.h>
35 # include <X11/SM/SMlib.h>
37 # include "nsThreadUtils.h"
42 #ifdef MOZ_ENABLE_DBUS
43 # include <dbus/dbus.h>
46 #define MIN_GTK_MAJOR_VERSION 2
47 #define MIN_GTK_MINOR_VERSION 10
48 #define UNSUPPORTED_GTK_MSG \
49 "We're sorry, this application requires a version of the GTK+ library that is not installed on your computer.\n\n\
50 You have GTK+ %d.%d.\nThis application requires GTK+ %d.%d or newer.\n\n\
51 Please upgrade your GTK+ library if you wish to use this application."
54 # undef IceSetIOErrorHandler
55 # undef IceAddConnectionWatch
56 # undef IceConnectionNumber
57 # undef IceProcessMessages
58 # undef IceGetConnectionContext
59 # undef SmcInteractDone
60 # undef SmcSaveYourselfDone
61 # undef SmcInteractRequest
62 # undef SmcCloseConnection
63 # undef SmcOpenConnection
64 # undef SmcSetProperties
66 typedef IceIOErrorHandler (*IceSetIOErrorHandlerFn
)(IceIOErrorHandler
);
67 typedef int (*IceAddConnectionWatchFn
)(IceWatchProc
, IcePointer
);
68 typedef int (*IceConnectionNumberFn
)(IceConn
);
69 typedef IceProcessMessagesStatus (*IceProcessMessagesFn
)(IceConn
,
72 typedef IcePointer (*IceGetConnectionContextFn
)(IceConn
);
74 typedef void (*SmcInteractDoneFn
)(SmcConn
, Bool
);
75 typedef void (*SmcSaveYourselfDoneFn
)(SmcConn
, Bool
);
76 typedef int (*SmcInteractRequestFn
)(SmcConn
, int, SmcInteractProc
, SmPointer
);
77 typedef SmcCloseStatus (*SmcCloseConnectionFn
)(SmcConn
, int, char**);
78 typedef SmcConn (*SmcOpenConnectionFn
)(char*, SmPointer
, int, int,
79 unsigned long, SmcCallbacks
*,
80 const char*, char**, int, char*);
81 typedef void (*SmcSetPropertiesFn
)(SmcConn
, int, SmProp
**);
83 static IceSetIOErrorHandlerFn IceSetIOErrorHandlerPtr
;
84 static IceAddConnectionWatchFn IceAddConnectionWatchPtr
;
85 static IceConnectionNumberFn IceConnectionNumberPtr
;
86 static IceProcessMessagesFn IceProcessMessagesPtr
;
87 static IceGetConnectionContextFn IceGetConnectionContextPtr
;
88 static SmcInteractDoneFn SmcInteractDonePtr
;
89 static SmcSaveYourselfDoneFn SmcSaveYourselfDonePtr
;
90 static SmcInteractRequestFn SmcInteractRequestPtr
;
91 static SmcCloseConnectionFn SmcCloseConnectionPtr
;
92 static SmcOpenConnectionFn SmcOpenConnectionPtr
;
93 static SmcSetPropertiesFn SmcSetPropertiesPtr
;
95 # define IceSetIOErrorHandler IceSetIOErrorHandlerPtr
96 # define IceAddConnectionWatch IceAddConnectionWatchPtr
97 # define IceConnectionNumber IceConnectionNumberPtr
98 # define IceProcessMessages IceProcessMessagesPtr
99 # define IceGetConnectionContext IceGetConnectionContextPtr
100 # define SmcInteractDone SmcInteractDonePtr
101 # define SmcSaveYourselfDone SmcSaveYourselfDonePtr
102 # define SmcInteractRequest SmcInteractRequestPtr
103 # define SmcCloseConnection SmcCloseConnectionPtr
104 # define SmcOpenConnection SmcOpenConnectionPtr
105 # define SmcSetProperties SmcSetPropertiesPtr
112 STATE_SHUTDOWN_CANCELLED
115 using namespace mozilla
;
117 static const char* gClientStateTable
[] = {"DISCONNECTED", "REGISTERING", "IDLE",
118 "INTERACTING", "SHUTDOWN_CANCELLED"};
120 static LazyLogModule
sMozSMLog("MozSM");
123 class nsNativeAppSupportUnix
: public nsNativeAppSupportBase
{
126 nsNativeAppSupportUnix()
127 : mSessionConnection(nullptr), mClientState(STATE_DISCONNECTED
){};
128 ~nsNativeAppSupportUnix() {
129 // this goes out of scope after "web-workers-shutdown" async shutdown phase
130 // so it's safe to disconnect here (i.e. the application won't lose data)
134 void DisconnectFromSM();
136 NS_IMETHOD
Start(bool* aRetVal
) override
;
137 NS_IMETHOD
Enable() override
;
141 static void SaveYourselfCB(SmcConn smc_conn
, SmPointer client_data
,
142 int save_style
, Bool shutdown
, int interact_style
,
144 static void DieCB(SmcConn smc_conn
, SmPointer client_data
);
145 static void InteractCB(SmcConn smc_conn
, SmPointer client_data
);
146 static void SaveCompleteCB(SmcConn smc_conn
, SmPointer client_data
){};
147 static void ShutdownCancelledCB(SmcConn smc_conn
, SmPointer client_data
);
149 void SetClientState(ClientState aState
) {
150 mClientState
= aState
;
151 MOZ_LOG(sMozSMLog
, LogLevel::Debug
,
152 ("New state = %s\n", gClientStateTable
[aState
]));
155 SmcConn mSessionConnection
;
156 ClientState mClientState
;
161 static gboolean
process_ice_messages(IceConn connection
) {
162 IceProcessMessagesStatus status
;
164 status
= IceProcessMessages(connection
, nullptr, nullptr);
167 case IceProcessMessagesSuccess
:
170 case IceProcessMessagesIOError
: {
171 nsNativeAppSupportUnix
* native
= static_cast<nsNativeAppSupportUnix
*>(
172 IceGetConnectionContext(connection
));
173 native
->DisconnectFromSM();
177 case IceProcessMessagesConnectionClosed
:
181 g_assert_not_reached();
185 static gboolean
ice_iochannel_watch(GIOChannel
* channel
, GIOCondition condition
,
186 gpointer client_data
) {
187 return process_ice_messages(static_cast<IceConn
>(client_data
));
190 static void ice_connection_watch(IceConn connection
, IcePointer client_data
,
191 Bool opening
, IcePointer
* watch_data
) {
196 int fd
= IceConnectionNumber(connection
);
198 fcntl(fd
, F_SETFD
, fcntl(fd
, F_GETFD
, 0) | FD_CLOEXEC
);
199 channel
= g_io_channel_unix_new(fd
);
201 g_io_add_watch(channel
, static_cast<GIOCondition
>(G_IO_IN
| G_IO_ERR
),
202 ice_iochannel_watch
, connection
);
203 g_io_channel_unref(channel
);
205 *watch_data
= GUINT_TO_POINTER(watch_id
);
207 watch_id
= GPOINTER_TO_UINT(*watch_data
);
208 g_source_remove(watch_id
);
212 static void ice_io_error_handler(IceConn connection
) {
213 // override the default handler which would exit the application;
214 // do nothing and let ICELib handle the failure of the connection gracefully.
217 static void ice_init(void) {
218 static bool initted
= false;
221 IceSetIOErrorHandler(ice_io_error_handler
);
222 IceAddConnectionWatch(ice_connection_watch
, nullptr);
227 void nsNativeAppSupportUnix::InteractCB(SmcConn smc_conn
,
228 SmPointer client_data
) {
229 nsNativeAppSupportUnix
* self
=
230 static_cast<nsNativeAppSupportUnix
*>(client_data
);
232 self
->SetClientState(STATE_INTERACTING
);
234 // We do this asynchronously, as we spin the event loop recursively if
235 // a dialog is displayed. If we do this synchronously, we don't finish
236 // processing the current ICE event whilst the dialog is displayed, which
237 // means we won't process any more. libsm hates us if we do the InteractDone
238 // with a pending ShutdownCancelled, and we would certainly like to handle Die
239 // whilst a dialog is displayed
240 NS_DispatchToCurrentThread(
241 NewRunnableMethod("nsNativeAppSupportUnix::DoInteract", self
,
242 &nsNativeAppSupportUnix::DoInteract
));
245 void nsNativeAppSupportUnix::DoInteract() {
246 nsCOMPtr
<nsIObserverService
> obsServ
=
247 mozilla::services::GetObserverService();
249 SmcInteractDone(mSessionConnection
, False
);
250 SmcSaveYourselfDone(mSessionConnection
, True
);
251 SetClientState(STATE_IDLE
);
255 nsCOMPtr
<nsISupportsPRBool
> cancelQuit
=
256 do_CreateInstance(NS_SUPPORTS_PRBOOL_CONTRACTID
);
258 bool abortQuit
= false;
260 cancelQuit
->SetData(false);
261 obsServ
->NotifyObservers(cancelQuit
, "quit-application-requested", nullptr);
263 cancelQuit
->GetData(&abortQuit
);
266 if (!abortQuit
&& mClientState
== STATE_DISCONNECTED
) {
267 // The session manager disappeared, whilst we were interacting, so
269 nsCOMPtr
<nsIAppStartup
> appService
=
270 do_GetService("@mozilla.org/toolkit/app-startup;1");
273 bool userAllowedQuit
= true;
274 appService
->Quit(nsIAppStartup::eForceQuit
, 0, &userAllowedQuit
);
277 if (mClientState
!= STATE_SHUTDOWN_CANCELLED
) {
278 // Only do this if the shutdown wasn't cancelled
279 SmcInteractDone(mSessionConnection
, !!abortQuit
);
280 SmcSaveYourselfDone(mSessionConnection
, !abortQuit
);
283 SetClientState(STATE_IDLE
);
287 void nsNativeAppSupportUnix::SaveYourselfCB(SmcConn smc_conn
,
288 SmPointer client_data
,
289 int save_style
, Bool shutdown
,
290 int interact_style
, Bool fast
) {
291 nsNativeAppSupportUnix
* self
=
292 static_cast<nsNativeAppSupportUnix
*>(client_data
);
294 // Expect a SaveYourselfCB if we're registering a new client.
295 // All properties are already set in Start() so just reply with
296 // SmcSaveYourselfDone if the callback matches the expected signature.
298 // Ancient versions (?) of xsm do not follow such an early SaveYourself with
299 // SaveComplete. This is a problem if the application freezes interaction
300 // while waiting for a response to SmcSaveYourselfDone. So never freeze
301 // interaction when in STATE_REGISTERING.
303 // That aside, we could treat each combination of flags appropriately and not
304 // special-case this.
305 if (self
->mClientState
== STATE_REGISTERING
) {
306 self
->SetClientState(STATE_IDLE
);
308 if (save_style
== SmSaveLocal
&& interact_style
== SmInteractStyleNone
&&
309 !shutdown
&& !fast
) {
310 SmcSaveYourselfDone(self
->mSessionConnection
, True
);
315 if (self
->mClientState
== STATE_SHUTDOWN_CANCELLED
) {
316 // The last shutdown request was cancelled whilst we were interacting,
317 // and we haven't finished interacting yet. Switch the state back again
318 self
->SetClientState(STATE_INTERACTING
);
321 nsCOMPtr
<nsIObserverService
> obsServ
=
322 mozilla::services::GetObserverService();
324 SmcSaveYourselfDone(smc_conn
, True
);
329 nsCOMPtr
<nsISupportsPRBool
> didSaveSession
=
330 do_CreateInstance(NS_SUPPORTS_PRBOOL_CONTRACTID
);
332 if (!didSaveSession
) {
333 SmcSaveYourselfDone(smc_conn
, True
);
337 // Notify observers to save the session state
338 didSaveSession
->SetData(false);
339 obsServ
->NotifyObservers(didSaveSession
, "session-save", nullptr);
341 didSaveSession
->GetData(&status
);
343 // If the interact style permits us to, we are shutting down and we didn't
344 // manage to (or weren't asked to) save the local state, then notify the user
345 // in advance that we are doing to quit (assuming that we aren't already
347 if (!status
&& shutdown
&& interact_style
!= SmInteractStyleNone
) {
348 if (self
->mClientState
!= STATE_INTERACTING
) {
349 SmcInteractRequest(smc_conn
, SmDialogNormal
,
350 nsNativeAppSupportUnix::InteractCB
, client_data
);
353 SmcSaveYourselfDone(smc_conn
, True
);
357 void nsNativeAppSupportUnix::DieCB(SmcConn smc_conn
, SmPointer client_data
) {
358 nsCOMPtr
<nsIAppStartup
> appService
=
359 do_GetService("@mozilla.org/toolkit/app-startup;1");
362 bool userAllowedQuit
= false;
363 appService
->Quit(nsIAppStartup::eForceQuit
, 0, &userAllowedQuit
);
365 // Quit causes the shutdown to begin but the shutdown process is asynchronous
366 // so we can't DisconnectFromSM() yet
369 void nsNativeAppSupportUnix::ShutdownCancelledCB(SmcConn smc_conn
,
370 SmPointer client_data
) {
371 nsNativeAppSupportUnix
* self
=
372 static_cast<nsNativeAppSupportUnix
*>(client_data
);
374 // Interacting is the only time when we wouldn't already have called
375 // SmcSaveYourselfDone. Do that now, then set the state to make sure we
376 // don't send it again after finishing interacting
377 if (self
->mClientState
== STATE_INTERACTING
) {
378 SmcSaveYourselfDone(smc_conn
, False
);
379 self
->SetClientState(STATE_SHUTDOWN_CANCELLED
);
383 void nsNativeAppSupportUnix::DisconnectFromSM() {
384 // the SM is free to exit any time after we disconnect, so callers must be
385 // sure to have reached a sufficiently advanced phase of shutdown that there
386 // is no risk of data loss:
387 // e.g. all async writes are complete by the end of "profile-before-change"
388 if (mSessionConnection
) {
389 SetClientState(STATE_DISCONNECTED
);
390 SmcCloseConnection(mSessionConnection
, 0, nullptr);
391 mSessionConnection
= nullptr;
392 gdk_x11_set_sm_client_id(nullptr); // follow gnome-client behaviour
396 static void SetSMValue(SmPropValue
& val
, const nsCString
& data
) {
397 val
.value
= static_cast<SmPointer
>(const_cast<char*>(data
.get()));
398 val
.length
= data
.Length();
401 static void SetSMProperty(SmProp
& prop
, const char* name
, const char* type
,
402 int numVals
, SmPropValue vals
[]) {
403 prop
.name
= const_cast<char*>(name
);
404 prop
.type
= const_cast<char*>(type
);
405 prop
.num_vals
= numVals
;
410 static void RemoveArg(char** argv
) {
420 nsNativeAppSupportUnix::Start(bool* aRetVal
) {
421 NS_ASSERTION(gAppData
, "gAppData must not be null.");
423 // The dbus library is used by both nsWifiScannerDBus and BluetoothDBusService,
424 // from diffrent threads. This could lead to race conditions if the dbus is not
425 // initialized before making any other library calls.
426 #ifdef MOZ_ENABLE_DBUS
427 dbus_threads_init_default();
433 gboolean sm_disable
= FALSE
;
434 if (!getenv("SESSION_MANAGER")) {
438 nsAutoCString prev_client_id
;
440 char** curarg
= gArgv
+ 1;
443 if (arg
[0] == '-' && arg
[1] == '-') {
445 if (!strcmp(arg
, "sm-disable")) {
449 } else if (!strcmp(arg
, "sm-client-id")) {
451 if (*curarg
[0] != '-') {
452 prev_client_id
= *curarg
;
462 if (prev_client_id
.IsEmpty()) {
463 prev_client_id
= getenv("DESKTOP_AUTOSTART_ID");
466 // We don't want child processes to use the same ID
467 unsetenv("DESKTOP_AUTOSTART_ID");
469 char* client_id
= nullptr;
471 PRLibrary
* iceLib
= PR_LoadLibrary("libICE.so.6");
476 PRLibrary
* smLib
= PR_LoadLibrary("libSM.so.6");
478 PR_UnloadLibrary(iceLib
);
482 IceSetIOErrorHandler
= (IceSetIOErrorHandlerFn
)PR_FindFunctionSymbol(
483 iceLib
, "IceSetIOErrorHandler");
484 IceAddConnectionWatch
= (IceAddConnectionWatchFn
)PR_FindFunctionSymbol(
485 iceLib
, "IceAddConnectionWatch");
486 IceConnectionNumber
= (IceConnectionNumberFn
)PR_FindFunctionSymbol(
487 iceLib
, "IceConnectionNumber");
488 IceProcessMessages
= (IceProcessMessagesFn
)PR_FindFunctionSymbol(
489 iceLib
, "IceProcessMessages");
490 IceGetConnectionContext
= (IceGetConnectionContextFn
)PR_FindFunctionSymbol(
491 iceLib
, "IceGetConnectionContext");
492 if (!IceSetIOErrorHandler
|| !IceAddConnectionWatch
||
493 !IceConnectionNumber
|| !IceProcessMessages
||
494 !IceGetConnectionContext
) {
495 PR_UnloadLibrary(iceLib
);
496 PR_UnloadLibrary(smLib
);
501 (SmcInteractDoneFn
)PR_FindFunctionSymbol(smLib
, "SmcInteractDone");
502 SmcSaveYourselfDone
= (SmcSaveYourselfDoneFn
)PR_FindFunctionSymbol(
503 smLib
, "SmcSaveYourselfDone");
504 SmcInteractRequest
= (SmcInteractRequestFn
)PR_FindFunctionSymbol(
505 smLib
, "SmcInteractRequest");
506 SmcCloseConnection
= (SmcCloseConnectionFn
)PR_FindFunctionSymbol(
507 smLib
, "SmcCloseConnection");
509 (SmcOpenConnectionFn
)PR_FindFunctionSymbol(smLib
, "SmcOpenConnection");
511 (SmcSetPropertiesFn
)PR_FindFunctionSymbol(smLib
, "SmcSetProperties");
512 if (!SmcInteractDone
|| !SmcSaveYourselfDone
|| !SmcInteractRequest
||
513 !SmcCloseConnection
|| !SmcOpenConnection
|| !SmcSetProperties
) {
514 PR_UnloadLibrary(iceLib
);
515 PR_UnloadLibrary(smLib
);
521 // all callbacks are mandatory in libSM 1.0, so listen even if we don't
523 unsigned long mask
= SmcSaveYourselfProcMask
| SmcDieProcMask
|
524 SmcSaveCompleteProcMask
| SmcShutdownCancelledProcMask
;
526 SmcCallbacks callbacks
;
527 callbacks
.save_yourself
.callback
= nsNativeAppSupportUnix::SaveYourselfCB
;
528 callbacks
.save_yourself
.client_data
= static_cast<SmPointer
>(this);
530 callbacks
.die
.callback
= nsNativeAppSupportUnix::DieCB
;
531 callbacks
.die
.client_data
= static_cast<SmPointer
>(this);
533 callbacks
.save_complete
.callback
= nsNativeAppSupportUnix::SaveCompleteCB
;
534 callbacks
.save_complete
.client_data
= nullptr;
536 callbacks
.shutdown_cancelled
.callback
=
537 nsNativeAppSupportUnix::ShutdownCancelledCB
;
538 callbacks
.shutdown_cancelled
.client_data
= static_cast<SmPointer
>(this);
541 mSessionConnection
= SmcOpenConnection(
542 nullptr, this, SmProtoMajor
, SmProtoMinor
, mask
, &callbacks
,
543 prev_client_id
.get(), &client_id
, sizeof(errbuf
), errbuf
);
546 if (!mSessionConnection
) {
551 gArgc
, gArgv
); // need to make sure initialized before SetClientState
552 if (prev_client_id
.IsEmpty() ||
553 (client_id
&& !prev_client_id
.Equals(client_id
))) {
554 SetClientState(STATE_REGISTERING
);
556 SetClientState(STATE_IDLE
);
559 gdk_x11_set_sm_client_id(client_id
);
562 // SmCloneCommand, SmProgram, SmRestartCommand, SmUserID are required
563 // properties so must be set, and must have a sensible fallback value.
565 // Determine executable path to use for XSMP session restore
567 // Is there a request to suppress default binary launcher?
568 nsAutoCString
path(getenv("MOZ_APP_LAUNCHER"));
570 if (path
.IsEmpty()) {
571 NS_ASSERTION(gDirServiceProvider
,
572 "gDirServiceProvider is NULL! This shouldn't happen!");
573 nsCOMPtr
<nsIFile
> executablePath
;
577 rv
= gDirServiceProvider
->GetFile(XRE_EXECUTABLE_FILE
, &dummy
,
578 getter_AddRefs(executablePath
));
580 if (NS_SUCCEEDED(rv
)) {
581 // Strip off the -bin suffix to get the shell script we should run; this
582 // is what Breakpad does
583 nsAutoCString leafName
;
584 rv
= executablePath
->GetNativeLeafName(leafName
);
585 if (NS_SUCCEEDED(rv
) && StringEndsWith(leafName
, "-bin"_ns
)) {
586 leafName
.SetLength(leafName
.Length() - strlen("-bin"));
587 executablePath
->SetNativeLeafName(leafName
);
590 executablePath
->GetNativePath(path
);
594 if (path
.IsEmpty()) {
595 // can't determine executable path. Best fallback is name from
596 // application.ini but it might not resolve to the same executable at
598 path
= gAppData
->name
; // will always be set
600 MOZ_LOG(sMozSMLog
, LogLevel::Warning
,
601 ("Could not determine executable path. Falling back to %s.",
605 SmProp propRestart
, propClone
, propProgram
, propUser
, *props
[4];
606 SmPropValue valsRestart
[3], valsClone
[1], valsProgram
[1], valsUser
[1];
609 constexpr auto kClientIDParam
= "--sm-client-id"_ns
;
611 SetSMValue(valsRestart
[0], path
);
612 SetSMValue(valsRestart
[1], kClientIDParam
);
613 SetSMValue(valsRestart
[2], nsDependentCString(client_id
));
614 SetSMProperty(propRestart
, SmRestartCommand
, SmLISTofARRAY8
, 3, valsRestart
);
615 props
[n
++] = &propRestart
;
617 SetSMValue(valsClone
[0], path
);
618 SetSMProperty(propClone
, SmCloneCommand
, SmLISTofARRAY8
, 1, valsClone
);
619 props
[n
++] = &propClone
;
621 nsAutoCString
appName(gAppData
->name
); // will always be set
622 ToLowerCase(appName
);
624 SetSMValue(valsProgram
[0], appName
);
625 SetSMProperty(propProgram
, SmProgram
, SmARRAY8
, 1, valsProgram
);
626 props
[n
++] = &propProgram
;
628 nsAutoCString userName
; // username that started the program
629 struct passwd
* pw
= getpwuid(getuid());
630 if (pw
&& pw
->pw_name
) {
631 userName
= pw
->pw_name
;
633 userName
= "nobody"_ns
;
635 sMozSMLog
, LogLevel::Warning
,
636 ("Could not determine user-name. Falling back to %s.", userName
.get()));
639 SetSMValue(valsUser
[0], userName
);
640 SetSMProperty(propUser
, SmUserID
, SmARRAY8
, 1, valsUser
);
641 props
[n
++] = &propUser
;
643 SmcSetProperties(mSessionConnection
, n
, props
);
652 nsNativeAppSupportUnix::Enable() { return NS_OK
; }
654 nsresult
NS_CreateNativeAppSupport(nsINativeAppSupport
** aResult
) {
655 nsNativeAppSupportBase
* native
= new nsNativeAppSupportUnix();
656 if (!native
) return NS_ERROR_OUT_OF_MEMORY
;