1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3  * License, v. 2.0. If a copy of the MPL was not distributed with this
4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 
6 #include "mozilla/ArrayUtils.h"
7 
8 #include "nsCOMPtr.h"
9 #include "nsGNOMEShellService.h"
10 #include "nsShellService.h"
11 #include "nsIServiceManager.h"
12 #include "nsIFile.h"
13 #include "nsIProperties.h"
14 #include "nsDirectoryServiceDefs.h"
15 #include "nsIPrefService.h"
16 #include "prenv.h"
17 #include "nsString.h"
18 #include "nsIGConfService.h"
19 #include "nsIGIOService.h"
20 #include "nsIGSettingsService.h"
21 #include "nsIStringBundle.h"
22 #include "nsIOutputStream.h"
23 #include "nsIProcess.h"
24 #include "nsServiceManagerUtils.h"
25 #include "nsComponentManagerUtils.h"
26 #include "nsIDOMElement.h"
27 #include "nsIImageLoadingContent.h"
28 #include "imgIRequest.h"
29 #include "imgIContainer.h"
30 #include "mozilla/Sprintf.h"
31 #if defined(MOZ_WIDGET_GTK)
32 #include "nsIImageToPixbuf.h"
33 #endif
34 #include "nsXULAppAPI.h"
35 #include "gfxPlatform.h"
36 
37 #include <glib.h>
38 #include <glib-object.h>
39 #include <gtk/gtk.h>
40 #include <gdk/gdk.h>
41 #include <gdk-pixbuf/gdk-pixbuf.h>
42 #include <limits.h>
43 #include <stdlib.h>
44 
45 using namespace mozilla;
46 
47 struct ProtocolAssociation {
48   const char *name;
49   bool essential;
50 };
51 
52 struct MimeTypeAssociation {
53   const char *mimeType;
54   const char *extensions;
55 };
56 
57 static const ProtocolAssociation appProtocols[] = {
58     // clang-format off
59   { "http",   true     },
60   { "https",  true     },
61   { "ftp",    false },
62   { "chrome", false }
63     // clang-format on
64 };
65 
66 static const MimeTypeAssociation appTypes[] = {
67     // clang-format off
68   { "text/html",             "htm html shtml" },
69   { "application/xhtml+xml", "xhtml xht"      }
70     // clang-format on
71 };
72 
73 // GConf registry key constants
74 #define DG_BACKGROUND "/desktop/gnome/background"
75 
76 #define kDesktopImageKey DG_BACKGROUND "/picture_filename"
77 #define kDesktopOptionsKey DG_BACKGROUND "/picture_options"
78 #define kDesktopDrawBGKey DG_BACKGROUND "/draw_background"
79 #define kDesktopColorKey DG_BACKGROUND "/primary_color"
80 
81 #define kDesktopBGSchema "org.gnome.desktop.background"
82 #define kDesktopImageGSKey "picture-uri"
83 #define kDesktopOptionGSKey "picture-options"
84 #define kDesktopDrawBGGSKey "draw-background"
85 #define kDesktopColorGSKey "primary-color"
86 
IsRunningAsASnap()87 static bool IsRunningAsASnap() { return (PR_GetEnv("SNAP") != nullptr); }
88 
Init()89 nsresult nsGNOMEShellService::Init() {
90   nsresult rv;
91 
92   if (gfxPlatform::IsHeadless()) {
93     return NS_ERROR_NOT_AVAILABLE;
94   }
95 
96   // GConf, GSettings or GIO _must_ be available, or we do not allow
97   // CreateInstance to succeed.
98 
99   nsCOMPtr<nsIGConfService> gconf = do_GetService(NS_GCONFSERVICE_CONTRACTID);
100   nsCOMPtr<nsIGIOService> giovfs = do_GetService(NS_GIOSERVICE_CONTRACTID);
101   nsCOMPtr<nsIGSettingsService> gsettings =
102       do_GetService(NS_GSETTINGSSERVICE_CONTRACTID);
103 
104   if (!gconf && !giovfs && !gsettings) return NS_ERROR_NOT_AVAILABLE;
105 
106   // Check G_BROKEN_FILENAMES.  If it's set, then filenames in glib use
107   // the locale encoding.  If it's not set, they use UTF-8.
108   mUseLocaleFilenames = PR_GetEnv("G_BROKEN_FILENAMES") != nullptr;
109 
110   if (GetAppPathFromLauncher()) return NS_OK;
111 
112   nsCOMPtr<nsIProperties> dirSvc(
113       do_GetService("@mozilla.org/file/directory_service;1"));
114   NS_ENSURE_TRUE(dirSvc, NS_ERROR_NOT_AVAILABLE);
115 
116   nsCOMPtr<nsIFile> appPath;
117   rv = dirSvc->Get(XRE_EXECUTABLE_FILE, NS_GET_IID(nsIFile),
118                    getter_AddRefs(appPath));
119   NS_ENSURE_SUCCESS(rv, rv);
120 
121   return appPath->GetNativePath(mAppPath);
122 }
123 
NS_IMPL_ISUPPORTS(nsGNOMEShellService,nsIGNOMEShellService,nsIShellService)124 NS_IMPL_ISUPPORTS(nsGNOMEShellService, nsIGNOMEShellService, nsIShellService)
125 
126 bool nsGNOMEShellService::GetAppPathFromLauncher() {
127   gchar *tmp;
128 
129   const char *launcher = PR_GetEnv("MOZ_APP_LAUNCHER");
130   if (!launcher) return false;
131 
132   if (g_path_is_absolute(launcher)) {
133     mAppPath = launcher;
134     tmp = g_path_get_basename(launcher);
135     gchar *fullpath = g_find_program_in_path(tmp);
136     if (fullpath && mAppPath.Equals(fullpath)) mAppIsInPath = true;
137     g_free(fullpath);
138   } else {
139     tmp = g_find_program_in_path(launcher);
140     if (!tmp) return false;
141     mAppPath = tmp;
142     mAppIsInPath = true;
143   }
144 
145   g_free(tmp);
146   return true;
147 }
148 
KeyMatchesAppName(const char * aKeyValue) const149 bool nsGNOMEShellService::KeyMatchesAppName(const char *aKeyValue) const {
150   gchar *commandPath;
151   if (mUseLocaleFilenames) {
152     gchar *nativePath =
153         g_filename_from_utf8(aKeyValue, -1, nullptr, nullptr, nullptr);
154     if (!nativePath) {
155       NS_ERROR("Error converting path to filesystem encoding");
156       return false;
157     }
158 
159     commandPath = g_find_program_in_path(nativePath);
160     g_free(nativePath);
161   } else {
162     commandPath = g_find_program_in_path(aKeyValue);
163   }
164 
165   if (!commandPath) return false;
166 
167   bool matches = mAppPath.Equals(commandPath);
168   g_free(commandPath);
169   return matches;
170 }
171 
CheckHandlerMatchesAppName(const nsACString & handler) const172 bool nsGNOMEShellService::CheckHandlerMatchesAppName(
173     const nsACString &handler) const {
174   gint argc;
175   gchar **argv;
176   nsAutoCString command(handler);
177 
178   // The string will be something of the form: [/path/to/]browser "%s"
179   // We want to remove all of the parameters and get just the binary name.
180 
181   if (g_shell_parse_argv(command.get(), &argc, &argv, nullptr) && argc > 0) {
182     command.Assign(argv[0]);
183     g_strfreev(argv);
184   }
185 
186   if (!KeyMatchesAppName(command.get()))
187     return false;  // the handler is set to another app
188 
189   return true;
190 }
191 
192 NS_IMETHODIMP
IsDefaultBrowser(bool aStartupCheck,bool aForAllTypes,bool * aIsDefaultBrowser)193 nsGNOMEShellService::IsDefaultBrowser(bool aStartupCheck, bool aForAllTypes,
194                                       bool *aIsDefaultBrowser) {
195   *aIsDefaultBrowser = false;
196 
197   if (IsRunningAsASnap()) {
198     const gchar *argv[] = {"xdg-settings", "check", "default-web-browser",
199                            "firefox.desktop", nullptr};
200     GSpawnFlags flags = static_cast<GSpawnFlags>(G_SPAWN_SEARCH_PATH |
201                                                  G_SPAWN_STDERR_TO_DEV_NULL);
202     gchar *output = nullptr;
203     gint exit_status = 0;
204     if (!g_spawn_sync(nullptr, (gchar **)argv, nullptr, flags, nullptr, nullptr,
205                       &output, nullptr, &exit_status, nullptr)) {
206       return NS_OK;
207     }
208     if (exit_status != 0) {
209       g_free(output);
210       return NS_OK;
211     }
212     if (strcmp(output, "yes\n") == 0) {
213       *aIsDefaultBrowser = true;
214     }
215     g_free(output);
216     return NS_OK;
217   }
218 
219   nsCOMPtr<nsIGConfService> gconf = do_GetService(NS_GCONFSERVICE_CONTRACTID);
220   nsCOMPtr<nsIGIOService> giovfs = do_GetService(NS_GIOSERVICE_CONTRACTID);
221 
222   bool enabled;
223   nsAutoCString handler;
224   nsCOMPtr<nsIGIOMimeApp> gioApp;
225 
226   for (unsigned int i = 0; i < ArrayLength(appProtocols); ++i) {
227     if (!appProtocols[i].essential) continue;
228 
229     if (gconf) {
230       handler.Truncate();
231       gconf->GetAppForProtocol(nsDependentCString(appProtocols[i].name),
232                                &enabled, handler);
233 
234       if (!CheckHandlerMatchesAppName(handler) || !enabled)
235         return NS_OK;  // the handler is disabled or set to another app
236     }
237 
238     if (giovfs) {
239       handler.Truncate();
240       nsCOMPtr<nsIHandlerApp> handlerApp;
241       giovfs->GetAppForURIScheme(nsDependentCString(appProtocols[i].name),
242                                  getter_AddRefs(handlerApp));
243       gioApp = do_QueryInterface(handlerApp);
244       if (!gioApp) return NS_OK;
245 
246       gioApp->GetCommand(handler);
247 
248       if (!CheckHandlerMatchesAppName(handler))
249         return NS_OK;  // the handler is set to another app
250     }
251   }
252 
253   *aIsDefaultBrowser = true;
254 
255   return NS_OK;
256 }
257 
258 NS_IMETHODIMP
SetDefaultBrowser(bool aClaimAllTypes,bool aForAllUsers)259 nsGNOMEShellService::SetDefaultBrowser(bool aClaimAllTypes, bool aForAllUsers) {
260 #ifdef DEBUG
261   if (aForAllUsers)
262     NS_WARNING(
263         "Setting the default browser for all users is not yet supported");
264 #endif
265 
266   if (IsRunningAsASnap()) {
267     const gchar *argv[] = {"xdg-settings", "set", "default-web-browser",
268                            "firefox.desktop", nullptr};
269     GSpawnFlags flags = static_cast<GSpawnFlags>(G_SPAWN_SEARCH_PATH |
270                                                  G_SPAWN_STDOUT_TO_DEV_NULL |
271                                                  G_SPAWN_STDERR_TO_DEV_NULL);
272     g_spawn_sync(nullptr, (gchar **)argv, nullptr, flags, nullptr, nullptr,
273                  nullptr, nullptr, nullptr, nullptr);
274     return NS_OK;
275   }
276 
277   nsCOMPtr<nsIGConfService> gconf = do_GetService(NS_GCONFSERVICE_CONTRACTID);
278   nsCOMPtr<nsIGIOService> giovfs = do_GetService(NS_GIOSERVICE_CONTRACTID);
279   if (gconf) {
280     nsAutoCString appKeyValue;
281     if (mAppIsInPath) {
282       // mAppPath is in the users path, so use only the basename as the launcher
283       gchar *tmp = g_path_get_basename(mAppPath.get());
284       appKeyValue = tmp;
285       g_free(tmp);
286     } else {
287       appKeyValue = mAppPath;
288     }
289 
290     appKeyValue.AppendLiteral(" %s");
291 
292     for (unsigned int i = 0; i < ArrayLength(appProtocols); ++i) {
293       if (appProtocols[i].essential || aClaimAllTypes) {
294         gconf->SetAppForProtocol(nsDependentCString(appProtocols[i].name),
295                                  appKeyValue);
296       }
297     }
298   }
299 
300   if (giovfs) {
301     nsresult rv;
302     nsCOMPtr<nsIStringBundleService> bundleService =
303         do_GetService(NS_STRINGBUNDLE_CONTRACTID, &rv);
304     NS_ENSURE_SUCCESS(rv, rv);
305 
306     nsCOMPtr<nsIStringBundle> brandBundle;
307     rv = bundleService->CreateBundle(BRAND_PROPERTIES,
308                                      getter_AddRefs(brandBundle));
309     NS_ENSURE_SUCCESS(rv, rv);
310 
311     nsAutoString brandShortName;
312     brandBundle->GetStringFromName("brandShortName", brandShortName);
313 
314     // use brandShortName as the application id.
315     NS_ConvertUTF16toUTF8 id(brandShortName);
316     nsCOMPtr<nsIGIOMimeApp> appInfo;
317     rv = giovfs->FindAppFromCommand(mAppPath, getter_AddRefs(appInfo));
318     if (NS_FAILED(rv)) {
319       // Application was not found in the list of installed applications
320       // provided by OS. Fallback to create appInfo from command and name.
321       rv = giovfs->CreateAppFromCommand(mAppPath, id, getter_AddRefs(appInfo));
322       NS_ENSURE_SUCCESS(rv, rv);
323     }
324 
325     // set handler for the protocols
326     for (unsigned int i = 0; i < ArrayLength(appProtocols); ++i) {
327       if (appProtocols[i].essential || aClaimAllTypes) {
328         appInfo->SetAsDefaultForURIScheme(
329             nsDependentCString(appProtocols[i].name));
330       }
331     }
332 
333     // set handler for .html and xhtml files and MIME types:
334     if (aClaimAllTypes) {
335       // Add mime types for html, xhtml extension and set app to just created
336       // appinfo.
337       for (unsigned int i = 0; i < ArrayLength(appTypes); ++i) {
338         appInfo->SetAsDefaultForMimeType(
339             nsDependentCString(appTypes[i].mimeType));
340         appInfo->SetAsDefaultForFileExtensions(
341             nsDependentCString(appTypes[i].extensions));
342       }
343     }
344   }
345 
346   nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID));
347   if (prefs) {
348     (void)prefs->SetBoolPref(PREF_CHECKDEFAULTBROWSER, true);
349     // Reset the number of times the dialog should be shown
350     // before it is silenced.
351     (void)prefs->SetIntPref(PREF_DEFAULTBROWSERCHECKCOUNT, 0);
352   }
353 
354   return NS_OK;
355 }
356 
357 NS_IMETHODIMP
GetCanSetDesktopBackground(bool * aResult)358 nsGNOMEShellService::GetCanSetDesktopBackground(bool *aResult) {
359   // setting desktop background is currently only supported
360   // for Gnome or desktops using the same GSettings and GConf keys
361   const char *gnomeSession = getenv("GNOME_DESKTOP_SESSION_ID");
362   if (gnomeSession) {
363     *aResult = true;
364   } else {
365     *aResult = false;
366   }
367 
368   return NS_OK;
369 }
370 
WriteImage(const nsCString & aPath,imgIContainer * aImage)371 static nsresult WriteImage(const nsCString &aPath, imgIContainer *aImage) {
372 #if !defined(MOZ_WIDGET_GTK)
373   return NS_ERROR_NOT_AVAILABLE;
374 #else
375   nsCOMPtr<nsIImageToPixbuf> imgToPixbuf =
376       do_GetService("@mozilla.org/widget/image-to-gdk-pixbuf;1");
377   if (!imgToPixbuf) return NS_ERROR_NOT_AVAILABLE;
378 
379   GdkPixbuf *pixbuf = imgToPixbuf->ConvertImageToPixbuf(aImage);
380   if (!pixbuf) return NS_ERROR_NOT_AVAILABLE;
381 
382   gboolean res = gdk_pixbuf_save(pixbuf, aPath.get(), "png", nullptr, nullptr);
383 
384   g_object_unref(pixbuf);
385   return res ? NS_OK : NS_ERROR_FAILURE;
386 #endif
387 }
388 
389 NS_IMETHODIMP
SetDesktopBackground(nsIDOMElement * aElement,int32_t aPosition,const nsACString & aImageName)390 nsGNOMEShellService::SetDesktopBackground(nsIDOMElement *aElement,
391                                           int32_t aPosition,
392                                           const nsACString &aImageName) {
393   nsresult rv;
394   nsCOMPtr<nsIImageLoadingContent> imageContent =
395       do_QueryInterface(aElement, &rv);
396   if (!imageContent) return rv;
397 
398   // get the image container
399   nsCOMPtr<imgIRequest> request;
400   rv = imageContent->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST,
401                                 getter_AddRefs(request));
402   if (!request) return rv;
403   nsCOMPtr<imgIContainer> container;
404   rv = request->GetImage(getter_AddRefs(container));
405   if (!container) return rv;
406 
407   // Set desktop wallpaper filling style
408   nsAutoCString options;
409   if (aPosition == BACKGROUND_TILE)
410     options.AssignLiteral("wallpaper");
411   else if (aPosition == BACKGROUND_STRETCH)
412     options.AssignLiteral("stretched");
413   else if (aPosition == BACKGROUND_FILL)
414     options.AssignLiteral("zoom");
415   else if (aPosition == BACKGROUND_FIT)
416     options.AssignLiteral("scaled");
417   else
418     options.AssignLiteral("centered");
419 
420   // Write the background file to the home directory.
421   nsAutoCString filePath(PR_GetEnv("HOME"));
422 
423   // get the product brand name from localized strings
424   nsAutoString brandName;
425   nsCID bundleCID = NS_STRINGBUNDLESERVICE_CID;
426   nsCOMPtr<nsIStringBundleService> bundleService(do_GetService(bundleCID));
427   if (bundleService) {
428     nsCOMPtr<nsIStringBundle> brandBundle;
429     rv = bundleService->CreateBundle(BRAND_PROPERTIES,
430                                      getter_AddRefs(brandBundle));
431     if (NS_SUCCEEDED(rv) && brandBundle) {
432       rv = brandBundle->GetStringFromName("brandShortName", brandName);
433       NS_ENSURE_SUCCESS(rv, rv);
434     }
435   }
436 
437   // build the file name
438   filePath.Append('/');
439   filePath.Append(NS_ConvertUTF16toUTF8(brandName));
440   filePath.AppendLiteral("_wallpaper.png");
441 
442   // write the image to a file in the home dir
443   rv = WriteImage(filePath, container);
444   NS_ENSURE_SUCCESS(rv, rv);
445 
446   // Try GSettings first. If we don't have GSettings or the right schema, fall
447   // back to using GConf instead. Note that if GSettings works ok, the changes
448   // get mirrored to GConf by the gsettings->gconf bridge in
449   // gnome-settings-daemon
450   nsCOMPtr<nsIGSettingsService> gsettings =
451       do_GetService(NS_GSETTINGSSERVICE_CONTRACTID);
452   if (gsettings) {
453     nsCOMPtr<nsIGSettingsCollection> background_settings;
454     gsettings->GetCollectionForSchema(NS_LITERAL_CSTRING(kDesktopBGSchema),
455                                       getter_AddRefs(background_settings));
456     if (background_settings) {
457       gchar *file_uri = g_filename_to_uri(filePath.get(), nullptr, nullptr);
458       if (!file_uri) return NS_ERROR_FAILURE;
459 
460       background_settings->SetString(NS_LITERAL_CSTRING(kDesktopOptionGSKey),
461                                      options);
462 
463       background_settings->SetString(NS_LITERAL_CSTRING(kDesktopImageGSKey),
464                                      nsDependentCString(file_uri));
465       g_free(file_uri);
466       background_settings->SetBoolean(NS_LITERAL_CSTRING(kDesktopDrawBGGSKey),
467                                       true);
468       return rv;
469     }
470   }
471 
472   // if the file was written successfully, set it as the system wallpaper
473   nsCOMPtr<nsIGConfService> gconf = do_GetService(NS_GCONFSERVICE_CONTRACTID);
474 
475   if (gconf) {
476     gconf->SetString(NS_LITERAL_CSTRING(kDesktopOptionsKey), options);
477 
478     // Set the image to an empty string first to force a refresh
479     // (since we could be writing a new image on top of an existing
480     // Firefox_wallpaper.png and nautilus doesn't monitor the file for changes)
481     gconf->SetString(NS_LITERAL_CSTRING(kDesktopImageKey), EmptyCString());
482 
483     gconf->SetString(NS_LITERAL_CSTRING(kDesktopImageKey), filePath);
484     gconf->SetBool(NS_LITERAL_CSTRING(kDesktopDrawBGKey), true);
485   }
486 
487   return rv;
488 }
489 
490 #define COLOR_16_TO_8_BIT(_c) ((_c) >> 8)
491 #define COLOR_8_TO_16_BIT(_c) ((_c) << 8 | (_c))
492 
493 NS_IMETHODIMP
GetDesktopBackgroundColor(uint32_t * aColor)494 nsGNOMEShellService::GetDesktopBackgroundColor(uint32_t *aColor) {
495   nsCOMPtr<nsIGSettingsService> gsettings =
496       do_GetService(NS_GSETTINGSSERVICE_CONTRACTID);
497   nsCOMPtr<nsIGSettingsCollection> background_settings;
498   nsAutoCString background;
499 
500   if (gsettings) {
501     gsettings->GetCollectionForSchema(NS_LITERAL_CSTRING(kDesktopBGSchema),
502                                       getter_AddRefs(background_settings));
503     if (background_settings) {
504       background_settings->GetString(NS_LITERAL_CSTRING(kDesktopColorGSKey),
505                                      background);
506     }
507   }
508 
509   if (!background_settings) {
510     nsCOMPtr<nsIGConfService> gconf = do_GetService(NS_GCONFSERVICE_CONTRACTID);
511     if (gconf)
512       gconf->GetString(NS_LITERAL_CSTRING(kDesktopColorKey), background);
513   }
514 
515   if (background.IsEmpty()) {
516     *aColor = 0;
517     return NS_OK;
518   }
519 
520   GdkColor color;
521   gboolean success = gdk_color_parse(background.get(), &color);
522 
523   NS_ENSURE_TRUE(success, NS_ERROR_FAILURE);
524 
525   *aColor = COLOR_16_TO_8_BIT(color.red) << 16 |
526             COLOR_16_TO_8_BIT(color.green) << 8 | COLOR_16_TO_8_BIT(color.blue);
527   return NS_OK;
528 }
529 
ColorToCString(uint32_t aColor,nsCString & aResult)530 static void ColorToCString(uint32_t aColor, nsCString &aResult) {
531   // The #rrrrggggbbbb format is used to match gdk_color_to_string()
532   aResult.SetLength(13);
533   char *buf = aResult.BeginWriting();
534   if (!buf) return;
535 
536   uint16_t red = COLOR_8_TO_16_BIT((aColor >> 16) & 0xff);
537   uint16_t green = COLOR_8_TO_16_BIT((aColor >> 8) & 0xff);
538   uint16_t blue = COLOR_8_TO_16_BIT(aColor & 0xff);
539 
540   snprintf(buf, 14, "#%04x%04x%04x", red, green, blue);
541 }
542 
543 NS_IMETHODIMP
SetDesktopBackgroundColor(uint32_t aColor)544 nsGNOMEShellService::SetDesktopBackgroundColor(uint32_t aColor) {
545   NS_ASSERTION(aColor <= 0xffffff, "aColor has extra bits");
546   nsAutoCString colorString;
547   ColorToCString(aColor, colorString);
548 
549   nsCOMPtr<nsIGSettingsService> gsettings =
550       do_GetService(NS_GSETTINGSSERVICE_CONTRACTID);
551   if (gsettings) {
552     nsCOMPtr<nsIGSettingsCollection> background_settings;
553     gsettings->GetCollectionForSchema(NS_LITERAL_CSTRING(kDesktopBGSchema),
554                                       getter_AddRefs(background_settings));
555     if (background_settings) {
556       background_settings->SetString(NS_LITERAL_CSTRING(kDesktopColorGSKey),
557                                      colorString);
558       return NS_OK;
559     }
560   }
561 
562   nsCOMPtr<nsIGConfService> gconf = do_GetService(NS_GCONFSERVICE_CONTRACTID);
563 
564   if (gconf) {
565     gconf->SetString(NS_LITERAL_CSTRING(kDesktopColorKey), colorString);
566   }
567 
568   return NS_OK;
569 }
570 
571 NS_IMETHODIMP
OpenApplication(int32_t aApplication)572 nsGNOMEShellService::OpenApplication(int32_t aApplication) {
573   nsAutoCString scheme;
574   if (aApplication == APPLICATION_MAIL)
575     scheme.AssignLiteral("mailto");
576   else if (aApplication == APPLICATION_NEWS)
577     scheme.AssignLiteral("news");
578   else
579     return NS_ERROR_NOT_AVAILABLE;
580 
581   nsCOMPtr<nsIGIOService> giovfs = do_GetService(NS_GIOSERVICE_CONTRACTID);
582   if (giovfs) {
583     nsCOMPtr<nsIHandlerApp> handlerApp;
584     giovfs->GetAppForURIScheme(scheme, getter_AddRefs(handlerApp));
585     if (handlerApp) return handlerApp->LaunchWithURI(nullptr, nullptr);
586   }
587 
588   nsCOMPtr<nsIGConfService> gconf = do_GetService(NS_GCONFSERVICE_CONTRACTID);
589   if (!gconf) return NS_ERROR_FAILURE;
590 
591   bool enabled;
592   nsAutoCString appCommand;
593   gconf->GetAppForProtocol(scheme, &enabled, appCommand);
594 
595   if (!enabled) return NS_ERROR_FAILURE;
596 
597   // XXX we don't currently handle launching a terminal window.
598   // If the handler requires a terminal, bail.
599   bool requiresTerminal;
600   gconf->HandlerRequiresTerminal(scheme, &requiresTerminal);
601   if (requiresTerminal) return NS_ERROR_FAILURE;
602 
603   // Perform shell argument expansion
604   int argc;
605   char **argv;
606   if (!g_shell_parse_argv(appCommand.get(), &argc, &argv, nullptr))
607     return NS_ERROR_FAILURE;
608 
609   char **newArgv = new char *[argc + 1];
610   int newArgc = 0;
611 
612   // Run through the list of arguments.  Copy all of them to the new
613   // argv except for %s, which we skip.
614   for (int i = 0; i < argc; ++i) {
615     if (strcmp(argv[i], "%s") != 0) newArgv[newArgc++] = argv[i];
616   }
617 
618   newArgv[newArgc] = nullptr;
619 
620   gboolean err = g_spawn_async(nullptr, newArgv, nullptr, G_SPAWN_SEARCH_PATH,
621                                nullptr, nullptr, nullptr, nullptr);
622 
623   g_strfreev(argv);
624   delete[] newArgv;
625 
626   return err ? NS_OK : NS_ERROR_FAILURE;
627 }
628 
629 NS_IMETHODIMP
OpenApplicationWithURI(nsIFile * aApplication,const nsACString & aURI)630 nsGNOMEShellService::OpenApplicationWithURI(nsIFile *aApplication,
631                                             const nsACString &aURI) {
632   nsresult rv;
633   nsCOMPtr<nsIProcess> process =
634       do_CreateInstance("@mozilla.org/process/util;1", &rv);
635   if (NS_FAILED(rv)) return rv;
636 
637   rv = process->Init(aApplication);
638   if (NS_FAILED(rv)) return rv;
639 
640   const nsCString spec(aURI);
641   const char *specStr = spec.get();
642   return process->Run(false, &specStr, 1);
643 }
644 
645 NS_IMETHODIMP
GetDefaultFeedReader(nsIFile ** _retval)646 nsGNOMEShellService::GetDefaultFeedReader(nsIFile **_retval) {
647   return NS_ERROR_NOT_IMPLEMENTED;
648 }
649