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"
10 #include "nsISupportsPrimitives.h"
11 #include "nsIObserverService.h"
12 #include "nsIAppStartup.h"
13 #include "nsServiceManagerUtils.h"
15 #include "nsXREDirProvider.h"
16 #include "nsReadableUtils.h"
19 #include "nsDirectoryServiceDefs.h"
20 #include "nsPIDOMWindow.h"
21 #include "nsIWidget.h"
22 #include "mozilla/Logging.h"
23 #include "mozilla/Services.h"
27 #include <glib-object.h>
31 # include <gdk/gdkx.h>
32 # include <X11/ICE/ICElib.h>
33 # include <X11/SM/SMlib.h>
35 # include "nsThreadUtils.h"
40 #ifdef MOZ_ENABLE_DBUS
41 # include <dbus/dbus.h>
44 #define MIN_GTK_MAJOR_VERSION 2
45 #define MIN_GTK_MINOR_VERSION 10
46 #define UNSUPPORTED_GTK_MSG \
47 "We're sorry, this application requires a version of the GTK+ library that is not installed on your computer.\n\n\
48 You have GTK+ %d.%d.\nThis application requires GTK+ %d.%d or newer.\n\n\
49 Please upgrade your GTK+ library if you wish to use this application."
52 # undef IceSetIOErrorHandler
53 # undef IceAddConnectionWatch
54 # undef IceConnectionNumber
55 # undef IceProcessMessages
56 # undef IceGetConnectionContext
57 # undef SmcInteractDone
58 # undef SmcSaveYourselfDone
59 # undef SmcInteractRequest
60 # undef SmcCloseConnection
61 # undef SmcOpenConnection
62 # undef SmcSetProperties
64 typedef IceIOErrorHandler (*IceSetIOErrorHandlerFn
)(IceIOErrorHandler
);
65 typedef int (*IceAddConnectionWatchFn
)(IceWatchProc
, IcePointer
);
66 typedef int (*IceConnectionNumberFn
)(IceConn
);
67 typedef IceProcessMessagesStatus (*IceProcessMessagesFn
)(IceConn
,
70 typedef IcePointer (*IceGetConnectionContextFn
)(IceConn
);
72 typedef void (*SmcInteractDoneFn
)(SmcConn
, Bool
);
73 typedef void (*SmcSaveYourselfDoneFn
)(SmcConn
, Bool
);
74 typedef int (*SmcInteractRequestFn
)(SmcConn
, int, SmcInteractProc
, SmPointer
);
75 typedef SmcCloseStatus (*SmcCloseConnectionFn
)(SmcConn
, int, char**);
76 typedef SmcConn (*SmcOpenConnectionFn
)(char*, SmPointer
, int, int,
77 unsigned long, SmcCallbacks
*,
78 const char*, char**, int, char*);
79 typedef void (*SmcSetPropertiesFn
)(SmcConn
, int, SmProp
**);
81 static IceSetIOErrorHandlerFn IceSetIOErrorHandlerPtr
;
82 static IceAddConnectionWatchFn IceAddConnectionWatchPtr
;
83 static IceConnectionNumberFn IceConnectionNumberPtr
;
84 static IceProcessMessagesFn IceProcessMessagesPtr
;
85 static IceGetConnectionContextFn IceGetConnectionContextPtr
;
86 static SmcInteractDoneFn SmcInteractDonePtr
;
87 static SmcSaveYourselfDoneFn SmcSaveYourselfDonePtr
;
88 static SmcInteractRequestFn SmcInteractRequestPtr
;
89 static SmcCloseConnectionFn SmcCloseConnectionPtr
;
90 static SmcOpenConnectionFn SmcOpenConnectionPtr
;
91 static SmcSetPropertiesFn SmcSetPropertiesPtr
;
93 # define IceSetIOErrorHandler IceSetIOErrorHandlerPtr
94 # define IceAddConnectionWatch IceAddConnectionWatchPtr
95 # define IceConnectionNumber IceConnectionNumberPtr
96 # define IceProcessMessages IceProcessMessagesPtr
97 # define IceGetConnectionContext IceGetConnectionContextPtr
98 # define SmcInteractDone SmcInteractDonePtr
99 # define SmcSaveYourselfDone SmcSaveYourselfDonePtr
100 # define SmcInteractRequest SmcInteractRequestPtr
101 # define SmcCloseConnection SmcCloseConnectionPtr
102 # define SmcOpenConnection SmcOpenConnectionPtr
103 # define SmcSetProperties SmcSetPropertiesPtr
110 STATE_SHUTDOWN_CANCELLED
113 static const char* gClientStateTable
[] = {"DISCONNECTED", "REGISTERING", "IDLE",
114 "INTERACTING", "SHUTDOWN_CANCELLED"};
116 static LazyLogModule
sMozSMLog("MozSM");
119 class nsNativeAppSupportUnix
: public nsNativeAppSupportBase
{
122 nsNativeAppSupportUnix()
123 : mSessionConnection(nullptr), mClientState(STATE_DISCONNECTED
){};
124 ~nsNativeAppSupportUnix() {
125 // this goes out of scope after "web-workers-shutdown" async shutdown phase
126 // so it's safe to disconnect here (i.e. the application won't lose data)
130 void DisconnectFromSM();
132 NS_IMETHOD
Start(bool* aRetVal
) override
;
133 NS_IMETHOD
Enable() override
;
137 static void SaveYourselfCB(SmcConn smc_conn
, SmPointer client_data
,
138 int save_style
, Bool shutdown
, int interact_style
,
140 static void DieCB(SmcConn smc_conn
, SmPointer client_data
);
141 static void InteractCB(SmcConn smc_conn
, SmPointer client_data
);
142 static void SaveCompleteCB(SmcConn smc_conn
, SmPointer client_data
){};
143 static void ShutdownCancelledCB(SmcConn smc_conn
, SmPointer client_data
);
145 void SetClientState(ClientState aState
) {
146 mClientState
= aState
;
147 MOZ_LOG(sMozSMLog
, LogLevel::Debug
,
148 ("New state = %s\n", gClientStateTable
[aState
]));
151 SmcConn mSessionConnection
;
152 ClientState mClientState
;
157 static gboolean
process_ice_messages(IceConn connection
) {
158 IceProcessMessagesStatus status
;
160 status
= IceProcessMessages(connection
, nullptr, nullptr);
163 case IceProcessMessagesSuccess
:
166 case IceProcessMessagesIOError
: {
167 nsNativeAppSupportUnix
* native
= static_cast<nsNativeAppSupportUnix
*>(
168 IceGetConnectionContext(connection
));
169 native
->DisconnectFromSM();
173 case IceProcessMessagesConnectionClosed
:
177 g_assert_not_reached();
181 static gboolean
ice_iochannel_watch(GIOChannel
* channel
, GIOCondition condition
,
182 gpointer client_data
) {
183 return process_ice_messages(static_cast<IceConn
>(client_data
));
186 static void ice_connection_watch(IceConn connection
, IcePointer client_data
,
187 Bool opening
, IcePointer
* watch_data
) {
192 int fd
= IceConnectionNumber(connection
);
194 fcntl(fd
, F_SETFD
, fcntl(fd
, F_GETFD
, 0) | FD_CLOEXEC
);
195 channel
= g_io_channel_unix_new(fd
);
197 g_io_add_watch(channel
, static_cast<GIOCondition
>(G_IO_IN
| G_IO_ERR
),
198 ice_iochannel_watch
, connection
);
199 g_io_channel_unref(channel
);
201 *watch_data
= GUINT_TO_POINTER(watch_id
);
203 watch_id
= GPOINTER_TO_UINT(*watch_data
);
204 g_source_remove(watch_id
);
208 static void ice_io_error_handler(IceConn connection
) {
209 // override the default handler which would exit the application;
210 // do nothing and let ICELib handle the failure of the connection gracefully.
213 static void ice_init(void) {
214 static bool initted
= false;
217 IceSetIOErrorHandler(ice_io_error_handler
);
218 IceAddConnectionWatch(ice_connection_watch
, nullptr);
223 void nsNativeAppSupportUnix::InteractCB(SmcConn smc_conn
,
224 SmPointer client_data
) {
225 nsNativeAppSupportUnix
* self
=
226 static_cast<nsNativeAppSupportUnix
*>(client_data
);
228 self
->SetClientState(STATE_INTERACTING
);
230 // We do this asynchronously, as we spin the event loop recursively if
231 // a dialog is displayed. If we do this synchronously, we don't finish
232 // processing the current ICE event whilst the dialog is displayed, which
233 // means we won't process any more. libsm hates us if we do the InteractDone
234 // with a pending ShutdownCancelled, and we would certainly like to handle Die
235 // whilst a dialog is displayed
236 NS_DispatchToCurrentThread(
237 NewRunnableMethod("nsNativeAppSupportUnix::DoInteract", self
,
238 &nsNativeAppSupportUnix::DoInteract
));
241 void nsNativeAppSupportUnix::DoInteract() {
242 nsCOMPtr
<nsIObserverService
> obsServ
=
243 mozilla::services::GetObserverService();
245 SmcInteractDone(mSessionConnection
, False
);
246 SmcSaveYourselfDone(mSessionConnection
, True
);
247 SetClientState(STATE_IDLE
);
251 nsCOMPtr
<nsISupportsPRBool
> cancelQuit
=
252 do_CreateInstance(NS_SUPPORTS_PRBOOL_CONTRACTID
);
254 bool abortQuit
= false;
256 cancelQuit
->SetData(false);
257 obsServ
->NotifyObservers(cancelQuit
, "quit-application-requested", nullptr);
259 cancelQuit
->GetData(&abortQuit
);
262 if (!abortQuit
&& mClientState
== STATE_DISCONNECTED
) {
263 // The session manager disappeared, whilst we were interacting, so
265 nsCOMPtr
<nsIAppStartup
> appService
=
266 do_GetService("@mozilla.org/toolkit/app-startup;1");
269 bool userAllowedQuit
= true;
270 appService
->Quit(nsIAppStartup::eForceQuit
, 0, &userAllowedQuit
);
273 if (mClientState
!= STATE_SHUTDOWN_CANCELLED
) {
274 // Only do this if the shutdown wasn't cancelled
275 SmcInteractDone(mSessionConnection
, !!abortQuit
);
276 SmcSaveYourselfDone(mSessionConnection
, !abortQuit
);
279 SetClientState(STATE_IDLE
);
283 void nsNativeAppSupportUnix::SaveYourselfCB(SmcConn smc_conn
,
284 SmPointer client_data
,
285 int save_style
, Bool shutdown
,
286 int interact_style
, Bool fast
) {
287 nsNativeAppSupportUnix
* self
=
288 static_cast<nsNativeAppSupportUnix
*>(client_data
);
290 // Expect a SaveYourselfCB if we're registering a new client.
291 // All properties are already set in Start() so just reply with
292 // SmcSaveYourselfDone if the callback matches the expected signature.
294 // Ancient versions (?) of xsm do not follow such an early SaveYourself with
295 // SaveComplete. This is a problem if the application freezes interaction
296 // while waiting for a response to SmcSaveYourselfDone. So never freeze
297 // interaction when in STATE_REGISTERING.
299 // That aside, we could treat each combination of flags appropriately and not
300 // special-case this.
301 if (self
->mClientState
== STATE_REGISTERING
) {
302 self
->SetClientState(STATE_IDLE
);
304 if (save_style
== SmSaveLocal
&& interact_style
== SmInteractStyleNone
&&
305 !shutdown
&& !fast
) {
306 SmcSaveYourselfDone(self
->mSessionConnection
, True
);
311 if (self
->mClientState
== STATE_SHUTDOWN_CANCELLED
) {
312 // The last shutdown request was cancelled whilst we were interacting,
313 // and we haven't finished interacting yet. Switch the state back again
314 self
->SetClientState(STATE_INTERACTING
);
317 nsCOMPtr
<nsIObserverService
> obsServ
=
318 mozilla::services::GetObserverService();
320 SmcSaveYourselfDone(smc_conn
, True
);
325 nsCOMPtr
<nsISupportsPRBool
> didSaveSession
=
326 do_CreateInstance(NS_SUPPORTS_PRBOOL_CONTRACTID
);
328 if (!didSaveSession
) {
329 SmcSaveYourselfDone(smc_conn
, True
);
333 // Notify observers to save the session state
334 didSaveSession
->SetData(false);
335 obsServ
->NotifyObservers(didSaveSession
, "session-save", nullptr);
337 didSaveSession
->GetData(&status
);
339 // If the interact style permits us to, we are shutting down and we didn't
340 // manage to (or weren't asked to) save the local state, then notify the user
341 // in advance that we are doing to quit (assuming that we aren't already
343 if (!status
&& shutdown
&& interact_style
!= SmInteractStyleNone
) {
344 if (self
->mClientState
!= STATE_INTERACTING
) {
345 SmcInteractRequest(smc_conn
, SmDialogNormal
,
346 nsNativeAppSupportUnix::InteractCB
, client_data
);
349 SmcSaveYourselfDone(smc_conn
, True
);
353 void nsNativeAppSupportUnix::DieCB(SmcConn smc_conn
, SmPointer client_data
) {
354 nsCOMPtr
<nsIAppStartup
> appService
=
355 do_GetService("@mozilla.org/toolkit/app-startup;1");
358 bool userAllowedQuit
= false;
359 appService
->Quit(nsIAppStartup::eForceQuit
, 0, &userAllowedQuit
);
361 // Quit causes the shutdown to begin but the shutdown process is asynchronous
362 // so we can't DisconnectFromSM() yet
365 void nsNativeAppSupportUnix::ShutdownCancelledCB(SmcConn smc_conn
,
366 SmPointer client_data
) {
367 nsNativeAppSupportUnix
* self
=
368 static_cast<nsNativeAppSupportUnix
*>(client_data
);
370 // Interacting is the only time when we wouldn't already have called
371 // SmcSaveYourselfDone. Do that now, then set the state to make sure we
372 // don't send it again after finishing interacting
373 if (self
->mClientState
== STATE_INTERACTING
) {
374 SmcSaveYourselfDone(smc_conn
, False
);
375 self
->SetClientState(STATE_SHUTDOWN_CANCELLED
);
379 void nsNativeAppSupportUnix::DisconnectFromSM() {
380 // the SM is free to exit any time after we disconnect, so callers must be
381 // sure to have reached a sufficiently advanced phase of shutdown that there
382 // is no risk of data loss:
383 // e.g. all async writes are complete by the end of "profile-before-change"
384 if (mSessionConnection
) {
385 SetClientState(STATE_DISCONNECTED
);
386 SmcCloseConnection(mSessionConnection
, 0, nullptr);
387 mSessionConnection
= nullptr;
388 gdk_x11_set_sm_client_id(nullptr); // follow gnome-client behaviour
392 static void SetSMValue(SmPropValue
& val
, const nsCString
& data
) {
393 val
.value
= static_cast<SmPointer
>(const_cast<char*>(data
.get()));
394 val
.length
= data
.Length();
397 static void SetSMProperty(SmProp
& prop
, const char* name
, const char* type
,
398 int numVals
, SmPropValue vals
[]) {
399 prop
.name
= const_cast<char*>(name
);
400 prop
.type
= const_cast<char*>(type
);
401 prop
.num_vals
= numVals
;
406 static void RemoveArg(char** argv
) {
416 nsNativeAppSupportUnix::Start(bool* aRetVal
) {
417 NS_ASSERTION(gAppData
, "gAppData must not be null.");
419 // The dbus library is used by both nsWifiScannerDBus and BluetoothDBusService,
420 // from diffrent threads. This could lead to race conditions if the dbus is not
421 // initialized before making any other library calls.
422 #ifdef MOZ_ENABLE_DBUS
423 dbus_threads_init_default();
429 gboolean sm_disable
= FALSE
;
430 if (!getenv("SESSION_MANAGER")) {
434 nsAutoCString prev_client_id
;
436 char** curarg
= gArgv
+ 1;
439 if (arg
[0] == '-' && arg
[1] == '-') {
441 if (!strcmp(arg
, "sm-disable")) {
445 } else if (!strcmp(arg
, "sm-client-id")) {
447 if (*curarg
[0] != '-') {
448 prev_client_id
= *curarg
;
458 if (prev_client_id
.IsEmpty()) {
459 prev_client_id
= getenv("DESKTOP_AUTOSTART_ID");
462 // We don't want child processes to use the same ID
463 unsetenv("DESKTOP_AUTOSTART_ID");
465 char* client_id
= nullptr;
467 PRLibrary
* iceLib
= PR_LoadLibrary("libICE.so.6");
472 PRLibrary
* smLib
= PR_LoadLibrary("libSM.so.6");
474 PR_UnloadLibrary(iceLib
);
478 IceSetIOErrorHandler
= (IceSetIOErrorHandlerFn
)PR_FindFunctionSymbol(
479 iceLib
, "IceSetIOErrorHandler");
480 IceAddConnectionWatch
= (IceAddConnectionWatchFn
)PR_FindFunctionSymbol(
481 iceLib
, "IceAddConnectionWatch");
482 IceConnectionNumber
= (IceConnectionNumberFn
)PR_FindFunctionSymbol(
483 iceLib
, "IceConnectionNumber");
484 IceProcessMessages
= (IceProcessMessagesFn
)PR_FindFunctionSymbol(
485 iceLib
, "IceProcessMessages");
486 IceGetConnectionContext
= (IceGetConnectionContextFn
)PR_FindFunctionSymbol(
487 iceLib
, "IceGetConnectionContext");
488 if (!IceSetIOErrorHandler
|| !IceAddConnectionWatch
||
489 !IceConnectionNumber
|| !IceProcessMessages
||
490 !IceGetConnectionContext
) {
491 PR_UnloadLibrary(iceLib
);
492 PR_UnloadLibrary(smLib
);
497 (SmcInteractDoneFn
)PR_FindFunctionSymbol(smLib
, "SmcInteractDone");
498 SmcSaveYourselfDone
= (SmcSaveYourselfDoneFn
)PR_FindFunctionSymbol(
499 smLib
, "SmcSaveYourselfDone");
500 SmcInteractRequest
= (SmcInteractRequestFn
)PR_FindFunctionSymbol(
501 smLib
, "SmcInteractRequest");
502 SmcCloseConnection
= (SmcCloseConnectionFn
)PR_FindFunctionSymbol(
503 smLib
, "SmcCloseConnection");
505 (SmcOpenConnectionFn
)PR_FindFunctionSymbol(smLib
, "SmcOpenConnection");
507 (SmcSetPropertiesFn
)PR_FindFunctionSymbol(smLib
, "SmcSetProperties");
508 if (!SmcInteractDone
|| !SmcSaveYourselfDone
|| !SmcInteractRequest
||
509 !SmcCloseConnection
|| !SmcOpenConnection
|| !SmcSetProperties
) {
510 PR_UnloadLibrary(iceLib
);
511 PR_UnloadLibrary(smLib
);
517 // all callbacks are mandatory in libSM 1.0, so listen even if we don't
519 unsigned long mask
= SmcSaveYourselfProcMask
| SmcDieProcMask
|
520 SmcSaveCompleteProcMask
| SmcShutdownCancelledProcMask
;
522 SmcCallbacks callbacks
;
523 callbacks
.save_yourself
.callback
= nsNativeAppSupportUnix::SaveYourselfCB
;
524 callbacks
.save_yourself
.client_data
= static_cast<SmPointer
>(this);
526 callbacks
.die
.callback
= nsNativeAppSupportUnix::DieCB
;
527 callbacks
.die
.client_data
= static_cast<SmPointer
>(this);
529 callbacks
.save_complete
.callback
= nsNativeAppSupportUnix::SaveCompleteCB
;
530 callbacks
.save_complete
.client_data
= nullptr;
532 callbacks
.shutdown_cancelled
.callback
=
533 nsNativeAppSupportUnix::ShutdownCancelledCB
;
534 callbacks
.shutdown_cancelled
.client_data
= static_cast<SmPointer
>(this);
537 mSessionConnection
= SmcOpenConnection(
538 nullptr, this, SmProtoMajor
, SmProtoMinor
, mask
, &callbacks
,
539 prev_client_id
.get(), &client_id
, sizeof(errbuf
), errbuf
);
542 if (!mSessionConnection
) {
547 gArgc
, gArgv
); // need to make sure initialized before SetClientState
548 if (prev_client_id
.IsEmpty() ||
549 (client_id
&& !prev_client_id
.Equals(client_id
))) {
550 SetClientState(STATE_REGISTERING
);
552 SetClientState(STATE_IDLE
);
555 gdk_x11_set_sm_client_id(client_id
);
558 // SmCloneCommand, SmProgram, SmRestartCommand, SmUserID are required
559 // properties so must be set, and must have a sensible fallback value.
561 // Determine executable path to use for XSMP session restore
563 // Is there a request to suppress default binary launcher?
564 nsAutoCString
path(getenv("MOZ_APP_LAUNCHER"));
566 if (path
.IsEmpty()) {
567 NS_ASSERTION(gDirServiceProvider
,
568 "gDirServiceProvider is NULL! This shouldn't happen!");
569 nsCOMPtr
<nsIFile
> executablePath
;
573 rv
= gDirServiceProvider
->GetFile(XRE_EXECUTABLE_FILE
, &dummy
,
574 getter_AddRefs(executablePath
));
576 if (NS_SUCCEEDED(rv
)) {
577 // Strip off the -bin suffix to get the shell script we should run; this
578 // is what Breakpad does
579 nsAutoCString leafName
;
580 rv
= executablePath
->GetNativeLeafName(leafName
);
581 if (NS_SUCCEEDED(rv
) && StringEndsWith(leafName
, "-bin"_ns
)) {
582 leafName
.SetLength(leafName
.Length() - strlen("-bin"));
583 executablePath
->SetNativeLeafName(leafName
);
586 executablePath
->GetNativePath(path
);
590 if (path
.IsEmpty()) {
591 // can't determine executable path. Best fallback is name from
592 // application.ini but it might not resolve to the same executable at
594 path
= gAppData
->name
; // will always be set
596 MOZ_LOG(sMozSMLog
, LogLevel::Warning
,
597 ("Could not determine executable path. Falling back to %s.",
601 SmProp propRestart
, propClone
, propProgram
, propUser
, *props
[4];
602 SmPropValue valsRestart
[3], valsClone
[1], valsProgram
[1], valsUser
[1];
605 constexpr auto kClientIDParam
= "--sm-client-id"_ns
;
607 SetSMValue(valsRestart
[0], path
);
608 SetSMValue(valsRestart
[1], kClientIDParam
);
609 SetSMValue(valsRestart
[2], nsDependentCString(client_id
));
610 SetSMProperty(propRestart
, SmRestartCommand
, SmLISTofARRAY8
, 3, valsRestart
);
611 props
[n
++] = &propRestart
;
613 SetSMValue(valsClone
[0], path
);
614 SetSMProperty(propClone
, SmCloneCommand
, SmLISTofARRAY8
, 1, valsClone
);
615 props
[n
++] = &propClone
;
617 nsAutoCString
appName(gAppData
->name
); // will always be set
618 ToLowerCase(appName
);
620 SetSMValue(valsProgram
[0], appName
);
621 SetSMProperty(propProgram
, SmProgram
, SmARRAY8
, 1, valsProgram
);
622 props
[n
++] = &propProgram
;
624 nsAutoCString userName
; // username that started the program
625 struct passwd
* pw
= getpwuid(getuid());
626 if (pw
&& pw
->pw_name
) {
627 userName
= pw
->pw_name
;
629 userName
= "nobody"_ns
;
631 sMozSMLog
, LogLevel::Warning
,
632 ("Could not determine user-name. Falling back to %s.", userName
.get()));
635 SetSMValue(valsUser
[0], userName
);
636 SetSMProperty(propUser
, SmUserID
, SmARRAY8
, 1, valsUser
);
637 props
[n
++] = &propUser
;
639 SmcSetProperties(mSessionConnection
, n
, props
);
648 nsNativeAppSupportUnix::Enable() { return NS_OK
; }
650 nsresult
NS_CreateNativeAppSupport(nsINativeAppSupport
** aResult
) {
651 nsNativeAppSupportBase
* native
= new nsNativeAppSupportUnix();
652 if (!native
) return NS_ERROR_OUT_OF_MEMORY
;