diff options
Diffstat (limited to '')
-rw-r--r-- | browser/components/shell/nsGNOMEShellService.cpp | 520 |
1 files changed, 520 insertions, 0 deletions
diff --git a/browser/components/shell/nsGNOMEShellService.cpp b/browser/components/shell/nsGNOMEShellService.cpp new file mode 100644 index 0000000000..c485a29955 --- /dev/null +++ b/browser/components/shell/nsGNOMEShellService.cpp @@ -0,0 +1,520 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/ArrayUtils.h" +#include "mozilla/Preferences.h" + +#include "nsCOMPtr.h" +#include "nsGNOMEShellService.h" +#include "nsShellService.h" +#include "nsIFile.h" +#include "nsIProperties.h" +#include "nsDirectoryServiceDefs.h" +#include "prenv.h" +#include "nsString.h" +#include "nsIGIOService.h" +#include "nsIGSettingsService.h" +#include "nsIStringBundle.h" +#include "nsServiceManagerUtils.h" +#include "nsComponentManagerUtils.h" +#include "nsIImageLoadingContent.h" +#include "imgIRequest.h" +#include "imgIContainer.h" +#include "mozilla/Sprintf.h" +#include "mozilla/dom/Element.h" +#if defined(MOZ_WIDGET_GTK) +# include "nsIImageToPixbuf.h" +#endif +#include "nsXULAppAPI.h" +#include "gfxPlatform.h" + +#include <glib.h> +#include <glib-object.h> +#include <gtk/gtk.h> +#include <gdk/gdk.h> +#include <gdk-pixbuf/gdk-pixbuf.h> +#include <limits.h> +#include <stdlib.h> + +using namespace mozilla; + +struct ProtocolAssociation { + const char* name; + bool essential; +}; + +struct MimeTypeAssociation { + const char* mimeType; + const char* extensions; +}; + +static const ProtocolAssociation appProtocols[] = { + // clang-format off + { "http", true }, + { "https", true }, + { "ftp", false }, + { "chrome", false } + // clang-format on +}; + +static const MimeTypeAssociation appTypes[] = { + // clang-format off + { "text/html", "htm html shtml" }, + { "application/xhtml+xml", "xhtml xht" } + // clang-format on +}; + +#define kDesktopBGSchema "org.gnome.desktop.background" +#define kDesktopImageGSKey "picture-uri" +#define kDesktopOptionGSKey "picture-options" +#define kDesktopDrawBGGSKey "draw-background" +#define kDesktopColorGSKey "primary-color" + +static bool IsRunningAsASnap() { + // SNAP holds the path to the snap, use SNAP_NAME + // which is easier to parse. + const char* snap_name = PR_GetEnv("SNAP_NAME"); + + // return early if not set. + if (snap_name == nullptr) { + return false; + } + + // snap_name as defined on https://snapcraft.io/firefox + return (strcmp(snap_name, "firefox") == 0); +} + +nsresult nsGNOMEShellService::Init() { + nsresult rv; + + if (gfxPlatform::IsHeadless()) { + return NS_ERROR_NOT_AVAILABLE; + } + + // GSettings or GIO _must_ be available, or we do not allow + // CreateInstance to succeed. + + nsCOMPtr<nsIGIOService> giovfs = do_GetService(NS_GIOSERVICE_CONTRACTID); + nsCOMPtr<nsIGSettingsService> gsettings = + do_GetService(NS_GSETTINGSSERVICE_CONTRACTID); + + if (!giovfs && !gsettings) return NS_ERROR_NOT_AVAILABLE; + +#ifdef MOZ_ENABLE_DBUS + const char* currentDesktop = getenv("XDG_CURRENT_DESKTOP"); + if (currentDesktop && strstr(currentDesktop, "GNOME") != nullptr && + Preferences::GetBool("browser.gnome-search-provider.enabled", false)) { + mSearchProvider.Startup(); + } +#endif + + // Check G_BROKEN_FILENAMES. If it's set, then filenames in glib use + // the locale encoding. If it's not set, they use UTF-8. + mUseLocaleFilenames = PR_GetEnv("G_BROKEN_FILENAMES") != nullptr; + + if (GetAppPathFromLauncher()) return NS_OK; + + nsCOMPtr<nsIProperties> dirSvc( + do_GetService("@mozilla.org/file/directory_service;1")); + NS_ENSURE_TRUE(dirSvc, NS_ERROR_NOT_AVAILABLE); + + nsCOMPtr<nsIFile> appPath; + rv = dirSvc->Get(XRE_EXECUTABLE_FILE, NS_GET_IID(nsIFile), + getter_AddRefs(appPath)); + NS_ENSURE_SUCCESS(rv, rv); + + return appPath->GetNativePath(mAppPath); +} + +NS_IMPL_ISUPPORTS(nsGNOMEShellService, nsIGNOMEShellService, nsIShellService, + nsIToolkitShellService) + +bool nsGNOMEShellService::GetAppPathFromLauncher() { + gchar* tmp; + + const char* launcher = PR_GetEnv("MOZ_APP_LAUNCHER"); + if (!launcher) return false; + + if (g_path_is_absolute(launcher)) { + mAppPath = launcher; + tmp = g_path_get_basename(launcher); + gchar* fullpath = g_find_program_in_path(tmp); + if (fullpath && mAppPath.Equals(fullpath)) mAppIsInPath = true; + g_free(fullpath); + } else { + tmp = g_find_program_in_path(launcher); + if (!tmp) return false; + mAppPath = tmp; + mAppIsInPath = true; + } + + g_free(tmp); + return true; +} + +bool nsGNOMEShellService::KeyMatchesAppName(const char* aKeyValue) const { + gchar* commandPath; + if (mUseLocaleFilenames) { + gchar* nativePath = + g_filename_from_utf8(aKeyValue, -1, nullptr, nullptr, nullptr); + if (!nativePath) { + NS_ERROR("Error converting path to filesystem encoding"); + return false; + } + + commandPath = g_find_program_in_path(nativePath); + g_free(nativePath); + } else { + commandPath = g_find_program_in_path(aKeyValue); + } + + if (!commandPath) return false; + + bool matches = mAppPath.Equals(commandPath); + g_free(commandPath); + return matches; +} + +bool nsGNOMEShellService::CheckHandlerMatchesAppName( + const nsACString& handler) const { + gint argc; + gchar** argv; + nsAutoCString command(handler); + + // The string will be something of the form: [/path/to/]browser "%s" + // We want to remove all of the parameters and get just the binary name. + + if (g_shell_parse_argv(command.get(), &argc, &argv, nullptr) && argc > 0) { + command.Assign(argv[0]); + g_strfreev(argv); + } + + if (!KeyMatchesAppName(command.get())) + return false; // the handler is set to another app + + return true; +} + +NS_IMETHODIMP +nsGNOMEShellService::IsDefaultBrowser(bool aForAllTypes, + bool* aIsDefaultBrowser) { + *aIsDefaultBrowser = false; + + if (IsRunningAsASnap()) { + const gchar* argv[] = {"xdg-settings", "check", "default-web-browser", + "firefox.desktop", nullptr}; + GSpawnFlags flags = static_cast<GSpawnFlags>(G_SPAWN_SEARCH_PATH | + G_SPAWN_STDERR_TO_DEV_NULL); + gchar* output = nullptr; + gint exit_status = 0; + if (!g_spawn_sync(nullptr, (gchar**)argv, nullptr, flags, nullptr, nullptr, + &output, nullptr, &exit_status, nullptr)) { + return NS_OK; + } + if (exit_status != 0) { + g_free(output); + return NS_OK; + } + if (strcmp(output, "yes\n") == 0) { + *aIsDefaultBrowser = true; + } + g_free(output); + return NS_OK; + } + + nsCOMPtr<nsIGIOService> giovfs = do_GetService(NS_GIOSERVICE_CONTRACTID); + nsAutoCString handler; + nsCOMPtr<nsIGIOMimeApp> gioApp; + + for (unsigned int i = 0; i < ArrayLength(appProtocols); ++i) { + if (!appProtocols[i].essential) continue; + + if (giovfs) { + handler.Truncate(); + nsCOMPtr<nsIHandlerApp> handlerApp; + giovfs->GetAppForURIScheme(nsDependentCString(appProtocols[i].name), + getter_AddRefs(handlerApp)); + gioApp = do_QueryInterface(handlerApp); + if (!gioApp) return NS_OK; + + gioApp->GetCommand(handler); + + if (!CheckHandlerMatchesAppName(handler)) + return NS_OK; // the handler is set to another app + } + } + + *aIsDefaultBrowser = true; + + return NS_OK; +} + +NS_IMETHODIMP +nsGNOMEShellService::SetDefaultBrowser(bool aClaimAllTypes, bool aForAllUsers) { +#ifdef DEBUG + if (aForAllUsers) + NS_WARNING( + "Setting the default browser for all users is not yet supported"); +#endif + + if (IsRunningAsASnap()) { + const gchar* argv[] = {"xdg-settings", "set", "default-web-browser", + "firefox.desktop", nullptr}; + GSpawnFlags flags = static_cast<GSpawnFlags>(G_SPAWN_SEARCH_PATH | + G_SPAWN_STDOUT_TO_DEV_NULL | + G_SPAWN_STDERR_TO_DEV_NULL); + g_spawn_sync(nullptr, (gchar**)argv, nullptr, flags, nullptr, nullptr, + nullptr, nullptr, nullptr, nullptr); + return NS_OK; + } + + nsCOMPtr<nsIGIOService> giovfs = do_GetService(NS_GIOSERVICE_CONTRACTID); + if (giovfs) { + nsresult rv; + nsCOMPtr<nsIStringBundleService> bundleService = + do_GetService(NS_STRINGBUNDLE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIStringBundle> brandBundle; + rv = bundleService->CreateBundle(BRAND_PROPERTIES, + getter_AddRefs(brandBundle)); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoString brandShortName; + brandBundle->GetStringFromName("brandShortName", brandShortName); + + // use brandShortName as the application id. + NS_ConvertUTF16toUTF8 id(brandShortName); + nsCOMPtr<nsIGIOMimeApp> appInfo; + rv = giovfs->FindAppFromCommand(mAppPath, getter_AddRefs(appInfo)); + if (NS_FAILED(rv)) { + // Application was not found in the list of installed applications + // provided by OS. Fallback to create appInfo from command and name. + rv = giovfs->CreateAppFromCommand(mAppPath, id, getter_AddRefs(appInfo)); + NS_ENSURE_SUCCESS(rv, rv); + } + + // set handler for the protocols + for (unsigned int i = 0; i < ArrayLength(appProtocols); ++i) { + if (appProtocols[i].essential || aClaimAllTypes) { + appInfo->SetAsDefaultForURIScheme( + nsDependentCString(appProtocols[i].name)); + } + } + + // set handler for .html and xhtml files and MIME types: + if (aClaimAllTypes) { + // Add mime types for html, xhtml extension and set app to just created + // appinfo. + for (unsigned int i = 0; i < ArrayLength(appTypes); ++i) { + appInfo->SetAsDefaultForMimeType( + nsDependentCString(appTypes[i].mimeType)); + appInfo->SetAsDefaultForFileExtensions( + nsDependentCString(appTypes[i].extensions)); + } + } + } + + nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID)); + if (prefs) { + (void)prefs->SetBoolPref(PREF_CHECKDEFAULTBROWSER, true); + // Reset the number of times the dialog should be shown + // before it is silenced. + (void)prefs->SetIntPref(PREF_DEFAULTBROWSERCHECKCOUNT, 0); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsGNOMEShellService::GetCanSetDesktopBackground(bool* aResult) { + // setting desktop background is currently only supported + // for Gnome or desktops using the same GSettings keys + const char* currentDesktop = getenv("XDG_CURRENT_DESKTOP"); + if (currentDesktop && strstr(currentDesktop, "GNOME") != nullptr) { + *aResult = true; + return NS_OK; + } + + const char* gnomeSession = getenv("GNOME_DESKTOP_SESSION_ID"); + if (gnomeSession) { + *aResult = true; + } else { + *aResult = false; + } + + return NS_OK; +} + +static nsresult WriteImage(const nsCString& aPath, imgIContainer* aImage) { +#if !defined(MOZ_WIDGET_GTK) + return NS_ERROR_NOT_AVAILABLE; +#else + nsCOMPtr<nsIImageToPixbuf> imgToPixbuf = + do_GetService("@mozilla.org/widget/image-to-gdk-pixbuf;1"); + if (!imgToPixbuf) return NS_ERROR_NOT_AVAILABLE; + + GdkPixbuf* pixbuf = imgToPixbuf->ConvertImageToPixbuf(aImage); + if (!pixbuf) return NS_ERROR_NOT_AVAILABLE; + + gboolean res = gdk_pixbuf_save(pixbuf, aPath.get(), "png", nullptr, nullptr); + + g_object_unref(pixbuf); + return res ? NS_OK : NS_ERROR_FAILURE; +#endif +} + +NS_IMETHODIMP +nsGNOMEShellService::SetDesktopBackground(dom::Element* aElement, + int32_t aPosition, + const nsACString& aImageName) { + nsresult rv; + nsCOMPtr<nsIImageLoadingContent> imageContent = + do_QueryInterface(aElement, &rv); + if (!imageContent) return rv; + + // get the image container + nsCOMPtr<imgIRequest> request; + rv = imageContent->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST, + getter_AddRefs(request)); + if (!request) return rv; + nsCOMPtr<imgIContainer> container; + rv = request->GetImage(getter_AddRefs(container)); + if (!container) return rv; + + // Set desktop wallpaper filling style + nsAutoCString options; + if (aPosition == BACKGROUND_TILE) + options.AssignLiteral("wallpaper"); + else if (aPosition == BACKGROUND_STRETCH) + options.AssignLiteral("stretched"); + else if (aPosition == BACKGROUND_FILL) + options.AssignLiteral("zoom"); + else if (aPosition == BACKGROUND_FIT) + options.AssignLiteral("scaled"); + else if (aPosition == BACKGROUND_SPAN) + options.AssignLiteral("spanned"); + else + options.AssignLiteral("centered"); + + // Write the background file to the home directory. + nsAutoCString filePath(PR_GetEnv("HOME")); + + // get the product brand name from localized strings + nsAutoString brandName; + nsCID bundleCID = NS_STRINGBUNDLESERVICE_CID; + nsCOMPtr<nsIStringBundleService> bundleService(do_GetService(bundleCID)); + if (bundleService) { + nsCOMPtr<nsIStringBundle> brandBundle; + rv = bundleService->CreateBundle(BRAND_PROPERTIES, + getter_AddRefs(brandBundle)); + if (NS_SUCCEEDED(rv) && brandBundle) { + rv = brandBundle->GetStringFromName("brandShortName", brandName); + NS_ENSURE_SUCCESS(rv, rv); + } + } + + // build the file name + filePath.Append('/'); + filePath.Append(NS_ConvertUTF16toUTF8(brandName)); + filePath.AppendLiteral("_wallpaper.png"); + + // write the image to a file in the home dir + rv = WriteImage(filePath, container); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIGSettingsService> gsettings = + do_GetService(NS_GSETTINGSSERVICE_CONTRACTID); + if (gsettings) { + nsCOMPtr<nsIGSettingsCollection> background_settings; + gsettings->GetCollectionForSchema(nsLiteralCString(kDesktopBGSchema), + getter_AddRefs(background_settings)); + if (background_settings) { + gchar* file_uri = g_filename_to_uri(filePath.get(), nullptr, nullptr); + if (!file_uri) return NS_ERROR_FAILURE; + + background_settings->SetString(nsLiteralCString(kDesktopOptionGSKey), + options); + + background_settings->SetString(nsLiteralCString(kDesktopImageGSKey), + nsDependentCString(file_uri)); + g_free(file_uri); + background_settings->SetBoolean(nsLiteralCString(kDesktopDrawBGGSKey), + true); + return rv; + } + } + + return NS_ERROR_FAILURE; +} + +#define COLOR_16_TO_8_BIT(_c) ((_c) >> 8) +#define COLOR_8_TO_16_BIT(_c) ((_c) << 8 | (_c)) + +NS_IMETHODIMP +nsGNOMEShellService::GetDesktopBackgroundColor(uint32_t* aColor) { + nsCOMPtr<nsIGSettingsService> gsettings = + do_GetService(NS_GSETTINGSSERVICE_CONTRACTID); + nsCOMPtr<nsIGSettingsCollection> background_settings; + nsAutoCString background; + + if (gsettings) { + gsettings->GetCollectionForSchema(nsLiteralCString(kDesktopBGSchema), + getter_AddRefs(background_settings)); + if (background_settings) { + background_settings->GetString(nsLiteralCString(kDesktopColorGSKey), + background); + } + } + + if (background.IsEmpty()) { + *aColor = 0; + return NS_OK; + } + + GdkColor color; + gboolean success = gdk_color_parse(background.get(), &color); + + NS_ENSURE_TRUE(success, NS_ERROR_FAILURE); + + *aColor = COLOR_16_TO_8_BIT(color.red) << 16 | + COLOR_16_TO_8_BIT(color.green) << 8 | COLOR_16_TO_8_BIT(color.blue); + return NS_OK; +} + +static void ColorToCString(uint32_t aColor, nsCString& aResult) { + // The #rrrrggggbbbb format is used to match gdk_color_to_string() + aResult.SetLength(13); + char* buf = aResult.BeginWriting(); + if (!buf) return; + + uint16_t red = COLOR_8_TO_16_BIT((aColor >> 16) & 0xff); + uint16_t green = COLOR_8_TO_16_BIT((aColor >> 8) & 0xff); + uint16_t blue = COLOR_8_TO_16_BIT(aColor & 0xff); + + snprintf(buf, 14, "#%04x%04x%04x", red, green, blue); +} + +NS_IMETHODIMP +nsGNOMEShellService::SetDesktopBackgroundColor(uint32_t aColor) { + NS_ASSERTION(aColor <= 0xffffff, "aColor has extra bits"); + nsAutoCString colorString; + ColorToCString(aColor, colorString); + + nsCOMPtr<nsIGSettingsService> gsettings = + do_GetService(NS_GSETTINGSSERVICE_CONTRACTID); + if (gsettings) { + nsCOMPtr<nsIGSettingsCollection> background_settings; + gsettings->GetCollectionForSchema(nsLiteralCString(kDesktopBGSchema), + getter_AddRefs(background_settings)); + if (background_settings) { + background_settings->SetString(nsLiteralCString(kDesktopColorGSKey), + colorString); + return NS_OK; + } + } + + return NS_ERROR_FAILURE; +} |