From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Wolfgang Rosenauer Date: Tue, 8 Aug 2023 16:13:48 +0300 Subject: [PATCH] Add KDE integration to Firefox (toolkit parts) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bug: https://bugzilla.mozilla.org/show_bug.cgi?id=140751 Bug: https://bugzilla.suse.com/show_bug.cgi?id=170055 EDIT: Björn Bidar: Removed handling for obsolete special files Co-authored-by: Wolfgang Rosenauer Co-authored-by: Lubos Lunak Co-authored-by: Björn Bidar --- modules/libpref/Preferences.cpp | 1 + modules/libpref/moz.build | 4 + python/mozbuild/mozpack/chrome/flags.py | 1 + python/mozbuild/mozpack/chrome/manifest.py | 1 + toolkit/components/downloads/moz.build | 4 + .../mozapps/downloads/HelperAppDlg.sys.mjs | 70 +++-- .../unixproxy/nsUnixSystemProxySettings.cpp | 29 ++ toolkit/xre/moz.build | 2 + toolkit/xre/nsKDEUtils.cpp | 286 ++++++++++++++++++ toolkit/xre/nsKDEUtils.h | 53 ++++ uriloader/exthandler/HandlerServiceParent.cpp | 6 +- uriloader/exthandler/moz.build | 3 + .../exthandler/unix/nsCommonRegistry.cpp | 42 +++ uriloader/exthandler/unix/nsCommonRegistry.h | 28 ++ uriloader/exthandler/unix/nsKDERegistry.cpp | 75 +++++ uriloader/exthandler/unix/nsKDERegistry.h | 35 +++ uriloader/exthandler/unix/nsMIMEInfoUnix.cpp | 28 +- .../exthandler/unix/nsOSHelperAppService.cpp | 10 +- widget/gtk/moz.build | 1 + widget/gtk/nsFilePicker.cpp | 230 +++++++++++++- widget/gtk/nsFilePicker.h | 6 + xpcom/components/ManifestParser.cpp | 10 + xpcom/components/moz.build | 1 + xpcom/io/nsLocalFileUnix.cpp | 20 +- 24 files changed, 910 insertions(+), 36 deletions(-) create mode 100644 toolkit/xre/nsKDEUtils.cpp create mode 100644 toolkit/xre/nsKDEUtils.h create mode 100644 uriloader/exthandler/unix/nsCommonRegistry.cpp create mode 100644 uriloader/exthandler/unix/nsCommonRegistry.h create mode 100644 uriloader/exthandler/unix/nsKDERegistry.cpp create mode 100644 uriloader/exthandler/unix/nsKDERegistry.h diff --git a/modules/libpref/Preferences.cpp b/modules/libpref/Preferences.cpp --- a/modules/libpref/Preferences.cpp +++ b/modules/libpref/Preferences.cpp @@ -91,16 +91,17 @@ #include "PLDHashTable.h" #include "prdtoa.h" #include "prlink.h" #include "xpcpublic.h" #include "js/RootingAPI.h" #ifdef MOZ_BACKGROUNDTASKS # include "mozilla/BackgroundTasks.h" #endif +#include "nsKDEUtils.h" #ifdef DEBUG # include #endif #ifdef MOZ_MEMORY # include "mozmemory.h" #endif diff --git a/modules/libpref/moz.build b/modules/libpref/moz.build --- a/modules/libpref/moz.build +++ b/modules/libpref/moz.build @@ -121,16 +121,20 @@ EXPORTS.mozilla += [ ] EXPORTS.mozilla += sorted(["!" + g for g in gen_h]) UNIFIED_SOURCES += [ "Preferences.cpp", "SharedPrefMap.cpp", ] +LOCAL_INCLUDES += [ + '/toolkit/xre' +] + gen_all_tuple = tuple(gen_h + gen_cpp + gen_rs) GeneratedFile( *gen_all_tuple, script="init/generate_static_pref_list.py", entry_point="emit_code", inputs=["init/StaticPrefList.yaml"] ) diff --git a/python/mozbuild/mozpack/chrome/flags.py b/python/mozbuild/mozpack/chrome/flags.py --- a/python/mozbuild/mozpack/chrome/flags.py +++ b/python/mozbuild/mozpack/chrome/flags.py @@ -229,16 +229,17 @@ class Flags(OrderedDict): "os": StringFlag, "osversion": VersionFlag, "abi": StringFlag, "platform": Flag, "xpcnativewrappers": Flag, "tablet": Flag, "process": StringFlag, "backgroundtask": StringFlag, + "desktop": StringFlag, } RE = re.compile(r"([!<>=]+)") def __init__(self, *flags): """ Initialize a set of flags given in string form. flags = Flags('contentaccessible=yes', 'appversion>=3.5') """ diff --git a/python/mozbuild/mozpack/chrome/manifest.py b/python/mozbuild/mozpack/chrome/manifest.py --- a/python/mozbuild/mozpack/chrome/manifest.py +++ b/python/mozbuild/mozpack/chrome/manifest.py @@ -38,16 +38,17 @@ class ManifestEntry(object): "os", "osversion", "abi", "xpcnativewrappers", "tablet", "process", "contentaccessible", "backgroundtask", + "desktop", ] def __init__(self, base, *flags): """ Initialize a manifest entry with the given base path and flags. """ self.base = base self.flags = Flags(*flags) diff --git a/toolkit/components/downloads/moz.build b/toolkit/components/downloads/moz.build --- a/toolkit/components/downloads/moz.build +++ b/toolkit/components/downloads/moz.build @@ -46,10 +46,14 @@ XPCOM_MANIFESTS += [ if CONFIG["MOZ_PLACES"]: EXTRA_JS_MODULES += [ "DownloadHistory.sys.mjs", ] FINAL_LIBRARY = "xul" +LOCAL_INCLUDES += [ + '/toolkit/xre' +] + with Files("**"): BUG_COMPONENT = ("Toolkit", "Downloads API") diff --git a/toolkit/mozapps/downloads/HelperAppDlg.sys.mjs b/toolkit/mozapps/downloads/HelperAppDlg.sys.mjs --- a/toolkit/mozapps/downloads/HelperAppDlg.sys.mjs +++ b/toolkit/mozapps/downloads/HelperAppDlg.sys.mjs @@ -1241,36 +1241,66 @@ nsUnknownContentTypeDialog.prototype = { params.handlerApp && params.handlerApp.executable && params.handlerApp.executable.isFile() ) { // Remember the file they chose to run. this.chosenApp = params.handlerApp; } } else if ("@mozilla.org/applicationchooser;1" in Cc) { - var nsIApplicationChooser = Ci.nsIApplicationChooser; - var appChooser = Cc["@mozilla.org/applicationchooser;1"].createInstance( - nsIApplicationChooser - ); - appChooser.init( - this.mDialog, - this.dialogElement("strings").getString("chooseAppFilePickerTitle") - ); - var contentTypeDialogObj = this; - let appChooserCallback = function appChooserCallback_done(aResult) { - if (aResult) { - contentTypeDialogObj.chosenApp = aResult.QueryInterface( - Ci.nsILocalHandlerApp - ); - } - contentTypeDialogObj.finishChooseApp(); - }; - appChooser.open(this.mLauncher.MIMEInfo.MIMEType, appChooserCallback); - // The finishChooseApp is called from appChooserCallback - return; + // handle the KDE case which is implemented in the filepicker + // therefore falling back to Gtk2 like behaviour if KDE is running + // FIXME this should be better handled in the nsIApplicationChooser + // interface + var env = Components.classes["@mozilla.org/process/environment;1"] + .getService(Components.interfaces.nsIEnvironment); + if (env.get('KDE_FULL_SESSION') == "true") + { + var nsIFilePicker = Ci.nsIFilePicker; + var fp = Cc["@mozilla.org/filepicker;1"] + .createInstance(nsIFilePicker); + fp.init(this.mDialog, + this.dialogElement("strings").getString("chooseAppFilePickerTitle"), + nsIFilePicker.modeOpen); + + fp.appendFilters(nsIFilePicker.filterApps); + + fp.open(aResult => { + if (aResult == nsIFilePicker.returnOK && fp.file) { + // Remember the file they chose to run. + var localHandlerApp = + Cc["@mozilla.org/uriloader/local-handler-app;1"]. + createInstance(Ci.nsILocalHandlerApp); + localHandlerApp.executable = fp.file; + this.chosenApp = localHandlerApp; + } + this.finishChooseApp(); + }); + } else { + var nsIApplicationChooser = Ci.nsIApplicationChooser; + var appChooser = Cc["@mozilla.org/applicationchooser;1"].createInstance( + nsIApplicationChooser + ); + appChooser.init( + this.mDialog, + this.dialogElement("strings").getString("chooseAppFilePickerTitle") + ); + var contentTypeDialogObj = this; + let appChooserCallback = function appChooserCallback_done(aResult) { + if (aResult) { + contentTypeDialogObj.chosenApp = aResult.QueryInterface( + Ci.nsILocalHandlerApp + ); + } + contentTypeDialogObj.finishChooseApp(); + }; + appChooser.open(this.mLauncher.MIMEInfo.MIMEType, appChooserCallback); + // The finishChooseApp is called from appChooserCallback + return; + } } else { var nsIFilePicker = Ci.nsIFilePicker; var fp = Cc["@mozilla.org/filepicker;1"].createInstance(nsIFilePicker); fp.init( this.mDialog, this.dialogElement("strings").getString("chooseAppFilePickerTitle"), nsIFilePicker.modeOpen ); diff --git a/toolkit/system/unixproxy/nsUnixSystemProxySettings.cpp b/toolkit/system/unixproxy/nsUnixSystemProxySettings.cpp --- a/toolkit/system/unixproxy/nsUnixSystemProxySettings.cpp +++ b/toolkit/system/unixproxy/nsUnixSystemProxySettings.cpp @@ -11,16 +11,18 @@ #include "prenv.h" #include "nsInterfaceHashtable.h" #include "nsHashtablesFwd.h" #include "nsHashKeys.h" #include "nsNetUtil.h" #include "nsISupportsPrimitives.h" #include "nsIGSettingsService.h" #include "nsReadableUtils.h" +#include "nsPrintfCString.h" +#include "nsKDEUtils.h" using namespace mozilla; class nsUnixSystemProxySettings final : public nsISystemProxySettings { public: NS_DECL_ISUPPORTS NS_DECL_NSISYSTEMPROXYSETTINGS @@ -34,16 +36,18 @@ class nsUnixSystemProxySettings final : nsCOMPtr mProxySettings; nsInterfaceHashtable mSchemeProxySettings; nsresult GetProxyFromGSettings(const nsACString& aScheme, const nsACString& aHost, int32_t aPort, nsACString& aResult); nsresult SetProxyResultFromGSettings(const char* aKeyBase, const char* aType, nsACString& aResult); + nsresult GetProxyFromKDE(const nsACString& aScheme, const nsACString& aHost, + PRInt32 aPort, nsACString& aResult); }; NS_IMPL_ISUPPORTS(nsUnixSystemProxySettings, nsISystemProxySettings) NS_IMETHODIMP nsUnixSystemProxySettings::GetMainThreadOnly(bool* aMainThreadOnly) { // dbus prevents us from being threadsafe, but this routine should not block // anyhow @@ -391,21 +395,46 @@ nsresult nsUnixSystemProxySettings::GetP return NS_OK; } nsresult nsUnixSystemProxySettings::GetProxyForURI(const nsACString& aSpec, const nsACString& aScheme, const nsACString& aHost, const int32_t aPort, nsACString& aResult) { + if (nsKDEUtils::kdeSupport()) + return GetProxyFromKDE(aScheme, aHost, aPort, aResult); + if (mProxySettings) { nsresult rv = GetProxyFromGSettings(aScheme, aHost, aPort, aResult); if (NS_SUCCEEDED(rv)) return rv; } return GetProxyFromEnvironment(aScheme, aHost, aPort, aResult); } +nsresult nsUnixSystemProxySettings::GetProxyFromKDE(const nsACString& aScheme, + const nsACString& aHost, + PRInt32 aPort, + nsACString& aResult) { + nsAutoCString url; + url = aScheme; + url += "://"; + url += aHost; + if (aPort >= 0) { + url += ":"; + url += nsPrintfCString("%d", aPort); + } + nsTArray command; + command.AppendElement("GETPROXY"_ns); + command.AppendElement(url); + nsTArray result; + if (!nsKDEUtils::command(command, &result) || result.Length() != 1) + return NS_ERROR_FAILURE; + aResult = result[0]; + return NS_OK; +} + NS_IMPL_COMPONENT_FACTORY(nsUnixSystemProxySettings) { auto result = MakeRefPtr(); result->Init(); return result.forget().downcast(); } diff --git a/toolkit/xre/moz.build b/toolkit/xre/moz.build --- a/toolkit/xre/moz.build +++ b/toolkit/xre/moz.build @@ -91,17 +91,19 @@ elif CONFIG["MOZ_WIDGET_TOOLKIT"] == "co "../components/printingui", ] elif CONFIG["MOZ_WIDGET_TOOLKIT"] == "uikit": UNIFIED_SOURCES += [ "nsNativeAppSupportDefault.cpp", "UIKitDirProvider.mm", ] elif CONFIG["MOZ_WIDGET_TOOLKIT"] == "gtk": + EXPORTS += ['nsKDEUtils.h'] UNIFIED_SOURCES += [ + "nsKDEUtils.cpp", "nsNativeAppSupportUnix.cpp", ] CXXFLAGS += CONFIG["MOZ_X11_SM_CFLAGS"] else: UNIFIED_SOURCES += [ "nsNativeAppSupportDefault.cpp", ] diff --git a/toolkit/xre/nsKDEUtils.cpp b/toolkit/xre/nsKDEUtils.cpp new file mode 100644 --- /dev/null +++ b/toolkit/xre/nsKDEUtils.cpp @@ -0,0 +1,286 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 "nsKDEUtils.h" +#include "nsIWidget.h" +#include "nsISupportsPrimitives.h" +#include "nsIMutableArray.h" +#include "nsComponentManagerUtils.h" +#include "nsArrayUtils.h" + +#include + +#include +#include +#include +#include +#include +#include +// copied from X11/X.h as a hack since for an unknown +// reason it's not picked up from X11/X.h +//#ifndef None +//# define None 0L /* universal null resource or null atom */ +//#endif + +// #define DEBUG_KDE +#ifdef DEBUG_KDE +# define KMOZILLAHELPER "kmozillahelper" +#else +// not need for lib64, it's a binary +# define KMOZILLAHELPER "/usr/lib/mozilla/kmozillahelper" +#endif + +#define KMOZILLAHELPER_VERSION 6 +#define MAKE_STR2(n) #n +#define MAKE_STR(n) MAKE_STR2(n) + +static bool getKdeSession() { + if (PR_GetEnv("KDE_FULL_SESSION")) { + return true; + } + return false; +} + +static bool getKdeSupport() { + nsTArray command; + command.AppendElement("CHECK"_ns); + command.AppendElement("KMOZILLAHELPER_VERSION"_ns); + bool kde = nsKDEUtils::command(command); +#ifdef DEBUG_KDE + fprintf(stderr, "KDE RUNNING %d\n", kde); +#endif + return kde; +} + +nsKDEUtils::nsKDEUtils() : commandFile(NULL), replyFile(NULL) {} + +nsKDEUtils::~nsKDEUtils() { + // closeHelper(); not actually useful, exiting will close the fd too +} + +nsKDEUtils* nsKDEUtils::self() { + static nsKDEUtils s; + return &s; +} + +static bool helperRunning = false; +static bool helperFailed = false; + +bool nsKDEUtils::kdeSession() { + static bool session = getKdeSession(); + return session; +} + +bool nsKDEUtils::kdeSupport() { + static bool support = kdeSession() && getKdeSupport(); + return support && helperRunning; +} + +struct nsKDECommandData { + FILE* file; + nsTArray* output; + GMainLoop* loop; + bool success; +}; + +static gboolean kdeReadFunc(GIOChannel*, GIOCondition, gpointer data) { + nsKDECommandData* p = static_cast(data); + char buf[8192]; // TODO big enough + bool command_done = false; + bool command_failed = false; + while (!command_done && !command_failed && + fgets(buf, 8192, p->file) != + NULL) { // TODO what if the kernel splits a line into two chunks? + // #ifdef DEBUG_KDE + // fprintf( stderr, "READ: %s %d\n", buf, feof( p->file )); + // #endif + if (char* eol = strchr(buf, '\n')) *eol = '\0'; + command_done = (strcmp(buf, "\\1") == 0); + command_failed = (strcmp(buf, "\\0") == 0); + nsAutoCString line(buf); + line.ReplaceSubstring("\\n", "\n"); + line.ReplaceSubstring( + "\\" + "\\", + "\\"); // \\ -> \ , i.e. unescape + if (p->output && !(command_done || command_failed)) + p->output->AppendElement(nsCString(buf)); // TODO utf8? + } + bool quit = false; + if (feof(p->file) || command_failed) { + quit = true; + p->success = false; + } + if (command_done) { // reading one reply finished + quit = true; + p->success = true; + } + if (quit) { + if (p->loop) g_main_loop_quit(p->loop); + return FALSE; + } + return TRUE; +} + +bool nsKDEUtils::command(const nsTArray& command, + nsTArray* output) { + return self()->internalCommand(command, NULL, false, output); +} + +bool nsKDEUtils::command(nsIArray* command, nsIArray** output) { + nsTArray in; + PRUint32 length; + command->GetLength(&length); + for (PRUint32 i = 0; i < length; i++) { + nsCOMPtr str = do_QueryElementAt(command, i); + if (str) { + nsAutoCString s; + str->GetData(s); + in.AppendElement(s); + } + } + + nsTArray out; + bool ret = self()->internalCommand(in, NULL, false, &out); + + if (!output) return ret; + + nsCOMPtr result = do_CreateInstance(NS_ARRAY_CONTRACTID); + if (!result) return false; + + for (PRUint32 i = 0; i < out.Length(); i++) { + nsCOMPtr rstr = + do_CreateInstance(NS_SUPPORTS_CSTRING_CONTRACTID); + if (!rstr) return false; + + rstr->SetData(out[i]); + result->AppendElement(rstr); + } + + NS_ADDREF(*output = result); + return ret; +} + +bool nsKDEUtils::commandBlockUi(const nsTArray& command, + GtkWindow* parent, + nsTArray* output) { + return self()->internalCommand(command, parent, true, output); +} + +bool nsKDEUtils::internalCommand(const nsTArray& command, + GtkWindow* parent, bool blockUi, + nsTArray* output) { + if (!startHelper()) return false; + feedCommand(command); + // do not store the data in 'this' but in extra structure, just in case there + // is reentrancy (can there be? the event loop is re-entered) + nsKDECommandData data; + data.file = replyFile; + data.output = output; + data.success = false; + if (blockUi) { + data.loop = g_main_loop_new(NULL, FALSE); + GtkWidget* window = gtk_window_new(GTK_WINDOW_TOPLEVEL); + if (parent && gtk_window_get_group(parent)) + gtk_window_group_add_window(gtk_window_get_group(parent), + GTK_WINDOW(window)); + gtk_widget_realize(window); + gtk_widget_set_sensitive(window, TRUE); + gtk_grab_add(window); + GIOChannel* channel = g_io_channel_unix_new(fileno(data.file)); + g_io_add_watch(channel, + static_cast(G_IO_IN | G_IO_ERR | G_IO_HUP), + kdeReadFunc, &data); + g_io_channel_unref(channel); + g_main_loop_run(data.loop); + g_main_loop_unref(data.loop); + gtk_grab_remove(window); + gtk_widget_destroy(window); + } else { + data.loop = NULL; + while (kdeReadFunc(NULL, static_cast(0), &data)) + ; + } + return data.success; +} + +bool nsKDEUtils::startHelper() { + if (helperRunning) return true; + if (helperFailed) return false; + helperFailed = true; + int fdcommand[2]; + int fdreply[2]; + if (pipe(fdcommand) < 0) return false; + if (pipe(fdreply) < 0) { + close(fdcommand[0]); + close(fdcommand[1]); + return false; + } + char* args[2] = {const_cast(KMOZILLAHELPER), NULL}; + switch (fork()) { + case -1: { + close(fdcommand[0]); + close(fdcommand[1]); + close(fdreply[0]); + close(fdreply[1]); + return false; + } + case 0: // child + { + if (dup2(fdcommand[0], STDIN_FILENO) < 0) _exit(1); + if (dup2(fdreply[1], STDOUT_FILENO) < 0) _exit(1); + int maxfd = 1024; // close all other fds + struct rlimit rl; + if (getrlimit(RLIMIT_NOFILE, &rl) == 0) maxfd = rl.rlim_max; + for (int i = 3; i < maxfd; ++i) close(i); +#ifdef DEBUG_KDE + execvp(KMOZILLAHELPER, args); +#else + execv(KMOZILLAHELPER, args); +#endif + _exit(1); // failed + } + default: // parent + { + commandFile = fdopen(fdcommand[1], "w"); + replyFile = fdopen(fdreply[0], "r"); + close(fdcommand[0]); + close(fdreply[1]); + if (commandFile == NULL || replyFile == NULL) { + closeHelper(); + return false; + } + // ok, helper ready, getKdeRunning() will check if it works + } + } + helperFailed = false; + helperRunning = true; + return true; +} + +void nsKDEUtils::closeHelper() { + if (commandFile != NULL) + fclose(commandFile); // this will also make the helper quit + if (replyFile != NULL) fclose(replyFile); + helperRunning = false; +} + +void nsKDEUtils::feedCommand(const nsTArray& command) { + for (int i = 0; i < command.Length(); ++i) { + nsCString line = command[i]; + line.ReplaceSubstring("\\", + "\\" + "\\"); // \ -> \\ , i.e. escape + line.ReplaceSubstring("\n", "\\n"); +#ifdef DEBUG_KDE + fprintf(stderr, "COMM: %s\n", line.get()); +#endif + fputs(line.get(), commandFile); + fputs("\n", commandFile); + } + fputs("\\E\n", + commandFile); // done as \E, so it cannot happen in normal data + fflush(commandFile); +} diff --git a/toolkit/xre/nsKDEUtils.h b/toolkit/xre/nsKDEUtils.h new file mode 100644 --- /dev/null +++ b/toolkit/xre/nsKDEUtils.h @@ -0,0 +1,53 @@ +/* -*- 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 nsKDEUtils_h__ +#define nsKDEUtils_h__ + +#include "nsString.h" +#include "nsTArray.h" +#include + +typedef struct _GtkWindow GtkWindow; + +class nsIArray; + +class NS_EXPORT nsKDEUtils { + public: + /* Returns true if running inside a KDE session (regardless of whether there + is KDE support available for Firefox). This should be used e.g. when + determining dialog button order but not for code that requires the KDE + support. */ + static bool kdeSession(); + /* Returns true if running inside a KDE session and KDE support is available + for Firefox. This should be used everywhere where the external helper is + needed. */ + static bool kdeSupport(); + /* Executes the given helper command, returns true if helper returned success. + */ + static bool command(const nsTArray& command, + nsTArray* output = NULL); + static bool command(nsIArray* command, nsIArray** output = NULL); + /* Like command(), but additionally blocks the parent widget like if there was + a modal dialog shown and enters the event loop (i.e. there are still paint + updates, this is for commands that take long). */ + static bool commandBlockUi(const nsTArray& command, + GtkWindow* parent, + nsTArray* output = NULL); + + private: + nsKDEUtils(); + ~nsKDEUtils(); + static nsKDEUtils* self(); + bool startHelper(); + void closeHelper(); + void feedCommand(const nsTArray& command); + bool internalCommand(const nsTArray& command, GtkWindow* parent, + bool isParent, nsTArray* output); + FILE* commandFile; + FILE* replyFile; +}; + +#endif // nsKDEUtils diff --git a/uriloader/exthandler/HandlerServiceParent.cpp b/uriloader/exthandler/HandlerServiceParent.cpp --- a/uriloader/exthandler/HandlerServiceParent.cpp +++ b/uriloader/exthandler/HandlerServiceParent.cpp @@ -13,17 +13,17 @@ #include "ContentHandlerService.h" #include "nsIExternalProtocolService.h" #include "nsStringEnumerator.h" #include "nsIMutableArray.h" #include "nsCExternalHandlerService.h" #include "nsComponentManagerUtils.h" #include "nsServiceManagerUtils.h" #ifdef MOZ_WIDGET_GTK -# include "unix/nsGNOMERegistry.h" +# include "unix/nsCommonRegistry.h" #endif using mozilla::dom::ContentHandlerService; using mozilla::dom::HandlerApp; using mozilla::dom::HandlerInfo; using mozilla::dom::RemoteHandlerApp; namespace { @@ -305,18 +305,18 @@ mozilla::ipc::IPCResult HandlerServicePa mozilla::ipc::IPCResult HandlerServiceParent::RecvExistsForProtocolOS( const nsACString& aProtocolScheme, bool* aHandlerExists) { if (aProtocolScheme.Length() > MAX_SCHEME_LENGTH) { *aHandlerExists = false; return IPC_OK(); } #ifdef MOZ_WIDGET_GTK // Check the GNOME registry for a protocol handler - *aHandlerExists = - nsGNOMERegistry::HandlerExists(PromiseFlatCString(aProtocolScheme).get()); + *aHandlerExists = nsCommonRegistry::HandlerExists( + PromiseFlatCString(aProtocolScheme).get()); #else *aHandlerExists = false; #endif return IPC_OK(); } /* * Check if a handler exists for the provided protocol. Check the datastore diff --git a/uriloader/exthandler/moz.build b/uriloader/exthandler/moz.build --- a/uriloader/exthandler/moz.build +++ b/uriloader/exthandler/moz.build @@ -81,17 +81,19 @@ elif CONFIG["MOZ_WIDGET_TOOLKIT"] == "ui else: # These files can't be built in unified mode because they redefine LOG. SOURCES += [ osdir + "/nsOSHelperAppService.cpp", ] if CONFIG["MOZ_WIDGET_TOOLKIT"] == "gtk": UNIFIED_SOURCES += [ + "unix/nsCommonRegistry.cpp", "unix/nsGNOMERegistry.cpp", + "unix/nsKDERegistry.cpp", "unix/nsMIMEInfoUnix.cpp", ] elif CONFIG["MOZ_WIDGET_TOOLKIT"] == "android": UNIFIED_SOURCES += [ "android/nsMIMEInfoAndroid.cpp", ] elif CONFIG["MOZ_WIDGET_TOOLKIT"] == "windows": UNIFIED_SOURCES += [ @@ -129,15 +131,16 @@ include("/ipc/chromium/chromium-config.m FINAL_LIBRARY = "xul" LOCAL_INCLUDES += [ "/docshell/base", "/dom/base", "/dom/ipc", "/netwerk/base", "/netwerk/protocol/http", + "/toolkit/xre", ] if CONFIG["MOZ_ENABLE_DBUS"]: CXXFLAGS += CONFIG["MOZ_DBUS_CFLAGS"] if CONFIG["MOZ_WIDGET_TOOLKIT"] == "gtk": CXXFLAGS += CONFIG["MOZ_GTK3_CFLAGS"] diff --git a/uriloader/exthandler/unix/nsCommonRegistry.cpp b/uriloader/exthandler/unix/nsCommonRegistry.cpp new file mode 100644 --- /dev/null +++ b/uriloader/exthandler/unix/nsCommonRegistry.cpp @@ -0,0 +1,42 @@ +/* -*- 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 "nsCommonRegistry.h" + +#include "nsGNOMERegistry.h" +#include "nsKDERegistry.h" +#include "nsString.h" +#include "nsKDEUtils.h" + +/* static */ bool nsCommonRegistry::HandlerExists(const char* aProtocolScheme) { + if (nsKDEUtils::kdeSupport()) + return nsKDERegistry::HandlerExists(aProtocolScheme); + return nsGNOMERegistry::HandlerExists(aProtocolScheme); +} + +/* static */ nsresult nsCommonRegistry::LoadURL(nsIURI* aURL) { + if (nsKDEUtils::kdeSupport()) return nsKDERegistry::LoadURL(aURL); + return nsGNOMERegistry::LoadURL(aURL); +} + +/* static */ void nsCommonRegistry::GetAppDescForScheme( + const nsACString& aScheme, nsAString& aDesc) { + if (nsKDEUtils::kdeSupport()) + return nsKDERegistry::GetAppDescForScheme(aScheme, aDesc); + return nsGNOMERegistry::GetAppDescForScheme(aScheme, aDesc); +} + +/* static */ already_AddRefed +nsCommonRegistry::GetFromExtension(const nsACString& aFileExt) { + if (nsKDEUtils::kdeSupport()) + return nsKDERegistry::GetFromExtension(aFileExt); + return nsGNOMERegistry::GetFromExtension(aFileExt); +} + +/* static */ already_AddRefed nsCommonRegistry::GetFromType( + const nsACString& aMIMEType) { + if (nsKDEUtils::kdeSupport()) return nsKDERegistry::GetFromType(aMIMEType); + return nsGNOMERegistry::GetFromType(aMIMEType); +} diff --git a/uriloader/exthandler/unix/nsCommonRegistry.h b/uriloader/exthandler/unix/nsCommonRegistry.h new file mode 100644 --- /dev/null +++ b/uriloader/exthandler/unix/nsCommonRegistry.h @@ -0,0 +1,28 @@ +/* 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 nsCommonRegistry_h__ +#define nsCommonRegistry_h__ + +#include "nsIURI.h" +#include "nsCOMPtr.h" + +class nsMIMEInfoBase; + +class nsCommonRegistry { + public: + static bool HandlerExists(const char* aProtocolScheme); + + static nsresult LoadURL(nsIURI* aURL); + + static void GetAppDescForScheme(const nsACString& aScheme, nsAString& aDesc); + + static already_AddRefed GetFromExtension( + const nsACString& aFileExt); + + static already_AddRefed GetFromType( + const nsACString& aMIMEType); +}; + +#endif diff --git a/uriloader/exthandler/unix/nsKDERegistry.cpp b/uriloader/exthandler/unix/nsKDERegistry.cpp new file mode 100644 --- /dev/null +++ b/uriloader/exthandler/unix/nsKDERegistry.cpp @@ -0,0 +1,75 @@ +/* -*- 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/StaticPrefs_browser.h" +#include "nsKDERegistry.h" +#include "prlink.h" +#include "prmem.h" +#include "nsString.h" +#include "nsMIMEInfoUnix.h" +#include "nsKDEUtils.h" + +/* static */ bool nsKDERegistry::HandlerExists(const char* aProtocolScheme) { + nsTArray command; + command.AppendElement("HANDLEREXISTS"_ns); + command.AppendElement(nsAutoCString(aProtocolScheme)); + return nsKDEUtils::command(command); +} + +/* static */ nsresult nsKDERegistry::LoadURL(nsIURI* aURL) { + nsTArray command; + command.AppendElement("OPEN"_ns); + nsCString url; + aURL->GetSpec(url); + command.AppendElement(url); + bool rv = nsKDEUtils::command(command); + if (!rv) return NS_ERROR_FAILURE; + + return NS_OK; +} + +/* static */ void nsKDERegistry::GetAppDescForScheme(const nsACString& aScheme, + nsAString& aDesc) { + nsTArray command; + command.AppendElement("GETAPPDESCFORSCHEME"_ns); + command.AppendElement(aScheme); + nsTArray output; + if (nsKDEUtils::command(command, &output) && output.Length() == 1) + CopyUTF8toUTF16(output[0], aDesc); +} + +/* static */ already_AddRefed nsKDERegistry::GetFromExtension( + const nsACString& aFileExt) { + NS_ASSERTION(aFileExt[0] != '.', "aFileExt shouldn't start with a dot"); + nsTArray command; + command.AppendElement("GETFROMEXTENSION"_ns); + command.AppendElement(aFileExt); + return GetFromHelper(command); +} + +/* static */ already_AddRefed nsKDERegistry::GetFromType( + const nsACString& aMIMEType) { + nsTArray command; + command.AppendElement("GETFROMTYPE"_ns); + command.AppendElement(aMIMEType); + return GetFromHelper(command); +} + +/* static */ already_AddRefed nsKDERegistry::GetFromHelper( + const nsTArray& command) { + nsTArray output; + if (nsKDEUtils::command(command, &output) && output.Length() == 3) { + nsCString mimetype = output[0]; + RefPtr mimeInfo = new nsMIMEInfoUnix(mimetype); + NS_ENSURE_TRUE(mimeInfo, nullptr); + nsCString description = output[1]; + mimeInfo->SetDescription(NS_ConvertUTF8toUTF16(description)); + nsCString handlerAppName = output[2]; + mimeInfo->SetPreferredAction(nsIMIMEInfo::saveToDisk); + mimeInfo->SetDefaultDescription(NS_ConvertUTF8toUTF16(handlerAppName)); + return mimeInfo.forget(); + } + return nullptr; +} diff --git a/uriloader/exthandler/unix/nsKDERegistry.h b/uriloader/exthandler/unix/nsKDERegistry.h new file mode 100644 --- /dev/null +++ b/uriloader/exthandler/unix/nsKDERegistry.h @@ -0,0 +1,35 @@ +/* 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 nsKDERegistry_h__ +#define nsKDERegistry_h__ + +#include "nsIURI.h" +#include "nsCOMPtr.h" +#include "nsTArray.h" + +class nsMIMEInfoBase; +// class nsAutoCString; +// class nsCString; + +class nsKDERegistry { + public: + static bool HandlerExists(const char* aProtocolScheme); + + static nsresult LoadURL(nsIURI* aURL); + + static void GetAppDescForScheme(const nsACString& aScheme, nsAString& aDesc); + + static already_AddRefed GetFromExtension( + const nsACString& aFileExt); + + static already_AddRefed GetFromType( + const nsACString& aMIMEType); + + private: + static already_AddRefed GetFromHelper( + const nsTArray& command); +}; + +#endif // nsKDERegistry_h__ diff --git a/uriloader/exthandler/unix/nsMIMEInfoUnix.cpp b/uriloader/exthandler/unix/nsMIMEInfoUnix.cpp --- a/uriloader/exthandler/unix/nsMIMEInfoUnix.cpp +++ b/uriloader/exthandler/unix/nsMIMEInfoUnix.cpp @@ -1,48 +1,51 @@ /* -*- Mode: C++; tab-width: 3; 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 "nsMIMEInfoUnix.h" -#include "nsGNOMERegistry.h" +#include "nsCommonRegistry.h" #include "nsIGIOService.h" #include "nsNetCID.h" #include "nsIIOService.h" #ifdef MOZ_ENABLE_DBUS # include "nsDBusHandlerApp.h" #endif +#if defined(XP_UNIX) && !defined(XP_MACOSX) +# include "nsKDEUtils.h" +#endif nsresult nsMIMEInfoUnix::LoadUriInternal(nsIURI* aURI) { - return nsGNOMERegistry::LoadURL(aURI); + return nsCommonRegistry::LoadURL(aURI); } NS_IMETHODIMP nsMIMEInfoUnix::GetHasDefaultHandler(bool* _retval) { // if a default app is set, it means the application has been set from // either /etc/mailcap or ${HOME}/.mailcap, in which case we don't want to // give the GNOME answer. if (GetDefaultApplication()) { return nsMIMEInfoImpl::GetHasDefaultHandler(_retval); } *_retval = false; if (mClass == eProtocolInfo) { - *_retval = nsGNOMERegistry::HandlerExists(mSchemeOrType.get()); + *_retval = nsCommonRegistry::HandlerExists(mSchemeOrType.get()); } else { RefPtr mimeInfo = - nsGNOMERegistry::GetFromType(mSchemeOrType); + nsCommonRegistry::GetFromType(mSchemeOrType); if (!mimeInfo) { nsAutoCString ext; nsresult rv = GetPrimaryExtension(ext); if (NS_SUCCEEDED(rv)) { - mimeInfo = nsGNOMERegistry::GetFromExtension(ext); + mimeInfo = nsCommonRegistry::GetFromExtension(ext); } } if (mimeInfo) *_retval = true; } if (*_retval) return NS_OK; return NS_OK; @@ -54,16 +57,31 @@ nsresult nsMIMEInfoUnix::LaunchDefaultWi // give the GNOME answer. if (GetDefaultApplication()) { return nsMIMEInfoImpl::LaunchDefaultWithFile(aFile); } nsAutoCString nativePath; aFile->GetNativePath(nativePath); + if (nsKDEUtils::kdeSupport()) { + bool supports; + if (NS_SUCCEEDED(GetHasDefaultHandler(&supports)) && supports) { + nsTArray command; + command.AppendElement("OPEN"_ns); + command.AppendElement(nativePath); + command.AppendElement("MIMETYPE"_ns); + command.AppendElement(mSchemeOrType); + if (nsKDEUtils::command(command)) return NS_OK; + } + if (!GetDefaultApplication()) return NS_ERROR_FILE_NOT_FOUND; + + return LaunchWithIProcess(GetDefaultApplication(), nativePath); + } + nsCOMPtr giovfs = do_GetService(NS_GIOSERVICE_CONTRACTID); if (!giovfs) { return NS_ERROR_FAILURE; } // nsGIOMimeApp->Launch wants a URI string instead of local file nsresult rv; nsCOMPtr ioservice = diff --git a/uriloader/exthandler/unix/nsOSHelperAppService.cpp b/uriloader/exthandler/unix/nsOSHelperAppService.cpp --- a/uriloader/exthandler/unix/nsOSHelperAppService.cpp +++ b/uriloader/exthandler/unix/nsOSHelperAppService.cpp @@ -5,17 +5,17 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include #include #include "nsOSHelperAppService.h" #include "nsMIMEInfoUnix.h" #ifdef MOZ_WIDGET_GTK -# include "nsGNOMERegistry.h" +# include "nsCommonRegistry.h" # ifdef MOZ_BUILD_APP_IS_BROWSER # include "nsIToolkitShellService.h" # include "nsIGNOMEShellService.h" # endif #endif #include "nsISupports.h" #include "nsString.h" #include "nsReadableUtils.h" @@ -1101,17 +1101,17 @@ nsresult nsOSHelperAppService::GetHandle nsresult nsOSHelperAppService::OSProtocolHandlerExists( const char* aProtocolScheme, bool* aHandlerExists) { nsresult rv = NS_OK; if (!XRE_IsContentProcess()) { #ifdef MOZ_WIDGET_GTK // Check the GNOME registry for a protocol handler - *aHandlerExists = nsGNOMERegistry::HandlerExists(aProtocolScheme); + *aHandlerExists = nsCommonRegistry::HandlerExists(aProtocolScheme); #else *aHandlerExists = false; #endif } else { *aHandlerExists = false; nsCOMPtr handlerSvc = do_GetService(NS_HANDLERSERVICE_CONTRACTID, &rv); if (NS_SUCCEEDED(rv) && handlerSvc) { @@ -1121,17 +1121,17 @@ nsresult nsOSHelperAppService::OSProtoco } return rv; } NS_IMETHODIMP nsOSHelperAppService::GetApplicationDescription( const nsACString& aScheme, nsAString& _retval) { #ifdef MOZ_WIDGET_GTK - nsGNOMERegistry::GetAppDescForScheme(aScheme, _retval); + nsCommonRegistry::GetAppDescForScheme(aScheme, _retval); return _retval.IsEmpty() ? NS_ERROR_NOT_AVAILABLE : NS_OK; #else return NS_ERROR_NOT_AVAILABLE; #endif } NS_IMETHODIMP nsOSHelperAppService::IsCurrentAppOSDefaultForProtocol( const nsACString& aScheme, bool* _retval) { @@ -1226,17 +1226,17 @@ already_AddRefed nsOSHel nsresult rv = LookUpTypeAndDescription(NS_ConvertUTF8toUTF16(aFileExt), majorType, minorType, mime_types_description, true); if (NS_FAILED(rv) || majorType.IsEmpty()) { #ifdef MOZ_WIDGET_GTK LOG("Looking in GNOME registry\n"); RefPtr gnomeInfo = - nsGNOMERegistry::GetFromExtension(aFileExt); + nsCommonRegistry::GetFromExtension(aFileExt); if (gnomeInfo) { LOG("Got MIMEInfo from GNOME registry\n"); return gnomeInfo.forget(); } #endif rv = LookUpTypeAndDescription(NS_ConvertUTF8toUTF16(aFileExt), majorType, minorType, mime_types_description, false); @@ -1339,17 +1339,17 @@ already_AddRefed nsOSHel // Now look up our extensions nsAutoString extensions, mime_types_description; LookUpExtensionsAndDescription(majorType, minorType, extensions, mime_types_description); #ifdef MOZ_WIDGET_GTK if (handler.IsEmpty()) { - RefPtr gnomeInfo = nsGNOMERegistry::GetFromType(aMIMEType); + RefPtr gnomeInfo = nsCommonRegistry::GetFromType(aMIMEType); if (gnomeInfo) { LOG("Got MIMEInfo from GNOME registry without extensions; setting them " "to %s\n", NS_LossyConvertUTF16toASCII(extensions).get()); NS_ASSERTION(!gnomeInfo->HasExtensions(), "How'd that happen?"); gnomeInfo->SetFileExtensions(NS_ConvertUTF16toUTF8(extensions)); return gnomeInfo.forget(); diff --git a/widget/gtk/moz.build b/widget/gtk/moz.build --- a/widget/gtk/moz.build +++ b/widget/gtk/moz.build @@ -146,16 +146,17 @@ FINAL_LIBRARY = "xul" LOCAL_INCLUDES += [ "/layout/base", "/layout/forms", "/layout/generic", "/layout/xul", "/other-licenses/atk-1.0", "/third_party/cups/include", + "/toolkit/xre", "/widget", "/widget/headless", "/widget/x11", ] DEFINES["CAIRO_GFX"] = True DEFINES["MOZ_APP_NAME"] = '"%s"' % CONFIG["MOZ_APP_NAME"] diff --git a/widget/gtk/nsFilePicker.cpp b/widget/gtk/nsFilePicker.cpp --- a/widget/gtk/nsFilePicker.cpp +++ b/widget/gtk/nsFilePicker.cpp @@ -1,15 +1,16 @@ /* -*- 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 #include +#include #include #include #include #include "mozilla/Types.h" #include "AsyncDBus.h" #include "nsGtkUtils.h" #include "nsIFileURL.h" @@ -25,16 +26,18 @@ #include "nsArrayEnumerator.h" #include "nsEnumeratorUtils.h" #include "nsNetUtil.h" #include "nsReadableUtils.h" #include "MozContainer.h" #include "WidgetUtilsGtk.h" #include "nsFilePicker.h" +#include "nsKDEUtils.h" +#include "nsURLHelper.h" #undef LOG #ifdef MOZ_LOGGING # include "mozilla/Logging.h" # include "nsTArray.h" # include "Units.h" extern mozilla::LazyLogModule gWidgetLog; # define LOG(args) MOZ_LOG(gWidgetLog, mozilla::LogLevel::Debug, args) @@ -305,17 +308,18 @@ nsFilePicker::AppendFilters(int32_t aFil mAllowURLs = !!(aFilterMask & filterAllowURLs); return nsBaseFilePicker::AppendFilters(aFilterMask); } NS_IMETHODIMP nsFilePicker::AppendFilter(const nsAString& aTitle, const nsAString& aFilter) { if (aFilter.EqualsLiteral("..apps")) { // No platform specific thing we can do here, really.... - return NS_OK; + // Unless it's KDE. + if (mMode != modeOpen || !nsKDEUtils::kdeSupport()) return NS_OK; } nsAutoCString filter, name; CopyUTF16toUTF8(aFilter, filter); CopyUTF16toUTF8(aTitle, name); mFilters.AppendElement(filter); mFilterNames.AppendElement(name); @@ -415,16 +419,41 @@ nsresult nsFilePicker::Show(nsIFilePicke return NS_OK; } NS_IMETHODIMP nsFilePicker::Open(nsIFilePickerShownCallback* aCallback) { // Can't show two dialogs concurrently with the same filepicker if (mRunning) return NS_ERROR_NOT_AVAILABLE; + // KDE file picker is not handled via callback + if (nsKDEUtils::kdeSupport()) { + mCallback = aCallback; + mRunning = true; + NS_ADDREF_THIS(); + g_idle_add( + [](gpointer data) -> gboolean { + nsFilePicker* queuedPicker = (nsFilePicker*)data; + nsIFilePicker::ResultCode result; + queuedPicker->kdeFileDialog(&result); + if (queuedPicker->mCallback) { + queuedPicker->mCallback->Done(result); + queuedPicker->mCallback = nullptr; + } else { + queuedPicker->mResult = result; + } + queuedPicker->mRunning = false; + NS_RELEASE(queuedPicker); + return G_SOURCE_REMOVE; + }, + this); + + return NS_OK; + } + NS_ConvertUTF16toUTF8 title(mTitle); GtkWindow* parent_widget = GTK_WINDOW(mParentWidget->GetNativeData(NS_NATIVE_SHELLWIDGET)); GtkFileChooserAction action = GetGtkFileChooserAction(mMode); const gchar* accept_button; @@ -696,16 +725,215 @@ void nsFilePicker::Done(void* file_choos mCallback->Done(result); mCallback = nullptr; } else { mResult = result; } NS_RELEASE_THIS(); } +nsCString nsFilePicker::kdeMakeFilter(int index) { + nsCString buf = mFilters[index]; + for (PRUint32 i = 0; i < buf.Length(); ++i) + if (buf[i] == ';') // KDE separates just using spaces + buf.SetCharAt(' ', i); + if (!mFilterNames[index].IsEmpty()) { + buf += "|"; + buf += mFilterNames[index].get(); + } + return buf; +} + +static PRInt32 windowToXid(nsIWidget* widget) { + GtkWindow* parent_widget = + GTK_WINDOW(widget->GetNativeData(NS_NATIVE_SHELLWIDGET)); + GdkWindow* gdk_window = + gtk_widget_get_window(gtk_widget_get_toplevel(GTK_WIDGET(parent_widget))); + return GDK_WINDOW_XID(gdk_window); +} + +NS_IMETHODIMP nsFilePicker::kdeFileDialog(nsIFilePicker::ResultCode* aReturn) { + NS_ENSURE_ARG_POINTER(aReturn); + + if (mMode == modeOpen && mFilters.Length() == 1 && + mFilters[0].EqualsLiteral("..apps")) + return kdeAppsDialog(aReturn); + + nsCString title; + title.Adopt(ToNewUTF8String(mTitle)); + + const char* arg = NULL; + if (mAllowURLs) { + switch (mMode) { + case nsIFilePicker::modeOpen: + case nsIFilePicker::modeOpenMultiple: + arg = "GETOPENURL"; + break; + case nsIFilePicker::modeSave: + arg = "GETSAVEURL"; + break; + case nsIFilePicker::modeGetFolder: + arg = "GETDIRECTORYURL"; + break; + } + } else { + switch (mMode) { + case nsIFilePicker::modeOpen: + case nsIFilePicker::modeOpenMultiple: + arg = "GETOPENFILENAME"; + break; + case nsIFilePicker::modeSave: + arg = "GETSAVEFILENAME"; + break; + case nsIFilePicker::modeGetFolder: + arg = "GETDIRECTORYFILENAME"; + break; + } + } + + nsAutoCString directory; + if (mDisplayDirectory) { + mDisplayDirectory->GetNativePath(directory); + } else if (mPrevDisplayDirectory) { + mPrevDisplayDirectory->GetNativePath(directory); + } + + nsAutoCString startdir; + if (!directory.IsEmpty()) { + startdir = directory; + } + if (mMode == nsIFilePicker::modeSave) { + if (!startdir.IsEmpty()) { + startdir += "/"; + startdir += ToNewUTF8String(mDefault); + } else + startdir = ToNewUTF8String(mDefault); + } + + nsAutoCString filters; + PRInt32 count = mFilters.Length(); + if (count == 0) // just in case + filters = "*"; + else { + filters = kdeMakeFilter(0); + for (PRInt32 i = 1; i < count; ++i) { + filters += "\n"; + filters += kdeMakeFilter(i); + } + } + + nsTArray command; + command.AppendElement(nsAutoCString(arg)); + command.AppendElement(startdir); + if (mMode != nsIFilePicker::modeGetFolder) { + command.AppendElement(filters); + nsAutoCString selected; + selected.AppendInt(mSelectedType); + command.AppendElement(selected); + } + command.AppendElement(title); + if (mMode == nsIFilePicker::modeOpenMultiple) + command.AppendElement("MULTIPLE"_ns); + if (PRInt32 xid = windowToXid(mParentWidget)) { + command.AppendElement("PARENT"_ns); + nsAutoCString parent; + parent.AppendInt(xid); + command.AppendElement(parent); + } + + nsTArray output; + if (nsKDEUtils::commandBlockUi( + command, + GTK_WINDOW(mParentWidget->GetNativeData(NS_NATIVE_SHELLWIDGET)), + &output)) { + *aReturn = nsIFilePicker::returnOK; + mFiles.Clear(); + if (mMode != nsIFilePicker::modeGetFolder) { + mSelectedType = atoi(output[0].get()); + output.RemoveElementAt(0); + } + if (mMode == nsIFilePicker::modeOpenMultiple) { + mFileURL.Truncate(); + PRUint32 count = output.Length(); + for (PRUint32 i = 0; i < count; ++i) { + nsCOMPtr localfile; + nsresult rv = NS_NewNativeLocalFile(output[i], PR_FALSE, + getter_AddRefs(localfile)); + if (NS_SUCCEEDED(rv)) mFiles.AppendObject(localfile); + } + } else { + if (output.Length() == 0) + mFileURL = nsCString(); + else if (mAllowURLs) + mFileURL = output[0]; + else // GetFile() actually requires it to be url even for local files :-/ + { + nsCOMPtr localfile; + nsresult rv = NS_NewNativeLocalFile(output[0], PR_FALSE, + getter_AddRefs(localfile)); + if (NS_SUCCEEDED(rv)) + rv = net_GetURLSpecFromActualFile(localfile, mFileURL); + } + } + // Remember last used directory. + nsCOMPtr file; + GetFile(getter_AddRefs(file)); + if (file) { + nsCOMPtr dir; + file->GetParent(getter_AddRefs(dir)); + nsCOMPtr localDir(dir); + if (localDir) { + localDir.swap(mPrevDisplayDirectory); + } + } + if (mMode == nsIFilePicker::modeSave) { + nsCOMPtr file; + GetFile(getter_AddRefs(file)); + if (file) { + bool exists = false; + file->Exists(&exists); + if (exists) // TODO do overwrite check in the helper app + *aReturn = nsIFilePicker::returnReplace; + } + } + } else { + *aReturn = nsIFilePicker::returnCancel; + } + return NS_OK; +} + +NS_IMETHODIMP nsFilePicker::kdeAppsDialog(nsIFilePicker::ResultCode* aReturn) { + NS_ENSURE_ARG_POINTER(aReturn); + + nsCString title; + title.Adopt(ToNewUTF8String(mTitle)); + + nsTArray command; + command.AppendElement("APPSDIALOG"_ns); + command.AppendElement(title); + if (PRInt32 xid = windowToXid(mParentWidget)) { + command.AppendElement("PARENT"_ns); + nsAutoCString parent; + parent.AppendInt(xid); + command.AppendElement(parent); + } + + nsTArray output; + if (nsKDEUtils::commandBlockUi( + command, + GTK_WINDOW(mParentWidget->GetNativeData(NS_NATIVE_SHELLWIDGET)), + &output)) { + *aReturn = nsIFilePicker::returnOK; + mFileURL = output.Length() > 0 ? output[0] : nsCString(); + } else { + *aReturn = nsIFilePicker::returnCancel; + } + return NS_OK; +} + // All below functions available as of GTK 3.20+ void* nsFilePicker::GtkFileChooserNew(const gchar* title, GtkWindow* parent, GtkFileChooserAction action, const gchar* accept_label) { static auto sGtkFileChooserNativeNewPtr = (void* (*)(const gchar*, GtkWindow*, GtkFileChooserAction, const gchar*, const gchar*))dlsym(RTLD_DEFAULT, "gtk_file_chooser_native_new"); diff --git a/widget/gtk/nsFilePicker.h b/widget/gtk/nsFilePicker.h --- a/widget/gtk/nsFilePicker.h +++ b/widget/gtk/nsFilePicker.h @@ -71,16 +71,22 @@ class nsFilePicker : public nsBaseFilePi nsString mDefaultExtension; nsTArray mFilters; nsTArray mFilterNames; private: static nsIFile* mPrevDisplayDirectory; + bool kdeRunning(); + bool getKdeRunning(); + NS_IMETHODIMP kdeFileDialog(nsIFilePicker::ResultCode* aReturn); + NS_IMETHODIMP kdeAppsDialog(nsIFilePicker::ResultCode* aReturn); + nsCString kdeMakeFilter(int index); + void* GtkFileChooserNew(const gchar* title, GtkWindow* parent, GtkFileChooserAction action, const gchar* accept_label); void GtkFileChooserShow(void* file_chooser); void GtkFileChooserDestroy(void* file_chooser); void GtkFileChooserSetModal(void* file_chooser, GtkWindow* parent_widget, gboolean modal); diff --git a/xpcom/components/ManifestParser.cpp b/xpcom/components/ManifestParser.cpp --- a/xpcom/components/ManifestParser.cpp +++ b/xpcom/components/ManifestParser.cpp @@ -38,16 +38,17 @@ #include "nsTextFormatter.h" #include "nsVersionComparator.h" #include "nsXPCOMCIDInternal.h" #include "nsIConsoleService.h" #include "nsIScriptError.h" #include "nsIXULAppInfo.h" #include "nsIXULRuntime.h" +#include "nsKDEUtils.h" using namespace mozilla; struct ManifestDirective { const char* directive; int argc; bool ischrome; @@ -389,16 +390,17 @@ void ParseManifest(NSLocationType aType, constexpr auto kRemoteEnabled = u"remoteenabled"_ns; constexpr auto kRemoteRequired = u"remoterequired"_ns; constexpr auto kApplication = u"application"_ns; constexpr auto kAppVersion = u"appversion"_ns; constexpr auto kGeckoVersion = u"platformversion"_ns; constexpr auto kOs = u"os"_ns; constexpr auto kOsVersion = u"osversion"_ns; constexpr auto kABI = u"abi"_ns; + constexpr auto kDesktop = u"desktop"_ns; constexpr auto kProcess = u"process"_ns; #if defined(MOZ_WIDGET_ANDROID) constexpr auto kTablet = u"tablet"_ns; #endif // You might expect this to be guarded by MOZ_BACKGROUNDTASKS, but it's not // possible to have conditional manifest contents, so we need to recognize and // discard these tokens even when MOZ_BACKGROUNDTASKS is not set. constexpr auto kBackgroundTask = u"backgroundtask"_ns; @@ -448,39 +450,44 @@ void ParseManifest(NSLocationType aType, CopyUTF8toUTF16(s, abi); abi.Insert(char16_t('_'), 0); abi.Insert(osTarget, 0); } } } nsAutoString osVersion; + nsAutoString desktop; #if defined(XP_WIN) # pragma warning(push) # pragma warning(disable : 4996) // VC12+ deprecates GetVersionEx OSVERSIONINFO info = {sizeof(OSVERSIONINFO)}; if (GetVersionEx(&info)) { nsTextFormatter::ssprintf(osVersion, u"%ld.%ld", info.dwMajorVersion, info.dwMinorVersion); } + desktop = u"win"_ns; # pragma warning(pop) #elif defined(MOZ_WIDGET_COCOA) SInt32 majorVersion = nsCocoaFeatures::macOSVersionMajor(); SInt32 minorVersion = nsCocoaFeatures::macOSVersionMinor(); nsTextFormatter::ssprintf(osVersion, u"%ld.%ld", majorVersion, minorVersion); + desktop = u"macosx"_ns); #elif defined(MOZ_WIDGET_GTK) nsTextFormatter::ssprintf(osVersion, u"%ld.%ld", gtk_major_version, gtk_minor_version); + desktop = nsKDEUtils::kdeSession() ? u"kde"_ns : u"gnome"_ns; #elif defined(MOZ_WIDGET_ANDROID) bool isTablet = false; if (jni::IsAvailable()) { jni::String::LocalRef release = java::sdk::Build::VERSION::RELEASE(); osVersion.Assign(release->ToString()); isTablet = java::GeckoAppShell::IsTablet(); } + desktop = u"android"_ns; #endif if (XRE_IsContentProcess()) { process = kContent; } else { process = kMain; } @@ -571,25 +578,27 @@ void ParseManifest(NSLocationType aType, // When in background task mode, default to not registering // category directivies unless backgroundtask=1 is specified. TriState stBackgroundTask = (BackgroundTasks::IsBackgroundTaskMode() && strcmp("category", directive->directive) == 0) ? eBad : eUnspecified; #endif int flags = 0; + TriState stDesktop = eUnspecified; while ((token = nsCRT::strtok(whitespace, kWhitespace, &whitespace)) && ok) { ToLowerCase(token); NS_ConvertASCIItoUTF16 wtoken(token); if (CheckStringFlag(kApplication, wtoken, appID, stApp) || CheckOsFlag(kOs, wtoken, osTarget, stOs) || CheckStringFlag(kABI, wtoken, abi, stABI) || + CheckStringFlag(kDesktop, wtoken, desktop, stDesktop) || CheckStringFlag(kProcess, wtoken, process, stProcess) || CheckVersionFlag(kOsVersion, wtoken, osVersion, stOsVersion) || CheckVersionFlag(kAppVersion, wtoken, appVersion, stAppVersion) || CheckVersionFlag(kGeckoVersion, wtoken, geckoVersion, stGeckoVersion)) { continue; } @@ -639,16 +648,17 @@ void ParseManifest(NSLocationType aType, LogMessageWithContext( aFile, line, "Unrecognized chrome manifest modifier '%s'.", token); ok = false; } if (!ok || stApp == eBad || stAppVersion == eBad || stGeckoVersion == eBad || stOs == eBad || stOsVersion == eBad || + stDesktop == eBad || #ifdef MOZ_WIDGET_ANDROID stTablet == eBad || #endif #ifdef MOZ_BACKGROUNDTASKS stBackgroundTask == eBad || #endif stABI == eBad || stProcess == eBad) { continue; diff --git a/xpcom/components/moz.build b/xpcom/components/moz.build --- a/xpcom/components/moz.build +++ b/xpcom/components/moz.build @@ -66,16 +66,17 @@ LOCAL_INCLUDES += [ "!..", "../base", "../build", "../ds", "/chrome", "/js/xpconnect/loader", "/layout/build", "/modules/libjar", + "/toolkit/xre", ] if CONFIG["MOZ_WIDGET_TOOLKIT"] == "gtk": CXXFLAGS += CONFIG["MOZ_GTK3_CFLAGS"] include("/ipc/chromium/chromium-config.mozbuild") PYTHON_UNITTEST_MANIFESTS += [ diff --git a/xpcom/io/nsLocalFileUnix.cpp b/xpcom/io/nsLocalFileUnix.cpp --- a/xpcom/io/nsLocalFileUnix.cpp +++ b/xpcom/io/nsLocalFileUnix.cpp @@ -46,16 +46,17 @@ #include "nsString.h" #include "nsIDirectoryEnumerator.h" #include "nsSimpleEnumerator.h" #include "private/pprio.h" #include "prlink.h" #ifdef MOZ_WIDGET_GTK # include "nsIGIOService.h" +# include "nsKDEUtils.h" #endif #ifdef MOZ_WIDGET_COCOA # include # include "CocoaFileUtils.h" # include "prmem.h" # include "plbase64.h" @@ -2205,20 +2206,28 @@ nsLocalFile::SetPersistentDescriptor(con NS_IMETHODIMP nsLocalFile::Reveal() { if (!FilePreferences::IsAllowedPath(mPath)) { return NS_ERROR_FILE_ACCESS_DENIED; } #ifdef MOZ_WIDGET_GTK + nsAutoCString url; nsCOMPtr giovfs = do_GetService(NS_GIOSERVICE_CONTRACTID); - if (!giovfs) { - return NS_ERROR_FAILURE; + url = mPath; + if (nsKDEUtils::kdeSupport()) { + nsTArray command; + command.AppendElement("REVEAL"_ns); + command.AppendElement(mPath); + return nsKDEUtils::command(command) ? NS_OK : NS_ERROR_FAILURE; } + + if (!giovfs) return NS_ERROR_FAILURE; + return giovfs->RevealFile(this); #elif defined(MOZ_WIDGET_COCOA) CFURLRef url; if (NS_SUCCEEDED(GetCFURL(&url))) { nsresult rv = CocoaFileUtils::RevealFileInFinder(url); ::CFRelease(url); return rv; } @@ -2230,16 +2239,23 @@ nsLocalFile::Reveal() { NS_IMETHODIMP nsLocalFile::Launch() { if (!FilePreferences::IsAllowedPath(mPath)) { return NS_ERROR_FILE_ACCESS_DENIED; } #ifdef MOZ_WIDGET_GTK + if (nsKDEUtils::kdeSupport()) { + nsTArray command; + command.AppendElement("OPEN"_ns); + command.AppendElement(mPath); + return nsKDEUtils::command(command) ? NS_OK : NS_ERROR_FAILURE; + } + nsCOMPtr giovfs = do_GetService(NS_GIOSERVICE_CONTRACTID); if (!giovfs) { return NS_ERROR_FAILURE; } return giovfs->LaunchFile(mPath); #elif defined(MOZ_WIDGET_ANDROID) // Not supported on GeckoView