diff options
Diffstat (limited to 'windows/nsis-plugins/src/tray/tray.cpp')
| -rw-r--r-- | windows/nsis-plugins/src/tray/tray.cpp | 188 |
1 files changed, 188 insertions, 0 deletions
diff --git a/windows/nsis-plugins/src/tray/tray.cpp b/windows/nsis-plugins/src/tray/tray.cpp new file mode 100644 index 0000000000..f270c47261 --- /dev/null +++ b/windows/nsis-plugins/src/tray/tray.cpp @@ -0,0 +1,188 @@ +#include "stdafx.h" +#include "trayparser.h" +#include "trayjuggler.h" +#include "resource.h" +#include <windows.h> +#include <log/log.h> +#include <libcommon/string.h> +#include <libcommon/registry/registry.h> +#include <libcommon/resourcedata.h> +#include <libcommon/filesystem.h> +#include <libcommon/process.h> +#include <libcommon/security.h> +#include <nsis/pluginapi.h> +#include <stdexcept> +#include <experimental/filesystem> + +namespace +{ + +EXTERN_C IMAGE_DOS_HEADER __ImageBase; + +void InjectMullvadRecord(TrayJuggler &juggler) +{ + // + // There's no existing mullvad tray record in the system. + // Load a template mullvad record, which is then fixed up and injected. + // + + auto moduleHandle = reinterpret_cast<HMODULE>(&__ImageBase); + + auto resource = common::resourcedata::LoadBinaryResource(moduleHandle, MULLVAD_TRAY_RECORD); + + if (resource.size != sizeof(ICON_STREAMS_RECORD)) + { + throw std::runtime_error("Invalid tray template, size mismatch"); + } + + ICON_STREAMS_RECORD newRecord(*reinterpret_cast<const ICON_STREAMS_RECORD *>(resource.data)); + + juggler.injectRecord(newRecord); +} + +void UpdateRegistry(common::registry::RegistryKey ®key, const std::wstring &valueName, const TrayJuggler &juggler) +{ + // + // Construct path to 'explorer.exe' + // + + const auto windir = common::fs::GetKnownFolderPath(FOLDERID_Windows, 0, nullptr); + const auto explorer = std::experimental::filesystem::path(windir).append(L"explorer.exe"); + + // + // Determine process id of active instance(s). + // There's a setting in 'explorer' that will open all folder views as separate processes. + // + + const auto pids = common::process::GetAllProcessIdsFromName(explorer); + + if (pids.empty()) + { + throw std::runtime_error("Could not determine PID of explorer.exe"); + } + + // + // Make a copy of the process security context before we start terminating processes. + // Should we make an effort to choose the process that has been alive the longest? + // + auto context = common::security::DuplicateSecurityContext(*pids.begin()); + + size_t terminated = 0; + + for (auto pid : pids) + { + auto handle = OpenProcess(PROCESS_TERMINATE, FALSE, pid); + + if (nullptr != handle) + { + // + // 'winlogon' is monitoring 'explorer' and immediately + // restarts it if the exit code is 0. + // + if (FALSE != TerminateProcess(handle, 1)) + { + WaitForSingleObject(handle, INFINITE); + ++terminated; + } + + CloseHandle(handle); + } + } + + if (0 == terminated) + { + throw std::runtime_error("Could not terminate explorer.exe"); + } + + // + // We've terminated one/more instances of explorer.exe so we have to follow through. + // + + if (pids.size() != terminated) + { + PluginLog(L"Could not terminate all instances of explorer.exe"); + } + + regkey.writeValue(valueName, juggler.pack()); + + common::process::RunInContext(*context, explorer); +} + +} // anonymous namespace + +// +// PromoteTrayIcon +// +// Ensure the GUI's tray icon is placed in the visible part of the notification area. +// This is accomplished by updating a binary blob in the registry. +// +enum class PromoteTrayIconStatus +{ + GENERAL_ERROR = 0, + SUCCESS +}; + +void __declspec(dllexport) NSISCALL PromoteTrayIcon +( + HWND hwndParent, + int string_size, + LPTSTR variables, + stack_t **stacktop, + extra_parameters *extra, + ... +) +{ + EXDLL_INIT(); + + try + { + static const wchar_t keyName[] = L"Software\\Classes\\Local Settings\\Software\\Microsoft\\Windows\\CurrentVersion\\TrayNotify"; + static const wchar_t valueName[] = L"IconStreams"; + + auto regkey = common::registry::Registry::OpenKey(HKEY_CURRENT_USER, keyName, true); + + TrayParser parser(regkey->readBinaryBlob(valueName)); + + TrayJuggler juggler(parser); + + bool updateRegistry = true; + + if (auto mullvadRecord = juggler.findRecord(L"Mullvad VPN")) + { + if (ICON_STREAMS_VISIBILITY::SHOW_ICON_AND_NOTIFICATIONS == mullvadRecord->Visibility) + { + updateRegistry = false; + } + else + { + juggler.promoteRecord(mullvadRecord); + } + } + else + { + InjectMullvadRecord(juggler); + } + + // + // Only update the registry if the record/record set was updated. + // + + if (updateRegistry) + { + UpdateRegistry(*regkey, valueName, juggler); + } + + pushstring(L""); + pushint(PromoteTrayIconStatus::SUCCESS); + } + catch (std::exception &err) + { + pushstring(common::string::ToWide(err.what()).c_str()); + pushint(PromoteTrayIconStatus::GENERAL_ERROR); + } + catch (...) + { + pushstring(L"Unspecified error"); + pushint(PromoteTrayIconStatus::GENERAL_ERROR); + } +} |
