diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
commit | 6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch) | |
tree | a68f146d7fa01f0134297619fbe7e33db084e0aa /comm/suite/components/shell | |
parent | Initial commit. (diff) | |
download | thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.tar.xz thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.zip |
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'comm/suite/components/shell')
17 files changed, 2258 insertions, 0 deletions
diff --git a/comm/suite/components/shell/ShellService.jsm b/comm/suite/components/shell/ShellService.jsm new file mode 100644 index 0000000000..2af3e75c6b --- /dev/null +++ b/comm/suite/components/shell/ShellService.jsm @@ -0,0 +1,110 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80 filetype=javascript: */ +/* 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/. */ + +"use strict"; + +var EXPORTED_SYMBOLS = ["ShellService"]; + +const {AppConstants} = ChromeUtils.import("resource://gre/modules/AppConstants.jsm"); +const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm"); +const {XPCOMUtils} = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm"); + +/** + * Internal functionality to save and restore the docShell.allow* properties. + */ +var ShellServiceInternal = { + /** + * Used to determine whether or not to offer "Set as desktop background" + * functionality. Even if shell service is available it is not + * guaranteed that it is able to set the background for every desktop + * which is especially true for Linux with its many different desktop + * environments. + */ + get canSetDesktopBackground() { + if (AppConstants.platform == "win" || + AppConstants.platform == "macosx") { + return true; + } + + if (AppConstants.platform == "linux") { + if (this.shellService) { + let linuxShellService = this.shellService + .QueryInterface(Ci.nsIGNOMEShellService); + return linuxShellService.canSetDesktopBackground; + } + } + + return false; + }, + + /** + * Used to determine whether or not to show a "Set Default Client" + * query dialog. This attribute is true if the application is starting + * up and "shell.checkDefaultClient" is true, otherwise it is false. + */ + _checkedThisSession: false, + get shouldCheckDefaultClient() { + // If we've already checked, the suite has been started and this is a + // new window open, and we don't want to check again. + if (this._checkedThisSession) { + return false; + } + + return Services.prefs.getBoolPref("shell.checkDefaultClient"); + }, + + set shouldCheckDefaultClient(shouldCheck) { + Services.prefs.setBoolPref("shell.checkDefaultClient", !!shouldCheck); + }, + + get shouldBeDefaultClientFor() { + return Services.prefs.getIntPref("shell.checkDefaultApps"); + }, + + set shouldBeDefaultClientFor(appTypes) { + Services.prefs.setIntPref("shell.checkDefaultApps", appTypes); + }, + + setDefaultClient(forAllUsers, claimAllTypes, appTypes) { + try { + this.shellService.setDefaultClient(forAllUsers, claimAllTypes, appTypes); + } catch (ex) { + Cu.reportError(ex); + } + }, + + isDefaultClient(startupCheck, appTypes) { + // If this is the first window, maintain internal state that we've + // checked this session (so that subsequent window opens don't show the + // default client dialog). + if (startupCheck) { + this._checkedThisSession = true; + } + if (this.shellService) { + return this.shellService.isDefaultClient(startupCheck, appTypes); + } + return false; + } +}; + +XPCOMUtils.defineLazyServiceGetter(ShellServiceInternal, "shellService", + "@mozilla.org/suite/shell-service;1", Ci.nsIShellService); + +/** + * The external API exported by this module. + */ +var ShellService = new Proxy(ShellServiceInternal, { + get(target, name) { + if (name in target) { + return target[name]; + } + if (target.shellService) { + return target.shellService[name]; + } + Services.console.logStringMessage(`${name} not found in ShellService: ${target.shellService}`); + return undefined; + } +}); diff --git a/comm/suite/components/shell/content/setDesktopBackground.js b/comm/suite/components/shell/content/setDesktopBackground.js new file mode 100644 index 0000000000..efcc5734e4 --- /dev/null +++ b/comm/suite/components/shell/content/setDesktopBackground.js @@ -0,0 +1,78 @@ +/* -*- 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/. */ + +var {AppConstants} = ChromeUtils.import( + "resource://gre/modules/AppConstants.jsm" +); + +var gShell = Cc["@mozilla.org/suite/shell-service;1"] + .getService(Ci.nsIShellService); + +var gImage, gImageName, gPosition, gPicker, gDesktop; + +function onLoad() +{ + document.getElementById("itemsBox").hidden = AppConstants.platform == "macosx"; + gImage = window.arguments[0]; + gImageName = window.arguments[1]; + gPosition = document.getElementById("position"); + gPicker = document.getElementById("picker"); + gDesktop = document.getElementById("desktop"); + + sizeToContent(); + window.innerWidth += screen.width / 2 - gDesktop.boxObject.width; + window.innerHeight += screen.height / 2 - gDesktop.boxObject.height; + + try { + var color = gShell.desktopBackgroundColor; + color = (0xF000000 | color).toString(16).toUpperCase().replace("F", "#"); + gDesktop.style.backgroundColor = color; + gPicker.color = color; + } catch (e) { + gPicker.parentNode.hidden = true; + } + + gDesktop.style.backgroundImage = 'url("' + gImage.src + '")'; + + updatePosition(); +} + +function onApply() +{ + if (!gPicker.parentNode.hidden) + gShell.desktopBackgroundColor = parseInt(gPicker.color.substr(1), 16); + + gShell.setDesktopBackground(gImage, Ci.nsIShellService[gPosition.value], + gImageName); +} + +function updatePosition() +{ + gDesktop.style.backgroundPosition = "center"; + gDesktop.style.backgroundRepeat = "no-repeat"; + switch (gPosition.value) { + case "BACKGROUND_FIT": + gDesktop.style.backgroundSize = "contain"; + return; + case "BACKGROUND_FILL": + gDesktop.style.backgroundSize = "cover"; + return; + case "BACKGROUND_STRETCH": + gDesktop.style.backgroundPosition = ""; + gDesktop.style.backgroundSize = "100% 100%"; + return; + case "BACKGROUND_TILE": + gDesktop.style.backgroundPosition = ""; + gDesktop.style.backgroundRepeat = "repeat"; + } + gDesktop.style.backgroundSize = + (gImage.naturalWidth / 2) + "px " + (gImage.naturalHeight / 2) + "px"; +} + +function updateColor() +{ + gDesktop.style.backgroundColor = gPicker.color; +} diff --git a/comm/suite/components/shell/content/setDesktopBackground.xul b/comm/suite/components/shell/content/setDesktopBackground.xul new file mode 100644 index 0000000000..16fb384658 --- /dev/null +++ b/comm/suite/components/shell/content/setDesktopBackground.xul @@ -0,0 +1,49 @@ +<?xml version="1.0"?> + +<!-- 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/. --> + +<?xml-stylesheet href="chrome://communicator/skin/" type="text/css"?> + +<!DOCTYPE dialog SYSTEM "chrome://communicator/locale/setDesktopBackground.dtd"> + +<dialog id="setDesktopBackground" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + onload="onLoad();" + buttons="accept,extra2" + buttoniconaccept="close" + buttonlabelaccept="&close.label;" + buttoniconextra2="apply" + buttonlabelextra2="&apply.label;" + buttonaccesskeyextra2="&apply.accesskey;" + ondialogextra2="onApply();" + title="&setDesktopBackground.title;"> + + <script src="chrome://communicator/content/setDesktopBackground.js"/> + + <hbox id="itemsBox" align="center"> + <label value="&position.label;" accesskey="&position.accesskey;"/> + <menulist id="position" + value="BACKGROUND_STRETCH" + persist="value" + oncommand="updatePosition();"> + <menupopup> + <menuitem value="BACKGROUND_TILE" label="&position.tile.label;"/> + <menuitem value="BACKGROUND_STRETCH" label="&position.stretch.label;"/> + <menuitem value="BACKGROUND_CENTER" label="&position.center.label;"/> + <menuitem value="BACKGROUND_FILL" label="&position.fill.label;"/> + <menuitem value="BACKGROUND_FIT" label="&position.fit.label;"/> + </menupopup> + </menulist> + <hbox flex="1" pack="end"> + <label value="&picker.label;" accesskey="&picker.accesskey;"/> + <colorpicker id="picker" type="button" onchange="updateColor();"/> + </hbox> + </hbox> + + <groupbox flex="1"> + <caption label="&preview.caption;"/> + <spacer id="desktop" flex="1"/> + </groupbox> +</dialog> diff --git a/comm/suite/components/shell/jar.mn b/comm/suite/components/shell/jar.mn new file mode 100644 index 0000000000..c9eb925472 --- /dev/null +++ b/comm/suite/components/shell/jar.mn @@ -0,0 +1,7 @@ +# 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/. + +comm.jar: + content/communicator/setDesktopBackground.js (content/setDesktopBackground.js) + content/communicator/setDesktopBackground.xul (content/setDesktopBackground.xul) diff --git a/comm/suite/components/shell/moz.build b/comm/suite/components/shell/moz.build new file mode 100644 index 0000000000..e508876c57 --- /dev/null +++ b/comm/suite/components/shell/moz.build @@ -0,0 +1,49 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +XPIDL_SOURCES += [ + "nsIShellService.idl", +] + +if CONFIG["MOZ_WIDGET_TOOLKIT"] == "cocoa": + XPIDL_SOURCES += [ + "nsIMacShellService.idl", + ] +elif CONFIG["MOZ_WIDGET_TOOLKIT"] == "gtk": + XPIDL_SOURCES += [ + "nsIGNOMEShellService.idl", + ] + +XPIDL_MODULE = "shellservice" + +if CONFIG["OS_ARCH"] == "WINNT": + SOURCES += [ + "nsWindowsShellService.cpp", + ] + LOCAL_INCLUDES += [ + "/other-licenses/nsis/Contrib/CityHash/cityhash", + ] +elif CONFIG["MOZ_WIDGET_TOOLKIT"] == "cocoa": + SOURCES += ["nsMacShellService.cpp"] +elif CONFIG["MOZ_WIDGET_TOOLKIT"] == "gtk": + SOURCES += ["nsGNOMEShellService.cpp"] + +if SOURCES: + EXTRA_COMPONENTS += [ + "nsSetDefault.js", + "nsSetDefault.manifest", + ] + +EXTRA_JS_MODULES += [ + "ShellService.jsm", +] + +FINAL_LIBRARY = "suite" + +if CONFIG["MOZ_WIDGET_TOOLKIT"] == "gtk": + CXXFLAGS += CONFIG["MOZ_GTK3_CFLAGS"] + +JAR_MANIFESTS += ["jar.mn"] diff --git a/comm/suite/components/shell/nsGNOMEShellService.cpp b/comm/suite/components/shell/nsGNOMEShellService.cpp new file mode 100644 index 0000000000..15ea0e1131 --- /dev/null +++ b/comm/suite/components/shell/nsGNOMEShellService.cpp @@ -0,0 +1,463 @@ +/* -*- 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 "nsCOMPtr.h" +#include "nsGNOMEShellService.h" +#include "nsShellService.h" +#include "nsIServiceManager.h" +#include "nsIFile.h" +#include "nsIProperties.h" +#include "nsDirectoryServiceDefs.h" +#include "nsIPrefService.h" +#include "prenv.h" +#include "nsString.h" +#include "nsIGIOService.h" +#include "nsIGSettingsService.h" +#include "nsIStringBundle.h" +#include "nsIOutputStream.h" +#include "nsIProcess.h" +#include "nsServiceManagerUtils.h" +#include "nsComponentManagerUtils.h" +#include "nsIImageLoadingContent.h" +#include "imgIRequest.h" +#include "imgIContainer.h" +#include "mozilla/GRefPtr.h" +#include "mozilla/Sprintf.h" +#include "mozilla/dom/Element.h" +#if defined(MOZ_WIDGET_GTK) +#include "nsImageToPixbuf.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 { + uint16_t app; + const char* protocol; + bool essential; +}; + +struct MimeTypeAssociation { + uint16_t app; + const char* mimeType; + const char* extensions; +}; + +static const ProtocolAssociation gProtocols[] = { + { nsIShellService::BROWSER, "http", true }, + { nsIShellService::BROWSER, "https", true }, + { nsIShellService::BROWSER, "ftp", false }, + { nsIShellService::BROWSER, "chrome", false }, + { nsIShellService::MAIL, "mailto", true }, + { nsIShellService::NEWS, "news", true }, + { nsIShellService::NEWS, "snews", true }, + { nsIShellService::RSS, "feed", true } +}; + +static const MimeTypeAssociation gMimeTypes[] = { + { nsIShellService::BROWSER, "text/html", "htm html shtml" }, + { nsIShellService::BROWSER, "application/xhtml+xml", "xhtml xht" }, + { nsIShellService::MAIL, "message/rfc822", "eml" }, + { nsIShellService::RSS, "application/rss+xml", "rss" } +}; + +#define kDesktopBGSchema "org.gnome.desktop.background" +#define kDesktopImageGSKey "picture-uri" +#define kDesktopOptionGSKey "picture-options" +#define kDesktopDrawBGGSKey "draw-background" +#define kDesktopColorGSKey "primary-color" + +NS_IMPL_ISUPPORTS(nsGNOMEShellService, nsIGNOMEShellService, nsIShellService) + +nsresult +GetBrandName(nsACString& aBrandName) +{ + // get the product brand name from localized strings + nsresult rv; + nsCOMPtr<nsIStringBundleService> bundleService(do_GetService("@mozilla.org/intl/stringbundle;1", &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIStringBundle> brandBundle; + rv = bundleService->CreateBundle(BRAND_PROPERTIES, getter_AddRefs(brandBundle)); + NS_ENSURE_TRUE(brandBundle, rv); + + nsAutoString brandName; + rv = brandBundle->GetStringFromName("brandShortName", brandName); + NS_ENSURE_SUCCESS(rv, rv); + + CopyUTF16toUTF8(brandName, aBrandName); + return rv; +} + +nsresult +nsGNOMEShellService::Init() +{ + nsresult rv; + + if (gfxPlatform::IsHeadless()) { + return NS_ERROR_NOT_AVAILABLE; + } + + // 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<nsIFile> appPath; + rv = NS_GetSpecialDirectory(XRE_EXECUTABLE_FILE, getter_AddRefs(appPath)); + NS_ENSURE_SUCCESS(rv, rv); + + return appPath->GetNativePath(mAppPath); +} + +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::CheckHandlerMatchesAppName(const nsACString &handler) const +{ + gint argc; + gchar** argv; + nsAutoCString command(handler); + + // The string will be something of the form: [/path/to/]application "%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); + } + + gchar *commandPath; + if (mUseLocaleFilenames) { + gchar *nativePath = + g_filename_from_utf8(command.get(), -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(command.get()); + } + + if (!commandPath) return false; + + bool matches = mAppPath.Equals(commandPath); + g_free(commandPath); + return matches; +} + +NS_IMETHODIMP +nsGNOMEShellService::IsDefaultClient(bool aStartupCheck, uint16_t aApps, + bool* aIsDefaultClient) +{ + *aIsDefaultClient = false; + + nsCOMPtr<nsIGIOService> giovfs = do_GetService(NS_GIOSERVICE_CONTRACTID); + nsAutoCString handler; + nsCOMPtr<nsIGIOMimeApp> gioApp; + + for (unsigned int i = 0; i < ArrayLength(gProtocols); i++) { + if (aApps & gProtocols[i].app) { + if (!gProtocols[i].essential) continue; + + if (giovfs) { + handler.Truncate(); + nsCOMPtr<nsIHandlerApp> handlerApp; + nsDependentCString protocol(gProtocols[i].protocol); + giovfs->GetAppForURIScheme(protocol, getter_AddRefs(handlerApp)); + gioApp = do_QueryInterface(handlerApp); + if (!gioApp) + return NS_OK; + + if (NS_SUCCEEDED(gioApp->GetCommand(handler)) && + !CheckHandlerMatchesAppName(handler)) + return NS_OK; + } + } + } + + *aIsDefaultClient = true; + + return NS_OK; +} + +NS_IMETHODIMP +nsGNOMEShellService::SetDefaultClient(bool aForAllUsers, + bool aClaimAllTypes, uint16_t aApps) +{ + nsCOMPtr<nsIGIOService> giovfs = do_GetService(NS_GIOSERVICE_CONTRACTID); + if (giovfs) { + nsresult rv; + nsCString brandName; + rv = GetBrandName(brandName); + NS_ENSURE_SUCCESS(rv, rv); + + 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, brandName, + getter_AddRefs(appInfo)); + NS_ENSURE_SUCCESS(rv, rv); + } + + // set handler for the protocols + for (unsigned int i = 0; i < ArrayLength(gProtocols); ++i) { + if (aApps & gProtocols[i].app) { + if (appInfo && (gProtocols[i].essential || aClaimAllTypes)) { + nsDependentCString protocol(gProtocols[i].protocol); + appInfo->SetAsDefaultForURIScheme(protocol); + } + } + } + + if (aClaimAllTypes) { + for (unsigned int i = 0; i < ArrayLength(gMimeTypes); i++) { + if (aApps & gMimeTypes[i].app) { + nsDependentCString type(gMimeTypes[i].mimeType); + appInfo->SetAsDefaultForMimeType(type); + nsDependentCString extensions(gMimeTypes[i].extensions); + appInfo->SetAsDefaultForFileExtensions(extensions); + } + } + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsGNOMEShellService::GetCanSetDesktopBackground(bool* aResult) +{ + // 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 + 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; +#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; + switch (aPosition) { + case BACKGROUND_TILE: + options.AssignLiteral("wallpaper"); + break; + case BACKGROUND_STRETCH: + options.AssignLiteral("stretched"); + break; + case BACKGROUND_FILL: + options.AssignLiteral("zoom"); + break; + case BACKGROUND_FIT: + options.AssignLiteral("scaled"); + break; + default: + options.AssignLiteral("centered"); + break; + } + + // Write the background file to the home directory. + nsCString filePath(PR_GetEnv("HOME")); + + nsCString brandName; + rv = GetBrandName(brandName); + NS_ENSURE_SUCCESS(rv, rv); + + // Build the file name. + filePath.Append('/'); + filePath.Append(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; + NS_ENSURE_TRUE(gdk_color_parse(background.get(), &color), 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; +} + +NS_IMETHODIMP +nsGNOMEShellService::SetDesktopBackgroundColor(uint32_t aColor) +{ + NS_ENSURE_ARG_MAX(aColor, 0xFFFFFF); + + 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); + char colorString[14]; + sprintf(colorString, "#%04x%04x%04x", red, green, blue); + + 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), + nsDependentCString(colorString)); + return NS_OK; + } + } + + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsGNOMEShellService::OpenApplicationWithURI(nsIFile* aApplication, const nsACString& aURI) +{ + nsresult rv; + nsCOMPtr<nsIProcess> process = + do_CreateInstance("@mozilla.org/process/util;1", &rv); + if (NS_FAILED(rv)) + return rv; + + rv = process->Init(aApplication); + if (NS_FAILED(rv)) + return rv; + + const nsCString& spec = PromiseFlatCString(aURI); + const char* specStr = spec.get(); + return process->Run(false, &specStr, 1); +} + +NS_IMETHODIMP +nsGNOMEShellService::GetDefaultFeedReader(nsIFile** _retval) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} diff --git a/comm/suite/components/shell/nsGNOMEShellService.h b/comm/suite/components/shell/nsGNOMEShellService.h new file mode 100644 index 0000000000..558663a2fe --- /dev/null +++ b/comm/suite/components/shell/nsGNOMEShellService.h @@ -0,0 +1,37 @@ +/* -*- 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/. */ + +#ifndef nsgnomeshellservice_h____ +#define nsgnomeshellservice_h____ + +#include "nsIGNOMEShellService.h" +#include "nsString.h" +#include "mozilla/Attributes.h" +#include "nsSuiteCID.h" + +class nsGNOMEShellService final : public nsIGNOMEShellService +{ +public: + nsGNOMEShellService() : mAppIsInPath(false) {} + + NS_DECL_ISUPPORTS + NS_DECL_NSISHELLSERVICE + NS_DECL_NSIGNOMESHELLSERVICE + + nsresult Init(); + +private: + ~nsGNOMEShellService() {} + + bool CheckHandlerMatchesAppName(const nsACString& handler) const; + + bool GetAppPathFromLauncher(); + bool mUseLocaleFilenames; + nsCString mAppPath; + bool mAppIsInPath; +}; + +#endif // nsgnomeshellservice_h____ + diff --git a/comm/suite/components/shell/nsIGNOMEShellService.idl b/comm/suite/components/shell/nsIGNOMEShellService.idl new file mode 100644 index 0000000000..64bf823c45 --- /dev/null +++ b/comm/suite/components/shell/nsIGNOMEShellService.idl @@ -0,0 +1,18 @@ +/* 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 "nsIShellService.idl" + +[scriptable, uuid(26ea117f-f1f1-4d53-8581-1322fcafa0d4)] +interface nsIGNOMEShellService : nsIShellService +{ + /** + * Used to determine whether or not to offer "Set as desktop background" + * functionality. Even if shell service is available it is not + * guaranteed that it is able to set the background for every desktop + * which is especially true for Linux with its many different desktop + * environments. + */ + readonly attribute boolean canSetDesktopBackground; +}; diff --git a/comm/suite/components/shell/nsIMacShellService.idl b/comm/suite/components/shell/nsIMacShellService.idl new file mode 100644 index 0000000000..a8e16c98a1 --- /dev/null +++ b/comm/suite/components/shell/nsIMacShellService.idl @@ -0,0 +1,14 @@ +/* -*- 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 "nsIShellService.idl" + +[scriptable, uuid(66f4a5cf-8807-43dd-8e65-9954f3c34cf2)] +interface nsIMacShellService : nsIShellService +{ + const long APPLICATION_KEYCHAIN_ACCESS = 2; + const long APPLICATION_NETWORK = 3; + const long APPLICATION_DESKTOP = 4; +}; diff --git a/comm/suite/components/shell/nsIShellService.idl b/comm/suite/components/shell/nsIShellService.idl new file mode 100644 index 0000000000..9320fc6afb --- /dev/null +++ b/comm/suite/components/shell/nsIShellService.idl @@ -0,0 +1,98 @@ +/* -*- Mode: IDL; 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 "nsISupports.idl" + +interface nsIFile; + +webidl Element; + +[scriptable, uuid(d7a19d24-9c98-4f88-b11e-52fa8c39ceea)] +interface nsIShellService : nsISupports +{ + /** + * app types we can be registered to handle + */ + const unsigned short BROWSER = 0x0001; + const unsigned short MAIL = 0x0002; + const unsigned short NEWS = 0x0004; + const unsigned short RSS = 0x0008; + + /** + * Determines whether or not SeaMonkey is the "Default Client" for the + * passed in app type. + * + * This is simply whether or not SeaMonkey is registered to handle + * the url schemes associated with the app. + * + * @param aStartupCheck true if this is the check being performed + * by the first window at startup, + * false otherwise. + * @param aApps the application types being tested (Browser, Mail, News, RSS) + */ + boolean isDefaultClient(in boolean aStartupCheck, in unsigned short aApps); + + /** + * Registers SeaMonkey as the "Default Client" for the + * passed in app types. + * + * @param aForAllUsers Whether or not SeaMonkey should attempt + * to become the default client for all + * users on a multi-user system. + * @param aClaimAllTypes Register SeaMonkey as the handler for + * additional protocols (ftp, chrome etc) + * and web documents (.html, .xhtml etc). + * @param aApps the application types being tested (Mail, News, Browser, RSS) + */ + void setDefaultClient(in boolean aForAllUsers, in boolean aClaimAllTypes, in unsigned short aApps); + + /** + * Sets the desktop background image using either the HTML <IMG> + * element supplied or the background image of the element supplied. + * + * @param aImageElement Either a HTML <IMG> element or an element with + * a background image from which to source the + * background image. + * @param aPosition How to place the image on the desktop + * @param aImageName The image name. Equivalent to the leaf name of the + * location.href. + */ + + void setDesktopBackground(in Element aElement, + in long aPosition, + in ACString aImageName); + + /** + * Flags for positioning/sizing of the Desktop Background image. + */ + const long BACKGROUND_TILE = 1; + const long BACKGROUND_STRETCH = 2; + const long BACKGROUND_CENTER = 3; + const long BACKGROUND_FILL = 4; + const long BACKGROUND_FIT = 5; + + /** + * The desktop background color, visible when no background image is + * used, or if the background image is centered and does not fill the + * entire screen. An RGB value (r << 16 | g << 8 | b) + */ + attribute unsigned long desktopBackgroundColor; + + /** + * Opens an application with a specific URI to load. + * @param application + * The application file (or bundle directory, on OS X) + * @param uri + * The uri to be loaded by the application + */ + void openApplicationWithURI(in nsIFile aApplication, in ACString aURI); + + /** + * The default system handler for web feeds + */ + readonly attribute nsIFile defaultFeedReader; +}; + diff --git a/comm/suite/components/shell/nsMacShellService.cpp b/comm/suite/components/shell/nsMacShellService.cpp new file mode 100644 index 0000000000..6aa8aa3088 --- /dev/null +++ b/comm/suite/components/shell/nsMacShellService.cpp @@ -0,0 +1,398 @@ +/* -*- 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 "nsDirectoryServiceDefs.h" +#include "nsIImageLoadingContent.h" +#include "mozilla/dom/Document.h" +#include "nsComponentManagerUtils.h" +#include "nsIContent.h" +#include "nsICookieJarSettings.h" +#include "nsILocalFileMac.h" +#include "nsIObserverService.h" +#include "nsIPrefService.h" +#include "nsIServiceManager.h" +#include "nsIStringBundle.h" +#include "nsIURL.h" +#include "nsIWebBrowserPersist.h" +#include "nsMacShellService.h" +#include "nsIProperties.h" +#include "nsServiceManagerUtils.h" +#include "nsShellService.h" +#include "nsString.h" +#include "nsIDocShell.h" +#include "nsILoadContext.h" +#include "nsIPrefService.h" +#include "mozilla/dom/Element.h" +#include "mozilla/dom/ReferrerInfo.h" + +#include <ApplicationServices/ApplicationServices.h> + +#define SAFARI_BUNDLE_IDENTIFIER "com.apple.Safari" + +using mozilla::dom::Element; + +NS_IMPL_ISUPPORTS(nsMacShellService, nsIShellService, nsIWebProgressListener) + +NS_IMETHODIMP +nsMacShellService::IsDefaultClient(bool aStartupCheck, uint16_t aApps, bool *aIsDefaultClient) +{ + *aIsDefaultClient = false; + + if (aApps & nsIShellService::BROWSER) + if(!isDefaultHandlerForProtocol(CFSTR("http"))) + return NS_OK; + if (aApps & nsIShellService::MAIL) + if(!isDefaultHandlerForProtocol(CFSTR("mailto"))) + return NS_OK; + if (aApps & nsIShellService::NEWS) + if(!isDefaultHandlerForProtocol(CFSTR("news"))) + return NS_OK; + if (aApps & nsIShellService::RSS) + if(!isDefaultHandlerForProtocol(CFSTR("feed"))) + return NS_OK; + + *aIsDefaultClient = true; + + return NS_OK; +} + +NS_IMETHODIMP +nsMacShellService::SetDefaultClient(bool aForAllUsers, + bool aClaimAllTypes, uint16_t aApps) +{ + // Note: We don't support aForAllUsers on macOS. + + CFStringRef suiteID = ::CFBundleGetIdentifier(::CFBundleGetMainBundle()); + if (!suiteID) + return NS_ERROR_FAILURE; + + if (aApps & nsIShellService::BROWSER) + { + if (::LSSetDefaultHandlerForURLScheme(CFSTR("http"), suiteID) != noErr) + return NS_ERROR_FAILURE; + if (::LSSetDefaultHandlerForURLScheme(CFSTR("https"), suiteID) != noErr) + return NS_ERROR_FAILURE; + if (::LSSetDefaultRoleHandlerForContentType(kUTTypeHTML, kLSRolesAll, suiteID) != noErr) + return NS_ERROR_FAILURE; + if (::LSSetDefaultRoleHandlerForContentType(CFSTR("public.xhtml"), kLSRolesAll, suiteID) != noErr) + return NS_ERROR_FAILURE; + } + + if (aApps & nsIShellService::MAIL) + if (::LSSetDefaultHandlerForURLScheme(CFSTR("mailto"), suiteID) != noErr) + return NS_ERROR_FAILURE; + if (aApps & nsIShellService::NEWS) + if (::LSSetDefaultHandlerForURLScheme(CFSTR("news"), suiteID) != noErr) + return NS_ERROR_FAILURE; + if (aApps & nsIShellService::RSS) + if (::LSSetDefaultHandlerForURLScheme(CFSTR("feed"), suiteID) != noErr) + return NS_ERROR_FAILURE; + + return NS_OK; +} + +bool +nsMacShellService::isDefaultHandlerForProtocol(CFStringRef aScheme) +{ + bool isDefault = false; + + CFStringRef suiteID = ::CFBundleGetIdentifier(::CFBundleGetMainBundle()); + if (!suiteID) + { + // CFBundleGetIdentifier is expected to return nullptr only if the specified + // bundle doesn't have a bundle identifier in its dictionary. In this case, + // that means a failure, since our bundle does have an identifier. + return isDefault; + } + + // Get the default handler's bundle ID for the scheme. + CFStringRef defaultHandlerID = ::LSCopyDefaultHandlerForURLScheme(aScheme); + if (defaultHandlerID) + { + // The handler ID in LaunchServices is in all lower case, but the bundle + // identifier could have upper case characters. So we're using + // CFStringCompare with the kCFCompareCaseInsensitive option here. + isDefault = ::CFStringCompare(suiteID, defaultHandlerID, + kCFCompareCaseInsensitive) == kCFCompareEqualTo; + ::CFRelease(defaultHandlerID); + } + + return isDefault; +} + +NS_IMETHODIMP +nsMacShellService::SetDesktopBackground(Element* aElement, + int32_t aPosition, + const nsACString& aImageName) +{ + // Note: We don't support aPosition on OS X. + + // Get the image URI: + nsresult rv; + nsCOMPtr<nsIImageLoadingContent> imageContent = do_QueryInterface(aElement, &rv); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr<nsIURI> imageURI; + rv = imageContent->GetCurrentURI(getter_AddRefs(imageURI)); + NS_ENSURE_SUCCESS(rv, rv); + + nsIURI *docURI = aElement->OwnerDoc()->GetDocumentURI(); + if (!docURI) + return NS_ERROR_FAILURE; + + nsCOMPtr<nsIProperties> fileLocator + (do_GetService("@mozilla.org/file/directory_service;1", &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + // Get the current user's "Pictures" folder (That's ~/Pictures): + fileLocator->Get(NS_OSX_PICTURE_DOCUMENTS_DIR, NS_GET_IID(nsIFile), + getter_AddRefs(mBackgroundFile)); + if (!mBackgroundFile) + return NS_ERROR_OUT_OF_MEMORY; + + nsAutoString fileNameUnicode; + CopyUTF8toUTF16(aImageName, fileNameUnicode); + + // and add the image file name itself: + mBackgroundFile->Append(fileNameUnicode); + + // Download the image; the desktop background will be set in OnStateChange(): + nsCOMPtr<nsIWebBrowserPersist> wbp + (do_CreateInstance("@mozilla.org/embedding/browser/nsWebBrowserPersist;1", &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + uint32_t flags = nsIWebBrowserPersist::PERSIST_FLAGS_NO_CONVERSION | + nsIWebBrowserPersist::PERSIST_FLAGS_REPLACE_EXISTING_FILES | + nsIWebBrowserPersist::PERSIST_FLAGS_FROM_CACHE; + + wbp->SetPersistFlags(flags); + wbp->SetProgressListener(this); + + nsCOMPtr<nsILoadContext> loadContext; + nsCOMPtr<nsISupports> container = aElement->OwnerDoc()->GetContainer(); + nsCOMPtr<nsIDocShell> docShell = do_QueryInterface(container); + if (docShell) + { + loadContext = do_QueryInterface(docShell); + } + + auto referrerInfo = mozilla::MakeRefPtr<mozilla::dom::ReferrerInfo>(*aElement); + nsCOMPtr<nsICookieJarSettings> cookieJarSettings = + aElement->OwnerDoc()->CookieJarSettings(); + return wbp->SaveURI(imageURI, aElement->NodePrincipal(), 0, referrerInfo, + cookieJarSettings, nullptr, nullptr, mBackgroundFile, + nsIContentPolicy::TYPE_IMAGE, loadContext); +} + +NS_IMETHODIMP +nsMacShellService::OnProgressChange(nsIWebProgress* aWebProgress, + nsIRequest* aRequest, + int32_t aCurSelfProgress, + int32_t aMaxSelfProgress, + int32_t aCurTotalProgress, + int32_t aMaxTotalProgress) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsMacShellService::OnLocationChange(nsIWebProgress* aWebProgress, + nsIRequest* aRequest, + nsIURI* aLocation, + uint32_t aFlags) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsMacShellService::OnStatusChange(nsIWebProgress* aWebProgress, + nsIRequest* aRequest, + nsresult aStatus, + const char16_t* aMessage) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsMacShellService::OnSecurityChange(nsIWebProgress* aWebProgress, + nsIRequest* aRequest, + uint32_t aState) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsMacShellService::OnContentBlockingEvent(nsIWebProgress* aWebProgress, + nsIRequest* aRequest, + uint32_t aEvent) { + return NS_OK; +} + +NS_IMETHODIMP +nsMacShellService::OnStateChange(nsIWebProgress* aWebProgress, + nsIRequest* aRequest, + uint32_t aStateFlags, + nsresult aStatus) +{ + if (aStateFlags & STATE_STOP) + { + bool exists = false; + mBackgroundFile->Exists(&exists); + if (!exists) + return NS_OK; + + nsAutoCString nativePath; + mBackgroundFile->GetNativePath(nativePath); + + AEDesc tAEDesc = { typeNull, nil }; + OSErr err = noErr; + AliasHandle aliasHandle = nil; + FSRef pictureRef; + OSStatus status; + + // Convert the path into a FSRef: + status = ::FSPathMakeRef((const UInt8*)nativePath.get(), &pictureRef, + nullptr); + if (status == noErr) + { + err = ::FSNewAlias(nil, &pictureRef, &aliasHandle); + if (err == noErr && aliasHandle == nil) + err = paramErr; + + if (err == noErr) + { + // We need the descriptor (based on the picture file reference) + // for the 'Set Desktop Picture' apple event. + char handleState = ::HGetState((Handle)aliasHandle); + ::HLock((Handle)aliasHandle); + err = ::AECreateDesc(typeAlias, *aliasHandle, + GetHandleSize((Handle)aliasHandle), &tAEDesc); + // Unlock the alias handler: + ::HSetState((Handle)aliasHandle, handleState); + ::DisposeHandle((Handle)aliasHandle); + } + if (err == noErr) + { + AppleEvent tAppleEvent; + OSType sig = 'MACS'; + AEBuildError tAEBuildError; + // Create a 'Set Desktop Picture' Apple Event: + err = ::AEBuildAppleEvent(kAECoreSuite, kAESetData, typeApplSignature, + &sig, sizeof(OSType), kAutoGenerateReturnID, + kAnyTransactionID, &tAppleEvent, &tAEBuildError, + "'----':'obj '{want:type (prop),form:prop" \ + ",seld:type('dpic'),from:'null'()},data:(@)", + &tAEDesc); + if (err == noErr) + { + AppleEvent reply = { typeNull, nil }; + // Send the event we built, the reply event isn't necessary: + err = ::AESend(&tAppleEvent, &reply, kAENoReply, kAENormalPriority, + kNoTimeOut, nil, nil); + ::AEDisposeDesc(&tAppleEvent); + } + } + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsMacShellService::GetDesktopBackgroundColor(uint32_t *aColor) +{ + // This method and |SetDesktopBackgroundColor| has no meaning on macOS. + // The mac desktop preferences UI uses pictures for the few solid colors it + // supports. + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsMacShellService::SetDesktopBackgroundColor(uint32_t aColor) +{ + // This method and |GetDesktopBackgroundColor| has no meaning on macOS. + // The mac desktop preferences UI uses pictures for the few solid colors it + // supports. + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsMacShellService::OpenApplicationWithURI(nsIFile* aApplication, const nsACString& aURI) +{ + nsCOMPtr<nsILocalFileMac> lfm(do_QueryInterface(aApplication)); + CFURLRef appURL; + nsresult rv = lfm->GetCFURL(&appURL); + if (NS_FAILED(rv)) + return rv; + + const nsCString& spec = PromiseFlatCString(aURI); + const UInt8* uriString = (const UInt8*)spec.get(); + CFURLRef uri = ::CFURLCreateWithBytes(nullptr, uriString, aURI.Length(), + kCFStringEncodingUTF8, nullptr); + if (!uri) + return NS_ERROR_OUT_OF_MEMORY; + + CFArrayRef uris = ::CFArrayCreate(nullptr, (const void**)&uri, 1, nullptr); + if (!uris) + { + ::CFRelease(uri); + return NS_ERROR_OUT_OF_MEMORY; + } + + LSLaunchURLSpec launchSpec; + launchSpec.appURL = appURL; + launchSpec.itemURLs = uris; + launchSpec.passThruParams = nullptr; + launchSpec.launchFlags = kLSLaunchDefaults; + launchSpec.asyncRefCon = nullptr; + + OSErr err = ::LSOpenFromURLSpec(&launchSpec, nullptr); + + ::CFRelease(uris); + ::CFRelease(uri); + + return err != noErr ? NS_ERROR_FAILURE : NS_OK; +} + +NS_IMETHODIMP +nsMacShellService::GetDefaultFeedReader(nsIFile** _retval) +{ + nsresult rv = NS_ERROR_FAILURE; + *_retval = nullptr; + + CFStringRef defaultHandlerID = ::LSCopyDefaultHandlerForURLScheme(CFSTR("feed")); + if (!defaultHandlerID) + { + defaultHandlerID = ::CFStringCreateWithCString(kCFAllocatorDefault, + SAFARI_BUNDLE_IDENTIFIER, + kCFStringEncodingASCII); + } + + CFURLRef defaultHandlerURL = nullptr; + OSStatus status = ::LSFindApplicationForInfo(kLSUnknownCreator, + defaultHandlerID, + nullptr, // inName + nullptr, // outAppRef + &defaultHandlerURL); + + if (status == noErr && defaultHandlerURL) + { + nsCOMPtr<nsILocalFileMac> defaultReader = + do_CreateInstance("@mozilla.org/file/local;1", &rv); + if (NS_SUCCEEDED(rv)) + { + rv = defaultReader->InitWithCFURL(defaultHandlerURL); + if (NS_SUCCEEDED(rv)) + { + NS_ADDREF(*_retval = defaultReader); + } + } + + ::CFRelease(defaultHandlerURL); + } + + ::CFRelease(defaultHandlerID); + + return rv; +} diff --git a/comm/suite/components/shell/nsMacShellService.h b/comm/suite/components/shell/nsMacShellService.h new file mode 100644 index 0000000000..4fa92513c2 --- /dev/null +++ b/comm/suite/components/shell/nsMacShellService.h @@ -0,0 +1,36 @@ +/* -*- 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/. */ + +#ifndef nsmacshellservice_h____ +#define nsmacshellservice_h____ + +#include "nsIMacShellService.h" +#include "nsIWebProgressListener.h" +#include "nsIFile.h" +#include "nsCOMPtr.h" +#include "mozilla/Attributes.h" +#include "nsSuiteCID.h" + +#include <CoreFoundation/CoreFoundation.h> + +class nsMacShellService final : public nsIShellService, + public nsIWebProgressListener +{ +public: + nsMacShellService() {}; + + NS_DECL_ISUPPORTS + NS_DECL_NSISHELLSERVICE + NS_DECL_NSIWEBPROGRESSLISTENER + +protected: + ~nsMacShellService() {} + bool isDefaultHandlerForProtocol(CFStringRef aScheme); + +private: + nsCOMPtr<nsIFile> mBackgroundFile; +}; + +#endif diff --git a/comm/suite/components/shell/nsSetDefault.js b/comm/suite/components/shell/nsSetDefault.js new file mode 100644 index 0000000000..4d35dc4531 --- /dev/null +++ b/comm/suite/components/shell/nsSetDefault.js @@ -0,0 +1,53 @@ +/* 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/. */ + +/* + * This component handles the startup command line arguments of the form: + * -setDefaultBrowser + * -setDefaultMail + * -setDefaultNews + * -setDefaultFeed + */ + +const nsICommandLineHandler = Ci.nsICommandLineHandler; +var {XPCOMUtils} = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm"); + +function nsSetDefault() { +} + +nsSetDefault.prototype = { + handle: function nsSetDefault_handle(aCmdline) { + if (aCmdline.handleFlag("setDefaultBrowser", false)) { + var shell = Cc["@mozilla.org/suite/shell-service;1"] + .getService(Ci.nsIShellService); + shell.setDefaultClient(true, true, Ci.nsIShellService.BROWSER); + } + else if (aCmdline.handleFlag("setDefaultMail", false)) { + var shell = Cc["@mozilla.org/suite/shell-service;1"] + .getService(Ci.nsIShellService); + shell.setDefaultClient(true, true, Ci.nsIShellService.MAIL); + } + else if (aCmdline.handleFlag("setDefaultNews", false)) { + var shell = Cc["@mozilla.org/suite/shell-service;1"] + .getService(Ci.nsIShellService); + shell.setDefaultClient(true, true, Ci.nsIShellService.NEWS); + } + else if (aCmdline.handleFlag("setDefaultFeed", false)) { + var shell = Cc["@mozilla.org/suite/shell-service;1"] + .getService(Ci.nsIShellService); + shell.setDefaultClient(true, true, Ci.nsIShellService.RSS); + } + }, + + helpInfo: " -setDefaultBrowser Set this app as the default browser client.\n" + + " -setDefaultMail Set this app as the default mail client.\n" + + " -setDefaultNews Set this app as the default newsreader.\n" + + " -setDefaultFeed Set this app as the default feedreader.\n", + + classID: Components.ID("{a3d5b950-690a-491f-a881-2c2cdcd241cb}"), + QueryInterface: XPCOMUtils.generateQI([nsICommandLineHandler]) +} + +var NSGetFactory = XPCOMUtils.generateNSGetFactory([nsSetDefault]); + diff --git a/comm/suite/components/shell/nsSetDefault.manifest b/comm/suite/components/shell/nsSetDefault.manifest new file mode 100644 index 0000000000..7d4a585bc7 --- /dev/null +++ b/comm/suite/components/shell/nsSetDefault.manifest @@ -0,0 +1,3 @@ +component {a3d5b950-690a-491f-a881-2c2cdcd241cb} nsSetDefault.js +contract @mozilla.org/suite/default-browser-clh;1 {a3d5b950-690a-491f-a881-2c2cdcd241cb} +category command-line-handler m-setdefault @mozilla.org/suite/default-browser-clh;1 diff --git a/comm/suite/components/shell/nsShellService.h b/comm/suite/components/shell/nsShellService.h new file mode 100644 index 0000000000..f5275a6556 --- /dev/null +++ b/comm/suite/components/shell/nsShellService.h @@ -0,0 +1,11 @@ +/* -*- 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 "nsIShellService.h" + +#define PREF_CHECKDEFAULTCLIENT "shell.checkDefaultClient" + +#define SHELLSERVICE_PROPERTIES "chrome://communicator/locale/shellservice.properties" +#define BRAND_PROPERTIES "chrome://branding/locale/brand.properties" diff --git a/comm/suite/components/shell/nsWindowsShellService.cpp b/comm/suite/components/shell/nsWindowsShellService.cpp new file mode 100644 index 0000000000..7cdf1cbd88 --- /dev/null +++ b/comm/suite/components/shell/nsWindowsShellService.cpp @@ -0,0 +1,793 @@ +/* -*- 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 "nsWindowsShellService.h" + +#include "imgIContainer.h" +#include "imgIRequest.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/RefPtr.h" +#include "nsIContent.h" +#include "nsIImageLoadingContent.h" +#include "nsIOutputStream.h" +#include "nsIPrefService.h" +#include "nsIPrefLocalizedString.h" +#include "nsIServiceManager.h" +#include "nsIStringBundle.h" +#include "nsNetUtil.h" +#include "nsServiceManagerUtils.h" +#include "nsShellService.h" +#include "nsIProcess.h" +#include "nsICategoryManager.h" +#include "nsDirectoryServiceUtils.h" +#include "nsAppDirectoryServiceDefs.h" +#include "nsDirectoryServiceDefs.h" +#include "nsIWindowsRegKey.h" +#include "nsUnicharUtils.h" +#include "nsIURLFormatter.h" +#include "nsXULAppAPI.h" +#include "mozilla/WindowsVersion.h" +#include "mozilla/dom/Element.h" + +#include "windows.h" +#include "shellapi.h" + +#ifdef _WIN32_WINNT +#undef _WIN32_WINNT +#endif +#define _WIN32_WINNT 0x0600 +#define INITGUID +#include <shlobj.h> + +#ifndef MAX_BUF +#define MAX_BUF 4096 +#endif + +#define REG_SUCCEEDED(val) \ + (val == ERROR_SUCCESS) + +#define REG_FAILED(val) \ + (val != ERROR_SUCCESS) + +using namespace mozilla; +using namespace mozilla::gfx; + +NS_IMPL_ISUPPORTS(nsWindowsShellService, nsIShellService) + +static nsresult +OpenKeyForReading(HKEY aKeyRoot, const wchar_t* aKeyName, HKEY* aKey) +{ + DWORD res = ::RegOpenKeyExW(aKeyRoot, aKeyName, 0, KEY_READ, aKey); + switch (res) { + case ERROR_SUCCESS: + break; + case ERROR_ACCESS_DENIED: + return NS_ERROR_FILE_ACCESS_DENIED; + case ERROR_FILE_NOT_FOUND: + return NS_ERROR_NOT_AVAILABLE; + } + + return NS_OK; +} + +/////////////////////////////////////////////////////////////////////////////// +// Default SeaMonkey OS integration Registry Settings +// Note: Some settings only exist when using the installer! +// The setting of SeaMonkey as default application is made by a helper +// application since writing those values may require elevation. +// +// Default Browser settings: +// - File Extension Mappings +// ----------------------- +// The following file extensions: +// .htm .html .shtml .xht .xhtml +// are mapped like so: +// +// HKCU\SOFTWARE\Classes\.<ext>\ (default) REG_SZ SeaMonkeyHTML +// +// as aliases to the class: +// +// HKCU\SOFTWARE\Classes\SeaMonkeyHTML\ +// DefaultIcon (default) REG_SZ <appfolder>\chrome\icons\default\html-file.ico +// shell\open\command (default) REG_SZ <apppath> -url "%1" +// +// - Windows Vista Protocol Handler +// +// HKCU\SOFTWARE\Classes\SeaMonkeyURL\(default) REG_SZ <appname> URL +// EditFlags REG_DWORD 2 +// FriendlyTypeName REG_SZ <appname> URL +// DefaultIcon (default) REG_SZ <apppath>,1 +// shell\open\command (default) REG_SZ <apppath> -requestPending -osint -url "%1" +// shell\open\ddeexec (default) REG_SZ "%1",,0,0,,,, +// shell\open\ddeexec NoActivateHandler REG_SZ +// \Application (default) REG_SZ SeaMonkey +// \Topic (default) REG_SZ WWW_OpenURL +// +// - Protocol Mappings +// ----------------- +// The following protocols: +// HTTP, HTTPS, FTP +// are mapped like so: +// +// HKCU\SOFTWARE\Classes\<protocol>\ +// DefaultIcon (default) REG_SZ <apppath>,0 +// shell\open\command (default) REG_SZ <apppath> -requestPending -osint -url "%1" +// shell\open\ddeexec (default) REG_SZ "%1",,0,0,,,, +// shell\open\ddeexec NoActivateHandler REG_SZ +// \Application (default) REG_SZ SeaMonkey +// \Topic (default) REG_SZ WWW_OpenURL +// +// - Windows Start Menu (Win2K SP2, XP SP1, and newer) +// ------------------------------------------------- +// The following keys are set to make SeaMonkey appear in the Start Menu as the +// browser: +// +// HKCU\SOFTWARE\Clients\StartMenuInternet\SEAMONKEY.EXE\ +// (default) REG_SZ <appname> +// DefaultIcon (default) REG_SZ <apppath>,0 +// InstallInfo HideIconsCommand REG_SZ <uninstpath> /HideShortcuts +// InstallInfo IconsVisible REG_DWORD 1 +// InstallInfo ReinstallCommand REG_SZ <uninstpath> /SetAsDefaultAppGlobal +// InstallInfo ShowIconsCommand REG_SZ <uninstpath> /ShowShortcuts +// shell\open\command (default) REG_SZ <apppath> +// shell\properties (default) REG_SZ <appname> &Preferences +// shell\properties\command (default) REG_SZ <apppath> -preferences +// shell\safemode (default) REG_SZ <appname> &Safe Mode +// shell\safemode\command (default) REG_SZ <apppath> -safe-mode +// +// +// +// Default Mail&News settings +// +// - File Extension Mappings +// ----------------------- +// The following file extension: +// .eml +// is mapped like this: +// +// HKCU\SOFTWARE\Classes\.eml (default) REG_SZ SeaMonkeyEML +// +// That aliases to this class: +// HKCU\SOFTWARE\Classes\SeaMonkeyEML\ (default) REG_SZ SeaMonkey (Mail) Document +// FriendlyTypeName REG_SZ SeaMonkey (Mail) Document +// DefaultIcon (default) REG_SZ <appfolder>\chrome\icons\default\html-file.ico +// shell\open\command (default) REG_SZ <apppath> "%1" +// +// - Windows Vista Protocol Handler +// +// HKCU\SOFTWARE\Classes\SeaMonkeyCOMPOSE (default) REG_SZ SeaMonkey (Mail) URL +// DefaultIcon REG_SZ <apppath>,0 +// EditFlags REG_DWORD 2 +// shell\open\command (default) REG_SZ <apppath> -osint -compose "%1" +// +// HKCU\SOFTWARE\Classes\SeaMonkeyNEWS (default) REG_SZ SeaMonkey (News) URL +// DefaultIcon REG_SZ <apppath>,0 +// EditFlags REG_DWORD 2 +// shell\open\command (default) REG_SZ <apppath> -osint -news "%1" +// +// +// - Protocol Mappings +// ----------------- +// The following protocol: +// mailto +// is mapped like this: +// +// HKCU\SOFTWARE\Classes\mailto\ (default) REG_SZ SeaMonkey (Mail) URL +// EditFlags REG_DWORD 2 +// URL Protocol REG_SZ +// DefaultIcon (default) REG_SZ <apppath>,0 +// shell\open\command (default) REG_SZ <apppath> -osint -compose "%1" +// +// The following protocols: +// news,nntp,snews +// are mapped like this: +// +// HKCU\SOFTWARE\Classes\<protocol>\ (default) REG_SZ SeaMonkey (News) URL +// EditFlags REG_DWORD 2 +// URL Protocol REG_SZ +// DefaultIcon (default) REG_SZ <appath>,0 +// shell\open\command (default) REG_SZ <appath> -osint -news "%1" +// +// - Windows Start Menu (Win2K SP2, XP SP1, and newer) +// ------------------------------------------------- +// The following keys are set to make SeaMonkey appear in the Start Menu as +// the default mail program: +// +// HKCU\SOFTWARE\Clients\Mail\SeaMonkey +// (default) REG_SZ <appname> +// DLLPath REG_SZ <appfolder>\mozMapi32.dll +// DefaultIcon (default) REG_SZ <apppath>,0 +// InstallInfo HideIconsCommand REG_SZ <uninstpath> /HideShortcuts +// InstallInfo ReinstallCommand REG_SZ <uninstpath> /SetAsDefaultAppGlobal +// InstallInfo ShowIconsCommand REG_SZ <uninstpath> /ShowShortcuts +// shell\open\command (default) REG_SZ <apppath> -mail +// shell\properties (default) REG_SZ <appname> &Preferences +// shell\properties\command (default) REG_SZ <apppath> -preferences +// +// Also set SeaMonkey as News reader (Usenet), though Windows does currently +// not expose a default news reader to UI. Applications like Outlook +// also add themselves to this registry key +// +// HKCU\SOFTWARE\Clients\News\SeaMonkey +// (default) REG_SZ <appname> +// DLLPath REG_SZ <appfolder>\mozMapi32.dll +// DefaultIcon (default) REG_SZ <apppath>,0 +// shell\open\command (default) REG_SZ <apppath> -news +// +/////////////////////////////////////////////////////////////////////////////// + + +typedef enum { + NO_SUBSTITUTION = 0x00, + APP_PATH_SUBSTITUTION = 0x01 +} SettingFlags; + +#define APP_REG_NAME L"SeaMonkey" +// APP_REG_NAME_MAIL and APP_REG_NAME_NEWS should be kept in synch with +// AppRegNameMail and AppRegNameNews in the installer file: defines.nsi.in +#define APP_REG_NAME_MAIL L"SeaMonkey (Mail)" +#define APP_REG_NAME_NEWS L"SeaMonkey (News)" +#define CLS_HTML "SeaMonkeyHTML" +#define CLS_URL "SeaMonkeyURL" +#define CLS_EML "SeaMonkeyEML" +#define CLS_MAILTOURL "SeaMonkeyCOMPOSE" +#define CLS_NEWSURL "SeaMonkeyNEWS" +#define CLS_FEEDURL "SeaMonkeyFEED" +#define SMI "SOFTWARE\\Clients\\StartMenuInternet\\" +#define DI "\\DefaultIcon" +#define II "\\InstallInfo" +#define SOP "\\shell\\open\\command" + +#define VAL_ICON "%APPPATH%,0" +#define VAL_HTML_OPEN "\"%APPPATH%\" -url \"%1\"" +#define VAL_URL_OPEN "\"%APPPATH%\" -requestPending -osint -url \"%1\"" +#define VAL_MAIL_OPEN "\"%APPPATH%\" \"%1\"" + +#define MAKE_KEY_NAME1(PREFIX, MID) \ + PREFIX MID + +// The DefaultIcon registry key value should never be used (e.g. NON_ESSENTIAL) +// when checking if SeaMonkey is the default browser since other applications +// (e.g. MS Office) may modify the DefaultIcon registry key value to add Icon +// Handlers. +// see http://msdn2.microsoft.com/en-us/library/aa969357.aspx for more info. +static SETTING gBrowserSettings[] = { + // File Extension Class - as of 1.8.1.2 the value for VAL_URL_OPEN is also + // checked for CLS_HTML since SeaMonkey should also own opening local files + // when set as the default browser. + { MAKE_KEY_NAME1(CLS_HTML, SOP), "", VAL_HTML_OPEN, APP_PATH_SUBSTITUTION }, + + // Protocol Handler Class - for Vista and above + { MAKE_KEY_NAME1(CLS_URL, SOP), "", VAL_URL_OPEN, APP_PATH_SUBSTITUTION }, + + // Protocol Handlers + { MAKE_KEY_NAME1("HTTP", DI), "", VAL_ICON, APP_PATH_SUBSTITUTION }, + { MAKE_KEY_NAME1("HTTP", SOP), "", VAL_URL_OPEN, APP_PATH_SUBSTITUTION }, + { MAKE_KEY_NAME1("HTTPS", DI), "", VAL_ICON, APP_PATH_SUBSTITUTION }, + { MAKE_KEY_NAME1("HTTPS", SOP), "", VAL_URL_OPEN, APP_PATH_SUBSTITUTION } + + // These values must be set by hand, since they contain localized strings. + // seamonkey.exe\shell\properties (default) REG_SZ SeaMonkey &Preferences + // seamonkey.exe\shell\safemode (default) REG_SZ SeaMonkey &Safe Mode +}; + + static SETTING gMailSettings[] = { + // File Extension Aliases + { ".eml", "", CLS_EML, NO_SUBSTITUTION }, + // File Extension Class + { MAKE_KEY_NAME1(CLS_EML, SOP), "", VAL_MAIL_OPEN, APP_PATH_SUBSTITUTION}, + + // Protocol Handler Class - for Vista and above + { MAKE_KEY_NAME1(CLS_MAILTOURL, SOP), "", "\"%APPPATH%\" -osint -compose \"%1\"", APP_PATH_SUBSTITUTION }, + + // Protocol Handlers + { MAKE_KEY_NAME1("mailto", SOP), "", "\"%APPPATH%\" -osint -compose \"%1\"", APP_PATH_SUBSTITUTION } + }; + + static SETTING gNewsSettings[] = { + // Protocol Handler Class - for Vista and above + { MAKE_KEY_NAME1(CLS_NEWSURL, SOP), "", "\"%APPPATH%\" -osint -mail \"%1\"", APP_PATH_SUBSTITUTION }, + + // Protocol Handlers + { MAKE_KEY_NAME1("news", SOP), "", "\"%APPPATH%\" -osint -mail \"%1\"", APP_PATH_SUBSTITUTION }, + { MAKE_KEY_NAME1("nntp", SOP), "", "\"%APPPATH%\" -osint -mail \"%1\"", APP_PATH_SUBSTITUTION }, +}; + + static SETTING gFeedSettings[] = { + // Protocol Handler Class - for Vista and above + { MAKE_KEY_NAME1(CLS_FEEDURL, SOP), "", "\"%APPPATH%\" -osint -mail \"%1\"", APP_PATH_SUBSTITUTION }, + + // Protocol Handlers + { MAKE_KEY_NAME1("feed", SOP), "", "\"%APPPATH%\" -osint -mail \"%1\"", APP_PATH_SUBSTITUTION }, +}; + +nsresult +GetHelperPath(nsString& aPath) +{ + nsresult rv; + nsCOMPtr<nsIProperties> directoryService = + do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIFile> appHelper; + rv = directoryService->Get(XRE_EXECUTABLE_FILE, + NS_GET_IID(nsIFile), + getter_AddRefs(appHelper)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = appHelper->SetNativeLeafName("uninstall"_ns); + NS_ENSURE_SUCCESS(rv, rv); + + rv = appHelper->AppendNative("helper.exe"_ns); + NS_ENSURE_SUCCESS(rv, rv); + + rv = appHelper->GetPath(aPath); + + aPath.Insert('"', 0); + aPath.Append('"'); + + return rv; +} + +nsresult +LaunchHelper(const nsString& aPath) +{ + STARTUPINFOW si = {sizeof(si), 0}; + PROCESS_INFORMATION pi = {0}; + + BOOL ok = CreateProcessW(nullptr, (LPWSTR)aPath.get(), nullptr, nullptr, + FALSE, 0, nullptr, nullptr, &si, &pi); + + if (!ok) + return NS_ERROR_FAILURE; + + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + return NS_OK; +} + +/* helper routine. Iterate over the passed in settings object, + testing each key to see if we are handling it. +*/ +bool +nsWindowsShellService::TestForDefault(SETTING aSettings[], int32_t aSize) +{ + wchar_t currValue[MAX_BUF]; + SETTING* end = aSettings + aSize; + for (SETTING * settings = aSettings; settings < end; ++settings) { + NS_ConvertUTF8toUTF16 dataLongPath(settings->valueData); + NS_ConvertUTF8toUTF16 dataShortPath(settings->valueData); + NS_ConvertUTF8toUTF16 key(settings->keyName); + NS_ConvertUTF8toUTF16 value(settings->valueName); + if (settings->flags & APP_PATH_SUBSTITUTION) { + int32_t offset = dataLongPath.Find(u"%APPPATH%"); + dataLongPath.Replace(offset, 9, mAppLongPath); + // Remove the quotes around %APPPATH% in VAL_OPEN for short paths + int32_t offsetQuoted = dataShortPath.Find(u"\"%APPPATH%\""); + if (offsetQuoted != -1) + dataShortPath.Replace(offsetQuoted, 11, mAppShortPath); + else + dataShortPath.Replace(offset, 9, mAppShortPath); + } + + ::ZeroMemory(currValue, sizeof(currValue)); + HKEY theKey; + nsresult rv = OpenKeyForReading(HKEY_CLASSES_ROOT, key.get(), &theKey); + if (NS_FAILED(rv)) + // Key does not exist + return false; + + DWORD len = sizeof currValue; + DWORD res = ::RegQueryValueExW(theKey, value.get(), + nullptr, nullptr, (LPBYTE)currValue, &len); + // Close the key we opened. + ::RegCloseKey(theKey); + if (REG_FAILED(res) || + _wcsicmp(dataLongPath.get(), currValue) && + _wcsicmp(dataShortPath.get(), currValue)) { + // Key wasn't set, or was set to something else (something else became the default client) + return false; + } + } + + return true; +} + +nsresult nsWindowsShellService::Init() +{ + wchar_t appPath[MAX_BUF]; + if (!::GetModuleFileNameW(0, appPath, MAX_BUF)) + return NS_ERROR_FAILURE; + + mAppLongPath.Assign(appPath); + + // Support short path to the exe so if it is already set the user is not + // prompted to set the default mail client again. + if (!::GetShortPathNameW(appPath, appPath, MAX_BUF)) + return NS_ERROR_FAILURE; + + mAppShortPath.Assign(appPath); + + return NS_OK; +} + +bool +nsWindowsShellService::IsDefaultClientVista(uint16_t aApps, bool* aIsDefaultClient) +{ + IApplicationAssociationRegistration* pAAR; + + HRESULT hr = CoCreateInstance(CLSID_ApplicationAssociationRegistration, + nullptr, + CLSCTX_INPROC, + IID_IApplicationAssociationRegistration, + (void**)&pAAR); + + if (SUCCEEDED(hr)) { + BOOL isDefaultBrowser = true; + BOOL isDefaultMail = true; + BOOL isDefaultNews = true; + if (aApps & nsIShellService::BROWSER) + pAAR->QueryAppIsDefaultAll(AL_EFFECTIVE, APP_REG_NAME, &isDefaultBrowser); + if (aApps & nsIShellService::MAIL) + pAAR->QueryAppIsDefaultAll(AL_EFFECTIVE, APP_REG_NAME_MAIL, &isDefaultMail); + if (aApps & nsIShellService::NEWS) + pAAR->QueryAppIsDefaultAll(AL_EFFECTIVE, APP_REG_NAME_NEWS, &isDefaultNews); + + *aIsDefaultClient = isDefaultBrowser && isDefaultNews && isDefaultMail; + + pAAR->Release(); + return true; + } + return false; +} + +NS_IMETHODIMP +nsWindowsShellService::IsDefaultClient(bool aStartupCheck, uint16_t aApps, bool *aIsDefaultClient) +{ + *aIsDefaultClient = true; + + // for each type, check if it is the default app + // browser check needs to be at the top + if (aApps & nsIShellService::BROWSER) { + *aIsDefaultClient &= TestForDefault(gBrowserSettings, sizeof(gBrowserSettings)/sizeof(SETTING)); + // Only check if this app is default on Vista if the previous checks + // indicate that this app is the default. + if (*aIsDefaultClient) + IsDefaultClientVista(nsIShellService::BROWSER, aIsDefaultClient); + } + if (aApps & nsIShellService::MAIL) { + *aIsDefaultClient &= TestForDefault(gMailSettings, sizeof(gMailSettings)/sizeof(SETTING)); + // Only check if this app is default on Vista if the previous checks + // indicate that this app is the default. + if (*aIsDefaultClient) + IsDefaultClientVista(nsIShellService::MAIL, aIsDefaultClient); + } + if (aApps & nsIShellService::NEWS) { + *aIsDefaultClient &= TestForDefault(gNewsSettings, sizeof(gNewsSettings)/sizeof(SETTING)); + // Only check if this app is default on Vista if the previous checks + // indicate that this app is the default. + if (*aIsDefaultClient) + IsDefaultClientVista(nsIShellService::NEWS, aIsDefaultClient); + } + + return NS_OK; +} + + +NS_IMETHODIMP +nsWindowsShellService::SetDefaultClient(bool aForAllUsers, + bool aClaimAllTypes, uint16_t aApps) +{ + nsAutoString appHelperPath; + if (NS_FAILED(GetHelperPath(appHelperPath))) + return NS_ERROR_FAILURE; + + if (aForAllUsers) + appHelperPath.AppendLiteral(" /SetAsDefaultAppGlobal"); + else { + appHelperPath.AppendLiteral(" /SetAsDefaultAppUser"); + if (aApps & nsIShellService::BROWSER) + appHelperPath.AppendLiteral(" Browser"); + + if (aApps & nsIShellService::MAIL) + appHelperPath.AppendLiteral(" Mail"); + + if (aApps & nsIShellService::NEWS) + appHelperPath.AppendLiteral(" News"); + } + + return LaunchHelper(appHelperPath); +} + +static nsresult +WriteBitmap(nsIFile* aFile, imgIContainer* aImage) +{ + nsresult rv; + + RefPtr<SourceSurface> surface = + aImage->GetFrame(imgIContainer::FRAME_CURRENT, + imgIContainer::FLAG_SYNC_DECODE); + NS_ENSURE_TRUE(surface, NS_ERROR_FAILURE); + + // For either of the following formats we want to set the biBitCount member + // of the BITMAPINFOHEADER struct to 32, below. For that value the bitmap + // format defines that the A8/X8 WORDs in the bitmap byte stream be ignored + // for the BI_RGB value we use for the biCompression member. + MOZ_ASSERT(surface->GetFormat() == SurfaceFormat::B8G8R8A8 || + surface->GetFormat() == SurfaceFormat::B8G8R8X8); + + RefPtr<DataSourceSurface> dataSurface = surface->GetDataSurface(); + NS_ENSURE_TRUE(dataSurface, NS_ERROR_FAILURE); + + int32_t width = dataSurface->GetSize().width; + int32_t height = dataSurface->GetSize().height; + int32_t bytesPerPixel = 4 * sizeof(uint8_t); + int32_t bytesPerRow = bytesPerPixel * width; + + // initialize these bitmap structs which we will later + // serialize directly to the head of the bitmap file + BITMAPINFOHEADER bmi; + bmi.biSize = sizeof(BITMAPINFOHEADER); + bmi.biWidth = width; + bmi.biHeight = height; + bmi.biPlanes = 1; + bmi.biBitCount = (WORD)bytesPerPixel*8; + bmi.biCompression = BI_RGB; + bmi.biSizeImage = bytesPerRow * height; + bmi.biXPelsPerMeter = 0; + bmi.biYPelsPerMeter = 0; + bmi.biClrUsed = 0; + bmi.biClrImportant = 0; + + BITMAPFILEHEADER bf; + bf.bfType = 0x4D42; // 'BM' + bf.bfReserved1 = 0; + bf.bfReserved2 = 0; + bf.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER); + bf.bfSize = bf.bfOffBits + bmi.biSizeImage; + + // get a file output stream + nsCOMPtr<nsIOutputStream> stream; + rv = NS_NewLocalFileOutputStream(getter_AddRefs(stream), aFile); + NS_ENSURE_SUCCESS(rv, rv); + + DataSourceSurface::MappedSurface map; + if (!dataSurface->Map(DataSourceSurface::MapType::READ, &map)) { + return NS_ERROR_FAILURE; + } + + // write the bitmap headers and rgb pixel data to the file + rv = NS_ERROR_FAILURE; + if (stream) { + uint32_t written; + stream->Write((const char*)&bf, sizeof(BITMAPFILEHEADER), &written); + if (written == sizeof(BITMAPFILEHEADER)) { + stream->Write((const char*)&bmi, sizeof(BITMAPINFOHEADER), &written); + if (written == sizeof(BITMAPINFOHEADER)) { + // write out the image data backwards because the desktop won't + // show bitmaps with negative heights for top-to-bottom + uint32_t i = map.mStride * height; + rv = NS_OK; + do { + i -= map.mStride; + stream->Write(((const char*)map.mData) + i, bytesPerRow, &written); + if (written != bytesPerRow) { + rv = NS_ERROR_FAILURE; + break; + } + } while (i != 0); + } + } + + stream->Close(); + } + + dataSurface->Unmap(); + + return rv; +} + +NS_IMETHODIMP +nsWindowsShellService::SetDesktopBackground(dom::Element* aElement, + int32_t aPosition, + const nsACString& aImageName) +{ + if (!aElement || !aElement->IsHTMLElement(nsGkAtoms::img)) { + // XXX write background loading stuff! + return NS_ERROR_NOT_AVAILABLE; + } + + 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 NS_ERROR_FAILURE; + + // get the file name from localized strings + nsCOMPtr<nsIStringBundleService> bundleService( + do_GetService(NS_STRINGBUNDLE_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIStringBundle> shellBundle; + rv = bundleService->CreateBundle(SHELLSERVICE_PROPERTIES, + getter_AddRefs(shellBundle)); + NS_ENSURE_SUCCESS(rv, rv); + + // e.g. "Desktop Background.bmp" + nsAutoString fileLeafName; + rv = shellBundle->GetStringFromName("desktopBackgroundLeafNameWin", + fileLeafName); + NS_ENSURE_SUCCESS(rv, rv); + + // get the profile root directory + nsCOMPtr<nsIFile> file; + rv = NS_GetSpecialDirectory(NS_APP_APPLICATION_REGISTRY_DIR, + getter_AddRefs(file)); + NS_ENSURE_SUCCESS(rv, rv); + + // eventually, the path is "%APPDATA%\Mozilla\SeaMonkey\Desktop Background.bmp" + rv = file->Append(fileLeafName); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoString path; + rv = file->GetPath(path); + NS_ENSURE_SUCCESS(rv, rv); + + // write the bitmap to a file in the profile directory + rv = WriteBitmap(file, container); + + // if the file was written successfully, set it as the system wallpaper + if (NS_SUCCEEDED(rv)) { + nsCOMPtr<nsIWindowsRegKey> key(do_CreateInstance("@mozilla.org/windows-registry-key;1", &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = key->Create(nsIWindowsRegKey::ROOT_KEY_CURRENT_USER, + u"Control Panel\\Desktop"_ns, + nsIWindowsRegKey::ACCESS_SET_VALUE); + NS_ENSURE_SUCCESS(rv, rv); + + int style = 0; + switch (aPosition) { + case BACKGROUND_STRETCH: + style = 2; + break; + case BACKGROUND_FILL: + style = 10; + break; + case BACKGROUND_FIT: + style = 6; + break; + } + + nsString value; + value.AppendInt(style); + rv = key->WriteStringValue(u"WallpaperStyle"_ns, value); + NS_ENSURE_SUCCESS(rv, rv); + + value.Assign(aPosition == BACKGROUND_TILE ? '1' : '0'); + rv = key->WriteStringValue(u"TileWallpaper"_ns, value); + NS_ENSURE_SUCCESS(rv, rv); + + rv = key->Close(); + NS_ENSURE_SUCCESS(rv, rv); + + ::SystemParametersInfoW(SPI_SETDESKWALLPAPER, 0, (PVOID)path.get(), + SPIF_UPDATEINIFILE | SPIF_SENDWININICHANGE); + } + return rv; +} + +NS_IMETHODIMP +nsWindowsShellService::GetDesktopBackgroundColor(uint32_t* aColor) +{ + uint32_t color = ::GetSysColor(COLOR_DESKTOP); + *aColor = (GetRValue(color) << 16) | (GetGValue(color) << 8) | GetBValue(color); + return NS_OK; +} + +NS_IMETHODIMP +nsWindowsShellService::SetDesktopBackgroundColor(uint32_t aColor) +{ + int parameter = COLOR_DESKTOP; + BYTE r = (aColor >> 16); + BYTE g = (aColor << 16) >> 24; + BYTE b = (aColor << 24) >> 24; + COLORREF color = RGB(r,g,b); + + ::SetSysColors(1, ¶meter, &color); + + nsresult rv; + nsCOMPtr<nsIWindowsRegKey> key(do_CreateInstance("@mozilla.org/windows-registry-key;1", &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = key->Create(nsIWindowsRegKey::ROOT_KEY_CURRENT_USER, + u"Control Panel\\Colors"_ns, + nsIWindowsRegKey::ACCESS_SET_VALUE); + NS_ENSURE_SUCCESS(rv, rv); + + wchar_t rgb[12]; + _snwprintf(rgb, 12, L"%u %u %u", r, g, b); + rv = key->WriteStringValue(u"Background"_ns, + nsDependentString(rgb)); + NS_ENSURE_SUCCESS(rv, rv); + + return key->Close(); +} + +NS_IMETHODIMP +nsWindowsShellService::OpenApplicationWithURI(nsIFile* aApplication, + const nsACString& aURI) +{ + nsresult rv; + nsCOMPtr<nsIProcess> process = + do_CreateInstance("@mozilla.org/process/util;1", &rv); + if (NS_FAILED(rv)) + return rv; + + rv = process->Init(aApplication); + if (NS_FAILED(rv)) + return rv; + + const nsCString& spec = PromiseFlatCString(aURI); + const char* specStr = spec.get(); + return process->Run(false, &specStr, 1); +} + +NS_IMETHODIMP +nsWindowsShellService::GetDefaultFeedReader(nsIFile** _retval) +{ + *_retval = nullptr; + + nsresult rv; + nsCOMPtr<nsIWindowsRegKey> key(do_CreateInstance("@mozilla.org/windows-registry-key;1", &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = key->Open(nsIWindowsRegKey::ROOT_KEY_CLASSES_ROOT, + u"feed\\shell\\open\\command"_ns, + nsIWindowsRegKey::ACCESS_READ); + NS_ENSURE_SUCCESS(rv, rv); + + nsString path; + rv = key->ReadStringValue(EmptyString(), path); + NS_ENSURE_SUCCESS(rv, rv); + if (path.IsEmpty()) + return NS_ERROR_FAILURE; + + if (path.First() == '"') { + // Everything inside the quotes + path = Substring(path, 1, path.FindChar('"', 1) - 1); + } else { + // Everything up to the first space + path = Substring(path, 0, path.FindChar(' ')); + } + + nsCOMPtr<nsIFile> defaultReader = + do_CreateInstance("@mozilla.org/file/local;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = defaultReader->InitWithPath(path); + NS_ENSURE_SUCCESS(rv, rv); + + bool exists; + rv = defaultReader->Exists(&exists); + NS_ENSURE_SUCCESS(rv, rv); + if (!exists) + return NS_ERROR_FAILURE; + + NS_ADDREF(*_retval = defaultReader); + return NS_OK; +} diff --git a/comm/suite/components/shell/nsWindowsShellService.h b/comm/suite/components/shell/nsWindowsShellService.h new file mode 100644 index 0000000000..a29dff2e5d --- /dev/null +++ b/comm/suite/components/shell/nsWindowsShellService.h @@ -0,0 +1,41 @@ +/* -*- 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 "nscore.h" +#include "nsShellService.h" +#include "nsString.h" +#include "nsIShellService.h" +#include "mozilla/Attributes.h" +#include "nsSuiteCID.h" + +#include <windows.h> + +typedef struct { + const char* keyName; + const char* valueName; + const char* valueData; + + int32_t flags; +} SETTING; + +class nsWindowsShellService final : public nsIShellService +{ +public: + nsWindowsShellService() {}; + nsresult Init(); + + NS_DECL_ISUPPORTS + NS_DECL_NSISHELLSERVICE + +protected: + ~nsWindowsShellService() {} + bool IsDefaultClientVista(uint16_t aApps, bool* aIsDefaultClient); + bool TestForDefault(SETTING aSettings[], int32_t aSize); + +private: + nsString mAppLongPath; + nsString mAppShortPath; +}; + |