827 lines
28 KiB
C++
827 lines
28 KiB
C++
/* 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/Preferences.h"
|
|
#include "mozilla/widget/GSettings.h"
|
|
#include "nsAppRunner.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 "nsIStringBundle.h"
|
|
#include "nsServiceManagerUtils.h"
|
|
#include "nsIImageLoadingContent.h"
|
|
#include "nsIINIParser.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 "mozilla/dom/Promise.h"
|
|
#include "nsImageToPixbuf.h"
|
|
#include "nsXULAppAPI.h"
|
|
|
|
#ifdef MOZ_ENABLE_DBUS
|
|
# include "nsWindow.h"
|
|
# include "WidgetUtils.h"
|
|
# include "mozilla/widget/AsyncDBus.h"
|
|
#endif
|
|
|
|
#include <glib.h>
|
|
#include <gio/gio.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;
|
|
|
|
#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 (auto appProtocol : appProtocols) {
|
|
if (!appProtocol.essential) continue;
|
|
|
|
if (!IsDefaultForSchemeHelper(nsDependentCString(appProtocol.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 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(SHELL_BRAND_PROPERTIES_URI,
|
|
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 (auto appProtocol : appProtocols) {
|
|
appInfo->SetAsDefaultForURIScheme(nsDependentCString(appProtocol.name));
|
|
}
|
|
|
|
// set handler for .html and xhtml files and MIME types:
|
|
// Add mime types for html, xhtml extension and set app to just created
|
|
// appinfo.
|
|
for (auto appType : appTypes) {
|
|
appInfo->SetAsDefaultForMimeType(nsDependentCString(appType.mimeType));
|
|
appInfo->SetAsDefaultForFileExtensions(
|
|
nsDependentCString(appType.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(SHELL_BRAND_PROPERTIES_URI,
|
|
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));
|
|
|
|
widget::GSettings::Collection bgSettings(kDesktopBGSchema);
|
|
if (!bgSettings) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
GUniquePtr<gchar> fileURI(
|
|
g_filename_to_uri(filePath.get(), nullptr, nullptr));
|
|
if (!fileURI) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
bgSettings.SetString("picture-options"_ns, options);
|
|
bgSettings.SetString("picture-uri"_ns, nsDependentCString(fileURI.get()));
|
|
bgSettings.SetString("picture-uri-dark"_ns,
|
|
nsDependentCString(fileURI.get()));
|
|
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) {
|
|
nsAutoCString background;
|
|
widget::GSettings::GetString(kDesktopBGSchema, 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);
|
|
|
|
widget::GSettings::Collection bgSettings(kDesktopBGSchema);
|
|
if (bgSettings) {
|
|
bgSettings.SetString(kDesktopColorGSKey, colorString);
|
|
return NS_OK;
|
|
}
|
|
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsGNOMEShellService::GetGSettingsString(const nsACString& aSchema,
|
|
const nsACString& aKey,
|
|
nsACString& aResult) {
|
|
widget::GSettings::GetString(PromiseFlatCString(aSchema),
|
|
PromiseFlatCString(aKey), aResult);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsGNOMEShellService::SetGSettingsString(const nsACString& aSchema,
|
|
const nsACString& aKey,
|
|
const nsACString& aValue) {
|
|
widget::GSettings::Collection settings(PromiseFlatCString(aSchema));
|
|
if (!settings) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
if (!settings.SetString(PromiseFlatCString(aKey),
|
|
PromiseFlatCString(aValue))) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP nsGNOMEShellService::GetArgv0(nsACString& output) {
|
|
output.Assign(gArgc <= 0 ? "" : gArgv[0]);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP nsGNOMEShellService::GetGlibPrgname(nsACString& output) {
|
|
output.Assign(g_get_prgname());
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP nsGNOMEShellService::GetDesktopEntryStatus(
|
|
const nsACString& aDesktopId,
|
|
nsIGNOMEShellService::DesktopEntryStatus* aResult) {
|
|
NS_ENSURE_ARG_POINTER(aResult);
|
|
|
|
RefPtr<GDesktopAppInfo> appinfo =
|
|
dont_AddRef(g_desktop_app_info_new(PromiseFlatCString(aDesktopId).get()));
|
|
if (!appinfo) {
|
|
*aResult = nsIGNOMEShellService::DESKTOP_ENTRY_ABSENT;
|
|
} else if (g_app_info_should_show(G_APP_INFO(appinfo.get()))) {
|
|
*aResult = nsIGNOMEShellService::DESKTOP_ENTRY_VISIBLE;
|
|
} else {
|
|
*aResult = nsIGNOMEShellService::DESKTOP_ENTRY_INVISIBLE;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
using GIconPromise = MozPromise<RefPtr<GIcon>, GUniquePtr<GError>, true>;
|
|
class AsyncGIconReader {
|
|
public:
|
|
// This may be freed by the background thread.
|
|
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AsyncGIconReader);
|
|
|
|
explicit AsyncGIconReader(const nsCString& aPath) { mPath.Assign(aPath); }
|
|
|
|
RefPtr<GIconPromise> Start() {
|
|
RefPtr<GIconPromise> promise = mHolder.Ensure(__func__);
|
|
MOZ_ASSERT(!mHolder.IsEmpty());
|
|
|
|
NS_DispatchBackgroundTask(
|
|
NS_NewRunnableFunction(
|
|
__func__,
|
|
[this, inst = RefPtr{this}] {
|
|
char* content = nullptr;
|
|
size_t length = 0;
|
|
GUniquePtr<GError> error;
|
|
if (!g_file_get_contents(mPath.get(), &content, &length,
|
|
getter_Transfers(error))) {
|
|
mHolder.Reject(std::move(error), __func__);
|
|
return;
|
|
}
|
|
|
|
GBytes* bytes = g_bytes_new_take(content, length);
|
|
// Note that this doesn't decode the image and only puts the
|
|
// bytes into a new container that serializes over D-Bus.
|
|
RefPtr<GIcon> icon = dont_AddRef(g_bytes_icon_new(bytes));
|
|
g_clear_pointer(&bytes, g_bytes_unref);
|
|
|
|
mHolder.Resolve(std::move(icon), __func__);
|
|
}),
|
|
NS_DISPATCH_EVENT_MAY_BLOCK);
|
|
|
|
return promise.forget();
|
|
}
|
|
|
|
private:
|
|
MozPromiseHolder<GIconPromise> mHolder;
|
|
nsCString mPath;
|
|
|
|
~AsyncGIconReader() = default;
|
|
};
|
|
|
|
#ifdef MOZ_ENABLE_DBUS
|
|
static RefPtr<widget::DBusProxyPromise> CreateDynamicLauncherProxy() {
|
|
return widget::CreateDBusProxyForBus(
|
|
G_BUS_TYPE_SESSION, G_DBUS_PROXY_FLAGS_NONE, nullptr,
|
|
"org.freedesktop.portal.Desktop", "/org/freedesktop/portal/desktop",
|
|
"org.freedesktop.portal.DynamicLauncher");
|
|
}
|
|
#endif
|
|
|
|
NS_IMETHODIMP nsGNOMEShellService::RequestInstallDynamicLauncher(
|
|
const nsACString& aEntryId, nsIINIParserWriter* aDesktopEntry,
|
|
mozIDOMWindowProxy* aWindow, JSContext* aCx, dom::Promise** aPromise) {
|
|
MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread());
|
|
|
|
#ifdef MOZ_ENABLE_DBUS
|
|
ErrorResult rv;
|
|
RefPtr<dom::Promise> promise =
|
|
dom::Promise::Create(xpc::CurrentNativeGlobal(aCx), rv);
|
|
if (MOZ_UNLIKELY(rv.Failed())) {
|
|
return rv.StealNSResult();
|
|
}
|
|
|
|
nsWindow* window = nullptr;
|
|
if (aWindow) {
|
|
auto* parent = nsPIDOMWindowOuter::From(aWindow);
|
|
RefPtr<nsIWidget> widget =
|
|
mozilla::widget::WidgetUtils::DOMWindowToWidget(parent);
|
|
NS_ENSURE_TRUE(widget, NS_ERROR_FAILURE);
|
|
|
|
window = nsWindow::FromWidget(widget);
|
|
}
|
|
|
|
nsCOMPtr<nsIINIParser> entryINI(do_QueryInterface(aDesktopEntry));
|
|
NS_ENSURE_ARG(entryINI);
|
|
|
|
nsCString entryName;
|
|
nsCString entryIcon;
|
|
nsCString entryContent;
|
|
MOZ_TRY(entryINI->GetString("Desktop Entry"_ns, "Name"_ns, entryName));
|
|
MOZ_TRY(entryINI->GetString("Desktop Entry"_ns, "Icon"_ns, entryIcon));
|
|
MOZ_TRY(aDesktopEntry->WriteToString(entryContent));
|
|
|
|
auto* target = GetCurrentSerialEventTarget();
|
|
|
|
// MozPromise doesn't seem to support ::All with different types, so make one
|
|
// type to contain them all.
|
|
using DynamicLauncherPromise =
|
|
MozPromise<Variant<RefPtr<GDBusProxy>, RefPtr<GIcon>, nsCString>,
|
|
GUniquePtr<GError>, true>;
|
|
|
|
RefPtr<DynamicLauncherPromise> dbusProxyPromise =
|
|
CreateDynamicLauncherProxy()->Map(
|
|
target, __func__,
|
|
[](RefPtr<GDBusProxy>&& proxy)
|
|
-> DynamicLauncherPromise::ResolveValueType {
|
|
return AsVariant(std::move(proxy));
|
|
});
|
|
|
|
RefPtr<AsyncGIconReader> reader = new AsyncGIconReader(entryIcon);
|
|
RefPtr<DynamicLauncherPromise> iconPromise = reader->Start()->Map(
|
|
target, __func__,
|
|
[](RefPtr<GIcon>&& icon) -> DynamicLauncherPromise::ResolveValueType {
|
|
return AsVariant(std::move(icon));
|
|
});
|
|
|
|
RefPtr<DynamicLauncherPromise> exportPromise =
|
|
(window
|
|
? window->ExportHandle()
|
|
: nsWindow::ExportHandlePromise::CreateAndResolve(""_ns, __func__))
|
|
->Then(
|
|
GetCurrentSerialEventTarget(), __func__,
|
|
[](nsWindow::ExportHandlePromise::ResolveOrRejectValue&& aValue) {
|
|
if (aValue.IsResolve()) {
|
|
return DynamicLauncherPromise::CreateAndResolve(
|
|
AsVariant(aValue.ResolveValue()), __func__);
|
|
}
|
|
|
|
NS_WARNING("Failed to export window for DynamicLauncher");
|
|
return DynamicLauncherPromise::CreateAndResolve(
|
|
AsVariant(EmptyCString()), __func__);
|
|
});
|
|
|
|
nsTArray<RefPtr<DynamicLauncherPromise>> promises = {
|
|
RefPtr(dbusProxyPromise), RefPtr(iconPromise), RefPtr(exportPromise)};
|
|
DynamicLauncherPromise::All(target, promises)
|
|
->Then(
|
|
target, __func__,
|
|
[target = nsCOMPtr{target}, entryId = nsCString(aEntryId),
|
|
entryContent = std::move(entryContent),
|
|
entryName = std::move(entryName)](
|
|
DynamicLauncherPromise::AllPromiseType::ResolveOrRejectValue&&
|
|
aValue) {
|
|
if (aValue.IsReject()) {
|
|
return widget::DBusCallPromise::AllPromiseType::CreateAndReject(
|
|
std::move(aValue.RejectValue()), __func__);
|
|
}
|
|
|
|
auto values = aValue.ResolveValue();
|
|
RefPtr<GDBusProxy> proxy =
|
|
std::move(values[0].template as<RefPtr<GDBusProxy>>());
|
|
RefPtr<GIcon> icon = values[1].template as<RefPtr<GIcon>>();
|
|
nsCString exportedWindow = values[2].template as<nsCString>();
|
|
|
|
nsAutoCString token;
|
|
widget::MakePortalRequestToken("DynamicLauncher"_ns, token);
|
|
|
|
nsAutoCString requestPath;
|
|
widget::GetPortalRequestPath(proxy.get(), token, requestPath);
|
|
|
|
auto holder =
|
|
std::make_shared<MozPromiseHolder<widget::DBusCallPromise>>();
|
|
RefPtr<widget::DBusCallPromise> installPromise =
|
|
holder->Ensure(__func__);
|
|
|
|
GVariantDict options;
|
|
g_variant_dict_init(&options, nullptr);
|
|
g_variant_dict_insert(&options, "handle_token", "s", token.get());
|
|
|
|
RefPtr<GVariant> args = dont_AddRef(g_variant_ref_sink(
|
|
g_variant_new("(ssv@a{sv})", //
|
|
exportedWindow.get(), /* parent window */
|
|
entryName.get(), /* name of entry */
|
|
g_icon_serialize(G_ICON(icon.get())), /* icon */
|
|
g_variant_dict_end(&options)))); /* options */
|
|
|
|
auto subscription = std::make_shared<unsigned>(0);
|
|
*subscription = widget::OnDBusPortalResponse(
|
|
G_DBUS_PROXY(proxy.get()), token,
|
|
[target = nsCOMPtr{target}, entryId = std::move(entryId),
|
|
entryContent, proxy, holder, subscription](GVariant* variant) {
|
|
*subscription = 0;
|
|
|
|
unsigned response = 2; // '2' indicates 'other error'
|
|
RefPtr<GVariant> options = nullptr;
|
|
g_variant_get(variant, "(u@a{sv})", &response,
|
|
options.StartAssignment());
|
|
if (response != 0) {
|
|
holder->Reject(GUniquePtr<GError>(g_error_new(
|
|
G_IO_ERROR, G_IO_ERROR_FAILED,
|
|
"Response was non-zero")),
|
|
__func__);
|
|
return;
|
|
}
|
|
|
|
const char* responseToken;
|
|
if (!g_variant_lookup(options, "token", "&s",
|
|
&responseToken)) {
|
|
holder->Reject(
|
|
GUniquePtr<GError>(g_error_new(
|
|
G_IO_ERROR, G_IO_ERROR_FAILED,
|
|
"No token was provided from the portal")),
|
|
__func__);
|
|
return;
|
|
}
|
|
|
|
nsPrintfCString entryIdWithExtension("%s.desktop",
|
|
entryId.get());
|
|
RefPtr<GVariant> args = dont_AddRef(g_variant_ref_sink(
|
|
g_variant_new("(sssa{sv})", responseToken,
|
|
entryIdWithExtension.get(),
|
|
entryContent.get(), nullptr)));
|
|
|
|
widget::DBusProxyCall(proxy.get(), "Install", args,
|
|
G_DBUS_CALL_FLAGS_NONE, -1)
|
|
->Then(target, __func__,
|
|
[holder = std::move(holder)](
|
|
widget::DBusCallPromise::ResolveOrRejectValue&&
|
|
aValue) {
|
|
holder->ResolveOrReject(std::move(aValue),
|
|
__func__);
|
|
});
|
|
});
|
|
|
|
RefPtr<widget::DBusCallPromise> prepareInstallPromise =
|
|
widget::DBusProxyCall(G_DBUS_PROXY(proxy.get()),
|
|
"PrepareInstall", args,
|
|
G_DBUS_CALL_FLAGS_NONE, -1)
|
|
->MapErr(target, __func__,
|
|
[subscriptionptr = std::move(subscription), proxy,
|
|
holder](GUniquePtr<GError>&& err) {
|
|
if (*subscriptionptr) {
|
|
g_dbus_connection_signal_unsubscribe(
|
|
g_dbus_proxy_get_connection(proxy.get()),
|
|
*subscriptionptr);
|
|
}
|
|
|
|
holder->Reject(
|
|
GUniquePtr<GError>(g_error_copy(err.get())),
|
|
__func__);
|
|
return std::move(err);
|
|
});
|
|
|
|
nsTArray<RefPtr<widget::DBusCallPromise>> promises = {
|
|
prepareInstallPromise, installPromise};
|
|
return widget::DBusCallPromise::All(target, promises);
|
|
})
|
|
->Then(target, __func__,
|
|
[promise](
|
|
widget::DBusCallPromise::AllPromiseType::ResolveOrRejectValue&&
|
|
aValue) {
|
|
if (aValue.IsReject()) {
|
|
nsDependentCString message(aValue.RejectValue()->message);
|
|
promise->MaybeRejectWithOperationError(message);
|
|
} else {
|
|
promise->MaybeResolveWithUndefined();
|
|
}
|
|
});
|
|
|
|
promise.forget(aPromise);
|
|
return NS_OK;
|
|
#else
|
|
return NS_ERROR_FAILURE;
|
|
#endif
|
|
}
|
|
|
|
NS_IMETHODIMP nsGNOMEShellService::RequestUninstallDynamicLauncher(
|
|
const nsACString& aEntryId, JSContext* aCx, dom::Promise** aPromise) {
|
|
MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread());
|
|
#ifdef MOZ_ENABLE_DBUS
|
|
ErrorResult rv;
|
|
RefPtr<dom::Promise> promise =
|
|
dom::Promise::Create(xpc::CurrentNativeGlobal(aCx), rv);
|
|
if (MOZ_UNLIKELY(rv.Failed())) {
|
|
return rv.StealNSResult();
|
|
}
|
|
|
|
nsCString entryId = PromiseFlatCString(aEntryId);
|
|
nsPrintfCString entryIdWithExtension("%s.desktop", entryId.get());
|
|
|
|
CreateDynamicLauncherProxy()
|
|
->Then(
|
|
GetCurrentSerialEventTarget(), __func__,
|
|
[entryIdWithExtension](const RefPtr<GDBusProxy>& aProxy) {
|
|
RefPtr<GVariant> args =
|
|
dont_AddRef(g_variant_ref_sink(g_variant_new(
|
|
"(sa{sv})", entryIdWithExtension.get(), nullptr)));
|
|
return widget::DBusProxyCall(aProxy.get(), "Uninstall", args,
|
|
G_DBUS_CALL_FLAGS_NONE, -1);
|
|
},
|
|
[](GUniquePtr<GError>&& error) {
|
|
return widget::DBusCallPromise::CreateAndReject(std::move(error),
|
|
__func__);
|
|
})
|
|
->Then(GetCurrentSerialEventTarget(), __func__,
|
|
[promise](widget::DBusCallPromise::ResolveOrRejectValue&& aValue) {
|
|
if (aValue.IsReject()) {
|
|
nsDependentCString message(aValue.RejectValue()->message);
|
|
promise->MaybeRejectWithOperationError(message);
|
|
} else {
|
|
promise->MaybeResolveWithUndefined();
|
|
}
|
|
});
|
|
|
|
promise.forget(aPromise);
|
|
return NS_OK;
|
|
#else
|
|
return NS_ERROR_FAILURE;
|
|
#endif
|
|
}
|