michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this file, michael@0: * You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: // GTK headers michael@0: #include michael@0: michael@0: // Linux headers michael@0: #include michael@0: #include michael@0: #include michael@0: michael@0: // Mozilla headers michael@0: #include "nsIFile.h" michael@0: #include "nsINIParser.h" michael@0: #include "nsXPCOMGlue.h" michael@0: #include "nsXPCOMPrivate.h" // for MAXPATHLEN and XPCOM_DLL michael@0: #include "nsXULAppAPI.h" michael@0: #include "BinaryPath.h" michael@0: michael@0: const char kAPP_INI[] = "application.ini"; michael@0: const char kWEBAPP_INI[] = "webapp.ini"; michael@0: const char kWEBAPP_JSON[] = "webapp.json"; michael@0: const char kWEBAPP_PACKAGE[] = "application.zip"; michael@0: const char kWEBAPPRT_INI[] = "webapprt.ini"; michael@0: const char kWEBAPPRT_PATH[] = "webapprt"; michael@0: const char kAPP_ENV_VAR[] = "XUL_APP_FILE"; michael@0: const char kAPP_RT[] = "webapprt-stub"; michael@0: michael@0: int* pargc; michael@0: char*** pargv; michael@0: char profile[MAXPATHLEN]; michael@0: bool isProfileOverridden = false; michael@0: michael@0: XRE_GetFileFromPathType XRE_GetFileFromPath; michael@0: XRE_CreateAppDataType XRE_CreateAppData; michael@0: XRE_FreeAppDataType XRE_FreeAppData; michael@0: XRE_mainType XRE_main; michael@0: michael@0: const nsDynamicFunctionLoad kXULFuncs[] = { michael@0: { "XRE_GetFileFromPath", (NSFuncPtr*) &XRE_GetFileFromPath }, michael@0: { "XRE_CreateAppData", (NSFuncPtr*) &XRE_CreateAppData }, michael@0: { "XRE_FreeAppData", (NSFuncPtr*) &XRE_FreeAppData }, michael@0: { "XRE_main", (NSFuncPtr*) &XRE_main }, michael@0: { nullptr, nullptr } michael@0: }; michael@0: michael@0: class ScopedLogging michael@0: { michael@0: public: michael@0: ScopedLogging() { NS_LogInit(); } michael@0: ~ScopedLogging() { NS_LogTerm(); } michael@0: }; michael@0: michael@0: // Copied from toolkit/xre/nsAppData.cpp. michael@0: void SetAllocatedString(const char *&str, const char *newvalue) michael@0: { michael@0: NS_Free(const_cast(str)); michael@0: if (newvalue) { michael@0: str = NS_strdup(newvalue); michael@0: } michael@0: else { michael@0: str = nullptr; michael@0: } michael@0: } michael@0: michael@0: // Function to open a dialog box displaying the message provided michael@0: void ErrorDialog(const char* message) michael@0: { michael@0: gtk_init(pargc, pargv); michael@0: michael@0: GtkWidget* dialog = gtk_message_dialog_new(nullptr, GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, "%s", message); michael@0: gtk_window_set_title(GTK_WINDOW(dialog), "Error launching webapp"); michael@0: gtk_window_set_skip_taskbar_hint(GTK_WINDOW(dialog), false); michael@0: gtk_dialog_run(GTK_DIALOG(dialog)); michael@0: gtk_widget_destroy(dialog); michael@0: } michael@0: michael@0: // Function to get the parent dir of a file michael@0: void GetDirFromPath(char* parentDir, char* filePath) michael@0: { michael@0: char* base = strrchr(filePath, '/'); michael@0: michael@0: if (!base) { michael@0: strcpy(parentDir, "."); michael@0: } michael@0: else { michael@0: while (base > filePath && *base == '/') michael@0: base--; michael@0: michael@0: int len = 1 + base - filePath; michael@0: strncpy(parentDir, filePath, len); michael@0: parentDir[len] = '\0'; michael@0: } michael@0: } michael@0: michael@0: bool CopyFile(const char* inputFile, const char* outputFile) michael@0: { michael@0: // Open input file michael@0: int inputFD = open(inputFile, O_RDONLY); michael@0: if (!inputFD) michael@0: return false; michael@0: michael@0: // Open output file michael@0: int outputFD = creat(outputFile, S_IRWXU); michael@0: if (!outputFD) michael@0: return false; michael@0: michael@0: // Copy file michael@0: char buf[BUFSIZ]; michael@0: ssize_t bytesRead; michael@0: michael@0: while ((bytesRead = read(inputFD, buf, BUFSIZ)) > 0) { michael@0: ssize_t bytesWritten = write(outputFD, buf, bytesRead); michael@0: if (bytesWritten < 0) { michael@0: bytesRead = -1; michael@0: break; michael@0: } michael@0: } michael@0: michael@0: // Close file descriptors michael@0: close(inputFD); michael@0: close(outputFD); michael@0: michael@0: return (bytesRead >= 0); michael@0: } michael@0: michael@0: bool GRELoadAndLaunch(const char* firefoxDir, bool silentFail) michael@0: { michael@0: char xpcomDllPath[MAXPATHLEN]; michael@0: snprintf(xpcomDllPath, MAXPATHLEN, "%s/%s", firefoxDir, XPCOM_DLL); michael@0: michael@0: if (silentFail && access(xpcomDllPath, F_OK) != 0) michael@0: return false; michael@0: michael@0: if (NS_FAILED(XPCOMGlueStartup(xpcomDllPath))) { michael@0: ErrorDialog("Couldn't load the XPCOM library"); michael@0: return false; michael@0: } michael@0: michael@0: if (NS_FAILED(XPCOMGlueLoadXULFunctions(kXULFuncs))) { michael@0: ErrorDialog("Couldn't load libxul"); michael@0: return false; michael@0: } michael@0: michael@0: // NOTE: The GRE has successfully loaded, so we can use XPCOM now michael@0: { // Scope for any XPCOM stuff we create michael@0: ScopedLogging log; michael@0: michael@0: // Get the path to the runtime michael@0: char rtPath[MAXPATHLEN]; michael@0: snprintf(rtPath, MAXPATHLEN, "%s/%s", firefoxDir, kWEBAPPRT_PATH); michael@0: michael@0: // Get the path to the runtime's INI file michael@0: char rtIniPath[MAXPATHLEN]; michael@0: snprintf(rtIniPath, MAXPATHLEN, "%s/%s", rtPath, kWEBAPPRT_INI); michael@0: michael@0: // Load the runtime's INI from its path michael@0: nsCOMPtr rtINI; michael@0: if (NS_FAILED(XRE_GetFileFromPath(rtIniPath, getter_AddRefs(rtINI)))) { michael@0: ErrorDialog("Couldn't load the runtime INI"); michael@0: return false; michael@0: } michael@0: michael@0: bool exists; michael@0: nsresult rv = rtINI->Exists(&exists); michael@0: if (NS_FAILED(rv) || !exists) { michael@0: ErrorDialog("The runtime INI doesn't exist"); michael@0: return false; michael@0: } michael@0: michael@0: nsXREAppData *webShellAppData; michael@0: if (NS_FAILED(XRE_CreateAppData(rtINI, &webShellAppData))) { michael@0: ErrorDialog("Couldn't read WebappRT application.ini"); michael@0: return false; michael@0: } michael@0: michael@0: if (!isProfileOverridden) { michael@0: SetAllocatedString(webShellAppData->profile, profile); michael@0: // nsXREAppData::name is used for the class name part of the WM_CLASS michael@0: // property. Set it so that the DE can match our window to the correct michael@0: // launcher. michael@0: char programClass[MAXPATHLEN]; michael@0: snprintf(programClass, MAXPATHLEN, "owa-%s", profile); michael@0: SetAllocatedString(webShellAppData->name, programClass); michael@0: } michael@0: michael@0: nsCOMPtr directory; michael@0: if (NS_FAILED(XRE_GetFileFromPath(rtPath, getter_AddRefs(directory)))) { michael@0: ErrorDialog("Couldn't open runtime directory"); michael@0: return false; michael@0: } michael@0: michael@0: nsCOMPtr xreDir; michael@0: if (NS_FAILED(XRE_GetFileFromPath(firefoxDir, getter_AddRefs(xreDir)))) { michael@0: ErrorDialog("Couldn't open XRE directory"); michael@0: return false; michael@0: } michael@0: michael@0: xreDir.forget(&webShellAppData->xreDirectory); michael@0: NS_IF_RELEASE(webShellAppData->directory); michael@0: directory.forget(&webShellAppData->directory); michael@0: michael@0: XRE_main(*pargc, *pargv, webShellAppData, 0); michael@0: michael@0: XRE_FreeAppData(webShellAppData); michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: void CopyAndRelaunch(const char* firefoxDir, const char* curExePath) michael@0: { michael@0: char newExePath[MAXPATHLEN]; michael@0: snprintf(newExePath, MAXPATHLEN, "%s/%s", firefoxDir, kAPP_RT); michael@0: michael@0: if (unlink(curExePath) == -1) { michael@0: ErrorDialog("Couldn't remove the old webapprt-stub executable"); michael@0: return; michael@0: } michael@0: michael@0: if (!CopyFile(newExePath, curExePath)) { michael@0: ErrorDialog("Couldn't copy the new webapprt-stub executable"); michael@0: return; michael@0: } michael@0: michael@0: execv(curExePath, *pargv); michael@0: michael@0: ErrorDialog("Couldn't execute the new webapprt-stub executable"); michael@0: } michael@0: michael@0: void RemoveApplication(nsINIParser& parser, const char* curExeDir, const char* profile) { michael@0: if (!isProfileOverridden) { michael@0: // Remove the desktop entry file. michael@0: char desktopEntryFilePath[MAXPATHLEN]; michael@0: michael@0: char* dataDir = getenv("XDG_DATA_HOME"); michael@0: michael@0: if (dataDir && *dataDir) { michael@0: snprintf(desktopEntryFilePath, MAXPATHLEN, "%s/applications/owa-%s.desktop", dataDir, profile); michael@0: } else { michael@0: char* home = getenv("HOME"); michael@0: snprintf(desktopEntryFilePath, MAXPATHLEN, "%s/.local/share/applications/owa-%s.desktop", home, profile); michael@0: } michael@0: michael@0: unlink(desktopEntryFilePath); michael@0: } michael@0: michael@0: // Remove the files from the installation directory. michael@0: char webAppIniPath[MAXPATHLEN]; michael@0: snprintf(webAppIniPath, MAXPATHLEN, "%s/%s", curExeDir, kWEBAPP_INI); michael@0: unlink(webAppIniPath); michael@0: michael@0: char curExePath[MAXPATHLEN]; michael@0: snprintf(curExePath, MAXPATHLEN, "%s/%s", curExeDir, kAPP_RT); michael@0: unlink(curExePath); michael@0: michael@0: char webAppJsonPath[MAXPATHLEN]; michael@0: snprintf(webAppJsonPath, MAXPATHLEN, "%s/%s", curExeDir, kWEBAPP_JSON); michael@0: unlink(webAppJsonPath); michael@0: michael@0: char iconPath[MAXPATHLEN]; michael@0: snprintf(iconPath, MAXPATHLEN, "%s/icon.png", curExeDir); michael@0: unlink(iconPath); michael@0: michael@0: char packagePath[MAXPATHLEN]; michael@0: snprintf(packagePath, MAXPATHLEN, "%s/%s", curExeDir, kWEBAPP_PACKAGE); michael@0: unlink(packagePath); michael@0: michael@0: char appName[MAXPATHLEN]; michael@0: if (NS_FAILED(parser.GetString("Webapp", "Name", appName, MAXPATHLEN))) { michael@0: strcpy(appName, profile); michael@0: } michael@0: michael@0: char uninstallMsg[MAXPATHLEN]; michael@0: if (NS_SUCCEEDED(parser.GetString("Webapp", "UninstallMsg", uninstallMsg, MAXPATHLEN))) { michael@0: /** michael@0: * The only difference between libnotify.so.4 and libnotify.so.1 for these symbols michael@0: * is that notify_notification_new takes three arguments in libnotify.so.4 and michael@0: * four in libnotify.so.1. michael@0: * Passing the fourth argument as nullptr is binary compatible. michael@0: */ michael@0: typedef void (*notify_init_t)(const char*); michael@0: typedef void* (*notify_notification_new_t)(const char*, const char*, const char*, const char*); michael@0: typedef void (*notify_notification_show_t)(void*, void**); michael@0: michael@0: void *handle = dlopen("libnotify.so.4", RTLD_LAZY); michael@0: if (!handle) { michael@0: handle = dlopen("libnotify.so.1", RTLD_LAZY); michael@0: if (!handle) michael@0: return; michael@0: } michael@0: michael@0: notify_init_t nn_init = (notify_init_t)(uintptr_t)dlsym(handle, "notify_init"); michael@0: notify_notification_new_t nn_new = (notify_notification_new_t)(uintptr_t)dlsym(handle, "notify_notification_new"); michael@0: notify_notification_show_t nn_show = (notify_notification_show_t)(uintptr_t)dlsym(handle, "notify_notification_show"); michael@0: if (!nn_init || !nn_new || !nn_show) { michael@0: dlclose(handle); michael@0: return; michael@0: } michael@0: michael@0: nn_init(appName); michael@0: michael@0: void* n = nn_new(uninstallMsg, nullptr, "dialog-information", nullptr); michael@0: michael@0: nn_show(n, nullptr); michael@0: michael@0: dlclose(handle); michael@0: } michael@0: } michael@0: michael@0: int main(int argc, char *argv[]) michael@0: { michael@0: pargc = &argc; michael@0: pargv = &argv; michael@0: michael@0: // Get current executable path michael@0: char curExePath[MAXPATHLEN]; michael@0: if (NS_FAILED(mozilla::BinaryPath::Get(argv[0], curExePath))) { michael@0: ErrorDialog("Couldn't read current executable path"); michael@0: return 255; michael@0: } michael@0: char curExeDir[MAXPATHLEN]; michael@0: GetDirFromPath(curExeDir, curExePath); michael@0: michael@0: bool removeApp = false; michael@0: for (int i = 1; i < argc; i++) { michael@0: if (!strcmp(argv[i], "-profile")) { michael@0: isProfileOverridden = true; michael@0: } michael@0: else if (!strcmp(argv[i], "-remove")) { michael@0: removeApp = true; michael@0: } michael@0: } michael@0: michael@0: char firefoxDir[MAXPATHLEN]; michael@0: michael@0: // Check if Firefox is in the same directory as the webapp runtime. michael@0: // This is the case for webapprt chrome and content tests. michael@0: if (GRELoadAndLaunch(curExeDir, true)) { michael@0: return 0; michael@0: } michael@0: michael@0: // Set up webAppIniPath with path to webapp.ini michael@0: char webAppIniPath[MAXPATHLEN]; michael@0: snprintf(webAppIniPath, MAXPATHLEN, "%s/%s", curExeDir, kWEBAPP_INI); michael@0: michael@0: // Open webapp.ini as an INI file michael@0: nsINIParser parser; michael@0: if (NS_FAILED(parser.Init(webAppIniPath))) { michael@0: ErrorDialog("Couldn't open webapp.ini"); michael@0: return 255; michael@0: } michael@0: michael@0: // Set up our environment to know where webapp.ini was loaded from michael@0: if (setenv(kAPP_ENV_VAR, webAppIniPath, 1) == -1) { michael@0: ErrorDialog("Couldn't set up app environment"); michael@0: return 255; michael@0: } michael@0: michael@0: // Get profile dir from webapp.ini michael@0: if (NS_FAILED(parser.GetString("Webapp", "Profile", profile, MAXPATHLEN))) { michael@0: ErrorDialog("Couldn't retrieve profile from web app INI file"); michael@0: return 255; michael@0: } michael@0: michael@0: if (removeApp) { michael@0: RemoveApplication(parser, curExeDir, profile); michael@0: return 0; michael@0: } michael@0: michael@0: // Get the location of Firefox from our webapp.ini michael@0: if (NS_FAILED(parser.GetString("WebappRT", "InstallDir", firefoxDir, MAXPATHLEN))) { michael@0: ErrorDialog("Couldn't find your Firefox install directory."); michael@0: return 255; michael@0: } michael@0: michael@0: // Set up appIniPath with path to application.ini. michael@0: // This is in the Firefox installation directory. michael@0: char appIniPath[MAXPATHLEN]; michael@0: snprintf(appIniPath, MAXPATHLEN, "%s/%s", firefoxDir, kAPP_INI); michael@0: michael@0: if (NS_FAILED(parser.Init(appIniPath))) { michael@0: ErrorDialog("This app requires that Firefox version 16 or above is installed." michael@0: " Firefox 16+ has not been detected."); michael@0: return 255; michael@0: } michael@0: michael@0: // Get buildid of Firefox we're trying to load (MAXPATHLEN only for convenience) michael@0: char buildid[MAXPATHLEN]; michael@0: if (NS_FAILED(parser.GetString("App", "BuildID", buildid, MAXPATHLEN))) { michael@0: ErrorDialog("Couldn't read BuildID from Firefox application.ini"); michael@0: return 255; michael@0: } michael@0: michael@0: // If WebAppRT version == Firefox version, load XUL and execute the application michael@0: if (!strcmp(buildid, NS_STRINGIFY(GRE_BUILDID))) { michael@0: if (GRELoadAndLaunch(firefoxDir, false)) michael@0: return 0; michael@0: } michael@0: // Else, copy WebAppRT from Firefox installation and re-execute the process michael@0: else michael@0: CopyAndRelaunch(firefoxDir, curExePath); michael@0: michael@0: return 255; michael@0: }