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