diff options
Diffstat (limited to 'browser/components/shell/nsGNOMEShellService.cpp')
-rw-r--r-- | browser/components/shell/nsGNOMEShellService.cpp | 506 |
1 files changed, 506 insertions, 0 deletions
diff --git a/browser/components/shell/nsGNOMEShellService.cpp b/browser/components/shell/nsGNOMEShellService.cpp new file mode 100644 index 0000000000..a07d37d109 --- /dev/null +++ b/browser/components/shell/nsGNOMEShellService.cpp @@ -0,0 +1,506 @@ +/* -*- 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 "nsIImageLoadingContent.h" +#include "imgIRequest.h" +#include "imgIContainer.h" +#include "mozilla/Components.h" +#include "mozilla/GRefPtr.h" +#include "mozilla/GUniquePtr.h" +#include "mozilla/WidgetUtilsGtk.h" +#include "mozilla/dom/Element.h" +#include "nsImageToPixbuf.h" +#include "nsXULAppAPI.h" +#include "gfxPlatform.h" + +#include <glib.h> +#include <gdk/gdk.h> +#include <gdk-pixbuf/gdk-pixbuf.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 }, + { "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"_ns +#define kDesktopColorGSKey "primary-color"_ns + +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 + if (widget::IsGnomeDesktopEnvironment() && + 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 (widget::IsRunningUnderSnap()) { + const gchar* argv[] = {"xdg-settings", "check", "default-web-browser", + (MOZ_APP_NAME ".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 (!IsDefaultForSchemeHelper(nsDependentCString(appProtocols[i].name), + giovfs)) { + return NS_OK; + } + } + + *aIsDefaultBrowser = true; + + return NS_OK; +} + +bool nsGNOMEShellService::IsDefaultForSchemeHelper( + const nsACString& aScheme, nsIGIOService* giovfs) const { + nsCOMPtr<nsIGIOService> gioService; + if (!giovfs) { + gioService = do_GetService(NS_GIOSERVICE_CONTRACTID); + giovfs = gioService.get(); + } + + if (!giovfs) { + return false; + } + + nsCOMPtr<nsIGIOMimeApp> gioApp; + nsCOMPtr<nsIHandlerApp> handlerApp; + giovfs->GetAppForURIScheme(aScheme, getter_AddRefs(handlerApp)); + gioApp = do_QueryInterface(handlerApp); + if (!gioApp) { + return false; + } + + nsAutoCString handler; + gioApp->GetCommand(handler); + return CheckHandlerMatchesAppName(handler); +} + +NS_IMETHODIMP +nsGNOMEShellService::IsDefaultForScheme(const nsACString& aScheme, + bool* aIsDefaultBrowser) { + *aIsDefaultBrowser = IsDefaultForSchemeHelper(aScheme, nullptr); + 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 (widget::IsRunningUnderSnap()) { + const gchar* argv[] = {"xdg-settings", "set", "default-web-browser", + (MOZ_APP_NAME ".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 = + components::StringBundle::Service(&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 + if (widget::IsGnomeDesktopEnvironment()) { + *aResult = true; + return NS_OK; + } + + *aResult = !!getenv("GNOME_DESKTOP_SESSION_ID"); + return NS_OK; +} + +static nsresult WriteImage(const nsCString& aPath, imgIContainer* aImage) { + RefPtr<GdkPixbuf> pixbuf = nsImageToPixbuf::ImageToPixbuf(aImage); + if (!pixbuf) { + return NS_ERROR_NOT_AVAILABLE; + } + + gboolean res = gdk_pixbuf_save(pixbuf, aPath.get(), "png", nullptr, nullptr); + return res ? NS_OK : NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsGNOMEShellService::SetDesktopBackground(dom::Element* aElement, + int32_t aPosition, + const nsACString& aImageName) { + nsCOMPtr<nsIImageLoadingContent> imageContent = do_QueryInterface(aElement); + if (!imageContent) { + return NS_ERROR_FAILURE; + } + + // get the image container + nsCOMPtr<imgIRequest> request; + imageContent->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST, + getter_AddRefs(request)); + if (!request) { + return NS_ERROR_FAILURE; + } + nsCOMPtr<imgIContainer> container; + request->GetImage(getter_AddRefs(container)); + if (!container) { + return NS_ERROR_FAILURE; + } + + // 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")); + nsAutoString brandName; + + // get the product brand name from localized strings + if (nsCOMPtr<nsIStringBundleService> bundleService = + components::StringBundle::Service()) { + nsCOMPtr<nsIStringBundle> brandBundle; + bundleService->CreateBundle(BRAND_PROPERTIES, getter_AddRefs(brandBundle)); + if (bundleService) { + brandBundle->GetStringFromName("brandShortName", brandName); + } + } + + // 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 + MOZ_TRY(WriteImage(filePath, container)); + + nsCOMPtr<nsIGSettingsService> gsettings = + do_GetService(NS_GSETTINGSSERVICE_CONTRACTID); + if (!gsettings) { + return NS_ERROR_FAILURE; + } + nsCOMPtr<nsIGSettingsCollection> backgroundSettings; + gsettings->GetCollectionForSchema(kDesktopBGSchema, + getter_AddRefs(backgroundSettings)); + if (!backgroundSettings) { + return NS_ERROR_FAILURE; + } + + GUniquePtr<gchar> fileURI( + g_filename_to_uri(filePath.get(), nullptr, nullptr)); + if (!fileURI) { + return NS_ERROR_FAILURE; + } + + backgroundSettings->SetString("picture-options"_ns, options); + backgroundSettings->SetString("picture-uri"_ns, + nsDependentCString(fileURI.get())); + backgroundSettings->SetString("picture-uri-dark"_ns, + nsDependentCString(fileURI.get())); + backgroundSettings->SetBoolean("draw-background"_ns, true); + return NS_OK; +} + +#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(kDesktopBGSchema, + getter_AddRefs(background_settings)); + if (background_settings) { + background_settings->GetString(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(kDesktopColorGSKey, colorString); + return NS_OK; + } + } + + return NS_ERROR_FAILURE; +} |