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 "nsIFile.h"
12 #include "nsIProperties.h"
13 #include "nsDirectoryServiceDefs.h"
14 #include "prenv.h"
15 #include "nsString.h"
16 #include "nsIGIOService.h"
17 #include "nsIGSettingsService.h"
18 #include "nsIStringBundle.h"
19 #include "nsServiceManagerUtils.h"
20 #include "nsComponentManagerUtils.h"
21 #include "nsIImageLoadingContent.h"
22 #include "imgIRequest.h"
23 #include "imgIContainer.h"
24 #include "mozilla/Sprintf.h"
25 #include "mozilla/dom/Element.h"
26 #if defined(MOZ_WIDGET_GTK)
27 #  include "nsIImageToPixbuf.h"
28 #endif
29 #include "nsXULAppAPI.h"
30 #include "gfxPlatform.h"
31 
32 #include <glib.h>
33 #include <glib-object.h>
34 #include <gtk/gtk.h>
35 #include <gdk/gdk.h>
36 #include <gdk-pixbuf/gdk-pixbuf.h>
37 #include <limits.h>
38 #include <stdlib.h>
39 
40 using namespace mozilla;
41 
42 struct ProtocolAssociation {
43   const char* name;
44   bool essential;
45 };
46 
47 struct MimeTypeAssociation {
48   const char* mimeType;
49   const char* extensions;
50 };
51 
52 static const ProtocolAssociation appProtocols[] = {
53     // clang-format off
54   { "http",   true     },
55   { "https",  true     },
56   { "ftp",    false },
57   { "chrome", false }
58     // clang-format on
59 };
60 
61 static const MimeTypeAssociation appTypes[] = {
62     // clang-format off
63   { "text/html",             "htm html shtml" },
64   { "application/xhtml+xml", "xhtml xht"      }
65     // clang-format on
66 };
67 
68 #define kDesktopBGSchema "org.gnome.desktop.background"
69 #define kDesktopImageGSKey "picture-uri"
70 #define kDesktopOptionGSKey "picture-options"
71 #define kDesktopDrawBGGSKey "draw-background"
72 #define kDesktopColorGSKey "primary-color"
73 
IsRunningAsASnap()74 static bool IsRunningAsASnap() {
75   // SNAP holds the path to the snap, use SNAP_NAME
76   // which is easier to parse.
77   const char* snap_name = PR_GetEnv("SNAP_NAME");
78 
79   // return early if not set.
80   if (snap_name == nullptr) {
81     return false;
82   }
83 
84   // snap_name as defined on https://snapcraft.io/firefox
85   return (strcmp(snap_name, "firefox") == 0);
86 }
87 
Init()88 nsresult nsGNOMEShellService::Init() {
89   nsresult rv;
90 
91   if (gfxPlatform::IsHeadless()) {
92     return NS_ERROR_NOT_AVAILABLE;
93   }
94 
95   // GSettings or GIO _must_ be available, or we do not allow
96   // CreateInstance to succeed.
97 
98   nsCOMPtr<nsIGIOService> giovfs = do_GetService(NS_GIOSERVICE_CONTRACTID);
99   nsCOMPtr<nsIGSettingsService> gsettings =
100       do_GetService(NS_GSETTINGSSERVICE_CONTRACTID);
101 
102   if (!giovfs && !gsettings) return NS_ERROR_NOT_AVAILABLE;
103 
104 #ifdef MOZ_ENABLE_DBUS
105   const char* currentDesktop = getenv("XDG_CURRENT_DESKTOP");
106   if (currentDesktop && strstr(currentDesktop, "GNOME") != nullptr &&
107       Preferences::GetBool("browser.gnome-search-provider.enabled", false)) {
108     mSearchProvider.Startup();
109   }
110 #endif
111 
112   // Check G_BROKEN_FILENAMES.  If it's set, then filenames in glib use
113   // the locale encoding.  If it's not set, they use UTF-8.
114   mUseLocaleFilenames = PR_GetEnv("G_BROKEN_FILENAMES") != nullptr;
115 
116   if (GetAppPathFromLauncher()) return NS_OK;
117 
118   nsCOMPtr<nsIProperties> dirSvc(
119       do_GetService("@mozilla.org/file/directory_service;1"));
120   NS_ENSURE_TRUE(dirSvc, NS_ERROR_NOT_AVAILABLE);
121 
122   nsCOMPtr<nsIFile> appPath;
123   rv = dirSvc->Get(XRE_EXECUTABLE_FILE, NS_GET_IID(nsIFile),
124                    getter_AddRefs(appPath));
125   NS_ENSURE_SUCCESS(rv, rv);
126 
127   return appPath->GetNativePath(mAppPath);
128 }
129 
NS_IMPL_ISUPPORTS(nsGNOMEShellService,nsIGNOMEShellService,nsIShellService,nsIToolkitShellService)130 NS_IMPL_ISUPPORTS(nsGNOMEShellService, nsIGNOMEShellService, nsIShellService,
131                   nsIToolkitShellService)
132 
133 bool nsGNOMEShellService::GetAppPathFromLauncher() {
134   gchar* tmp;
135 
136   const char* launcher = PR_GetEnv("MOZ_APP_LAUNCHER");
137   if (!launcher) return false;
138 
139   if (g_path_is_absolute(launcher)) {
140     mAppPath = launcher;
141     tmp = g_path_get_basename(launcher);
142     gchar* fullpath = g_find_program_in_path(tmp);
143     if (fullpath && mAppPath.Equals(fullpath)) mAppIsInPath = true;
144     g_free(fullpath);
145   } else {
146     tmp = g_find_program_in_path(launcher);
147     if (!tmp) return false;
148     mAppPath = tmp;
149     mAppIsInPath = true;
150   }
151 
152   g_free(tmp);
153   return true;
154 }
155 
KeyMatchesAppName(const char * aKeyValue) const156 bool nsGNOMEShellService::KeyMatchesAppName(const char* aKeyValue) const {
157   gchar* commandPath;
158   if (mUseLocaleFilenames) {
159     gchar* nativePath =
160         g_filename_from_utf8(aKeyValue, -1, nullptr, nullptr, nullptr);
161     if (!nativePath) {
162       NS_ERROR("Error converting path to filesystem encoding");
163       return false;
164     }
165 
166     commandPath = g_find_program_in_path(nativePath);
167     g_free(nativePath);
168   } else {
169     commandPath = g_find_program_in_path(aKeyValue);
170   }
171 
172   if (!commandPath) return false;
173 
174   bool matches = mAppPath.Equals(commandPath);
175   g_free(commandPath);
176   return matches;
177 }
178 
CheckHandlerMatchesAppName(const nsACString & handler) const179 bool nsGNOMEShellService::CheckHandlerMatchesAppName(
180     const nsACString& handler) const {
181   gint argc;
182   gchar** argv;
183   nsAutoCString command(handler);
184 
185   // The string will be something of the form: [/path/to/]browser "%s"
186   // We want to remove all of the parameters and get just the binary name.
187 
188   if (g_shell_parse_argv(command.get(), &argc, &argv, nullptr) && argc > 0) {
189     command.Assign(argv[0]);
190     g_strfreev(argv);
191   }
192 
193   if (!KeyMatchesAppName(command.get()))
194     return false;  // the handler is set to another app
195 
196   return true;
197 }
198 
199 NS_IMETHODIMP
IsDefaultBrowser(bool aForAllTypes,bool * aIsDefaultBrowser)200 nsGNOMEShellService::IsDefaultBrowser(bool aForAllTypes,
201                                       bool* aIsDefaultBrowser) {
202   *aIsDefaultBrowser = false;
203 
204   if (IsRunningAsASnap()) {
205     const gchar* argv[] = {"xdg-settings", "check", "default-web-browser",
206                            "firefox.desktop", nullptr};
207     GSpawnFlags flags = static_cast<GSpawnFlags>(G_SPAWN_SEARCH_PATH |
208                                                  G_SPAWN_STDERR_TO_DEV_NULL);
209     gchar* output = nullptr;
210     gint exit_status = 0;
211     if (!g_spawn_sync(nullptr, (gchar**)argv, nullptr, flags, nullptr, nullptr,
212                       &output, nullptr, &exit_status, nullptr)) {
213       return NS_OK;
214     }
215     if (exit_status != 0) {
216       g_free(output);
217       return NS_OK;
218     }
219     if (strcmp(output, "yes\n") == 0) {
220       *aIsDefaultBrowser = true;
221     }
222     g_free(output);
223     return NS_OK;
224   }
225 
226   nsCOMPtr<nsIGIOService> giovfs = do_GetService(NS_GIOSERVICE_CONTRACTID);
227   nsAutoCString handler;
228   nsCOMPtr<nsIGIOMimeApp> gioApp;
229 
230   for (unsigned int i = 0; i < ArrayLength(appProtocols); ++i) {
231     if (!appProtocols[i].essential) continue;
232 
233     if (giovfs) {
234       handler.Truncate();
235       nsCOMPtr<nsIHandlerApp> handlerApp;
236       giovfs->GetAppForURIScheme(nsDependentCString(appProtocols[i].name),
237                                  getter_AddRefs(handlerApp));
238       gioApp = do_QueryInterface(handlerApp);
239       if (!gioApp) return NS_OK;
240 
241       gioApp->GetCommand(handler);
242 
243       if (!CheckHandlerMatchesAppName(handler))
244         return NS_OK;  // the handler is set to another app
245     }
246   }
247 
248   *aIsDefaultBrowser = true;
249 
250   return NS_OK;
251 }
252 
253 NS_IMETHODIMP
SetDefaultBrowser(bool aClaimAllTypes,bool aForAllUsers)254 nsGNOMEShellService::SetDefaultBrowser(bool aClaimAllTypes, bool aForAllUsers) {
255 #ifdef DEBUG
256   if (aForAllUsers)
257     NS_WARNING(
258         "Setting the default browser for all users is not yet supported");
259 #endif
260 
261   if (IsRunningAsASnap()) {
262     const gchar* argv[] = {"xdg-settings", "set", "default-web-browser",
263                            "firefox.desktop", nullptr};
264     GSpawnFlags flags = static_cast<GSpawnFlags>(G_SPAWN_SEARCH_PATH |
265                                                  G_SPAWN_STDOUT_TO_DEV_NULL |
266                                                  G_SPAWN_STDERR_TO_DEV_NULL);
267     g_spawn_sync(nullptr, (gchar**)argv, nullptr, flags, nullptr, nullptr,
268                  nullptr, nullptr, nullptr, nullptr);
269     return NS_OK;
270   }
271 
272   nsCOMPtr<nsIGIOService> giovfs = do_GetService(NS_GIOSERVICE_CONTRACTID);
273   if (giovfs) {
274     nsresult rv;
275     nsCOMPtr<nsIStringBundleService> bundleService =
276         do_GetService(NS_STRINGBUNDLE_CONTRACTID, &rv);
277     NS_ENSURE_SUCCESS(rv, rv);
278 
279     nsCOMPtr<nsIStringBundle> brandBundle;
280     rv = bundleService->CreateBundle(BRAND_PROPERTIES,
281                                      getter_AddRefs(brandBundle));
282     NS_ENSURE_SUCCESS(rv, rv);
283 
284     nsAutoString brandShortName;
285     brandBundle->GetStringFromName("brandShortName", brandShortName);
286 
287     // use brandShortName as the application id.
288     NS_ConvertUTF16toUTF8 id(brandShortName);
289     nsCOMPtr<nsIGIOMimeApp> appInfo;
290     rv = giovfs->FindAppFromCommand(mAppPath, getter_AddRefs(appInfo));
291     if (NS_FAILED(rv)) {
292       // Application was not found in the list of installed applications
293       // provided by OS. Fallback to create appInfo from command and name.
294       rv = giovfs->CreateAppFromCommand(mAppPath, id, getter_AddRefs(appInfo));
295       NS_ENSURE_SUCCESS(rv, rv);
296     }
297 
298     // set handler for the protocols
299     for (unsigned int i = 0; i < ArrayLength(appProtocols); ++i) {
300       if (appProtocols[i].essential || aClaimAllTypes) {
301         appInfo->SetAsDefaultForURIScheme(
302             nsDependentCString(appProtocols[i].name));
303       }
304     }
305 
306     // set handler for .html and xhtml files and MIME types:
307     if (aClaimAllTypes) {
308       // Add mime types for html, xhtml extension and set app to just created
309       // appinfo.
310       for (unsigned int i = 0; i < ArrayLength(appTypes); ++i) {
311         appInfo->SetAsDefaultForMimeType(
312             nsDependentCString(appTypes[i].mimeType));
313         appInfo->SetAsDefaultForFileExtensions(
314             nsDependentCString(appTypes[i].extensions));
315       }
316     }
317   }
318 
319   nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID));
320   if (prefs) {
321     (void)prefs->SetBoolPref(PREF_CHECKDEFAULTBROWSER, true);
322     // Reset the number of times the dialog should be shown
323     // before it is silenced.
324     (void)prefs->SetIntPref(PREF_DEFAULTBROWSERCHECKCOUNT, 0);
325   }
326 
327   return NS_OK;
328 }
329 
330 NS_IMETHODIMP
GetCanSetDesktopBackground(bool * aResult)331 nsGNOMEShellService::GetCanSetDesktopBackground(bool* aResult) {
332   // setting desktop background is currently only supported
333   // for Gnome or desktops using the same GSettings keys
334   const char* currentDesktop = getenv("XDG_CURRENT_DESKTOP");
335   if (currentDesktop && strstr(currentDesktop, "GNOME") != nullptr) {
336     *aResult = true;
337     return NS_OK;
338   }
339 
340   const char* gnomeSession = getenv("GNOME_DESKTOP_SESSION_ID");
341   if (gnomeSession) {
342     *aResult = true;
343   } else {
344     *aResult = false;
345   }
346 
347   return NS_OK;
348 }
349 
WriteImage(const nsCString & aPath,imgIContainer * aImage)350 static nsresult WriteImage(const nsCString& aPath, imgIContainer* aImage) {
351 #if !defined(MOZ_WIDGET_GTK)
352   return NS_ERROR_NOT_AVAILABLE;
353 #else
354   nsCOMPtr<nsIImageToPixbuf> imgToPixbuf =
355       do_GetService("@mozilla.org/widget/image-to-gdk-pixbuf;1");
356   if (!imgToPixbuf) return NS_ERROR_NOT_AVAILABLE;
357 
358   GdkPixbuf* pixbuf = imgToPixbuf->ConvertImageToPixbuf(aImage);
359   if (!pixbuf) return NS_ERROR_NOT_AVAILABLE;
360 
361   gboolean res = gdk_pixbuf_save(pixbuf, aPath.get(), "png", nullptr, nullptr);
362 
363   g_object_unref(pixbuf);
364   return res ? NS_OK : NS_ERROR_FAILURE;
365 #endif
366 }
367 
368 NS_IMETHODIMP
SetDesktopBackground(dom::Element * aElement,int32_t aPosition,const nsACString & aImageName)369 nsGNOMEShellService::SetDesktopBackground(dom::Element* aElement,
370                                           int32_t aPosition,
371                                           const nsACString& aImageName) {
372   nsresult rv;
373   nsCOMPtr<nsIImageLoadingContent> imageContent =
374       do_QueryInterface(aElement, &rv);
375   if (!imageContent) return rv;
376 
377   // get the image container
378   nsCOMPtr<imgIRequest> request;
379   rv = imageContent->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST,
380                                 getter_AddRefs(request));
381   if (!request) return rv;
382   nsCOMPtr<imgIContainer> container;
383   rv = request->GetImage(getter_AddRefs(container));
384   if (!container) return rv;
385 
386   // Set desktop wallpaper filling style
387   nsAutoCString options;
388   if (aPosition == BACKGROUND_TILE)
389     options.AssignLiteral("wallpaper");
390   else if (aPosition == BACKGROUND_STRETCH)
391     options.AssignLiteral("stretched");
392   else if (aPosition == BACKGROUND_FILL)
393     options.AssignLiteral("zoom");
394   else if (aPosition == BACKGROUND_FIT)
395     options.AssignLiteral("scaled");
396   else if (aPosition == BACKGROUND_SPAN)
397     options.AssignLiteral("spanned");
398   else
399     options.AssignLiteral("centered");
400 
401   // Write the background file to the home directory.
402   nsAutoCString filePath(PR_GetEnv("HOME"));
403 
404   // get the product brand name from localized strings
405   nsAutoString brandName;
406   nsCID bundleCID = NS_STRINGBUNDLESERVICE_CID;
407   nsCOMPtr<nsIStringBundleService> bundleService(do_GetService(bundleCID));
408   if (bundleService) {
409     nsCOMPtr<nsIStringBundle> brandBundle;
410     rv = bundleService->CreateBundle(BRAND_PROPERTIES,
411                                      getter_AddRefs(brandBundle));
412     if (NS_SUCCEEDED(rv) && brandBundle) {
413       rv = brandBundle->GetStringFromName("brandShortName", brandName);
414       NS_ENSURE_SUCCESS(rv, rv);
415     }
416   }
417 
418   // build the file name
419   filePath.Append('/');
420   filePath.Append(NS_ConvertUTF16toUTF8(brandName));
421   filePath.AppendLiteral("_wallpaper.png");
422 
423   // write the image to a file in the home dir
424   rv = WriteImage(filePath, container);
425   NS_ENSURE_SUCCESS(rv, rv);
426 
427   nsCOMPtr<nsIGSettingsService> gsettings =
428       do_GetService(NS_GSETTINGSSERVICE_CONTRACTID);
429   if (gsettings) {
430     nsCOMPtr<nsIGSettingsCollection> background_settings;
431     gsettings->GetCollectionForSchema(NS_LITERAL_CSTRING(kDesktopBGSchema),
432                                       getter_AddRefs(background_settings));
433     if (background_settings) {
434       gchar* file_uri = g_filename_to_uri(filePath.get(), nullptr, nullptr);
435       if (!file_uri) return NS_ERROR_FAILURE;
436 
437       background_settings->SetString(NS_LITERAL_CSTRING(kDesktopOptionGSKey),
438                                      options);
439 
440       background_settings->SetString(NS_LITERAL_CSTRING(kDesktopImageGSKey),
441                                      nsDependentCString(file_uri));
442       g_free(file_uri);
443       background_settings->SetBoolean(NS_LITERAL_CSTRING(kDesktopDrawBGGSKey),
444                                       true);
445       return rv;
446     }
447   }
448 
449   return NS_ERROR_FAILURE;
450 }
451 
452 #define COLOR_16_TO_8_BIT(_c) ((_c) >> 8)
453 #define COLOR_8_TO_16_BIT(_c) ((_c) << 8 | (_c))
454 
455 NS_IMETHODIMP
GetDesktopBackgroundColor(uint32_t * aColor)456 nsGNOMEShellService::GetDesktopBackgroundColor(uint32_t* aColor) {
457   nsCOMPtr<nsIGSettingsService> gsettings =
458       do_GetService(NS_GSETTINGSSERVICE_CONTRACTID);
459   nsCOMPtr<nsIGSettingsCollection> background_settings;
460   nsAutoCString background;
461 
462   if (gsettings) {
463     gsettings->GetCollectionForSchema(NS_LITERAL_CSTRING(kDesktopBGSchema),
464                                       getter_AddRefs(background_settings));
465     if (background_settings) {
466       background_settings->GetString(NS_LITERAL_CSTRING(kDesktopColorGSKey),
467                                      background);
468     }
469   }
470 
471   if (background.IsEmpty()) {
472     *aColor = 0;
473     return NS_OK;
474   }
475 
476   GdkColor color;
477   gboolean success = gdk_color_parse(background.get(), &color);
478 
479   NS_ENSURE_TRUE(success, NS_ERROR_FAILURE);
480 
481   *aColor = COLOR_16_TO_8_BIT(color.red) << 16 |
482             COLOR_16_TO_8_BIT(color.green) << 8 | COLOR_16_TO_8_BIT(color.blue);
483   return NS_OK;
484 }
485 
ColorToCString(uint32_t aColor,nsCString & aResult)486 static void ColorToCString(uint32_t aColor, nsCString& aResult) {
487   // The #rrrrggggbbbb format is used to match gdk_color_to_string()
488   aResult.SetLength(13);
489   char* buf = aResult.BeginWriting();
490   if (!buf) return;
491 
492   uint16_t red = COLOR_8_TO_16_BIT((aColor >> 16) & 0xff);
493   uint16_t green = COLOR_8_TO_16_BIT((aColor >> 8) & 0xff);
494   uint16_t blue = COLOR_8_TO_16_BIT(aColor & 0xff);
495 
496   snprintf(buf, 14, "#%04x%04x%04x", red, green, blue);
497 }
498 
499 NS_IMETHODIMP
SetDesktopBackgroundColor(uint32_t aColor)500 nsGNOMEShellService::SetDesktopBackgroundColor(uint32_t aColor) {
501   NS_ASSERTION(aColor <= 0xffffff, "aColor has extra bits");
502   nsAutoCString colorString;
503   ColorToCString(aColor, colorString);
504 
505   nsCOMPtr<nsIGSettingsService> gsettings =
506       do_GetService(NS_GSETTINGSSERVICE_CONTRACTID);
507   if (gsettings) {
508     nsCOMPtr<nsIGSettingsCollection> background_settings;
509     gsettings->GetCollectionForSchema(NS_LITERAL_CSTRING(kDesktopBGSchema),
510                                       getter_AddRefs(background_settings));
511     if (background_settings) {
512       background_settings->SetString(NS_LITERAL_CSTRING(kDesktopColorGSKey),
513                                      colorString);
514       return NS_OK;
515     }
516   }
517 
518   return NS_ERROR_FAILURE;
519 }
520