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