|
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
|
2 |
|
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 file, |
|
5 * You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
6 |
|
7 // GTK headers |
|
8 #include <gtk/gtk.h> |
|
9 |
|
10 // Linux headers |
|
11 #include <fcntl.h> |
|
12 #include <unistd.h> |
|
13 #include <dlfcn.h> |
|
14 |
|
15 // Mozilla headers |
|
16 #include "nsIFile.h" |
|
17 #include "nsINIParser.h" |
|
18 #include "nsXPCOMGlue.h" |
|
19 #include "nsXPCOMPrivate.h" // for MAXPATHLEN and XPCOM_DLL |
|
20 #include "nsXULAppAPI.h" |
|
21 #include "BinaryPath.h" |
|
22 |
|
23 const char kAPP_INI[] = "application.ini"; |
|
24 const char kWEBAPP_INI[] = "webapp.ini"; |
|
25 const char kWEBAPP_JSON[] = "webapp.json"; |
|
26 const char kWEBAPP_PACKAGE[] = "application.zip"; |
|
27 const char kWEBAPPRT_INI[] = "webapprt.ini"; |
|
28 const char kWEBAPPRT_PATH[] = "webapprt"; |
|
29 const char kAPP_ENV_VAR[] = "XUL_APP_FILE"; |
|
30 const char kAPP_RT[] = "webapprt-stub"; |
|
31 |
|
32 int* pargc; |
|
33 char*** pargv; |
|
34 char profile[MAXPATHLEN]; |
|
35 bool isProfileOverridden = false; |
|
36 |
|
37 XRE_GetFileFromPathType XRE_GetFileFromPath; |
|
38 XRE_CreateAppDataType XRE_CreateAppData; |
|
39 XRE_FreeAppDataType XRE_FreeAppData; |
|
40 XRE_mainType XRE_main; |
|
41 |
|
42 const nsDynamicFunctionLoad kXULFuncs[] = { |
|
43 { "XRE_GetFileFromPath", (NSFuncPtr*) &XRE_GetFileFromPath }, |
|
44 { "XRE_CreateAppData", (NSFuncPtr*) &XRE_CreateAppData }, |
|
45 { "XRE_FreeAppData", (NSFuncPtr*) &XRE_FreeAppData }, |
|
46 { "XRE_main", (NSFuncPtr*) &XRE_main }, |
|
47 { nullptr, nullptr } |
|
48 }; |
|
49 |
|
50 class ScopedLogging |
|
51 { |
|
52 public: |
|
53 ScopedLogging() { NS_LogInit(); } |
|
54 ~ScopedLogging() { NS_LogTerm(); } |
|
55 }; |
|
56 |
|
57 // Copied from toolkit/xre/nsAppData.cpp. |
|
58 void SetAllocatedString(const char *&str, const char *newvalue) |
|
59 { |
|
60 NS_Free(const_cast<char*>(str)); |
|
61 if (newvalue) { |
|
62 str = NS_strdup(newvalue); |
|
63 } |
|
64 else { |
|
65 str = nullptr; |
|
66 } |
|
67 } |
|
68 |
|
69 // Function to open a dialog box displaying the message provided |
|
70 void ErrorDialog(const char* message) |
|
71 { |
|
72 gtk_init(pargc, pargv); |
|
73 |
|
74 GtkWidget* dialog = gtk_message_dialog_new(nullptr, GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, "%s", message); |
|
75 gtk_window_set_title(GTK_WINDOW(dialog), "Error launching webapp"); |
|
76 gtk_window_set_skip_taskbar_hint(GTK_WINDOW(dialog), false); |
|
77 gtk_dialog_run(GTK_DIALOG(dialog)); |
|
78 gtk_widget_destroy(dialog); |
|
79 } |
|
80 |
|
81 // Function to get the parent dir of a file |
|
82 void GetDirFromPath(char* parentDir, char* filePath) |
|
83 { |
|
84 char* base = strrchr(filePath, '/'); |
|
85 |
|
86 if (!base) { |
|
87 strcpy(parentDir, "."); |
|
88 } |
|
89 else { |
|
90 while (base > filePath && *base == '/') |
|
91 base--; |
|
92 |
|
93 int len = 1 + base - filePath; |
|
94 strncpy(parentDir, filePath, len); |
|
95 parentDir[len] = '\0'; |
|
96 } |
|
97 } |
|
98 |
|
99 bool CopyFile(const char* inputFile, const char* outputFile) |
|
100 { |
|
101 // Open input file |
|
102 int inputFD = open(inputFile, O_RDONLY); |
|
103 if (!inputFD) |
|
104 return false; |
|
105 |
|
106 // Open output file |
|
107 int outputFD = creat(outputFile, S_IRWXU); |
|
108 if (!outputFD) |
|
109 return false; |
|
110 |
|
111 // Copy file |
|
112 char buf[BUFSIZ]; |
|
113 ssize_t bytesRead; |
|
114 |
|
115 while ((bytesRead = read(inputFD, buf, BUFSIZ)) > 0) { |
|
116 ssize_t bytesWritten = write(outputFD, buf, bytesRead); |
|
117 if (bytesWritten < 0) { |
|
118 bytesRead = -1; |
|
119 break; |
|
120 } |
|
121 } |
|
122 |
|
123 // Close file descriptors |
|
124 close(inputFD); |
|
125 close(outputFD); |
|
126 |
|
127 return (bytesRead >= 0); |
|
128 } |
|
129 |
|
130 bool GRELoadAndLaunch(const char* firefoxDir, bool silentFail) |
|
131 { |
|
132 char xpcomDllPath[MAXPATHLEN]; |
|
133 snprintf(xpcomDllPath, MAXPATHLEN, "%s/%s", firefoxDir, XPCOM_DLL); |
|
134 |
|
135 if (silentFail && access(xpcomDllPath, F_OK) != 0) |
|
136 return false; |
|
137 |
|
138 if (NS_FAILED(XPCOMGlueStartup(xpcomDllPath))) { |
|
139 ErrorDialog("Couldn't load the XPCOM library"); |
|
140 return false; |
|
141 } |
|
142 |
|
143 if (NS_FAILED(XPCOMGlueLoadXULFunctions(kXULFuncs))) { |
|
144 ErrorDialog("Couldn't load libxul"); |
|
145 return false; |
|
146 } |
|
147 |
|
148 // NOTE: The GRE has successfully loaded, so we can use XPCOM now |
|
149 { // Scope for any XPCOM stuff we create |
|
150 ScopedLogging log; |
|
151 |
|
152 // Get the path to the runtime |
|
153 char rtPath[MAXPATHLEN]; |
|
154 snprintf(rtPath, MAXPATHLEN, "%s/%s", firefoxDir, kWEBAPPRT_PATH); |
|
155 |
|
156 // Get the path to the runtime's INI file |
|
157 char rtIniPath[MAXPATHLEN]; |
|
158 snprintf(rtIniPath, MAXPATHLEN, "%s/%s", rtPath, kWEBAPPRT_INI); |
|
159 |
|
160 // Load the runtime's INI from its path |
|
161 nsCOMPtr<nsIFile> rtINI; |
|
162 if (NS_FAILED(XRE_GetFileFromPath(rtIniPath, getter_AddRefs(rtINI)))) { |
|
163 ErrorDialog("Couldn't load the runtime INI"); |
|
164 return false; |
|
165 } |
|
166 |
|
167 bool exists; |
|
168 nsresult rv = rtINI->Exists(&exists); |
|
169 if (NS_FAILED(rv) || !exists) { |
|
170 ErrorDialog("The runtime INI doesn't exist"); |
|
171 return false; |
|
172 } |
|
173 |
|
174 nsXREAppData *webShellAppData; |
|
175 if (NS_FAILED(XRE_CreateAppData(rtINI, &webShellAppData))) { |
|
176 ErrorDialog("Couldn't read WebappRT application.ini"); |
|
177 return false; |
|
178 } |
|
179 |
|
180 if (!isProfileOverridden) { |
|
181 SetAllocatedString(webShellAppData->profile, profile); |
|
182 // nsXREAppData::name is used for the class name part of the WM_CLASS |
|
183 // property. Set it so that the DE can match our window to the correct |
|
184 // launcher. |
|
185 char programClass[MAXPATHLEN]; |
|
186 snprintf(programClass, MAXPATHLEN, "owa-%s", profile); |
|
187 SetAllocatedString(webShellAppData->name, programClass); |
|
188 } |
|
189 |
|
190 nsCOMPtr<nsIFile> directory; |
|
191 if (NS_FAILED(XRE_GetFileFromPath(rtPath, getter_AddRefs(directory)))) { |
|
192 ErrorDialog("Couldn't open runtime directory"); |
|
193 return false; |
|
194 } |
|
195 |
|
196 nsCOMPtr<nsIFile> xreDir; |
|
197 if (NS_FAILED(XRE_GetFileFromPath(firefoxDir, getter_AddRefs(xreDir)))) { |
|
198 ErrorDialog("Couldn't open XRE directory"); |
|
199 return false; |
|
200 } |
|
201 |
|
202 xreDir.forget(&webShellAppData->xreDirectory); |
|
203 NS_IF_RELEASE(webShellAppData->directory); |
|
204 directory.forget(&webShellAppData->directory); |
|
205 |
|
206 XRE_main(*pargc, *pargv, webShellAppData, 0); |
|
207 |
|
208 XRE_FreeAppData(webShellAppData); |
|
209 } |
|
210 |
|
211 return true; |
|
212 } |
|
213 |
|
214 void CopyAndRelaunch(const char* firefoxDir, const char* curExePath) |
|
215 { |
|
216 char newExePath[MAXPATHLEN]; |
|
217 snprintf(newExePath, MAXPATHLEN, "%s/%s", firefoxDir, kAPP_RT); |
|
218 |
|
219 if (unlink(curExePath) == -1) { |
|
220 ErrorDialog("Couldn't remove the old webapprt-stub executable"); |
|
221 return; |
|
222 } |
|
223 |
|
224 if (!CopyFile(newExePath, curExePath)) { |
|
225 ErrorDialog("Couldn't copy the new webapprt-stub executable"); |
|
226 return; |
|
227 } |
|
228 |
|
229 execv(curExePath, *pargv); |
|
230 |
|
231 ErrorDialog("Couldn't execute the new webapprt-stub executable"); |
|
232 } |
|
233 |
|
234 void RemoveApplication(nsINIParser& parser, const char* curExeDir, const char* profile) { |
|
235 if (!isProfileOverridden) { |
|
236 // Remove the desktop entry file. |
|
237 char desktopEntryFilePath[MAXPATHLEN]; |
|
238 |
|
239 char* dataDir = getenv("XDG_DATA_HOME"); |
|
240 |
|
241 if (dataDir && *dataDir) { |
|
242 snprintf(desktopEntryFilePath, MAXPATHLEN, "%s/applications/owa-%s.desktop", dataDir, profile); |
|
243 } else { |
|
244 char* home = getenv("HOME"); |
|
245 snprintf(desktopEntryFilePath, MAXPATHLEN, "%s/.local/share/applications/owa-%s.desktop", home, profile); |
|
246 } |
|
247 |
|
248 unlink(desktopEntryFilePath); |
|
249 } |
|
250 |
|
251 // Remove the files from the installation directory. |
|
252 char webAppIniPath[MAXPATHLEN]; |
|
253 snprintf(webAppIniPath, MAXPATHLEN, "%s/%s", curExeDir, kWEBAPP_INI); |
|
254 unlink(webAppIniPath); |
|
255 |
|
256 char curExePath[MAXPATHLEN]; |
|
257 snprintf(curExePath, MAXPATHLEN, "%s/%s", curExeDir, kAPP_RT); |
|
258 unlink(curExePath); |
|
259 |
|
260 char webAppJsonPath[MAXPATHLEN]; |
|
261 snprintf(webAppJsonPath, MAXPATHLEN, "%s/%s", curExeDir, kWEBAPP_JSON); |
|
262 unlink(webAppJsonPath); |
|
263 |
|
264 char iconPath[MAXPATHLEN]; |
|
265 snprintf(iconPath, MAXPATHLEN, "%s/icon.png", curExeDir); |
|
266 unlink(iconPath); |
|
267 |
|
268 char packagePath[MAXPATHLEN]; |
|
269 snprintf(packagePath, MAXPATHLEN, "%s/%s", curExeDir, kWEBAPP_PACKAGE); |
|
270 unlink(packagePath); |
|
271 |
|
272 char appName[MAXPATHLEN]; |
|
273 if (NS_FAILED(parser.GetString("Webapp", "Name", appName, MAXPATHLEN))) { |
|
274 strcpy(appName, profile); |
|
275 } |
|
276 |
|
277 char uninstallMsg[MAXPATHLEN]; |
|
278 if (NS_SUCCEEDED(parser.GetString("Webapp", "UninstallMsg", uninstallMsg, MAXPATHLEN))) { |
|
279 /** |
|
280 * The only difference between libnotify.so.4 and libnotify.so.1 for these symbols |
|
281 * is that notify_notification_new takes three arguments in libnotify.so.4 and |
|
282 * four in libnotify.so.1. |
|
283 * Passing the fourth argument as nullptr is binary compatible. |
|
284 */ |
|
285 typedef void (*notify_init_t)(const char*); |
|
286 typedef void* (*notify_notification_new_t)(const char*, const char*, const char*, const char*); |
|
287 typedef void (*notify_notification_show_t)(void*, void**); |
|
288 |
|
289 void *handle = dlopen("libnotify.so.4", RTLD_LAZY); |
|
290 if (!handle) { |
|
291 handle = dlopen("libnotify.so.1", RTLD_LAZY); |
|
292 if (!handle) |
|
293 return; |
|
294 } |
|
295 |
|
296 notify_init_t nn_init = (notify_init_t)(uintptr_t)dlsym(handle, "notify_init"); |
|
297 notify_notification_new_t nn_new = (notify_notification_new_t)(uintptr_t)dlsym(handle, "notify_notification_new"); |
|
298 notify_notification_show_t nn_show = (notify_notification_show_t)(uintptr_t)dlsym(handle, "notify_notification_show"); |
|
299 if (!nn_init || !nn_new || !nn_show) { |
|
300 dlclose(handle); |
|
301 return; |
|
302 } |
|
303 |
|
304 nn_init(appName); |
|
305 |
|
306 void* n = nn_new(uninstallMsg, nullptr, "dialog-information", nullptr); |
|
307 |
|
308 nn_show(n, nullptr); |
|
309 |
|
310 dlclose(handle); |
|
311 } |
|
312 } |
|
313 |
|
314 int main(int argc, char *argv[]) |
|
315 { |
|
316 pargc = &argc; |
|
317 pargv = &argv; |
|
318 |
|
319 // Get current executable path |
|
320 char curExePath[MAXPATHLEN]; |
|
321 if (NS_FAILED(mozilla::BinaryPath::Get(argv[0], curExePath))) { |
|
322 ErrorDialog("Couldn't read current executable path"); |
|
323 return 255; |
|
324 } |
|
325 char curExeDir[MAXPATHLEN]; |
|
326 GetDirFromPath(curExeDir, curExePath); |
|
327 |
|
328 bool removeApp = false; |
|
329 for (int i = 1; i < argc; i++) { |
|
330 if (!strcmp(argv[i], "-profile")) { |
|
331 isProfileOverridden = true; |
|
332 } |
|
333 else if (!strcmp(argv[i], "-remove")) { |
|
334 removeApp = true; |
|
335 } |
|
336 } |
|
337 |
|
338 char firefoxDir[MAXPATHLEN]; |
|
339 |
|
340 // Check if Firefox is in the same directory as the webapp runtime. |
|
341 // This is the case for webapprt chrome and content tests. |
|
342 if (GRELoadAndLaunch(curExeDir, true)) { |
|
343 return 0; |
|
344 } |
|
345 |
|
346 // Set up webAppIniPath with path to webapp.ini |
|
347 char webAppIniPath[MAXPATHLEN]; |
|
348 snprintf(webAppIniPath, MAXPATHLEN, "%s/%s", curExeDir, kWEBAPP_INI); |
|
349 |
|
350 // Open webapp.ini as an INI file |
|
351 nsINIParser parser; |
|
352 if (NS_FAILED(parser.Init(webAppIniPath))) { |
|
353 ErrorDialog("Couldn't open webapp.ini"); |
|
354 return 255; |
|
355 } |
|
356 |
|
357 // Set up our environment to know where webapp.ini was loaded from |
|
358 if (setenv(kAPP_ENV_VAR, webAppIniPath, 1) == -1) { |
|
359 ErrorDialog("Couldn't set up app environment"); |
|
360 return 255; |
|
361 } |
|
362 |
|
363 // Get profile dir from webapp.ini |
|
364 if (NS_FAILED(parser.GetString("Webapp", "Profile", profile, MAXPATHLEN))) { |
|
365 ErrorDialog("Couldn't retrieve profile from web app INI file"); |
|
366 return 255; |
|
367 } |
|
368 |
|
369 if (removeApp) { |
|
370 RemoveApplication(parser, curExeDir, profile); |
|
371 return 0; |
|
372 } |
|
373 |
|
374 // Get the location of Firefox from our webapp.ini |
|
375 if (NS_FAILED(parser.GetString("WebappRT", "InstallDir", firefoxDir, MAXPATHLEN))) { |
|
376 ErrorDialog("Couldn't find your Firefox install directory."); |
|
377 return 255; |
|
378 } |
|
379 |
|
380 // Set up appIniPath with path to application.ini. |
|
381 // This is in the Firefox installation directory. |
|
382 char appIniPath[MAXPATHLEN]; |
|
383 snprintf(appIniPath, MAXPATHLEN, "%s/%s", firefoxDir, kAPP_INI); |
|
384 |
|
385 if (NS_FAILED(parser.Init(appIniPath))) { |
|
386 ErrorDialog("This app requires that Firefox version 16 or above is installed." |
|
387 " Firefox 16+ has not been detected."); |
|
388 return 255; |
|
389 } |
|
390 |
|
391 // Get buildid of Firefox we're trying to load (MAXPATHLEN only for convenience) |
|
392 char buildid[MAXPATHLEN]; |
|
393 if (NS_FAILED(parser.GetString("App", "BuildID", buildid, MAXPATHLEN))) { |
|
394 ErrorDialog("Couldn't read BuildID from Firefox application.ini"); |
|
395 return 255; |
|
396 } |
|
397 |
|
398 // If WebAppRT version == Firefox version, load XUL and execute the application |
|
399 if (!strcmp(buildid, NS_STRINGIFY(GRE_BUILDID))) { |
|
400 if (GRELoadAndLaunch(firefoxDir, false)) |
|
401 return 0; |
|
402 } |
|
403 // Else, copy WebAppRT from Firefox installation and re-execute the process |
|
404 else |
|
405 CopyAndRelaunch(firefoxDir, curExePath); |
|
406 |
|
407 return 255; |
|
408 } |