diff options
Diffstat (limited to 'toolkit/components/remote')
29 files changed, 2787 insertions, 0 deletions
diff --git a/toolkit/components/remote/RemoteUtils.cpp b/toolkit/components/remote/RemoteUtils.cpp new file mode 100644 index 0000000000..f7677f92b9 --- /dev/null +++ b/toolkit/components/remote/RemoteUtils.cpp @@ -0,0 +1,102 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:expandtab:shiftwidth=2:tabstop=8: + */ +/* vim:set ts=8 sw=2 et cindent: */ +/* 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 <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <limits.h> + +#include "RemoteUtils.h" + +#ifdef IS_BIG_ENDIAN +# define TO_LITTLE_ENDIAN32(x) \ + ((((x)&0xff000000) >> 24) | (((x)&0x00ff0000) >> 8) | \ + (((x)&0x0000ff00) << 8) | (((x)&0x000000ff) << 24)) +#else +# define TO_LITTLE_ENDIAN32(x) (x) +#endif + +#ifndef MAX_PATH +# ifdef PATH_MAX +# define MAX_PATH PATH_MAX +# else +# define MAX_PATH 1024 +# endif +#endif + +/* like strcpy, but return the char after the final null */ +static char* estrcpy(const char* s, char* d) { + while (*s) *d++ = *s++; + + *d++ = '\0'; + return d; +} + +/* Construct a command line from given args and desktop startup ID. + * Returned buffer must be released by free(). + */ +char* ConstructCommandLine(int32_t argc, char** argv, const char* aStartupToken, + int* aCommandLineLength) { + char cwdbuf[MAX_PATH]; + if (!getcwd(cwdbuf, MAX_PATH)) return nullptr; + + // the commandline property is constructed as an array of int32_t + // followed by a series of null-terminated strings: + // + // [argc][offsetargv0][offsetargv1...]<workingdir>\0<argv[0]>\0argv[1]...\0 + // (offset is from the beginning of the buffer) + + static char startupTokenPrefix[] = " STARTUP_TOKEN="; + + int32_t argvlen = strlen(cwdbuf); + for (int i = 0; i < argc; ++i) { + int32_t len = strlen(argv[i]); + if (i == 0 && aStartupToken) { + len += sizeof(startupTokenPrefix) - 1 + strlen(aStartupToken); + } + argvlen += len; + } + + auto* buffer = + (int32_t*)malloc(argvlen + argc + 1 + sizeof(int32_t) * (argc + 1)); + if (!buffer) return nullptr; + + buffer[0] = TO_LITTLE_ENDIAN32(argc); + + auto* bufend = (char*)(buffer + argc + 1); + + bufend = estrcpy(cwdbuf, bufend); + + for (int i = 0; i < argc; ++i) { + buffer[i + 1] = TO_LITTLE_ENDIAN32(bufend - ((char*)buffer)); + bufend = estrcpy(argv[i], bufend); + if (i == 0 && aStartupToken) { + bufend = estrcpy(startupTokenPrefix, bufend - 1); + bufend = estrcpy(aStartupToken, bufend - 1); + } + } + +#ifdef DEBUG_command_line + int32_t debug_argc = TO_LITTLE_ENDIAN32(*buffer); + char* debug_workingdir = (char*)(buffer + argc + 1); + + printf( + "Sending command line:\n" + " working dir: %s\n" + " argc:\t%i", + debug_workingdir, debug_argc); + + int32_t* debug_offset = buffer + 1; + for (int debug_i = 0; debug_i < debug_argc; ++debug_i) + printf(" argv[%i]:\t%s\n", debug_i, + ((char*)buffer) + TO_LITTLE_ENDIAN32(debug_offset[debug_i])); +#endif + + *aCommandLineLength = bufend - reinterpret_cast<char*>(buffer); + return reinterpret_cast<char*>(buffer); +} diff --git a/toolkit/components/remote/RemoteUtils.h b/toolkit/components/remote/RemoteUtils.h new file mode 100644 index 0000000000..2d21293eb5 --- /dev/null +++ b/toolkit/components/remote/RemoteUtils.h @@ -0,0 +1,31 @@ +/* -*- Mode: C++; tab-width: 8; 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 TOOLKIT_COMPONENTS_REMOTE_REMOTEUTILS_H_ +#define TOOLKIT_COMPONENTS_REMOTE_REMOTEUTILS_H_ + +#include "nsString.h" +#if defined XP_WIN +# include "WinUtils.h" +#endif + +#if defined XP_WIN || defined XP_MACOSX +static void BuildClassName(const char* aProgram, const char* aProfile, + nsString& aClassName) { + aClassName.AppendPrintf("Mozilla_%s", aProgram); +# if defined XP_WIN + nsString pfn = mozilla::widget::WinUtils::GetPackageFamilyName(); + if (!pfn.IsEmpty()) { + aClassName.AppendPrintf("_%S", static_cast<const wchar_t*>(pfn.get())); + } +# endif + aClassName.AppendPrintf("_%s_RemoteWindow", aProfile); +} +#endif + +char* ConstructCommandLine(int32_t argc, char** argv, const char* aStartupToken, + int* aCommandLineLength); + +#endif // TOOLKIT_COMPONENTS_REMOTE_REMOTEUTILS_H_ diff --git a/toolkit/components/remote/WinRemoteMessage.cpp b/toolkit/components/remote/WinRemoteMessage.cpp new file mode 100644 index 0000000000..98f425ba1a --- /dev/null +++ b/toolkit/components/remote/WinRemoteMessage.cpp @@ -0,0 +1,124 @@ +/* -*- 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 "nsCommandLine.h" +#include "mozilla/CmdLineAndEnvUtils.h" +#include "WinRemoteMessage.h" + +using namespace mozilla; + +WinRemoteMessageSender::WinRemoteMessageSender(const char* aCommandLine) + : mData({static_cast<DWORD>(WinRemoteMessageVersion::CommandLineOnly)}), + mUtf8Buffer(aCommandLine) { + mUtf8Buffer.Append('\0'); + + char* mutableBuffer; + mData.cbData = mUtf8Buffer.GetMutableData(&mutableBuffer); + mData.lpData = mutableBuffer; +} + +WinRemoteMessageSender::WinRemoteMessageSender(const char* aCommandLine, + const char* aWorkingDir) + : mData({static_cast<DWORD>( + WinRemoteMessageVersion::CommandLineAndWorkingDir)}), + mUtf8Buffer(aCommandLine) { + mUtf8Buffer.Append('\0'); + mUtf8Buffer.Append(aWorkingDir); + mUtf8Buffer.Append('\0'); + + char* mutableBuffer; + mData.cbData = mUtf8Buffer.GetMutableData(&mutableBuffer); + mData.lpData = mutableBuffer; +} + +WinRemoteMessageSender::WinRemoteMessageSender(const wchar_t* aCommandLine, + const wchar_t* aWorkingDir) + : mData({static_cast<DWORD>( + WinRemoteMessageVersion::CommandLineAndWorkingDirInUtf16)}), + mUtf16Buffer(aCommandLine) { + mUtf16Buffer.Append(u'\0'); + mUtf16Buffer.Append(aWorkingDir); + mUtf16Buffer.Append(u'\0'); + + char16_t* mutableBuffer; + mData.cbData = mUtf16Buffer.GetMutableData(&mutableBuffer) * sizeof(char16_t); + mData.lpData = mutableBuffer; +} + +COPYDATASTRUCT* WinRemoteMessageSender::CopyData() { return &mData; } + +nsresult WinRemoteMessageReceiver::ParseV0(const nsACString& aBuffer) { + CommandLineParserWin<char> parser; + parser.HandleCommandLine(aBuffer); + + mCommandLine = new nsCommandLine(); + return mCommandLine->Init(parser.Argc(), parser.Argv(), nullptr, + nsICommandLine::STATE_REMOTE_AUTO); +} + +nsresult WinRemoteMessageReceiver::ParseV1(const nsACString& aBuffer) { + CommandLineParserWin<char> parser; + size_t cch = parser.HandleCommandLine(aBuffer); + ++cch; // skip a null char + + nsCOMPtr<nsIFile> workingDir; + if (cch < aBuffer.Length()) { + NS_NewLocalFile(NS_ConvertUTF8toUTF16(Substring(aBuffer, cch)), false, + getter_AddRefs(workingDir)); + } + + mCommandLine = new nsCommandLine(); + return mCommandLine->Init(parser.Argc(), parser.Argv(), workingDir, + nsICommandLine::STATE_REMOTE_AUTO); +} + +nsresult WinRemoteMessageReceiver::ParseV2(const nsAString& aBuffer) { + CommandLineParserWin<char16_t> parser; + size_t cch = parser.HandleCommandLine(aBuffer); + ++cch; // skip a null char + + nsCOMPtr<nsIFile> workingDir; + if (cch < aBuffer.Length()) { + NS_NewLocalFile(Substring(aBuffer, cch), false, getter_AddRefs(workingDir)); + } + + int argc = parser.Argc(); + Vector<nsAutoCString> utf8args; + if (!utf8args.reserve(argc)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + UniquePtr<const char*[]> argv(new const char*[argc]); + for (int i = 0; i < argc; ++i) { + utf8args.infallibleAppend(NS_ConvertUTF16toUTF8(parser.Argv()[i])); + argv[i] = utf8args[i].get(); + } + + mCommandLine = new nsCommandLine(); + return mCommandLine->Init(argc, argv.get(), workingDir, + nsICommandLine::STATE_REMOTE_AUTO); +} + +nsresult WinRemoteMessageReceiver::Parse(const COPYDATASTRUCT* aMessageData) { + switch (static_cast<WinRemoteMessageVersion>(aMessageData->dwData)) { + case WinRemoteMessageVersion::CommandLineOnly: + return ParseV0(nsDependentCSubstring( + reinterpret_cast<char*>(aMessageData->lpData), aMessageData->cbData)); + case WinRemoteMessageVersion::CommandLineAndWorkingDir: + return ParseV1(nsDependentCSubstring( + reinterpret_cast<char*>(aMessageData->lpData), aMessageData->cbData)); + case WinRemoteMessageVersion::CommandLineAndWorkingDirInUtf16: + return ParseV2(nsDependentSubstring( + reinterpret_cast<char16_t*>(aMessageData->lpData), + aMessageData->cbData / sizeof(char16_t))); + default: + MOZ_ASSERT_UNREACHABLE("Unsupported message version"); + return NS_ERROR_FAILURE; + } +} + +nsICommandLineRunner* WinRemoteMessageReceiver::CommandLineRunner() { + return mCommandLine; +} diff --git a/toolkit/components/remote/WinRemoteMessage.h b/toolkit/components/remote/WinRemoteMessage.h new file mode 100644 index 0000000000..948c6db310 --- /dev/null +++ b/toolkit/components/remote/WinRemoteMessage.h @@ -0,0 +1,69 @@ +/* -*- 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 __WinRemoteMessage_h__ +#define __WinRemoteMessage_h__ + +#include <windows.h> + +#include "nsICommandLineRunner.h" +#include "nsCOMPtr.h" +#include "nsString.h" + +// This version defines the format of COPYDATASTRUCT::lpData in a message of +// WM_COPYDATA. +// Always use the latest version for production use because the older versions +// have a bug that a non-ascii character in a utf-8 message cannot be parsed +// correctly (bug 1650637). We keep the older versions for backward +// compatibility and the testing purpose only. +enum class WinRemoteMessageVersion : uint32_t { + // "<CommandLine>\0" in utf8 + CommandLineOnly = 0, + // "<CommandLine>\0<WorkingDir>\0" in utf8 + CommandLineAndWorkingDir, + // L"<CommandLine>\0<WorkingDir>\0" in utf16 + CommandLineAndWorkingDirInUtf16, +}; + +class WinRemoteMessageSender final { + COPYDATASTRUCT mData; + nsAutoString mUtf16Buffer; + nsAutoCString mUtf8Buffer; + + public: + WinRemoteMessageSender(const wchar_t* aCommandLine, + const wchar_t* aWorkingDir); + + WinRemoteMessageSender(const WinRemoteMessageSender&) = delete; + WinRemoteMessageSender(WinRemoteMessageSender&&) = delete; + WinRemoteMessageSender& operator=(const WinRemoteMessageSender&) = delete; + WinRemoteMessageSender& operator=(WinRemoteMessageSender&&) = delete; + + COPYDATASTRUCT* CopyData(); + + // Constructors for the old formats. Testing purpose only. + explicit WinRemoteMessageSender(const char* aCommandLine); + WinRemoteMessageSender(const char* aCommandLine, const char* aWorkingDir); +}; + +class WinRemoteMessageReceiver final { + nsCOMPtr<nsICommandLineRunner> mCommandLine; + + nsresult ParseV0(const nsACString& aBuffer); + nsresult ParseV1(const nsACString& aBuffer); + nsresult ParseV2(const nsAString& aBuffer); + + public: + WinRemoteMessageReceiver() = default; + WinRemoteMessageReceiver(const WinRemoteMessageReceiver&) = delete; + WinRemoteMessageReceiver(WinRemoteMessageReceiver&&) = delete; + WinRemoteMessageReceiver& operator=(const WinRemoteMessageReceiver&) = delete; + WinRemoteMessageReceiver& operator=(WinRemoteMessageReceiver&&) = delete; + + nsresult Parse(const COPYDATASTRUCT* aMessageData); + nsICommandLineRunner* CommandLineRunner(); +}; + +#endif // __WinRemoteMessage_h__ diff --git a/toolkit/components/remote/moz.build b/toolkit/components/remote/moz.build new file mode 100644 index 0000000000..d3bab6cf9a --- /dev/null +++ b/toolkit/components/remote/moz.build @@ -0,0 +1,53 @@ +# -*- 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/. + +with Files("**"): + BUG_COMPONENT = ("Toolkit", "Startup and Profile System") + +SOURCES += [ + "nsRemoteService.cpp", +] + +if CONFIG["MOZ_WIDGET_TOOLKIT"] == "gtk": + SOURCES += [ + "nsUnixRemoteServer.cpp", + "RemoteUtils.cpp", + ] + if CONFIG["MOZ_ENABLE_DBUS"]: + SOURCES += [ + "nsDBusRemoteClient.cpp", + "nsDBusRemoteServer.cpp", + ] + CXXFLAGS += CONFIG["MOZ_DBUS_GLIB_CFLAGS"] + EXPORTS += [ + "nsUnixRemoteServer.h", + "RemoteUtils.h", + ] + else: + SOURCES += [ + "nsGTKRemoteServer.cpp", + "nsXRemoteClient.cpp", + "nsXRemoteServer.cpp", + ] + CXXFLAGS += CONFIG["MOZ_GTK3_CFLAGS"] + +if CONFIG["MOZ_WIDGET_TOOLKIT"] == "windows": + SOURCES += [ + "nsWinRemoteClient.cpp", + "nsWinRemoteServer.cpp", + "WinRemoteMessage.cpp", + ] +if CONFIG["MOZ_WIDGET_TOOLKIT"] == "cocoa": + SOURCES += [ + "nsMacRemoteClient.mm", + "nsMacRemoteServer.mm", + ] + +LOCAL_INCLUDES += [ + "../../profile", + "../../xre", +] +FINAL_LIBRARY = "xul" diff --git a/toolkit/components/remote/nsDBusRemoteClient.cpp b/toolkit/components/remote/nsDBusRemoteClient.cpp new file mode 100644 index 0000000000..69e8d07c72 --- /dev/null +++ b/toolkit/components/remote/nsDBusRemoteClient.cpp @@ -0,0 +1,188 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:expandtab:shiftwidth=2:tabstop=8: + */ +/* vim:set ts=8 sw=2 et cindent: */ +/* 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 "nsDBusRemoteClient.h" +#include "RemoteUtils.h" +#include "mozilla/XREAppData.h" +#include "mozilla/Logging.h" +#include "mozilla/Base64.h" +#include "nsPrintfCString.h" + +#include <dlfcn.h> +#include <dbus/dbus-glib-lowlevel.h> + +#undef LOG +#ifdef MOZ_LOGGING +static mozilla::LazyLogModule sRemoteLm("nsDBusRemoteClient"); +# define LOG(str, ...) \ + MOZ_LOG(sRemoteLm, mozilla::LogLevel::Debug, (str, ##__VA_ARGS__)) +#else +# define LOG(...) +#endif + +nsDBusRemoteClient::nsDBusRemoteClient() { + mConnection = nullptr; + LOG("nsDBusRemoteClient::nsDBusRemoteClient"); +} + +nsDBusRemoteClient::~nsDBusRemoteClient() { + LOG("nsDBusRemoteClient::~nsDBusRemoteClient"); + Shutdown(); +} + +nsresult nsDBusRemoteClient::Init() { + LOG("nsDBusRemoteClient::Init"); + + if (mConnection) return NS_OK; + + mConnection = + already_AddRefed<DBusConnection>(dbus_bus_get(DBUS_BUS_SESSION, nullptr)); + if (!mConnection) { + LOG(" failed to get DBus session"); + return NS_ERROR_FAILURE; + } + + dbus_connection_set_exit_on_disconnect(mConnection, false); + dbus_connection_setup_with_g_main(mConnection, nullptr); + + return NS_OK; +} + +void nsDBusRemoteClient::Shutdown(void) { + LOG("nsDBusRemoteClient::Shutdown"); + // This connection is owned by libdbus and we don't need to close it + mConnection = nullptr; +} + +nsresult nsDBusRemoteClient::SendCommandLine( + const char* aProgram, const char* aProfile, int32_t argc, char** argv, + const char* aStartupToken, char** aResponse, bool* aWindowFound) { + NS_ENSURE_TRUE(aProgram, NS_ERROR_INVALID_ARG); + + LOG("nsDBusRemoteClient::SendCommandLine"); + + int commandLineLength; + char* commandLine = + ConstructCommandLine(argc, argv, aStartupToken, &commandLineLength); + if (!commandLine) { + LOG(" failed to create command line"); + return NS_ERROR_FAILURE; + } + + nsresult rv = + DoSendDBusCommandLine(aProgram, aProfile, commandLine, commandLineLength); + free(commandLine); + + *aWindowFound = NS_SUCCEEDED(rv); + + LOG("DoSendDBusCommandLine %s", NS_SUCCEEDED(rv) ? "OK" : "FAILED"); + return rv; +} + +bool nsDBusRemoteClient::GetRemoteDestinationName(const char* aProgram, + const char* aProfile, + nsCString& aDestinationName) { + // We have a profile name - just create the destination. + // D-Bus names can contain only [a-z][A-Z][0-9]_ + // characters so adjust the profile string properly. + nsAutoCString profileName; + nsresult rv = mozilla::Base64Encode(nsAutoCString(aProfile), profileName); + NS_ENSURE_SUCCESS(rv, false); + + mozilla::XREAppData::SanitizeNameForDBus(profileName); + + aDestinationName = + nsPrintfCString("org.mozilla.%s.%s", aProgram, profileName.get()); + if (aDestinationName.Length() > DBUS_MAXIMUM_NAME_LENGTH) + aDestinationName.Truncate(DBUS_MAXIMUM_NAME_LENGTH); + + static auto sDBusValidateBusName = (bool (*)(const char*, DBusError*))dlsym( + RTLD_DEFAULT, "dbus_validate_bus_name"); + if (!sDBusValidateBusName) { + LOG(" failed to get dbus_validate_bus_name()"); + return false; + } + + if (!sDBusValidateBusName(aDestinationName.get(), nullptr)) { + // We don't have a valid busName yet - try to create a default one. + aDestinationName = + nsPrintfCString("org.mozilla.%s.%s", aProgram, "default"); + if (!sDBusValidateBusName(aDestinationName.get(), nullptr)) { + // We failed completelly to get a valid bus name - just quit + // to prevent crash at dbus_bus_request_name(). + LOG(" failed to validate profile DBus name"); + return false; + } + } + + return true; +} + +nsresult nsDBusRemoteClient::DoSendDBusCommandLine(const char* aProgram, + const char* aProfile, + const char* aBuffer, + int aLength) { + LOG("nsDBusRemoteClient::DoSendDBusCommandLine()"); + + nsAutoCString appName(aProgram); + mozilla::XREAppData::SanitizeNameForDBus(appName); + + nsAutoCString destinationName; + if (!GetRemoteDestinationName(appName.get(), aProfile, destinationName)) { + LOG(" failed to get remote destination name"); + return NS_ERROR_FAILURE; + } + + nsAutoCString pathName; + pathName = nsPrintfCString("/org/mozilla/%s/Remote", appName.get()); + + static auto sDBusValidatePathName = (bool (*)(const char*, DBusError*))dlsym( + RTLD_DEFAULT, "dbus_validate_path"); + if (!sDBusValidatePathName || + !sDBusValidatePathName(pathName.get(), nullptr)) { + LOG(" failed to validate path name"); + return NS_ERROR_FAILURE; + } + + nsAutoCString remoteInterfaceName; + remoteInterfaceName = nsPrintfCString("org.mozilla.%s", appName.get()); + + LOG(" DBus destination: %s\n", destinationName.get()); + LOG(" DBus path: %s\n", pathName.get()); + LOG(" DBus interface: %s\n", remoteInterfaceName.get()); + + RefPtr<DBusMessage> msg = + already_AddRefed<DBusMessage>(dbus_message_new_method_call( + destinationName.get(), + pathName.get(), // object to call on + remoteInterfaceName.get(), // interface to call on + "OpenURL")); // method name + if (!msg) { + LOG(" failed to create DBus message"); + return NS_ERROR_FAILURE; + } + + // append arguments + if (!dbus_message_append_args(msg, DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE, &aBuffer, + aLength, DBUS_TYPE_INVALID)) { + LOG(" failed to create DBus message"); + return NS_ERROR_FAILURE; + } + + // send message and get a handle for a reply + RefPtr<DBusMessage> reply = already_AddRefed<DBusMessage>( + dbus_connection_send_with_reply_and_block(mConnection, msg, -1, nullptr)); + +#ifdef MOZ_LOGGING + if (!reply) { + LOG(" failed to get DBus reply"); + } +#endif + + return reply ? NS_OK : NS_ERROR_FAILURE; +} diff --git a/toolkit/components/remote/nsDBusRemoteClient.h b/toolkit/components/remote/nsDBusRemoteClient.h new file mode 100644 index 0000000000..fdb4dbc665 --- /dev/null +++ b/toolkit/components/remote/nsDBusRemoteClient.h @@ -0,0 +1,34 @@ +/* -*- Mode: C++; tab-width: 8; 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 DBusRemoteClient_h__ +#define DBusRemoteClient_h__ + +#include "nsRemoteClient.h" +#include "mozilla/DBusHelpers.h" +#include "mozilla/RefPtr.h" +#include "nsStringFwd.h" +#include "nscore.h" + +class nsDBusRemoteClient : public nsRemoteClient { + public: + nsDBusRemoteClient(); + ~nsDBusRemoteClient(); + + nsresult Init() override; + nsresult SendCommandLine(const char* aProgram, const char* aProfile, + int32_t argc, char** argv, const char* aStartupToken, + char** aResponse, bool* aSucceeded) override; + void Shutdown(); + + private: + bool GetRemoteDestinationName(const char* aProgram, const char* aProfile, + nsCString& aDestinationName); + nsresult DoSendDBusCommandLine(const char* aProgram, const char* aProfile, + const char* aBuffer, int aLength); + RefPtr<DBusConnection> mConnection; +}; + +#endif // DBusRemoteClient_h__ diff --git a/toolkit/components/remote/nsDBusRemoteServer.cpp b/toolkit/components/remote/nsDBusRemoteServer.cpp new file mode 100644 index 0000000000..34167177d9 --- /dev/null +++ b/toolkit/components/remote/nsDBusRemoteServer.cpp @@ -0,0 +1,214 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:expandtab:shiftwidth=2:tabstop=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 "nsDBusRemoteServer.h" + +#include "nsCOMPtr.h" +#include "mozilla/XREAppData.h" +#include "mozilla/Base64.h" +#include "mozilla/ScopeExit.h" +#include "nsPrintfCString.h" + +#include "nsGTKToolkit.h" + +#include <dbus/dbus.h> +#include <dbus/dbus-glib-lowlevel.h> + +#include <dlfcn.h> + +static const char* introspect_template = + "<!DOCTYPE node PUBLIC \"-//freedesktop//DTD D-BUS Object Introspection " + "1.0//EN\"\n" + "\"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd\">\n" + "<node>\n" + " <interface name=\"org.freedesktop.DBus.Introspectable\">\n" + " <method name=\"Introspect\">\n" + " <arg name=\"data\" direction=\"out\" type=\"s\"/>\n" + " </method>\n" + " </interface>\n" + " <interface name=\"org.mozilla.%s\">\n" + " <method name=\"OpenURL\">\n" + " <arg name=\"url\" direction=\"in\" type=\"ay\"/>\n" + " </method>\n" + " </interface>\n" + "</node>\n"; + +DBusHandlerResult nsDBusRemoteServer::Introspect(DBusMessage* msg) { + DBusMessage* reply = dbus_message_new_method_return(msg); + if (!reply) return DBUS_HANDLER_RESULT_NEED_MEMORY; + + nsAutoCString introspect_xml; + introspect_xml = nsPrintfCString(introspect_template, mAppName.get()); + + const char* message = introspect_xml.get(); + dbus_message_append_args(reply, DBUS_TYPE_STRING, &message, + DBUS_TYPE_INVALID); + + dbus_connection_send(mConnection, reply, nullptr); + dbus_message_unref(reply); + + return DBUS_HANDLER_RESULT_HANDLED; +} + +DBusHandlerResult nsDBusRemoteServer::OpenURL(DBusMessage* msg) { + DBusMessage* reply = nullptr; + const char* commandLine; + int length; + + if (!dbus_message_get_args(msg, nullptr, DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE, + &commandLine, &length, DBUS_TYPE_INVALID) || + length == 0) { + nsAutoCString errorMsg; + errorMsg = nsPrintfCString("org.mozilla.%s.Error", mAppName.get()); + reply = dbus_message_new_error(msg, errorMsg.get(), "Wrong argument"); + } else { + guint32 timestamp = gtk_get_current_event_time(); + if (timestamp == GDK_CURRENT_TIME) { + timestamp = guint32(g_get_monotonic_time() / 1000); + } + HandleCommandLine(commandLine, timestamp); + reply = dbus_message_new_method_return(msg); + } + + dbus_connection_send(mConnection, reply, nullptr); + dbus_message_unref(reply); + + return DBUS_HANDLER_RESULT_HANDLED; +} + +DBusHandlerResult nsDBusRemoteServer::HandleDBusMessage( + DBusConnection* aConnection, DBusMessage* msg) { + NS_ASSERTION(mConnection == aConnection, "Wrong D-Bus connection."); + + const char* method = dbus_message_get_member(msg); + const char* iface = dbus_message_get_interface(msg); + + if ((strcmp("Introspect", method) == 0) && + (strcmp("org.freedesktop.DBus.Introspectable", iface) == 0)) { + return Introspect(msg); + } + + nsAutoCString ourInterfaceName; + ourInterfaceName = nsPrintfCString("org.mozilla.%s", mAppName.get()); + + if ((strcmp("OpenURL", method) == 0) && + (strcmp(ourInterfaceName.get(), iface) == 0)) { + return OpenURL(msg); + } + + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} + +void nsDBusRemoteServer::UnregisterDBusInterface(DBusConnection* aConnection) { + NS_ASSERTION(mConnection == aConnection, "Wrong D-Bus connection."); + // Not implemented +} + +static DBusHandlerResult message_handler(DBusConnection* conn, DBusMessage* msg, + void* user_data) { + auto interface = static_cast<nsDBusRemoteServer*>(user_data); + return interface->HandleDBusMessage(conn, msg); +} + +static void unregister(DBusConnection* conn, void* user_data) { + auto interface = static_cast<nsDBusRemoteServer*>(user_data); + interface->UnregisterDBusInterface(conn); +} + +static DBusObjectPathVTable remoteHandlersTable = { + .unregister_function = unregister, + .message_function = message_handler, +}; + +nsresult nsDBusRemoteServer::Startup(const char* aAppName, + const char* aProfileName) { + if (mConnection && dbus_connection_get_is_connected(mConnection)) { + // We're already connected so we don't need to reconnect + return NS_ERROR_ALREADY_INITIALIZED; + } + + // Don't even try to start without any application/profile name + if (!aAppName || aAppName[0] == '\0' || !aProfileName || + aProfileName[0] == '\0') + return NS_ERROR_INVALID_ARG; + + mConnection = dont_AddRef(dbus_bus_get(DBUS_BUS_SESSION, nullptr)); + if (!mConnection) { + return NS_ERROR_FAILURE; + } + auto releaseDBusConnection = + mozilla::MakeScopeExit([&] { mConnection = nullptr; }); + dbus_connection_set_exit_on_disconnect(mConnection, false); + dbus_connection_setup_with_g_main(mConnection, nullptr); + + mAppName = aAppName; + mozilla::XREAppData::SanitizeNameForDBus(mAppName); + + nsAutoCString profileName; + MOZ_TRY( + mozilla::Base64Encode(aProfileName, strlen(aProfileName), profileName)); + + mozilla::XREAppData::SanitizeNameForDBus(profileName); + + nsPrintfCString busName("org.mozilla.%s.%s", mAppName.get(), + profileName.get()); + if (busName.Length() > DBUS_MAXIMUM_NAME_LENGTH) + busName.Truncate(DBUS_MAXIMUM_NAME_LENGTH); + + static auto sDBusValidateBusName = (bool (*)(const char*, DBusError*))dlsym( + RTLD_DEFAULT, "dbus_validate_bus_name"); + if (!sDBusValidateBusName) { + return NS_ERROR_FAILURE; + } + + // We don't have a valid busName yet - try to create a default one. + if (!sDBusValidateBusName(busName.get(), nullptr)) { + busName = nsPrintfCString("org.mozilla.%s.%s", mAppName.get(), "default"); + if (!sDBusValidateBusName(busName.get(), nullptr)) { + // We failed completelly to get a valid bus name - just quit + // to prevent crash at dbus_bus_request_name(). + return NS_ERROR_FAILURE; + } + } + + DBusError err; + dbus_error_init(&err); + dbus_bus_request_name(mConnection, busName.get(), DBUS_NAME_FLAG_DO_NOT_QUEUE, + &err); + // The interface is already owned - there is another application/profile + // instance already running. + if (dbus_error_is_set(&err)) { + dbus_error_free(&err); + return NS_ERROR_FAILURE; + } + + mPathName = nsPrintfCString("/org/mozilla/%s/Remote", mAppName.get()); + static auto sDBusValidatePathName = (bool (*)(const char*, DBusError*))dlsym( + RTLD_DEFAULT, "dbus_validate_path"); + if (!sDBusValidatePathName || + !sDBusValidatePathName(mPathName.get(), nullptr)) { + return NS_ERROR_FAILURE; + } + if (!dbus_connection_register_object_path(mConnection, mPathName.get(), + &remoteHandlersTable, this)) { + return NS_ERROR_FAILURE; + } + + releaseDBusConnection.release(); + return NS_OK; +} + +void nsDBusRemoteServer::Shutdown() { + if (!mConnection) { + return; + } + + dbus_connection_unregister_object_path(mConnection, mPathName.get()); + + // dbus_connection_unref() will be called by RefPtr here. + mConnection = nullptr; +} diff --git a/toolkit/components/remote/nsDBusRemoteServer.h b/toolkit/components/remote/nsDBusRemoteServer.h new file mode 100644 index 0000000000..ce23a28c36 --- /dev/null +++ b/toolkit/components/remote/nsDBusRemoteServer.h @@ -0,0 +1,38 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:expandtab:shiftwidth=2:tabstop=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 __nsDBusRemoteServer_h__ +#define __nsDBusRemoteServer_h__ + +#include "nsRemoteServer.h" +#include "nsUnixRemoteServer.h" +#include "mozilla/DBusHelpers.h" + +class nsDBusRemoteServer final : public nsRemoteServer, + public nsUnixRemoteServer { + public: + nsDBusRemoteServer() : mConnection(nullptr), mAppName(nullptr) {} + ~nsDBusRemoteServer() override { Shutdown(); } + + nsresult Startup(const char* aAppName, const char* aProfileName) override; + void Shutdown() override; + + DBusHandlerResult HandleDBusMessage(DBusConnection* aConnection, + DBusMessage* msg); + void UnregisterDBusInterface(DBusConnection* aConnection); + + private: + DBusHandlerResult OpenURL(DBusMessage* msg); + DBusHandlerResult Introspect(DBusMessage* msg); + + // The connection is owned by DBus library + RefPtr<DBusConnection> mConnection; + nsCString mAppName; + nsCString mPathName; +}; + +#endif // __nsDBusRemoteServer_h__ diff --git a/toolkit/components/remote/nsGTKRemoteServer.cpp b/toolkit/components/remote/nsGTKRemoteServer.cpp new file mode 100644 index 0000000000..5b6eda3f89 --- /dev/null +++ b/toolkit/components/remote/nsGTKRemoteServer.cpp @@ -0,0 +1,69 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:expandtab:shiftwidth=2:tabstop=8: + */ +/* 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 "nsGTKRemoteServer.h" + +#include <gtk/gtk.h> +#include <gdk/gdk.h> +#include <gdk/gdkx.h> + +#include "mozilla/ModuleUtils.h" +#include "nsAppShellCID.h" + +#include "nsCOMPtr.h" + +#include "nsGTKToolkit.h" + +nsresult nsGTKRemoteServer::Startup(const char* aAppName, + const char* aProfileName) { + NS_ASSERTION(aAppName, "Don't pass a null appname!"); + + if (mServerWindow) { + return NS_ERROR_ALREADY_INITIALIZED; + } + + XRemoteBaseStartup(aAppName, aProfileName); + + mServerWindow = gtk_invisible_new(); + gtk_widget_realize(mServerWindow); + HandleCommandsFor(mServerWindow); + + return NS_OK; +} + +void nsGTKRemoteServer::Shutdown() { + if (!mServerWindow) { + return; + } + + gtk_widget_destroy(mServerWindow); + mServerWindow = nullptr; +} + +void nsGTKRemoteServer::HandleCommandsFor(GtkWidget* widget) { + g_signal_connect(G_OBJECT(widget), "property_notify_event", + G_CALLBACK(HandlePropertyChange), this); + + gtk_widget_add_events(widget, GDK_PROPERTY_CHANGE_MASK); + + Window window = gdk_x11_window_get_xid(gtk_widget_get_window(widget)); + nsXRemoteServer::HandleCommandsFor(window); +} + +gboolean nsGTKRemoteServer::HandlePropertyChange(GtkWidget* aWidget, + GdkEventProperty* pevent, + nsGTKRemoteServer* aThis) { + if (pevent->state == GDK_PROPERTY_NEW_VALUE) { + Atom changedAtom = gdk_x11_atom_to_xatom(pevent->atom); + + XID window = gdk_x11_window_get_xid(gtk_widget_get_window(aWidget)); + return aThis->HandleNewProperty( + window, GDK_DISPLAY_XDISPLAY(gdk_display_get_default()), pevent->time, + changedAtom); + } + return FALSE; +} diff --git a/toolkit/components/remote/nsGTKRemoteServer.h b/toolkit/components/remote/nsGTKRemoteServer.h new file mode 100644 index 0000000000..767f2f54ad --- /dev/null +++ b/toolkit/components/remote/nsGTKRemoteServer.h @@ -0,0 +1,37 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:expandtab:shiftwidth=2:tabstop=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 __nsGTKRemoteServer_h__ +#define __nsGTKRemoteServer_h__ + +#include <gdk/gdk.h> +#include <gdk/gdkx.h> +#include <gtk/gtk.h> + +#include "nsRemoteServer.h" +#include "nsXRemoteServer.h" +#include "mozilla/Attributes.h" + +class nsGTKRemoteServer final : public nsXRemoteServer { + public: + nsGTKRemoteServer() : mServerWindow(nullptr) {} + ~nsGTKRemoteServer() override { Shutdown(); } + + nsresult Startup(const char* aAppName, const char* aProfileName) override; + void Shutdown() override; + + static gboolean HandlePropertyChange(GtkWidget* widget, + GdkEventProperty* event, + nsGTKRemoteServer* aData); + + private: + void HandleCommandsFor(GtkWidget* aWidget); + + GtkWidget* mServerWindow; +}; + +#endif // __nsGTKRemoteService_h__ diff --git a/toolkit/components/remote/nsMacRemoteClient.h b/toolkit/components/remote/nsMacRemoteClient.h new file mode 100644 index 0000000000..96ad4e2a7f --- /dev/null +++ b/toolkit/components/remote/nsMacRemoteClient.h @@ -0,0 +1,27 @@ +/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:expandtab:shiftwidth=4:tabstop=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/. */ + +#ifndef TOOLKIT_COMPONENTS_REMOTE_NSMACREMOTECLIENT_H_ +#define TOOLKIT_COMPONENTS_REMOTE_NSMACREMOTECLIENT_H_ + +#import <CoreFoundation/CoreFoundation.h> + +#include "nscore.h" +#include "nsRemoteClient.h" + +class nsMacRemoteClient : public nsRemoteClient { + public: + virtual ~nsMacRemoteClient() = default; + + nsresult Init() override; + + nsresult SendCommandLine(const char* aProgram, const char* aProfile, + int32_t argc, char** argv, const char* aStartupToken, + char** aResponse, bool* aSucceeded) override; +}; + +#endif // TOOLKIT_COMPONENTS_REMOTE_NSMACREMOTECLIENT_H_ diff --git a/toolkit/components/remote/nsMacRemoteClient.mm b/toolkit/components/remote/nsMacRemoteClient.mm new file mode 100644 index 0000000000..314eebc030 --- /dev/null +++ b/toolkit/components/remote/nsMacRemoteClient.mm @@ -0,0 +1,62 @@ +/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:expandtab:shiftwidth=4:tabstop=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/. */ + +#import <CoreFoundation/CoreFoundation.h> +#import <Foundation/Foundation.h> + +#include <sys/param.h> + +#include "MacAutoreleasePool.h" +#include "nsMacRemoteClient.h" +#include "RemoteUtils.h" + +using namespace mozilla; + +nsresult nsMacRemoteClient::Init() { return NS_OK; } + +nsresult nsMacRemoteClient::SendCommandLine(const char* aProgram, const char* aProfile, + int32_t argc, char** argv, + const char* aDesktopStartupID, char** aResponse, + bool* aSucceeded) { + mozilla::MacAutoreleasePool pool; + + *aSucceeded = false; + + nsString className; + BuildClassName(aProgram, aProfile, className); + NSString* serverNameString = + [NSString stringWithCharacters:reinterpret_cast<const unichar*>(className.get()) + length:className.Length()]; + + CFMessagePortRef messageServer = CFMessagePortCreateRemote(0, (CFStringRef)serverNameString); + + if (messageServer) { + // Getting current process directory + char cwdPtr[MAXPATHLEN + 1]; + getcwd(cwdPtr, MAXPATHLEN + 1); + + NSMutableArray* argumentsArray = [NSMutableArray array]; + for (int i = 0; i < argc; i++) { + NSString* argument = [NSString stringWithUTF8String:argv[i]]; + [argumentsArray addObject:argument]; + } + NSDictionary* dict = @{@"args" : argumentsArray}; + + NSData* data = [NSKeyedArchiver archivedDataWithRootObject:dict]; + + CFMessagePortSendRequest(messageServer, 0, (CFDataRef)data, 10.0, 0.0, NULL, NULL); + + CFMessagePortInvalidate(messageServer); + CFRelease(messageServer); + *aSucceeded = true; + + } else { + // Remote Server not found. Doing nothing. + } + + return NS_OK; +} diff --git a/toolkit/components/remote/nsMacRemoteServer.h b/toolkit/components/remote/nsMacRemoteServer.h new file mode 100644 index 0000000000..0e2bda8a32 --- /dev/null +++ b/toolkit/components/remote/nsMacRemoteServer.h @@ -0,0 +1,30 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:expandtab:shiftwidth=2:tabstop=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 TOOLKIT_COMPONENTS_REMOTE_NSMACREMOTESERVER_H_ +#define TOOLKIT_COMPONENTS_REMOTE_NSMACREMOTESERVER_H_ + +#import <CoreFoundation/CoreFoundation.h> + +#include "nsRemoteServer.h" + +class nsMacRemoteServer final : public nsRemoteServer { + public: + nsMacRemoteServer() = default; + ~nsMacRemoteServer() override { Shutdown(); } + + nsresult Startup(const char* aAppName, const char* aProfileName) override; + void Shutdown() override; + + void HandleCommandLine(CFDataRef aData); + + private: + CFRunLoopSourceRef mRunLoopSource; + CFMessagePortRef mMessageServer; +}; + +#endif // TOOLKIT_COMPONENTS_REMOTE_NSMACREMOTESERVER_H_ diff --git a/toolkit/components/remote/nsMacRemoteServer.mm b/toolkit/components/remote/nsMacRemoteServer.mm new file mode 100644 index 0000000000..0936d4d2fd --- /dev/null +++ b/toolkit/components/remote/nsMacRemoteServer.mm @@ -0,0 +1,134 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:expandtab:shiftwidth=2:tabstop=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/. */ + +#import <Cocoa/Cocoa.h> +#import <CoreServices/CoreServices.h> + +#include "MacAutoreleasePool.h" +#include "nsCOMPtr.h" +#include "nsIComponentManager.h" +#include "nsIServiceManager.h" +#include "nsIWindowMediator.h" +#include "nsIWidget.h" +#include "nsICommandLineRunner.h" +#include "nsICommandLine.h" +#include "nsCommandLine.h" +#include "nsIDocShell.h" +#include "nsMacRemoteServer.h" +#include "nsXPCOM.h" +#include "RemoteUtils.h" + +CFDataRef messageServerCallback(CFMessagePortRef aLocal, int32_t aMsgid, CFDataRef aData, + void* aInfo) { + // One of the clients submitted a structure. + static_cast<nsMacRemoteServer*>(aInfo)->HandleCommandLine(aData); + + return NULL; +} + +// aData contains serialized Dictionary, which in turn contains command line arguments +void nsMacRemoteServer::HandleCommandLine(CFDataRef aData) { + mozilla::MacAutoreleasePool pool; + + if (aData) { + NSDictionary* dict = [NSKeyedUnarchiver unarchiveObjectWithData:(NSData*)aData]; + if (dict && [dict isKindOfClass:[NSDictionary class]]) { + NSArray* args = dict[@"args"]; + if (!args) { + NS_ERROR("Wrong parameters passed to the Remote Server"); + return; + } + + nsCOMPtr<nsICommandLineRunner> cmdLine(new nsCommandLine()); + + // Converting Objective-C array into a C array, + // which nsICommandLineRunner understands. + int argc = [args count]; + const char** argv = new const char*[argc]; + for (int i = 0; i < argc; i++) { + const char* arg = [[args objectAtIndex:i] UTF8String]; + argv[i] = arg; + } + + nsresult rv = cmdLine->Init(argc, argv, nullptr, nsICommandLine::STATE_REMOTE_AUTO); + + // Cleaning up C array. + delete[] argv; + + if (NS_FAILED(rv)) { + NS_ERROR("Error initializing command line."); + return; + } + + // Processing the command line, passed from a remote instance + // in the current instance. + cmdLine->Run(); + + // And bring the app's window to front. + [[NSRunningApplication currentApplication] + activateWithOptions:NSApplicationActivateIgnoringOtherApps]; + } + } +} + +nsresult nsMacRemoteServer::Startup(const char* aAppName, const char* aProfileName) { + // This is the first instance ever. + // Let's register a notification listener here, + // In case future instances would want to notify us about command line arguments + // passed to them. Note, that if mozilla process is restarting, we still need to + // register for notifications. + + mozilla::MacAutoreleasePool pool; + + nsString className; + BuildClassName(aAppName, aProfileName, className); + + NSString* serverNameString = + [NSString stringWithCharacters:reinterpret_cast<const unichar*>(className.get()) + length:className.Length()]; + + CFMessagePortContext context; + context.copyDescription = NULL; + context.info = this; + context.release = NULL; + context.retain = NULL; + context.version = NULL; + mMessageServer = CFMessagePortCreateLocal(NULL, (CFStringRef)serverNameString, + messageServerCallback, &context, NULL); + if (!mMessageServer) { + return NS_ERROR_FAILURE; + } + mRunLoopSource = CFMessagePortCreateRunLoopSource(NULL, mMessageServer, 0); + if (!mRunLoopSource) { + CFRelease(mMessageServer); + mMessageServer = NULL; + return NS_ERROR_FAILURE; + } + CFRunLoopRef runLoop = CFRunLoopGetMain(); + CFRunLoopAddSource(runLoop, mRunLoopSource, kCFRunLoopDefaultMode); + + return NS_OK; +} + +void nsMacRemoteServer::Shutdown() { + // 1) Invalidate server connection + if (mMessageServer) { + CFMessagePortInvalidate(mMessageServer); + } + + // 2) Release run loop source + if (mRunLoopSource) { + CFRelease(mRunLoopSource); + mRunLoopSource = NULL; + } + + // 3) Release server connection + if (mMessageServer) { + CFRelease(mMessageServer); + mMessageServer = NULL; + } +} diff --git a/toolkit/components/remote/nsRemoteClient.h b/toolkit/components/remote/nsRemoteClient.h new file mode 100644 index 0000000000..f36c33287b --- /dev/null +++ b/toolkit/components/remote/nsRemoteClient.h @@ -0,0 +1,64 @@ +/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:expandtab:shiftwidth=4:tabstop=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/. */ + +#ifndef TOOLKIT_COMPONENTS_REMOTE_NSREMOTECLIENT_H_ +#define TOOLKIT_COMPONENTS_REMOTE_NSREMOTECLIENT_H_ + +#include "nscore.h" + +/** + * Pure-virtual common base class for remoting implementations. + */ + +class nsRemoteClient { + public: + virtual ~nsRemoteClient() = default; + + /** + * Initializes the client + */ + virtual nsresult Init() = 0; + + /** + * Send a complete command line to a running instance. + * + * @param aProgram This is the preferred program that we want to use + * for this particular command. + * + * @param aUsername This allows someone to only talk to an instance + * of the server that's running under a particular username. If + * this isn't specified here it's pulled from the LOGNAME + * environmental variable if it's set. + * + * @param aProfile This allows you to specify a particular server + * running under a named profile. If it is not specified the + * profile is not checked. + * + * @param argc The number of command-line arguments. + * + * @param argv The command-line arguments. + * + * @param aStartupToken the contents of the DESKTOP_STARTUP_ID environment + * variable defined by the Startup Notification specification, or the + * XDG_ACTIVATION_TOKEN defined by Wayland. + * http://standards.freedesktop.org/startup-notification-spec/startup-notification-0.1.txt + * https://wayland.app/protocols/xdg-activation-v1 + * + * @param aResponse If there is a response, it will be here. This + * includes error messages. The string is allocated using stdlib + * string functions, so free it with free(). + * + * @return true if succeeded, false if no running instance was found. + * + */ + virtual nsresult SendCommandLine(const char* aProgram, const char* aProfile, + int32_t argc, char** argv, + const char* aStartupToken, char** aResponse, + bool* aSucceeded) = 0; +}; + +#endif // TOOLKIT_COMPONENTS_REMOTE_NSREMOTECLIENT_H_ diff --git a/toolkit/components/remote/nsRemoteServer.h b/toolkit/components/remote/nsRemoteServer.h new file mode 100644 index 0000000000..7c24bdce71 --- /dev/null +++ b/toolkit/components/remote/nsRemoteServer.h @@ -0,0 +1,21 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:expandtab:shiftwidth=2:tabstop=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 __nsRemoteServer_h__ +#define __nsRemoteServer_h__ + +#include "nsString.h" + +class nsRemoteServer { + public: + virtual ~nsRemoteServer() = default; + + virtual nsresult Startup(const char* aAppName, const char* aProfileName) = 0; + virtual void Shutdown() = 0; +}; + +#endif // __nsRemoteServer_h__ diff --git a/toolkit/components/remote/nsRemoteService.cpp b/toolkit/components/remote/nsRemoteService.cpp new file mode 100644 index 0000000000..3e8e4b5cb8 --- /dev/null +++ b/toolkit/components/remote/nsRemoteService.cpp @@ -0,0 +1,192 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:expandtab:shiftwidth=2:tabstop=8: + */ +/* 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/. */ + +#ifdef MOZ_WIDGET_GTK +# ifdef MOZ_ENABLE_DBUS +# include "nsDBusRemoteServer.h" +# include "nsDBusRemoteClient.h" +# else +# include "nsGTKRemoteServer.h" +# include "nsXRemoteClient.h" +# endif +#elif defined(XP_WIN) +# include "nsWinRemoteServer.h" +# include "nsWinRemoteClient.h" +#elif defined(XP_DARWIN) +# include "nsMacRemoteServer.h" +# include "nsMacRemoteClient.h" +#endif +#include "nsRemoteService.h" + +#include "nsIObserverService.h" +#include "nsString.h" +#include "nsServiceManagerUtils.h" +#include "SpecialSystemDirectory.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/UniquePtr.h" + +// Time to wait for the remoting service to start +#define START_TIMEOUT_SEC 5 +#define START_SLEEP_MSEC 100 + +using namespace mozilla; + +extern int gArgc; +extern char** gArgv; + +using namespace mozilla; + +NS_IMPL_ISUPPORTS(nsRemoteService, nsIObserver) + +nsRemoteService::nsRemoteService(const char* aProgram) : mProgram(aProgram) { + ToLowerCase(mProgram); +} + +void nsRemoteService::SetProfile(nsACString& aProfile) { mProfile = aProfile; } + +void nsRemoteService::LockStartup() { + nsCOMPtr<nsIFile> mutexDir; + nsresult rv = GetSpecialSystemDirectory(OS_TemporaryDirectory, + getter_AddRefs(mutexDir)); + NS_ENSURE_SUCCESS_VOID(rv); + rv = mutexDir->AppendNative(mProgram); + NS_ENSURE_SUCCESS_VOID(rv); + + const mozilla::TimeStamp epoch = mozilla::TimeStamp::Now(); + do { + // If we have been waiting for another instance to release the lock it will + // have deleted the lock directory when doing so we have to make sure it + // exists every time we poll for the lock. + rv = mutexDir->Create(nsIFile::DIRECTORY_TYPE, 0700); + if (NS_SUCCEEDED(rv) || rv == NS_ERROR_FILE_ALREADY_EXISTS) { + mRemoteLockDir = mutexDir; + } else { + NS_WARNING("Unable to create startup lock directory."); + return; + } + + rv = mRemoteLock.Lock(mRemoteLockDir, nullptr); + if (NS_SUCCEEDED(rv)) { + return; + } + + mRemoteLockDir = nullptr; + PR_Sleep(START_SLEEP_MSEC); + } while ((mozilla::TimeStamp::Now() - epoch) < + mozilla::TimeDuration::FromSeconds(START_TIMEOUT_SEC)); + + NS_WARNING("Failed to lock for startup, continuing anyway."); +} + +void nsRemoteService::UnlockStartup() { + if (mRemoteLockDir) { + mRemoteLock.Unlock(); + mRemoteLock.Cleanup(); + + mRemoteLockDir->Remove(false); + mRemoteLockDir = nullptr; + } +} + +RemoteResult nsRemoteService::StartClient(const char* aStartupToken) { + if (mProfile.IsEmpty()) { + return REMOTE_NOT_FOUND; + } + + UniquePtr<nsRemoteClient> client; +#ifdef MOZ_WIDGET_GTK +# if defined(MOZ_ENABLE_DBUS) + client = MakeUnique<nsDBusRemoteClient>(); +# else + client = MakeUnique<nsXRemoteClient>(); +# endif +#elif defined(XP_WIN) + client = MakeUnique<nsWinRemoteClient>(); +#elif defined(XP_DARWIN) + client = MakeUnique<nsMacRemoteClient>(); +#else + return REMOTE_NOT_FOUND; +#endif + + nsresult rv = client ? client->Init() : NS_ERROR_FAILURE; + if (NS_FAILED(rv)) return REMOTE_NOT_FOUND; + + nsCString response; + bool success = false; + rv = + client->SendCommandLine(mProgram.get(), mProfile.get(), gArgc, gArgv, + aStartupToken, getter_Copies(response), &success); + // did the command fail? + if (!success) return REMOTE_NOT_FOUND; + + // The "command not parseable" error is returned when the + // nsICommandLineHandler throws a NS_ERROR_ABORT. + if (response.EqualsLiteral("500 command not parseable")) + return REMOTE_ARG_BAD; + + if (NS_FAILED(rv)) return REMOTE_NOT_FOUND; + + return REMOTE_FOUND; +} + +void nsRemoteService::StartupServer() { + if (mRemoteServer) { + return; + } + + if (mProfile.IsEmpty()) { + return; + } + +#ifdef MOZ_WIDGET_GTK +# if defined(MOZ_ENABLE_DBUS) + mRemoteServer = MakeUnique<nsDBusRemoteServer>(); +# else + mRemoteServer = MakeUnique<nsGTKRemoteServer>(); +# endif +#elif defined(XP_WIN) + mRemoteServer = MakeUnique<nsWinRemoteServer>(); +#elif defined(XP_DARWIN) + mRemoteServer = MakeUnique<nsMacRemoteServer>(); +#else + return; +#endif + + if (!mRemoteServer) { + return; + } + + nsresult rv = mRemoteServer->Startup(mProgram.get(), mProfile.get()); + + if (NS_FAILED(rv)) { + mRemoteServer = nullptr; + return; + } + + nsCOMPtr<nsIObserverService> obs( + do_GetService("@mozilla.org/observer-service;1")); + if (obs) { + obs->AddObserver(this, "xpcom-shutdown", false); + obs->AddObserver(this, "quit-application", false); + } +} + +void nsRemoteService::ShutdownServer() { mRemoteServer = nullptr; } + +nsRemoteService::~nsRemoteService() { + UnlockStartup(); + ShutdownServer(); +} + +NS_IMETHODIMP +nsRemoteService::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) { + // This can be xpcom-shutdown or quit-application, but it's the same either + // way. + ShutdownServer(); + return NS_OK; +} diff --git a/toolkit/components/remote/nsRemoteService.h b/toolkit/components/remote/nsRemoteService.h new file mode 100644 index 0000000000..3105913a7f --- /dev/null +++ b/toolkit/components/remote/nsRemoteService.h @@ -0,0 +1,49 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:expandtab:shiftwidth=2:tabstop=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 TOOLKIT_COMPONENTS_REMOTE_NSREMOTESERVER_H_ +#define TOOLKIT_COMPONENTS_REMOTE_NSREMOTESERVER_H_ + +#include "nsRemoteServer.h" +#include "nsIObserver.h" +#include "mozilla/UniquePtr.h" +#include "nsIFile.h" +#include "nsProfileLock.h" + +enum RemoteResult { + REMOTE_NOT_FOUND = 0, + REMOTE_FOUND = 1, + REMOTE_ARG_BAD = 2 +}; + +class nsRemoteService final : public nsIObserver { + public: + // We will be a static singleton, so don't use the ordinary methods. + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER + + explicit nsRemoteService(const char* aProgram); + void SetProfile(nsACString& aProfile); + + void LockStartup(); + void UnlockStartup(); + + RemoteResult StartClient(const char* aStartupToken); + void StartupServer(); + void ShutdownServer(); + + private: + ~nsRemoteService(); + + mozilla::UniquePtr<nsRemoteServer> mRemoteServer; + nsProfileLock mRemoteLock; + nsCOMPtr<nsIFile> mRemoteLockDir; + nsCString mProgram; + nsCString mProfile; +}; + +#endif // TOOLKIT_COMPONENTS_REMOTE_NSREMOTESERVER_H_ diff --git a/toolkit/components/remote/nsUnixRemoteServer.cpp b/toolkit/components/remote/nsUnixRemoteServer.cpp new file mode 100644 index 0000000000..f26c90af4a --- /dev/null +++ b/toolkit/components/remote/nsUnixRemoteServer.cpp @@ -0,0 +1,113 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:expandtab:shiftwidth=2:tabstop=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 "nsUnixRemoteServer.h" +#include "nsGTKToolkit.h" +#include "nsCOMPtr.h" +#include "nsICommandLineRunner.h" +#include "nsCommandLine.h" +#include "nsIFile.h" + +// Set desktop startup ID to the passed ID, if there is one, so that any created +// windows get created with the right window manager metadata, and any windows +// that get new tabs and are activated also get the right WM metadata. +// The timestamp will be used if there is no desktop startup ID, or if we're +// raising an existing window rather than showing a new window for the first +// time. +void nsUnixRemoteServer::SetStartupTokenOrTimestamp( + const nsACString& aStartupToken, uint32_t aTimestamp) { + nsGTKToolkit* toolkit = nsGTKToolkit::GetToolkit(); + if (!toolkit) { + return; + } + + if (!aStartupToken.IsEmpty()) { + toolkit->SetStartupToken(aStartupToken); + } + + toolkit->SetFocusTimestamp(aTimestamp); +} + +static bool FindExtensionParameterInCommand(const char* aParameterName, + const nsACString& aCommand, + char aSeparator, + nsACString* aValue) { + nsAutoCString searchFor; + searchFor.Append(aSeparator); + searchFor.Append(aParameterName); + searchFor.Append('='); + + nsACString::const_iterator start, end; + aCommand.BeginReading(start); + aCommand.EndReading(end); + if (!FindInReadable(searchFor, start, end)) return false; + + nsACString::const_iterator charStart, charEnd; + charStart = end; + aCommand.EndReading(charEnd); + nsACString::const_iterator idStart = charStart, idEnd; + if (FindCharInReadable(aSeparator, charStart, charEnd)) { + idEnd = charStart; + } else { + idEnd = charEnd; + } + *aValue = nsDependentCSubstring(idStart, idEnd); + return true; +} + +const char* nsUnixRemoteServer::HandleCommandLine(const char* aBuffer, + uint32_t aTimestamp) { + nsCOMPtr<nsICommandLineRunner> cmdline(new nsCommandLine()); + + // the commandline property is constructed as an array of int32_t + // followed by a series of null-terminated strings: + // + // [argc][offsetargv0][offsetargv1...]<workingdir>\0<argv[0]>\0argv[1]...\0 + // (offset is from the beginning of the buffer) + + int32_t argc = TO_LITTLE_ENDIAN32(*reinterpret_cast<const int32_t*>(aBuffer)); + const char* wd = aBuffer + ((argc + 1) * sizeof(int32_t)); + + nsCOMPtr<nsIFile> lf; + nsresult rv = + NS_NewNativeLocalFile(nsDependentCString(wd), true, getter_AddRefs(lf)); + if (NS_FAILED(rv)) return "509 internal error"; + + nsAutoCString desktopStartupID; + + const char** argv = (const char**)malloc(sizeof(char*) * argc); + if (!argv) return "509 internal error"; + + const int32_t* offset = reinterpret_cast<const int32_t*>(aBuffer) + 1; + + for (int i = 0; i < argc; ++i) { + argv[i] = aBuffer + TO_LITTLE_ENDIAN32(offset[i]); + + if (i == 0) { + nsDependentCString cmd(argv[0]); + FindExtensionParameterInCommand("STARTUP_TOKEN", cmd, ' ', + &desktopStartupID); + } + } + + rv = cmdline->Init(argc, argv, lf, nsICommandLine::STATE_REMOTE_AUTO); + + free(argv); + if (NS_FAILED(rv)) { + return "509 internal error"; + } + + SetStartupTokenOrTimestamp(desktopStartupID, aTimestamp); + + rv = cmdline->Run(); + + if (NS_ERROR_ABORT == rv) return "500 command not parseable"; + + if (NS_FAILED(rv)) return "509 internal error"; + + return "200 executed command"; +} diff --git a/toolkit/components/remote/nsUnixRemoteServer.h b/toolkit/components/remote/nsUnixRemoteServer.h new file mode 100644 index 0000000000..003f2efbf8 --- /dev/null +++ b/toolkit/components/remote/nsUnixRemoteServer.h @@ -0,0 +1,28 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:expandtab:shiftwidth=2:tabstop=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 __nsUnixRemoteServer_h__ +#define __nsUnixRemoteServer_h__ + +#include "nsStringFwd.h" + +#ifdef IS_BIG_ENDIAN +# define TO_LITTLE_ENDIAN32(x) \ + ((((x)&0xff000000) >> 24) | (((x)&0x00ff0000) >> 8) | \ + (((x)&0x0000ff00) << 8) | (((x)&0x000000ff) << 24)) +#else +# define TO_LITTLE_ENDIAN32(x) (x) +#endif + +class nsUnixRemoteServer { + protected: + void SetStartupTokenOrTimestamp(const nsACString& aStartupToken, + uint32_t aTimestamp); + const char* HandleCommandLine(const char* aBuffer, uint32_t aTimestamp); +}; + +#endif // __nsGTKRemoteService_h__ diff --git a/toolkit/components/remote/nsWinRemoteClient.cpp b/toolkit/components/remote/nsWinRemoteClient.cpp new file mode 100644 index 0000000000..4428070bfa --- /dev/null +++ b/toolkit/components/remote/nsWinRemoteClient.cpp @@ -0,0 +1,44 @@ +/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:expandtab:shiftwidth=4:tabstop=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 "nsWinRemoteClient.h" +#include <windows.h> +#include "RemoteUtils.h" +#include "WinRemoteMessage.h" + +using namespace mozilla; + +nsresult nsWinRemoteClient::Init() { return NS_OK; } + +nsresult nsWinRemoteClient::SendCommandLine( + const char* aProgram, const char* aProfile, int32_t argc, char** argv, + const char* aStartupToken, char** aResponse, bool* aSucceeded) { + *aSucceeded = false; + + nsString className; + BuildClassName(aProgram, aProfile, className); + + HWND handle = ::FindWindowW(className.get(), 0); + + if (!handle) { + return NS_OK; + } + + WCHAR cwd[MAX_PATH]; + _wgetcwd(cwd, MAX_PATH); + WinRemoteMessageSender sender(::GetCommandLineW(), cwd); + + // Bring the already running Mozilla process to the foreground. + // nsWindow will restore the window (if minimized) and raise it. + ::SetForegroundWindow(handle); + ::SendMessageW(handle, WM_COPYDATA, 0, + reinterpret_cast<LPARAM>(sender.CopyData())); + + *aSucceeded = true; + + return NS_OK; +} diff --git a/toolkit/components/remote/nsWinRemoteClient.h b/toolkit/components/remote/nsWinRemoteClient.h new file mode 100644 index 0000000000..64859b69a1 --- /dev/null +++ b/toolkit/components/remote/nsWinRemoteClient.h @@ -0,0 +1,25 @@ +/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:expandtab:shiftwidth=4:tabstop=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/. */ + +#ifndef nsWinRemoteClient_h__ +#define nsWinRemoteClient_h__ + +#include "nscore.h" +#include "nsRemoteClient.h" + +class nsWinRemoteClient : public nsRemoteClient { + public: + virtual ~nsWinRemoteClient() = default; + + nsresult Init() override; + + nsresult SendCommandLine(const char* aProgram, const char* aProfile, + int32_t argc, char** argv, const char* aStartupToken, + char** aResponse, bool* aSucceeded) override; +}; + +#endif // nsWinRemoteClient_h__ diff --git a/toolkit/components/remote/nsWinRemoteServer.cpp b/toolkit/components/remote/nsWinRemoteServer.cpp new file mode 100644 index 0000000000..0fa4f5facc --- /dev/null +++ b/toolkit/components/remote/nsWinRemoteServer.cpp @@ -0,0 +1,104 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:expandtab:shiftwidth=2:tabstop=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 "CmdLineAndEnvUtils.h" +#include "nsWinRemoteServer.h" +#include "RemoteUtils.h" +#include "nsCOMPtr.h" +#include "nsXPCOM.h" +#include "nsPIDOMWindow.h" +#include "nsIWindowMediator.h" +#include "nsIBaseWindow.h" +#include "nsIWidget.h" +#include "nsICommandLineRunner.h" +#include "nsICommandLine.h" +#include "nsCommandLine.h" +#include "nsIDocShell.h" +#include "WinRemoteMessage.h" + +HWND hwndForDOMWindow(mozIDOMWindowProxy* window) { + if (!window) { + return 0; + } + nsCOMPtr<nsPIDOMWindowOuter> pidomwindow = nsPIDOMWindowOuter::From(window); + + nsCOMPtr<nsIBaseWindow> ppBaseWindow = + do_QueryInterface(pidomwindow->GetDocShell()); + if (!ppBaseWindow) { + return 0; + } + + nsCOMPtr<nsIWidget> ppWidget; + ppBaseWindow->GetMainWidget(getter_AddRefs(ppWidget)); + + return (HWND)(ppWidget->GetNativeData(NS_NATIVE_WIDGET)); +} + +static nsresult GetMostRecentWindow(mozIDOMWindowProxy** aWindow) { + nsresult rv; + nsCOMPtr<nsIWindowMediator> med( + do_GetService(NS_WINDOWMEDIATOR_CONTRACTID, &rv)); + if (NS_FAILED(rv)) return rv; + + if (med) return med->GetMostRecentWindow(nullptr, aWindow); + + return NS_ERROR_FAILURE; +} + +LRESULT CALLBACK WindowProc(HWND msgWindow, UINT msg, WPARAM wp, LPARAM lp) { + if (msg == WM_COPYDATA) { + WinRemoteMessageReceiver receiver; + if (NS_SUCCEEDED(receiver.Parse(reinterpret_cast<COPYDATASTRUCT*>(lp)))) { + receiver.CommandLineRunner()->Run(); + } else { + NS_ERROR("Error initializing command line."); + } + + // Get current window and return its window handle. + nsCOMPtr<mozIDOMWindowProxy> win; + GetMostRecentWindow(getter_AddRefs(win)); + return win ? (LRESULT)hwndForDOMWindow(win) : 0; + } + return DefWindowProcW(msgWindow, msg, wp, lp); +} + +nsresult nsWinRemoteServer::Startup(const char* aAppName, + const char* aProfileName) { + nsString className; + BuildClassName(aAppName, aProfileName, className); + + WNDCLASSW classStruct = {0, // style + &WindowProc, // lpfnWndProc + 0, // cbClsExtra + 0, // cbWndExtra + 0, // hInstance + 0, // hIcon + 0, // hCursor + 0, // hbrBackground + 0, // lpszMenuName + className.get()}; // lpszClassName + + // Register the window class. + NS_ENSURE_TRUE(::RegisterClassW(&classStruct), NS_ERROR_FAILURE); + + // Create the window. + mHandle = ::CreateWindowW(className.get(), + 0, // title + WS_CAPTION, // style + 0, 0, 0, 0, // x, y, cx, cy + 0, // parent + 0, // menu + 0, // instance + 0); // create struct + + return mHandle ? NS_OK : NS_ERROR_FAILURE; +} + +void nsWinRemoteServer::Shutdown() { + DestroyWindow(mHandle); + mHandle = nullptr; +} diff --git a/toolkit/components/remote/nsWinRemoteServer.h b/toolkit/components/remote/nsWinRemoteServer.h new file mode 100644 index 0000000000..1687e2d44b --- /dev/null +++ b/toolkit/components/remote/nsWinRemoteServer.h @@ -0,0 +1,27 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:expandtab:shiftwidth=2:tabstop=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 __nsWinRemoteServer_h__ +#define __nsWinRemoteServer_h__ + +#include "nsRemoteServer.h" + +#include <windows.h> + +class nsWinRemoteServer final : public nsRemoteServer { + public: + nsWinRemoteServer() = default; + ~nsWinRemoteServer() override { Shutdown(); } + + nsresult Startup(const char* aAppName, const char* aProfileName) override; + void Shutdown() override; + + private: + HWND mHandle; +}; + +#endif // __nsWinRemoteService_h__ diff --git a/toolkit/components/remote/nsXRemoteClient.cpp b/toolkit/components/remote/nsXRemoteClient.cpp new file mode 100644 index 0000000000..e8af9eaf56 --- /dev/null +++ b/toolkit/components/remote/nsXRemoteClient.cpp @@ -0,0 +1,654 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:expandtab:shiftwidth=2:tabstop=8: + */ +/* vim:set ts=8 sw=2 et cindent: */ +/* 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 "nsDebug.h" +#include "mozilla/ArrayUtils.h" +#include "mozilla/IntegerPrintfMacros.h" +#include "mozilla/Sprintf.h" +#include "mozilla/Unused.h" +#include "nsXRemoteClient.h" +#include "RemoteUtils.h" +#include "plstr.h" +#include "prsystem.h" +#include "mozilla/Logging.h" +#include "prenv.h" +#include "prdtoa.h" +#include <X11/Xatom.h> +#include <limits.h> +#include <poll.h> +#include <stdlib.h> +#include <string.h> +#include <strings.h> +#include <sys/time.h> +#include <sys/types.h> +#include <unistd.h> + +#define MOZILLA_VERSION_PROP "_MOZILLA_VERSION" +#define MOZILLA_LOCK_PROP "_MOZILLA_LOCK" +#define MOZILLA_COMMANDLINE_PROP "_MOZILLA_COMMANDLINE" +#define MOZILLA_RESPONSE_PROP "_MOZILLA_RESPONSE" +#define MOZILLA_USER_PROP "_MOZILLA_USER" +#define MOZILLA_PROFILE_PROP "_MOZILLA_PROFILE" +#define MOZILLA_PROGRAM_PROP "_MOZILLA_PROGRAM" + +#ifdef IS_BIG_ENDIAN +# define TO_LITTLE_ENDIAN32(x) \ + ((((x)&0xff000000) >> 24) | (((x)&0x00ff0000) >> 8) | \ + (((x)&0x0000ff00) << 8) | (((x)&0x000000ff) << 24)) +#else +# define TO_LITTLE_ENDIAN32(x) (x) +#endif + +#ifndef MAX_PATH +# ifdef PATH_MAX +# define MAX_PATH PATH_MAX +# else +# define MAX_PATH 1024 +# endif +#endif + +using mozilla::LogLevel; +using mozilla::Unused; + +static mozilla::LazyLogModule sRemoteLm("nsXRemoteClient"); + +static int (*sOldHandler)(Display*, XErrorEvent*); +static bool sGotBadWindow; + +nsXRemoteClient::nsXRemoteClient() { + mDisplay = 0; + mInitialized = false; + mMozVersionAtom = 0; + mMozLockAtom = 0; + mMozCommandLineAtom = 0; + mMozResponseAtom = 0; + mMozWMStateAtom = 0; + mMozUserAtom = 0; + mMozProfileAtom = 0; + mMozProgramAtom = 0; + mLockData = 0; + MOZ_LOG(sRemoteLm, LogLevel::Debug, ("nsXRemoteClient::nsXRemoteClient")); +} + +nsXRemoteClient::~nsXRemoteClient() { + MOZ_LOG(sRemoteLm, LogLevel::Debug, ("nsXRemoteClient::~nsXRemoteClient")); + if (mInitialized) Shutdown(); +} + +// Minimize the roundtrips to the X-server +static const char* XAtomNames[] = { + MOZILLA_VERSION_PROP, MOZILLA_LOCK_PROP, MOZILLA_RESPONSE_PROP, + "WM_STATE", MOZILLA_USER_PROP, MOZILLA_PROFILE_PROP, + MOZILLA_PROGRAM_PROP, MOZILLA_COMMANDLINE_PROP}; +static Atom XAtoms[MOZ_ARRAY_LENGTH(XAtomNames)]; + +nsresult nsXRemoteClient::Init() { + MOZ_LOG(sRemoteLm, LogLevel::Debug, ("nsXRemoteClient::Init")); + + if (mInitialized) return NS_OK; + + // try to open the display + mDisplay = XOpenDisplay(0); + if (!mDisplay) return NS_ERROR_FAILURE; + + // get our atoms + XInternAtoms(mDisplay, const_cast<char**>(XAtomNames), + MOZ_ARRAY_LENGTH(XAtomNames), False, XAtoms); + + int i = 0; + mMozVersionAtom = XAtoms[i++]; + mMozLockAtom = XAtoms[i++]; + mMozResponseAtom = XAtoms[i++]; + mMozWMStateAtom = XAtoms[i++]; + mMozUserAtom = XAtoms[i++]; + mMozProfileAtom = XAtoms[i++]; + mMozProgramAtom = XAtoms[i++]; + mMozCommandLineAtom = XAtoms[i]; + + mInitialized = true; + + return NS_OK; +} + +void nsXRemoteClient::Shutdown(void) { + MOZ_LOG(sRemoteLm, LogLevel::Debug, ("nsXRemoteClient::Shutdown")); + + if (!mInitialized) return; + + // shut everything down + XCloseDisplay(mDisplay); + mDisplay = 0; + mInitialized = false; + if (mLockData) { + free(mLockData); + mLockData = 0; + } +} + +static int HandleBadWindow(Display* display, XErrorEvent* event) { + if (event->error_code == BadWindow) { + sGotBadWindow = true; + return 0; // ignored + } + + return (*sOldHandler)(display, event); +} + +nsresult nsXRemoteClient::SendCommandLine( + const char* aProgram, const char* aProfile, int32_t argc, char** argv, + const char* aStartupToken, char** aResponse, bool* aWindowFound) { + NS_ENSURE_TRUE(aProgram, NS_ERROR_INVALID_ARG); + + MOZ_LOG(sRemoteLm, LogLevel::Debug, ("nsXRemoteClient::SendCommandLine")); + + *aWindowFound = false; + + // FindBestWindow() iterates down the window hierarchy, so catch X errors + // when windows get destroyed before being accessed. + sOldHandler = XSetErrorHandler(HandleBadWindow); + + Window w = FindBestWindow(aProgram, aProfile); + + nsresult rv = NS_OK; + + if (w) { + // ok, let the caller know that we at least found a window. + *aWindowFound = true; + + // Ignore BadWindow errors up to this point. The last request from + // FindBestWindow() was a synchronous XGetWindowProperty(), so no need to + // Sync. Leave the error handler installed to detect if w gets destroyed. + sGotBadWindow = false; + + // make sure we get the right events on that window + XSelectInput(mDisplay, w, (PropertyChangeMask | StructureNotifyMask)); + + bool destroyed = false; + + // get the lock on the window + rv = GetLock(w, &destroyed); + + if (NS_SUCCEEDED(rv)) { + // send our command + rv = DoSendCommandLine(w, argc, argv, aStartupToken, aResponse, + &destroyed); + + // if the window was destroyed, don't bother trying to free the + // lock. + if (!destroyed) FreeLock(w); // doesn't really matter what this returns + } + } + + XSetErrorHandler(sOldHandler); + + MOZ_LOG(sRemoteLm, LogLevel::Debug, + ("SendCommandInternal returning 0x%" PRIx32 "\n", + static_cast<uint32_t>(rv))); + + return rv; +} + +Window nsXRemoteClient::CheckWindow(Window aWindow) { + Atom type = None; + int format; + unsigned long nitems, bytesafter; + unsigned char* data; + Window innerWindow; + + XGetWindowProperty(mDisplay, aWindow, mMozWMStateAtom, 0, 0, False, + AnyPropertyType, &type, &format, &nitems, &bytesafter, + &data); + + if (type) { + XFree(data); + return aWindow; + } + + // didn't find it here so check the children of this window + innerWindow = CheckChildren(aWindow); + + if (innerWindow) return innerWindow; + + return aWindow; +} + +Window nsXRemoteClient::CheckChildren(Window aWindow) { + Window root, parent; + Window* children; + unsigned int nchildren; + unsigned int i; + Atom type = None; + int format; + unsigned long nitems, after; + unsigned char* data; + Window retval = None; + + if (!XQueryTree(mDisplay, aWindow, &root, &parent, &children, &nchildren)) + return None; + + // scan the list first before recursing into the list of windows + // which can get quite deep. + for (i = 0; !retval && (i < nchildren); i++) { + XGetWindowProperty(mDisplay, children[i], mMozWMStateAtom, 0, 0, False, + AnyPropertyType, &type, &format, &nitems, &after, &data); + if (type) { + XFree(data); + retval = children[i]; + } + } + + // otherwise recurse into the list + for (i = 0; !retval && (i < nchildren); i++) { + retval = CheckChildren(children[i]); + } + + if (children) XFree((char*)children); + + return retval; +} + +nsresult nsXRemoteClient::GetLock(Window aWindow, bool* aDestroyed) { + bool locked = false; + bool waited = false; + *aDestroyed = false; + + nsresult rv = NS_OK; + + if (!mLockData) { + char pidstr[32]; + char sysinfobuf[SYS_INFO_BUFFER_LENGTH]; + SprintfLiteral(pidstr, "pid%d@", getpid()); + PRStatus status; + status = + PR_GetSystemInfo(PR_SI_HOSTNAME, sysinfobuf, SYS_INFO_BUFFER_LENGTH); + if (status != PR_SUCCESS) { + return NS_ERROR_FAILURE; + } + + // allocate enough space for the string plus the terminating + // char + mLockData = (char*)malloc(strlen(pidstr) + strlen(sysinfobuf) + 1); + if (!mLockData) return NS_ERROR_OUT_OF_MEMORY; + + strcpy(mLockData, pidstr); + if (!strcat(mLockData, sysinfobuf)) return NS_ERROR_FAILURE; + } + + do { + int result; + Atom actual_type; + int actual_format; + unsigned long nitems, bytes_after; + unsigned char* data = 0; + + XGrabServer(mDisplay); + + result = XGetWindowProperty( + mDisplay, aWindow, mMozLockAtom, 0, (65536 / sizeof(long)), + False, /* don't delete */ + XA_STRING, &actual_type, &actual_format, &nitems, &bytes_after, &data); + + // aWindow may have been destroyed before XSelectInput was processed, in + // which case there may not be any DestroyNotify event in the queue to + // tell us. XGetWindowProperty() was synchronous so error responses have + // now been processed, setting sGotBadWindow. + if (sGotBadWindow) { + *aDestroyed = true; + rv = NS_ERROR_FAILURE; + } else if (result != Success || actual_type == None) { + /* It's not now locked - lock it. */ + XChangeProperty(mDisplay, aWindow, mMozLockAtom, XA_STRING, 8, + PropModeReplace, (unsigned char*)mLockData, + strlen(mLockData)); + locked = True; + } + + XUngrabServer(mDisplay); + XFlush(mDisplay); // ungrab now! + + if (!locked && !NS_FAILED(rv)) { + /* We tried to grab the lock this time, and failed because someone + else is holding it already. So, wait for a PropertyDelete event + to come in, and try again. */ + MOZ_LOG(sRemoteLm, LogLevel::Debug, + ("window 0x%x is locked by %s; waiting...\n", + (unsigned int)aWindow, data)); + waited = True; + while (true) { + XEvent event; + int poll_retval; + struct pollfd pfd; + + pfd.fd = ConnectionNumber(mDisplay); + pfd.events = POLLIN; + + poll_retval = poll(&pfd, 1, 10 * 1000); + // did we time out? + if (poll_retval == 0) { + MOZ_LOG(sRemoteLm, LogLevel::Debug, + ("timed out waiting for window\n")); + rv = NS_ERROR_FAILURE; + break; + } + MOZ_LOG(sRemoteLm, LogLevel::Debug, ("xevent...\n")); + // FIXME check the return value from this? + XNextEvent(mDisplay, &event); + if (event.xany.type == DestroyNotify && + event.xdestroywindow.window == aWindow) { + *aDestroyed = true; + rv = NS_ERROR_FAILURE; + break; + } + if (event.xany.type == PropertyNotify && + event.xproperty.state == PropertyDelete && + event.xproperty.window == aWindow && + event.xproperty.atom == mMozLockAtom) { + /* Ok! Someone deleted their lock, so now we can try + again. */ + MOZ_LOG( + sRemoteLm, LogLevel::Debug, + ("(0x%x unlocked, trying again...)\n", (unsigned int)aWindow)); + break; + } + } + } + if (data) XFree(data); + } while (!locked && !NS_FAILED(rv)); + + if (waited && locked) { + MOZ_LOG(sRemoteLm, LogLevel::Debug, ("obtained lock.\n")); + } else if (*aDestroyed) { + MOZ_LOG(sRemoteLm, LogLevel::Debug, + ("window 0x%x unexpectedly destroyed.\n", (unsigned int)aWindow)); + } + + return rv; +} + +Window nsXRemoteClient::FindBestWindow(const char* aProgram, + const char* aProfile) { + Window root = RootWindowOfScreen(DefaultScreenOfDisplay(mDisplay)); + Window bestWindow = 0; + Window root2, parent, *kids; + unsigned int nkids; + + // Get a list of the children of the root window, walk the list + // looking for the best window that fits the criteria. + if (!XQueryTree(mDisplay, root, &root2, &parent, &kids, &nkids)) { + MOZ_LOG(sRemoteLm, LogLevel::Debug, + ("XQueryTree failed in nsXRemoteClient::FindBestWindow")); + return 0; + } + + if (!(kids && nkids)) { + MOZ_LOG(sRemoteLm, LogLevel::Debug, ("root window has no children")); + return 0; + } + + // We'll walk the list of windows looking for a window that best + // fits the criteria here. + + for (unsigned int i = 0; i < nkids; i++) { + Atom type; + int format; + unsigned long nitems, bytesafter; + unsigned char* data_return = 0; + Window w; + w = kids[i]; + // find the inner window with WM_STATE on it + w = CheckWindow(w); + + int status = XGetWindowProperty( + mDisplay, w, mMozVersionAtom, 0, (65536 / sizeof(long)), False, + XA_STRING, &type, &format, &nitems, &bytesafter, &data_return); + + if (!data_return) continue; + + double version = PR_strtod((char*)data_return, nullptr); + XFree(data_return); + + if (!(version >= 5.1 && version < 6)) continue; + + data_return = 0; + + if (status != Success || type == None) continue; + + // Check that this window is from the right program. + Unused << XGetWindowProperty( + mDisplay, w, mMozProgramAtom, 0, (65536 / sizeof(long)), False, + XA_STRING, &type, &format, &nitems, &bytesafter, &data_return); + + // If the return name is not the same as this program name, we don't want + // this window. + if (data_return) { + if (strcmp(aProgram, (const char*)data_return)) { + XFree(data_return); + continue; + } + + // This is actually the success condition. + XFree(data_return); + } else { + // Doesn't support the protocol, even though the user + // requested it. So we're not going to use this window. + continue; + } + + // Check to see if it has the user atom on that window. If there + // is then we need to make sure that it matches what we have. + const char* username = PR_GetEnv("LOGNAME"); + + if (username) { + Unused << XGetWindowProperty( + mDisplay, w, mMozUserAtom, 0, (65536 / sizeof(long)), False, + XA_STRING, &type, &format, &nitems, &bytesafter, &data_return); + + // if there's a username compare it with what we have + if (data_return) { + // If the IDs aren't equal, we don't want this window. + if (strcmp(username, (const char*)data_return)) { + XFree(data_return); + continue; + } + + XFree(data_return); + } + } + + // Check to see if there's a profile name on this window. If + // there is, then we need to make sure it matches what someone + // passed in. + Unused << XGetWindowProperty( + mDisplay, w, mMozProfileAtom, 0, (65536 / sizeof(long)), False, + XA_STRING, &type, &format, &nitems, &bytesafter, &data_return); + + // If there's a profile compare it with what we have + if (data_return) { + // If the profiles aren't equal, we don't want this window. + if (strcmp(aProfile, (const char*)data_return)) { + XFree(data_return); + continue; + } + + XFree(data_return); + } else { + // This isn't the window for this profile. + continue; + } + + // Check to see if the window supports the new command-line passing + // protocol, if that is requested. + + // If we got this far, this is the best window. It passed + // all the tests. + bestWindow = w; + break; + } + + if (kids) XFree((char*)kids); + + return bestWindow; +} + +nsresult nsXRemoteClient::FreeLock(Window aWindow) { + int result; + Atom actual_type; + int actual_format; + unsigned long nitems, bytes_after; + unsigned char* data = 0; + + result = XGetWindowProperty( + mDisplay, aWindow, mMozLockAtom, 0, (65536 / sizeof(long)), + True, /* atomic delete after */ + XA_STRING, &actual_type, &actual_format, &nitems, &bytes_after, &data); + if (result != Success) { + MOZ_LOG(sRemoteLm, LogLevel::Debug, + ("unable to read and delete " MOZILLA_LOCK_PROP " property\n")); + return NS_ERROR_FAILURE; + } + if (!data || !*data) { + MOZ_LOG(sRemoteLm, LogLevel::Debug, + ("invalid data on " MOZILLA_LOCK_PROP " of window 0x%x.\n", + (unsigned int)aWindow)); + return NS_ERROR_FAILURE; + } else if (strcmp((char*)data, mLockData)) { + MOZ_LOG(sRemoteLm, LogLevel::Debug, + (MOZILLA_LOCK_PROP " was stolen! Expected \"%s\", saw \"%s\"!\n", + mLockData, data)); + return NS_ERROR_FAILURE; + } + + if (data) XFree(data); + return NS_OK; +} + +nsresult nsXRemoteClient::DoSendCommandLine(Window aWindow, int32_t argc, + char** argv, + const char* aStartupToken, + char** aResponse, + bool* aDestroyed) { + *aDestroyed = false; + + int commandLineLength; + char* commandLine = + ConstructCommandLine(argc, argv, aStartupToken, &commandLineLength); + XChangeProperty(mDisplay, aWindow, mMozCommandLineAtom, XA_STRING, 8, + PropModeReplace, (unsigned char*)commandLine, + commandLineLength); + free(commandLine); + + if (!WaitForResponse(aWindow, aResponse, aDestroyed, mMozCommandLineAtom)) + return NS_ERROR_FAILURE; + + return NS_OK; +} + +bool nsXRemoteClient::WaitForResponse(Window aWindow, char** aResponse, + bool* aDestroyed, Atom aCommandAtom) { + bool done = false; + bool accepted = false; + + while (!done) { + XEvent event; + XNextEvent(mDisplay, &event); + if (event.xany.type == DestroyNotify && + event.xdestroywindow.window == aWindow) { + /* Print to warn user...*/ + MOZ_LOG(sRemoteLm, LogLevel::Debug, + ("window 0x%x was destroyed.\n", (unsigned int)aWindow)); + *aResponse = strdup("Window was destroyed while reading response."); + *aDestroyed = true; + return false; + } + if (event.xany.type == PropertyNotify && + event.xproperty.state == PropertyNewValue && + event.xproperty.window == aWindow && + event.xproperty.atom == mMozResponseAtom) { + Atom actual_type; + int actual_format; + unsigned long nitems, bytes_after; + unsigned char* data = 0; + Bool result; + result = XGetWindowProperty(mDisplay, aWindow, mMozResponseAtom, 0, + (65536 / sizeof(long)), + True, /* atomic delete after */ + XA_STRING, &actual_type, &actual_format, + &nitems, &bytes_after, &data); + if (result != Success) { + MOZ_LOG( + sRemoteLm, LogLevel::Debug, + ("failed reading " MOZILLA_RESPONSE_PROP " from window 0x%0x.\n", + (unsigned int)aWindow)); + *aResponse = strdup("Internal error reading response from window."); + done = true; + } else if (!data || strlen((char*)data) < 5) { + MOZ_LOG(sRemoteLm, LogLevel::Debug, + ("invalid data on " MOZILLA_RESPONSE_PROP + " property of window 0x%0x.\n", + (unsigned int)aWindow)); + *aResponse = strdup("Server returned invalid data in response."); + done = true; + } else if (*data == '1') { /* positive preliminary reply */ + MOZ_LOG(sRemoteLm, LogLevel::Debug, ("%s\n", data + 4)); + /* keep going */ + done = false; + } + + else if (!strncmp((char*)data, "200", 3)) { /* positive completion */ + *aResponse = strdup((char*)data); + accepted = true; + done = true; + } + + else if (*data == '2') { /* positive completion */ + MOZ_LOG(sRemoteLm, LogLevel::Debug, ("%s\n", data + 4)); + *aResponse = strdup((char*)data); + accepted = true; + done = true; + } + + else if (*data == '3') { /* positive intermediate reply */ + MOZ_LOG(sRemoteLm, LogLevel::Debug, + ("internal error: " + "server wants more information? (%s)\n", + data)); + *aResponse = strdup((char*)data); + done = true; + } + + else if (*data == '4' || /* transient negative completion */ + *data == '5') { /* permanent negative completion */ + MOZ_LOG(sRemoteLm, LogLevel::Debug, ("%s\n", data + 4)); + *aResponse = strdup((char*)data); + done = true; + } + + else { + MOZ_LOG( + sRemoteLm, LogLevel::Debug, + ("unrecognised " MOZILLA_RESPONSE_PROP " from window 0x%x: %s\n", + (unsigned int)aWindow, data)); + *aResponse = strdup((char*)data); + done = true; + } + + if (data) XFree(data); + } + + else if (event.xany.type == PropertyNotify && + event.xproperty.window == aWindow && + event.xproperty.state == PropertyDelete && + event.xproperty.atom == aCommandAtom) { + MOZ_LOG(sRemoteLm, LogLevel::Debug, + ("(server 0x%x has accepted " MOZILLA_COMMANDLINE_PROP ".)\n", + (unsigned int)aWindow)); + } + } + + return accepted; +} diff --git a/toolkit/components/remote/nsXRemoteClient.h b/toolkit/components/remote/nsXRemoteClient.h new file mode 100644 index 0000000000..ad8e7d3787 --- /dev/null +++ b/toolkit/components/remote/nsXRemoteClient.h @@ -0,0 +1,49 @@ +/* -*- Mode: C++; tab-width: 8; 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 <X11/X.h> +#include <X11/Xlib.h> + +#include "nsRemoteClient.h" + +class nsXRemoteClient : public nsRemoteClient { + public: + nsXRemoteClient(); + ~nsXRemoteClient(); + + virtual nsresult Init() override; + virtual nsresult SendCommandLine(const char* aProgram, const char* aProfile, + int32_t argc, char** argv, + const char* aStartupToken, char** aResponse, + bool* aSucceeded) override; + void Shutdown(); + + private: + Window CheckWindow(Window aWindow); + Window CheckChildren(Window aWindow); + nsresult GetLock(Window aWindow, bool* aDestroyed); + nsresult FreeLock(Window aWindow); + Window FindBestWindow(const char* aProgram, const char* aProfile); + nsresult DoSendCommandLine(Window aWindow, int32_t argc, char** argv, + const char* aStartupToken, char** aResponse, + bool* aDestroyed); + bool WaitForResponse(Window aWindow, char** aResponse, bool* aDestroyed, + Atom aCommandAtom); + + Display* mDisplay; + + Atom mMozVersionAtom; + Atom mMozLockAtom; + Atom mMozCommandLineAtom; + Atom mMozResponseAtom; + Atom mMozWMStateAtom; + Atom mMozUserAtom; + Atom mMozProfileAtom; + Atom mMozProgramAtom; + + char* mLockData; + + bool mInitialized; +}; diff --git a/toolkit/components/remote/nsXRemoteServer.cpp b/toolkit/components/remote/nsXRemoteServer.cpp new file mode 100644 index 0000000000..1f97b18e20 --- /dev/null +++ b/toolkit/components/remote/nsXRemoteServer.cpp @@ -0,0 +1,161 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:expandtab:shiftwidth=2:tabstop=8: + */ +/* 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 "nsXRemoteServer.h" +#include "nsCOMPtr.h" +#include "nsICommandLine.h" + +#include "nsIWidget.h" +#include "nsAppShellCID.h" +#include "nsPIDOMWindow.h" +#include "mozilla/X11Util.h" + +#include "nsCOMPtr.h" +#include "nsString.h" +#include "prenv.h" +#include "nsCRT.h" + +#include "nsXULAppAPI.h" + +#include <X11/Xlib.h> +#include <X11/Xatom.h> + +using namespace mozilla; + +#define MOZILLA_VERSION_PROP "_MOZILLA_VERSION" +#define MOZILLA_LOCK_PROP "_MOZILLA_LOCK" +#define MOZILLA_RESPONSE_PROP "_MOZILLA_RESPONSE" +#define MOZILLA_USER_PROP "_MOZILLA_USER" +#define MOZILLA_PROFILE_PROP "_MOZILLA_PROFILE" +#define MOZILLA_PROGRAM_PROP "_MOZILLA_PROGRAM" +#define MOZILLA_COMMANDLINE_PROP "_MOZILLA_COMMANDLINE" + +const unsigned char kRemoteVersion[] = "5.1"; + +// Minimize the roundtrips to the X server by getting all the atoms at once +static const char* XAtomNames[] = { + MOZILLA_VERSION_PROP, MOZILLA_LOCK_PROP, MOZILLA_RESPONSE_PROP, + MOZILLA_USER_PROP, MOZILLA_PROFILE_PROP, MOZILLA_PROGRAM_PROP, + MOZILLA_COMMANDLINE_PROP}; +static Atom XAtoms[MOZ_ARRAY_LENGTH(XAtomNames)]; + +Atom nsXRemoteServer::sMozVersionAtom; +Atom nsXRemoteServer::sMozLockAtom; +Atom nsXRemoteServer::sMozResponseAtom; +Atom nsXRemoteServer::sMozUserAtom; +Atom nsXRemoteServer::sMozProfileAtom; +Atom nsXRemoteServer::sMozProgramAtom; +Atom nsXRemoteServer::sMozCommandLineAtom; + +nsXRemoteServer::nsXRemoteServer() = default; + +void nsXRemoteServer::XRemoteBaseStartup(const char* aAppName, + const char* aProfileName) { + EnsureAtoms(); + + mAppName = aAppName; + ToLowerCase(mAppName); + + mProfileName = aProfileName; +} + +void nsXRemoteServer::HandleCommandsFor(Window aWindowId) { + // set our version + XChangeProperty(mozilla::DefaultXDisplay(), aWindowId, sMozVersionAtom, + XA_STRING, 8, PropModeReplace, kRemoteVersion, + sizeof(kRemoteVersion) - 1); + + // get our username + unsigned char* logname; + logname = (unsigned char*)PR_GetEnv("LOGNAME"); + if (logname) { + // set the property on the window if it's available + XChangeProperty(mozilla::DefaultXDisplay(), aWindowId, sMozUserAtom, + XA_STRING, 8, PropModeReplace, logname, + strlen((char*)logname)); + } + + XChangeProperty(mozilla::DefaultXDisplay(), aWindowId, sMozProgramAtom, + XA_STRING, 8, PropModeReplace, (unsigned char*)mAppName.get(), + mAppName.Length()); + + XChangeProperty(mozilla::DefaultXDisplay(), aWindowId, sMozProfileAtom, + XA_STRING, 8, PropModeReplace, + (unsigned char*)mProfileName.get(), mProfileName.Length()); +} + +bool nsXRemoteServer::HandleNewProperty(XID aWindowId, Display* aDisplay, + Time aEventTime, Atom aChangedAtom) { + if (aChangedAtom == sMozCommandLineAtom) { + // We got a new command atom. + int result; + Atom actual_type; + int actual_format; + unsigned long nitems, bytes_after; + char* data = 0; + + result = XGetWindowProperty(aDisplay, aWindowId, aChangedAtom, + 0, /* long_offset */ + (65536 / sizeof(long)), /* long_length */ + X11True, /* atomic delete after */ + XA_STRING, /* req_type */ + &actual_type, /* actual_type return */ + &actual_format, /* actual_format_return */ + &nitems, /* nitems_return */ + &bytes_after, /* bytes_after_return */ + (unsigned char**)&data); /* prop_return + (we only care + about the first ) */ + + // Failed to get property off the window? + if (result != Success) return false; + + // Failed to get the data off the window or it was the wrong type? + if (!data || !TO_LITTLE_ENDIAN32(*reinterpret_cast<int32_t*>(data))) + return false; + + // cool, we got the property data. + const char* response = HandleCommandLine(data, aEventTime); + + // put the property onto the window as the response + XChangeProperty(aDisplay, aWindowId, sMozResponseAtom, XA_STRING, 8, + PropModeReplace, (const unsigned char*)response, + strlen(response)); + XFree(data); + return true; + } + + if (aChangedAtom == sMozResponseAtom) { + // client accepted the response. party on wayne. + return true; + } + + else if (aChangedAtom == sMozLockAtom) { + // someone locked the window + return true; + } + + return false; +} + +void nsXRemoteServer::EnsureAtoms(void) { + if (sMozVersionAtom) return; + + XInternAtoms(mozilla::DefaultXDisplay(), const_cast<char**>(XAtomNames), + ArrayLength(XAtomNames), X11False, XAtoms); + + int i = 0; + sMozVersionAtom = XAtoms[i++]; + sMozLockAtom = XAtoms[i++]; + sMozResponseAtom = XAtoms[i++]; + sMozUserAtom = XAtoms[i++]; + sMozProfileAtom = XAtoms[i++]; + sMozProgramAtom = XAtoms[i++]; + sMozCommandLineAtom = XAtoms[i]; +} diff --git a/toolkit/components/remote/nsXRemoteServer.h b/toolkit/components/remote/nsXRemoteServer.h new file mode 100644 index 0000000000..405bafe789 --- /dev/null +++ b/toolkit/components/remote/nsXRemoteServer.h @@ -0,0 +1,44 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:expandtab:shiftwidth=2:tabstop=8: + */ +/* 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 NSXREMOTESERVER_H +#define NSXREMOTESERVER_H + +#include "nsString.h" +#include "nsRemoteServer.h" +#include "nsUnixRemoteServer.h" + +#include <X11/Xlib.h> +#include <X11/X.h> + +/** + Base class for GTK/Qt remote service +*/ +class nsXRemoteServer : public nsRemoteServer, public nsUnixRemoteServer { + protected: + nsXRemoteServer(); + bool HandleNewProperty(Window aWindowId, Display* aDisplay, Time aEventTime, + Atom aChangedAtom); + void XRemoteBaseStartup(const char* aAppName, const char* aProfileName); + void HandleCommandsFor(Window aWindowId); + + private: + void EnsureAtoms(); + + nsCString mAppName; + nsCString mProfileName; + + static Atom sMozVersionAtom; + static Atom sMozLockAtom; + static Atom sMozResponseAtom; + static Atom sMozUserAtom; + static Atom sMozProfileAtom; + static Atom sMozProgramAtom; + static Atom sMozCommandLineAtom; +}; + +#endif // NSXREMOTESERVER_H |