1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/webapprt/gtk2/webapprt.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,408 @@ 1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 + 1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this file, 1.8 + * You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.9 + 1.10 +// GTK headers 1.11 +#include <gtk/gtk.h> 1.12 + 1.13 +// Linux headers 1.14 +#include <fcntl.h> 1.15 +#include <unistd.h> 1.16 +#include <dlfcn.h> 1.17 + 1.18 +// Mozilla headers 1.19 +#include "nsIFile.h" 1.20 +#include "nsINIParser.h" 1.21 +#include "nsXPCOMGlue.h" 1.22 +#include "nsXPCOMPrivate.h" // for MAXPATHLEN and XPCOM_DLL 1.23 +#include "nsXULAppAPI.h" 1.24 +#include "BinaryPath.h" 1.25 + 1.26 +const char kAPP_INI[] = "application.ini"; 1.27 +const char kWEBAPP_INI[] = "webapp.ini"; 1.28 +const char kWEBAPP_JSON[] = "webapp.json"; 1.29 +const char kWEBAPP_PACKAGE[] = "application.zip"; 1.30 +const char kWEBAPPRT_INI[] = "webapprt.ini"; 1.31 +const char kWEBAPPRT_PATH[] = "webapprt"; 1.32 +const char kAPP_ENV_VAR[] = "XUL_APP_FILE"; 1.33 +const char kAPP_RT[] = "webapprt-stub"; 1.34 + 1.35 +int* pargc; 1.36 +char*** pargv; 1.37 +char profile[MAXPATHLEN]; 1.38 +bool isProfileOverridden = false; 1.39 + 1.40 +XRE_GetFileFromPathType XRE_GetFileFromPath; 1.41 +XRE_CreateAppDataType XRE_CreateAppData; 1.42 +XRE_FreeAppDataType XRE_FreeAppData; 1.43 +XRE_mainType XRE_main; 1.44 + 1.45 +const nsDynamicFunctionLoad kXULFuncs[] = { 1.46 + { "XRE_GetFileFromPath", (NSFuncPtr*) &XRE_GetFileFromPath }, 1.47 + { "XRE_CreateAppData", (NSFuncPtr*) &XRE_CreateAppData }, 1.48 + { "XRE_FreeAppData", (NSFuncPtr*) &XRE_FreeAppData }, 1.49 + { "XRE_main", (NSFuncPtr*) &XRE_main }, 1.50 + { nullptr, nullptr } 1.51 +}; 1.52 + 1.53 +class ScopedLogging 1.54 +{ 1.55 + public: 1.56 + ScopedLogging() { NS_LogInit(); } 1.57 + ~ScopedLogging() { NS_LogTerm(); } 1.58 +}; 1.59 + 1.60 +// Copied from toolkit/xre/nsAppData.cpp. 1.61 +void SetAllocatedString(const char *&str, const char *newvalue) 1.62 +{ 1.63 + NS_Free(const_cast<char*>(str)); 1.64 + if (newvalue) { 1.65 + str = NS_strdup(newvalue); 1.66 + } 1.67 + else { 1.68 + str = nullptr; 1.69 + } 1.70 +} 1.71 + 1.72 +// Function to open a dialog box displaying the message provided 1.73 +void ErrorDialog(const char* message) 1.74 +{ 1.75 + gtk_init(pargc, pargv); 1.76 + 1.77 + GtkWidget* dialog = gtk_message_dialog_new(nullptr, GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, "%s", message); 1.78 + gtk_window_set_title(GTK_WINDOW(dialog), "Error launching webapp"); 1.79 + gtk_window_set_skip_taskbar_hint(GTK_WINDOW(dialog), false); 1.80 + gtk_dialog_run(GTK_DIALOG(dialog)); 1.81 + gtk_widget_destroy(dialog); 1.82 +} 1.83 + 1.84 +// Function to get the parent dir of a file 1.85 +void GetDirFromPath(char* parentDir, char* filePath) 1.86 +{ 1.87 + char* base = strrchr(filePath, '/'); 1.88 + 1.89 + if (!base) { 1.90 + strcpy(parentDir, "."); 1.91 + } 1.92 + else { 1.93 + while (base > filePath && *base == '/') 1.94 + base--; 1.95 + 1.96 + int len = 1 + base - filePath; 1.97 + strncpy(parentDir, filePath, len); 1.98 + parentDir[len] = '\0'; 1.99 + } 1.100 +} 1.101 + 1.102 +bool CopyFile(const char* inputFile, const char* outputFile) 1.103 +{ 1.104 + // Open input file 1.105 + int inputFD = open(inputFile, O_RDONLY); 1.106 + if (!inputFD) 1.107 + return false; 1.108 + 1.109 + // Open output file 1.110 + int outputFD = creat(outputFile, S_IRWXU); 1.111 + if (!outputFD) 1.112 + return false; 1.113 + 1.114 + // Copy file 1.115 + char buf[BUFSIZ]; 1.116 + ssize_t bytesRead; 1.117 + 1.118 + while ((bytesRead = read(inputFD, buf, BUFSIZ)) > 0) { 1.119 + ssize_t bytesWritten = write(outputFD, buf, bytesRead); 1.120 + if (bytesWritten < 0) { 1.121 + bytesRead = -1; 1.122 + break; 1.123 + } 1.124 + } 1.125 + 1.126 + // Close file descriptors 1.127 + close(inputFD); 1.128 + close(outputFD); 1.129 + 1.130 + return (bytesRead >= 0); 1.131 +} 1.132 + 1.133 +bool GRELoadAndLaunch(const char* firefoxDir, bool silentFail) 1.134 +{ 1.135 + char xpcomDllPath[MAXPATHLEN]; 1.136 + snprintf(xpcomDllPath, MAXPATHLEN, "%s/%s", firefoxDir, XPCOM_DLL); 1.137 + 1.138 + if (silentFail && access(xpcomDllPath, F_OK) != 0) 1.139 + return false; 1.140 + 1.141 + if (NS_FAILED(XPCOMGlueStartup(xpcomDllPath))) { 1.142 + ErrorDialog("Couldn't load the XPCOM library"); 1.143 + return false; 1.144 + } 1.145 + 1.146 + if (NS_FAILED(XPCOMGlueLoadXULFunctions(kXULFuncs))) { 1.147 + ErrorDialog("Couldn't load libxul"); 1.148 + return false; 1.149 + } 1.150 + 1.151 + // NOTE: The GRE has successfully loaded, so we can use XPCOM now 1.152 + { // Scope for any XPCOM stuff we create 1.153 + ScopedLogging log; 1.154 + 1.155 + // Get the path to the runtime 1.156 + char rtPath[MAXPATHLEN]; 1.157 + snprintf(rtPath, MAXPATHLEN, "%s/%s", firefoxDir, kWEBAPPRT_PATH); 1.158 + 1.159 + // Get the path to the runtime's INI file 1.160 + char rtIniPath[MAXPATHLEN]; 1.161 + snprintf(rtIniPath, MAXPATHLEN, "%s/%s", rtPath, kWEBAPPRT_INI); 1.162 + 1.163 + // Load the runtime's INI from its path 1.164 + nsCOMPtr<nsIFile> rtINI; 1.165 + if (NS_FAILED(XRE_GetFileFromPath(rtIniPath, getter_AddRefs(rtINI)))) { 1.166 + ErrorDialog("Couldn't load the runtime INI"); 1.167 + return false; 1.168 + } 1.169 + 1.170 + bool exists; 1.171 + nsresult rv = rtINI->Exists(&exists); 1.172 + if (NS_FAILED(rv) || !exists) { 1.173 + ErrorDialog("The runtime INI doesn't exist"); 1.174 + return false; 1.175 + } 1.176 + 1.177 + nsXREAppData *webShellAppData; 1.178 + if (NS_FAILED(XRE_CreateAppData(rtINI, &webShellAppData))) { 1.179 + ErrorDialog("Couldn't read WebappRT application.ini"); 1.180 + return false; 1.181 + } 1.182 + 1.183 + if (!isProfileOverridden) { 1.184 + SetAllocatedString(webShellAppData->profile, profile); 1.185 + // nsXREAppData::name is used for the class name part of the WM_CLASS 1.186 + // property. Set it so that the DE can match our window to the correct 1.187 + // launcher. 1.188 + char programClass[MAXPATHLEN]; 1.189 + snprintf(programClass, MAXPATHLEN, "owa-%s", profile); 1.190 + SetAllocatedString(webShellAppData->name, programClass); 1.191 + } 1.192 + 1.193 + nsCOMPtr<nsIFile> directory; 1.194 + if (NS_FAILED(XRE_GetFileFromPath(rtPath, getter_AddRefs(directory)))) { 1.195 + ErrorDialog("Couldn't open runtime directory"); 1.196 + return false; 1.197 + } 1.198 + 1.199 + nsCOMPtr<nsIFile> xreDir; 1.200 + if (NS_FAILED(XRE_GetFileFromPath(firefoxDir, getter_AddRefs(xreDir)))) { 1.201 + ErrorDialog("Couldn't open XRE directory"); 1.202 + return false; 1.203 + } 1.204 + 1.205 + xreDir.forget(&webShellAppData->xreDirectory); 1.206 + NS_IF_RELEASE(webShellAppData->directory); 1.207 + directory.forget(&webShellAppData->directory); 1.208 + 1.209 + XRE_main(*pargc, *pargv, webShellAppData, 0); 1.210 + 1.211 + XRE_FreeAppData(webShellAppData); 1.212 + } 1.213 + 1.214 + return true; 1.215 +} 1.216 + 1.217 +void CopyAndRelaunch(const char* firefoxDir, const char* curExePath) 1.218 +{ 1.219 + char newExePath[MAXPATHLEN]; 1.220 + snprintf(newExePath, MAXPATHLEN, "%s/%s", firefoxDir, kAPP_RT); 1.221 + 1.222 + if (unlink(curExePath) == -1) { 1.223 + ErrorDialog("Couldn't remove the old webapprt-stub executable"); 1.224 + return; 1.225 + } 1.226 + 1.227 + if (!CopyFile(newExePath, curExePath)) { 1.228 + ErrorDialog("Couldn't copy the new webapprt-stub executable"); 1.229 + return; 1.230 + } 1.231 + 1.232 + execv(curExePath, *pargv); 1.233 + 1.234 + ErrorDialog("Couldn't execute the new webapprt-stub executable"); 1.235 +} 1.236 + 1.237 +void RemoveApplication(nsINIParser& parser, const char* curExeDir, const char* profile) { 1.238 + if (!isProfileOverridden) { 1.239 + // Remove the desktop entry file. 1.240 + char desktopEntryFilePath[MAXPATHLEN]; 1.241 + 1.242 + char* dataDir = getenv("XDG_DATA_HOME"); 1.243 + 1.244 + if (dataDir && *dataDir) { 1.245 + snprintf(desktopEntryFilePath, MAXPATHLEN, "%s/applications/owa-%s.desktop", dataDir, profile); 1.246 + } else { 1.247 + char* home = getenv("HOME"); 1.248 + snprintf(desktopEntryFilePath, MAXPATHLEN, "%s/.local/share/applications/owa-%s.desktop", home, profile); 1.249 + } 1.250 + 1.251 + unlink(desktopEntryFilePath); 1.252 + } 1.253 + 1.254 + // Remove the files from the installation directory. 1.255 + char webAppIniPath[MAXPATHLEN]; 1.256 + snprintf(webAppIniPath, MAXPATHLEN, "%s/%s", curExeDir, kWEBAPP_INI); 1.257 + unlink(webAppIniPath); 1.258 + 1.259 + char curExePath[MAXPATHLEN]; 1.260 + snprintf(curExePath, MAXPATHLEN, "%s/%s", curExeDir, kAPP_RT); 1.261 + unlink(curExePath); 1.262 + 1.263 + char webAppJsonPath[MAXPATHLEN]; 1.264 + snprintf(webAppJsonPath, MAXPATHLEN, "%s/%s", curExeDir, kWEBAPP_JSON); 1.265 + unlink(webAppJsonPath); 1.266 + 1.267 + char iconPath[MAXPATHLEN]; 1.268 + snprintf(iconPath, MAXPATHLEN, "%s/icon.png", curExeDir); 1.269 + unlink(iconPath); 1.270 + 1.271 + char packagePath[MAXPATHLEN]; 1.272 + snprintf(packagePath, MAXPATHLEN, "%s/%s", curExeDir, kWEBAPP_PACKAGE); 1.273 + unlink(packagePath); 1.274 + 1.275 + char appName[MAXPATHLEN]; 1.276 + if (NS_FAILED(parser.GetString("Webapp", "Name", appName, MAXPATHLEN))) { 1.277 + strcpy(appName, profile); 1.278 + } 1.279 + 1.280 + char uninstallMsg[MAXPATHLEN]; 1.281 + if (NS_SUCCEEDED(parser.GetString("Webapp", "UninstallMsg", uninstallMsg, MAXPATHLEN))) { 1.282 + /** 1.283 + * The only difference between libnotify.so.4 and libnotify.so.1 for these symbols 1.284 + * is that notify_notification_new takes three arguments in libnotify.so.4 and 1.285 + * four in libnotify.so.1. 1.286 + * Passing the fourth argument as nullptr is binary compatible. 1.287 + */ 1.288 + typedef void (*notify_init_t)(const char*); 1.289 + typedef void* (*notify_notification_new_t)(const char*, const char*, const char*, const char*); 1.290 + typedef void (*notify_notification_show_t)(void*, void**); 1.291 + 1.292 + void *handle = dlopen("libnotify.so.4", RTLD_LAZY); 1.293 + if (!handle) { 1.294 + handle = dlopen("libnotify.so.1", RTLD_LAZY); 1.295 + if (!handle) 1.296 + return; 1.297 + } 1.298 + 1.299 + notify_init_t nn_init = (notify_init_t)(uintptr_t)dlsym(handle, "notify_init"); 1.300 + notify_notification_new_t nn_new = (notify_notification_new_t)(uintptr_t)dlsym(handle, "notify_notification_new"); 1.301 + notify_notification_show_t nn_show = (notify_notification_show_t)(uintptr_t)dlsym(handle, "notify_notification_show"); 1.302 + if (!nn_init || !nn_new || !nn_show) { 1.303 + dlclose(handle); 1.304 + return; 1.305 + } 1.306 + 1.307 + nn_init(appName); 1.308 + 1.309 + void* n = nn_new(uninstallMsg, nullptr, "dialog-information", nullptr); 1.310 + 1.311 + nn_show(n, nullptr); 1.312 + 1.313 + dlclose(handle); 1.314 + } 1.315 +} 1.316 + 1.317 +int main(int argc, char *argv[]) 1.318 +{ 1.319 + pargc = &argc; 1.320 + pargv = &argv; 1.321 + 1.322 + // Get current executable path 1.323 + char curExePath[MAXPATHLEN]; 1.324 + if (NS_FAILED(mozilla::BinaryPath::Get(argv[0], curExePath))) { 1.325 + ErrorDialog("Couldn't read current executable path"); 1.326 + return 255; 1.327 + } 1.328 + char curExeDir[MAXPATHLEN]; 1.329 + GetDirFromPath(curExeDir, curExePath); 1.330 + 1.331 + bool removeApp = false; 1.332 + for (int i = 1; i < argc; i++) { 1.333 + if (!strcmp(argv[i], "-profile")) { 1.334 + isProfileOverridden = true; 1.335 + } 1.336 + else if (!strcmp(argv[i], "-remove")) { 1.337 + removeApp = true; 1.338 + } 1.339 + } 1.340 + 1.341 + char firefoxDir[MAXPATHLEN]; 1.342 + 1.343 + // Check if Firefox is in the same directory as the webapp runtime. 1.344 + // This is the case for webapprt chrome and content tests. 1.345 + if (GRELoadAndLaunch(curExeDir, true)) { 1.346 + return 0; 1.347 + } 1.348 + 1.349 + // Set up webAppIniPath with path to webapp.ini 1.350 + char webAppIniPath[MAXPATHLEN]; 1.351 + snprintf(webAppIniPath, MAXPATHLEN, "%s/%s", curExeDir, kWEBAPP_INI); 1.352 + 1.353 + // Open webapp.ini as an INI file 1.354 + nsINIParser parser; 1.355 + if (NS_FAILED(parser.Init(webAppIniPath))) { 1.356 + ErrorDialog("Couldn't open webapp.ini"); 1.357 + return 255; 1.358 + } 1.359 + 1.360 + // Set up our environment to know where webapp.ini was loaded from 1.361 + if (setenv(kAPP_ENV_VAR, webAppIniPath, 1) == -1) { 1.362 + ErrorDialog("Couldn't set up app environment"); 1.363 + return 255; 1.364 + } 1.365 + 1.366 + // Get profile dir from webapp.ini 1.367 + if (NS_FAILED(parser.GetString("Webapp", "Profile", profile, MAXPATHLEN))) { 1.368 + ErrorDialog("Couldn't retrieve profile from web app INI file"); 1.369 + return 255; 1.370 + } 1.371 + 1.372 + if (removeApp) { 1.373 + RemoveApplication(parser, curExeDir, profile); 1.374 + return 0; 1.375 + } 1.376 + 1.377 + // Get the location of Firefox from our webapp.ini 1.378 + if (NS_FAILED(parser.GetString("WebappRT", "InstallDir", firefoxDir, MAXPATHLEN))) { 1.379 + ErrorDialog("Couldn't find your Firefox install directory."); 1.380 + return 255; 1.381 + } 1.382 + 1.383 + // Set up appIniPath with path to application.ini. 1.384 + // This is in the Firefox installation directory. 1.385 + char appIniPath[MAXPATHLEN]; 1.386 + snprintf(appIniPath, MAXPATHLEN, "%s/%s", firefoxDir, kAPP_INI); 1.387 + 1.388 + if (NS_FAILED(parser.Init(appIniPath))) { 1.389 + ErrorDialog("This app requires that Firefox version 16 or above is installed." 1.390 + " Firefox 16+ has not been detected."); 1.391 + return 255; 1.392 + } 1.393 + 1.394 + // Get buildid of Firefox we're trying to load (MAXPATHLEN only for convenience) 1.395 + char buildid[MAXPATHLEN]; 1.396 + if (NS_FAILED(parser.GetString("App", "BuildID", buildid, MAXPATHLEN))) { 1.397 + ErrorDialog("Couldn't read BuildID from Firefox application.ini"); 1.398 + return 255; 1.399 + } 1.400 + 1.401 + // If WebAppRT version == Firefox version, load XUL and execute the application 1.402 + if (!strcmp(buildid, NS_STRINGIFY(GRE_BUILDID))) { 1.403 + if (GRELoadAndLaunch(firefoxDir, false)) 1.404 + return 0; 1.405 + } 1.406 + // Else, copy WebAppRT from Firefox installation and re-execute the process 1.407 + else 1.408 + CopyAndRelaunch(firefoxDir, curExePath); 1.409 + 1.410 + return 255; 1.411 +}