diff options
Diffstat (limited to 'windows/nsis-plugins/src/tray')
| -rw-r--r-- | windows/nsis-plugins/src/tray/dllmain.cpp | 11 | ||||
| -rw-r--r-- | windows/nsis-plugins/src/tray/iconstreams.h | 68 | ||||
| -rw-r--r-- | windows/nsis-plugins/src/tray/mullvad_tray_record.bin | bin | 0 -> 1640 bytes | |||
| -rw-r--r-- | windows/nsis-plugins/src/tray/resource.h | 1 | ||||
| -rw-r--r-- | windows/nsis-plugins/src/tray/stdafx.cpp | 8 | ||||
| -rw-r--r-- | windows/nsis-plugins/src/tray/stdafx.h | 16 | ||||
| -rw-r--r-- | windows/nsis-plugins/src/tray/targetver.h | 12 | ||||
| -rw-r--r-- | windows/nsis-plugins/src/tray/tray.cpp | 188 | ||||
| -rw-r--r-- | windows/nsis-plugins/src/tray/tray.def | 5 | ||||
| -rw-r--r-- | windows/nsis-plugins/src/tray/tray.rc | 3 | ||||
| -rw-r--r-- | windows/nsis-plugins/src/tray/tray.vcxproj | 131 | ||||
| -rw-r--r-- | windows/nsis-plugins/src/tray/tray.vcxproj.filters | 24 | ||||
| -rw-r--r-- | windows/nsis-plugins/src/tray/trayjuggler.cpp | 220 | ||||
| -rw-r--r-- | windows/nsis-plugins/src/tray/trayjuggler.h | 58 | ||||
| -rw-r--r-- | windows/nsis-plugins/src/tray/trayparser.cpp | 73 | ||||
| -rw-r--r-- | windows/nsis-plugins/src/tray/trayparser.h | 22 |
16 files changed, 840 insertions, 0 deletions
diff --git a/windows/nsis-plugins/src/tray/dllmain.cpp b/windows/nsis-plugins/src/tray/dllmain.cpp new file mode 100644 index 0000000000..a5a44613dd --- /dev/null +++ b/windows/nsis-plugins/src/tray/dllmain.cpp @@ -0,0 +1,11 @@ +#include "stdafx.h" +#include <windows.h> + +BOOL APIENTRY DllMain(HMODULE, DWORD reason, LPVOID) +{ + // + // Avoid doing work in DllMain since the loader lock is held + // + + return TRUE; +} diff --git a/windows/nsis-plugins/src/tray/iconstreams.h b/windows/nsis-plugins/src/tray/iconstreams.h new file mode 100644 index 0000000000..b096915a4a --- /dev/null +++ b/windows/nsis-plugins/src/tray/iconstreams.h @@ -0,0 +1,68 @@ +#pragma once + +#include <cstdint> +#include <windows.h> + +#pragma pack(push, 1) +typedef struct ICON_STREAMS_HEADER_ +{ + uint32_t HeaderSize; + uint32_t u1; // 7 + uint16_t u2; // 1 + uint16_t u3; // 1 + uint32_t NumberRecords; + uint32_t OffsetFirstRecord; +} +ICON_STREAMS_HEADER; +#pragma pack(pop) + +enum ICON_STREAMS_VISIBILITY +{ + NOTIFICATIONS_ONLY = 0, + HIDE_ICON_AND_NOTIFICATIONS = 1, + SHOW_ICON_AND_NOTIFICATIONS = 2, +}; + +#pragma pack(push, 1) +typedef struct ICON_STREAMS_RECORD_ +{ + uint16_t ApplicationPath[MAX_PATH]; + uint32_t u1; // id that the owning application uses to identify the icon? + // this value is constant except when used for the networking icon + // where it cycles through a set of ids. + + uint32_t u2; // 0 + uint32_t Visibility; + uint16_t YearCreated; + uint16_t MonthCreated; + uint16_t LastTooltip[MAX_PATH]; + uint32_t u6; // 0 + uint32_t u7; // 0 or 1 but why? + uint32_t ImagelistId; // id of cached icon, or -1 + GUID Guid; // + uint32_t u8; // 0 + uint32_t u9; // 0 + uint32_t u10; // 0 + FILETIME Time1; // discrete event 1 UTC + FILETIME Time2; // discrete event 2 UTC, this can be 0 + uint32_t u11; // 0 + + union + { + struct + { + uint16_t ApplicationName[256 + 1]; + uint8_t Padding[6]; + } details; + struct + { + uint32_t u12; // 200d0000 + uint16_t u13; // b0fe + uint16_t ApplicationName[256 + 1]; + } extended_details; + } DUMMYUNIONNAME; + + uint32_t Ordinal; // determines ordering within group. +} +ICON_STREAMS_RECORD; +#pragma pack(pop) diff --git a/windows/nsis-plugins/src/tray/mullvad_tray_record.bin b/windows/nsis-plugins/src/tray/mullvad_tray_record.bin Binary files differnew file mode 100644 index 0000000000..347af479bc --- /dev/null +++ b/windows/nsis-plugins/src/tray/mullvad_tray_record.bin diff --git a/windows/nsis-plugins/src/tray/resource.h b/windows/nsis-plugins/src/tray/resource.h new file mode 100644 index 0000000000..df7adc2724 --- /dev/null +++ b/windows/nsis-plugins/src/tray/resource.h @@ -0,0 +1 @@ +#define MULLVAD_TRAY_RECORD 9001 diff --git a/windows/nsis-plugins/src/tray/stdafx.cpp b/windows/nsis-plugins/src/tray/stdafx.cpp new file mode 100644 index 0000000000..3b6341d106 --- /dev/null +++ b/windows/nsis-plugins/src/tray/stdafx.cpp @@ -0,0 +1,8 @@ +// stdafx.cpp : source file that includes just the standard includes +// driverlogic.pch will be the pre-compiled header +// stdafx.obj will contain the pre-compiled type information + +#include "stdafx.h" + +// TODO: reference any additional headers you need in STDAFX.H +// and not in this file diff --git a/windows/nsis-plugins/src/tray/stdafx.h b/windows/nsis-plugins/src/tray/stdafx.h new file mode 100644 index 0000000000..f3a07375c7 --- /dev/null +++ b/windows/nsis-plugins/src/tray/stdafx.h @@ -0,0 +1,16 @@ +// stdafx.h : include file for standard system include files, +// or project specific include files that are used frequently, but +// are changed infrequently +// + +#pragma once + +#include "targetver.h" + +#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers +// Windows Header Files: +#include <windows.h> + + + +// TODO: reference additional headers your program requires here diff --git a/windows/nsis-plugins/src/tray/targetver.h b/windows/nsis-plugins/src/tray/targetver.h new file mode 100644 index 0000000000..ae4a5c032c --- /dev/null +++ b/windows/nsis-plugins/src/tray/targetver.h @@ -0,0 +1,12 @@ +#pragma once + +// Including SDKDDKVer.h defines the highest available Windows platform. + +// If you wish to build your application for a previous Windows platform, include WinSDKVer.h and +// set the _WIN32_WINNT macro to the platform you wish to support before including SDKDDKVer.h. + +#include <WinSDKVer.h> + +#define _WIN32_WINNT _WIN32_WINNT_WIN7 + +#include <SDKDDKVer.h> 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); + } +} diff --git a/windows/nsis-plugins/src/tray/tray.def b/windows/nsis-plugins/src/tray/tray.def new file mode 100644 index 0000000000..c32b9d9698 --- /dev/null +++ b/windows/nsis-plugins/src/tray/tray.def @@ -0,0 +1,5 @@ +LIBRARY tray + +EXPORTS + +PromoteTrayIcon diff --git a/windows/nsis-plugins/src/tray/tray.rc b/windows/nsis-plugins/src/tray/tray.rc new file mode 100644 index 0000000000..761bd7e71f --- /dev/null +++ b/windows/nsis-plugins/src/tray/tray.rc @@ -0,0 +1,3 @@ +#include "resource.h" + +MULLVAD_TRAY_RECORD RCDATA "mullvad_tray_record.bin" diff --git a/windows/nsis-plugins/src/tray/tray.vcxproj b/windows/nsis-plugins/src/tray/tray.vcxproj new file mode 100644 index 0000000000..a8db9a7b63 --- /dev/null +++ b/windows/nsis-plugins/src/tray/tray.vcxproj @@ -0,0 +1,131 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <ItemGroup Label="ProjectConfigurations"> + <ProjectConfiguration Include="Debug|Win32"> + <Configuration>Debug</Configuration> + <Platform>Win32</Platform> + </ProjectConfiguration> + <ProjectConfiguration Include="Release|Win32"> + <Configuration>Release</Configuration> + <Platform>Win32</Platform> + </ProjectConfiguration> + </ItemGroup> + <PropertyGroup Label="Globals"> + <VCProjectVersion>15.0</VCProjectVersion> + <ProjectGuid>{E3E05654-D5D6-4D39-A9B4-BB14B012C5FF}</ProjectGuid> + <Keyword>Win32Proj</Keyword> + <RootNamespace>tray</RootNamespace> + <WindowsTargetPlatformVersion>10.0.16299.0</WindowsTargetPlatformVersion> + </PropertyGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" /> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration"> + <ConfigurationType>DynamicLibrary</ConfigurationType> + <UseDebugLibraries>true</UseDebugLibraries> + <PlatformToolset>v141</PlatformToolset> + <CharacterSet>Unicode</CharacterSet> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration"> + <ConfigurationType>DynamicLibrary</ConfigurationType> + <UseDebugLibraries>false</UseDebugLibraries> + <PlatformToolset>v141</PlatformToolset> + <WholeProgramOptimization>true</WholeProgramOptimization> + <CharacterSet>Unicode</CharacterSet> + </PropertyGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" /> + <ImportGroup Label="ExtensionSettings"> + </ImportGroup> + <ImportGroup Label="Shared"> + </ImportGroup> + <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> + <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> + </ImportGroup> + <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> + <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> + </ImportGroup> + <PropertyGroup Label="UserMacros" /> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> + <LinkIncremental>true</LinkIncremental> + <OutDir>$(SolutionDir)bin\$(Platform)-$(Configuration)\</OutDir> + <IntDir>$(SolutionDir)bin\temp\$(Platform)-$(Configuration)\$(ProjectName)\</IntDir> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> + <LinkIncremental>false</LinkIncremental> + <OutDir>$(SolutionDir)bin\$(Platform)-$(Configuration)\</OutDir> + <IntDir>$(SolutionDir)bin\temp\$(Platform)-$(Configuration)\$(ProjectName)\</IntDir> + </PropertyGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> + <ClCompile> + <PrecompiledHeader>Use</PrecompiledHeader> + <WarningLevel>Level3</WarningLevel> + <Optimization>Disabled</Optimization> + <SDLCheck>true</SDLCheck> + <PreprocessorDefinitions>WIN32;_DEBUG;TRAY_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <ConformanceMode>true</ConformanceMode> + <AdditionalIncludeDirectories>$(ProjectDir)../../../../dist-assets/binaries/windows/;$(ProjectDir)../../../windows-libraries/src/;$(ProjectDir)../</AdditionalIncludeDirectories> + <LanguageStandard>stdcpplatest</LanguageStandard> + <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary> + </ClCompile> + <Link> + <SubSystem>Windows</SubSystem> + <GenerateDebugInformation>true</GenerateDebugInformation> + <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers> + <AdditionalLibraryDirectories>$(ProjectDir)../../../../dist-assets/binaries/windows/nsis/;$(SolutionDir)bin\$(Platform)-$(Configuration)\</AdditionalLibraryDirectories> + <AdditionalDependencies>log.lib;libcommon.lib;pluginapi-x86-unicode.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies)</AdditionalDependencies> + <IgnoreSpecificDefaultLibraries>libc.lib</IgnoreSpecificDefaultLibraries> + <ModuleDefinitionFile>tray.def</ModuleDefinitionFile> + </Link> + </ItemDefinitionGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> + <ClCompile> + <PrecompiledHeader>Use</PrecompiledHeader> + <WarningLevel>Level3</WarningLevel> + <Optimization>MaxSpeed</Optimization> + <FunctionLevelLinking>true</FunctionLevelLinking> + <IntrinsicFunctions>true</IntrinsicFunctions> + <SDLCheck>true</SDLCheck> + <PreprocessorDefinitions>WIN32;NDEBUG;TRAY_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <ConformanceMode>true</ConformanceMode> + <AdditionalIncludeDirectories>$(ProjectDir)../../../../dist-assets/binaries/windows/;$(ProjectDir)../../../windows-libraries/src/;$(ProjectDir)../</AdditionalIncludeDirectories> + <RuntimeLibrary>MultiThreaded</RuntimeLibrary> + <LanguageStandard>stdcpplatest</LanguageStandard> + </ClCompile> + <Link> + <SubSystem>Windows</SubSystem> + <EnableCOMDATFolding>true</EnableCOMDATFolding> + <OptimizeReferences>true</OptimizeReferences> + <GenerateDebugInformation>true</GenerateDebugInformation> + <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers> + <AdditionalLibraryDirectories>$(ProjectDir)../../../../dist-assets/binaries/windows/nsis/;$(SolutionDir)bin\$(Platform)-$(Configuration)\</AdditionalLibraryDirectories> + <AdditionalDependencies>log.lib;libcommon.lib;pluginapi-x86-unicode.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies)</AdditionalDependencies> + <IgnoreSpecificDefaultLibraries>libc.lib</IgnoreSpecificDefaultLibraries> + <ModuleDefinitionFile>tray.def</ModuleDefinitionFile> + </Link> + </ItemDefinitionGroup> + <ItemGroup> + <ClInclude Include="iconstreams.h" /> + <ClInclude Include="resource.h" /> + <ClInclude Include="stdafx.h" /> + <ClInclude Include="targetver.h" /> + <ClInclude Include="trayjuggler.h" /> + <ClInclude Include="trayparser.h" /> + </ItemGroup> + <ItemGroup> + <ClCompile Include="dllmain.cpp" /> + <ClCompile Include="tray.cpp" /> + <ClCompile Include="stdafx.cpp"> + <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Create</PrecompiledHeader> + <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">Create</PrecompiledHeader> + </ClCompile> + <ClCompile Include="trayjuggler.cpp" /> + <ClCompile Include="trayparser.cpp" /> + </ItemGroup> + <ItemGroup> + <None Include="tray.def" /> + </ItemGroup> + <ItemGroup> + <ResourceCompile Include="tray.rc" /> + </ItemGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> + <ImportGroup Label="ExtensionTargets"> + </ImportGroup> +</Project>
\ No newline at end of file diff --git a/windows/nsis-plugins/src/tray/tray.vcxproj.filters b/windows/nsis-plugins/src/tray/tray.vcxproj.filters new file mode 100644 index 0000000000..5f89a1538e --- /dev/null +++ b/windows/nsis-plugins/src/tray/tray.vcxproj.filters @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <ItemGroup> + <ClInclude Include="stdafx.h" /> + <ClInclude Include="targetver.h" /> + <ClInclude Include="resource.h" /> + <ClInclude Include="trayjuggler.h" /> + <ClInclude Include="trayparser.h" /> + <ClInclude Include="iconstreams.h" /> + </ItemGroup> + <ItemGroup> + <ClCompile Include="dllmain.cpp" /> + <ClCompile Include="stdafx.cpp" /> + <ClCompile Include="tray.cpp" /> + <ClCompile Include="trayjuggler.cpp" /> + <ClCompile Include="trayparser.cpp" /> + </ItemGroup> + <ItemGroup> + <None Include="tray.def" /> + </ItemGroup> + <ItemGroup> + <ResourceCompile Include="tray.rc" /> + </ItemGroup> +</Project>
\ No newline at end of file diff --git a/windows/nsis-plugins/src/tray/trayjuggler.cpp b/windows/nsis-plugins/src/tray/trayjuggler.cpp new file mode 100644 index 0000000000..21c65ee3d2 --- /dev/null +++ b/windows/nsis-plugins/src/tray/trayjuggler.cpp @@ -0,0 +1,220 @@ +#include "stdafx.h" +#include "trayjuggler.h" +#include <algorithm> + +namespace +{ + +template<typename T> +void AppendBuffer(std::vector<uint8_t> &buffer, const T &t) +{ + const auto prevSize = buffer.size(); + + buffer.resize(prevSize + sizeof(T)); + + memcpy(&buffer[prevSize], &t, sizeof(T)); +} + +inline wchar_t rot13(wchar_t c) +{ + if (c >= L'A' && c <= L'Z') + { + auto temp = (wchar_t)(c + 13); + return (temp <= L'Z' ? temp : (wchar_t)(L'A' + (temp - L'Z' - 1))); + } + else if (c >= L'a' && c <= L'z') + { + auto temp = (wchar_t)(c + 13); + return (temp <= L'z' ? temp : (wchar_t)(L'a' + (temp - L'z' - 1))); + } + + return c; +} + +} // anonymous namespace + +TrayJuggler::TrayJuggler(const TrayParser &parser) +{ + m_header = parser.getHeader(); + + for (const auto &record : parser.getRecords()) + { + m_records.push_back(std::make_shared<ICON_STREAMS_RECORD>(record)); + } +} + +std::shared_ptr<ICON_STREAMS_RECORD> TrayJuggler::findRecord(const std::wstring &path) const +{ + for (const auto &record : m_records) + { + const auto appPath = DecodeString(record->ApplicationPath, sizeof(record->ApplicationPath)); + + if (nullptr != wcsstr(appPath.c_str(), path.c_str())) + { + return record; + } + } + + return std::shared_ptr<ICON_STREAMS_RECORD>(); +} + +uint32_t TrayJuggler::getNextFreeOrdinal(TraySearchGroup searchGroup) const +{ + uint32_t highestOrdinal = 0; + + bool noMatchingRecord = true; + + enumerateRecords([&](std::shared_ptr<ICON_STREAMS_RECORD> record) + { + if ((TraySearchGroup::Visible == searchGroup && ICON_STREAMS_VISIBILITY::SHOW_ICON_AND_NOTIFICATIONS == record->Visibility) + || (TraySearchGroup::Hidden == searchGroup) && ICON_STREAMS_VISIBILITY::SHOW_ICON_AND_NOTIFICATIONS != record->Visibility) + { + noMatchingRecord = false; + + if (record->Ordinal > highestOrdinal) + { + highestOrdinal = record->Ordinal; + } + } + + return true; + }); + + return (noMatchingRecord ? 0 : highestOrdinal + 1); +} + + +void TrayJuggler::injectRecord(const ICON_STREAMS_RECORD &record) +{ + SYSTEMTIME time; + + GetSystemTime(&time); + + auto newRecord = std::make_shared<ICON_STREAMS_RECORD>(record); + + { + newRecord->Visibility = ICON_STREAMS_VISIBILITY::SHOW_ICON_AND_NOTIFICATIONS; + + newRecord->YearCreated = time.wYear; + newRecord->MonthCreated = time.wMonth; + + // TODO: Meaning of this bool? + newRecord->u7 = 0; + + newRecord->ImagelistId = 0xFFFFFFFF; + + FILETIME fileTime; + + SystemTimeToFileTime(&time, &fileTime); + + newRecord->Time1 = fileTime; + + newRecord->Time2.dwHighDateTime = 0; + newRecord->Time2.dwLowDateTime = 0; + + newRecord->Ordinal = getNextFreeOrdinal(TraySearchGroup::Visible); + } + + // + // By default, the first few records will always be Microsoft icons such as + // battery, networking, sounds. + // + // These have a visibility of SHOW_ICON_AND_NOTIFICATIONS. + // + // There can be other icons with visibility SHOW_ICON_AND_NOTIFICATIONS towards + // the end of the array. But these icons typically don't have a valid ImagelistId. + // + // In the end, the ordering of records doesn't seem to matter. + // + // Insert new record at the end. + // + + m_records.push_back(newRecord); +} + +void TrayJuggler::promoteRecord(std::shared_ptr<ICON_STREAMS_RECORD> record) +{ + if (ICON_STREAMS_VISIBILITY::SHOW_ICON_AND_NOTIFICATIONS == record->Visibility) + { + // Abort if the icon is already visible. + return; + } + + record->Visibility = ICON_STREAMS_VISIBILITY::SHOW_ICON_AND_NOTIFICATIONS; + record->Ordinal = getNextFreeOrdinal(TraySearchGroup::Visible); + + SYSTEMTIME time; + + GetSystemTime(&time); + + FILETIME fileTime; + + SystemTimeToFileTime(&time, &fileTime); + + record->Time1 = fileTime; +} + +bool TrayJuggler::enumerateRecords(std::function<bool(std::shared_ptr<ICON_STREAMS_RECORD> record)> callback) const +{ + for (auto record : m_records) + { + if (false == callback(record)) + { + return false; + } + } + + return true; +} + +std::vector<uint8_t> TrayJuggler::pack() const +{ + std::vector<uint8_t> blob; + + const auto requiredSize = (m_records.size() * sizeof(ICON_STREAMS_RECORD)) + + sizeof(ICON_STREAMS_HEADER); + + blob.reserve(requiredSize); + + // Use original header as a template. + ICON_STREAMS_HEADER header = m_header; + + header.HeaderSize = sizeof(header); + header.NumberRecords = static_cast<uint32_t>(m_records.size()); + header.OffsetFirstRecord = sizeof(header); + + AppendBuffer(blob, header); + + for (const auto record : m_records) + { + AppendBuffer(blob, *record); + } + + return blob; +} + +//static +std::wstring TrayJuggler::DecodeString(const uint16_t *encoded, size_t bufferSize) +{ + const auto numCharacters = bufferSize / sizeof(uint16_t); + + std::wstring decoded; + + decoded.reserve(numCharacters); + + const auto end = encoded + numCharacters; + + for (; encoded != end; ++encoded) + { + auto source = (wchar_t)*encoded; + + if (L'\0' == source) + { + break; + } + + decoded.push_back(rot13(source)); + } + + return decoded; +} diff --git a/windows/nsis-plugins/src/tray/trayjuggler.h b/windows/nsis-plugins/src/tray/trayjuggler.h new file mode 100644 index 0000000000..9fcfe35f67 --- /dev/null +++ b/windows/nsis-plugins/src/tray/trayjuggler.h @@ -0,0 +1,58 @@ +#pragma once + +#include "iconstreams.h" +#include "trayparser.h" +#include <cstdint> +#include <vector> +#include <memory> +#include <string> +#include <functional> +#include <list> + +class TrayJuggler +{ +public: + + TrayJuggler(const TrayParser &parser); + + // Find record based on substring present in record's application path. + std::shared_ptr<ICON_STREAMS_RECORD> findRecord(const std::wstring &path) const; + + enum class TraySearchGroup + { + Visible, + Hidden + }; + + uint32_t getNextFreeOrdinal(TraySearchGroup searchGroup) const; + + // + // Fix up and inject record. + // The icon will be displayed in the rightmost position. + // + void injectRecord(const ICON_STREAMS_RECORD &record); + + // + // Update and promote existing record. + // The icon will be displayed in the rightmost position. + // + void promoteRecord(std::shared_ptr<ICON_STREAMS_RECORD> record); + + bool enumerateRecords(std::function<bool(std::shared_ptr<ICON_STREAMS_RECORD> record)> callback) const; + + // Generate a valid stream including header. + std::vector<uint8_t> pack() const; + + static std::wstring DecodeString(const uint16_t *encoded, size_t bufferSize); + +private: + + // + // This is the original header. + // We keep it around to be able to preserve the values for + // the unknown fields. + // + ICON_STREAMS_HEADER m_header; + + std::list<std::shared_ptr<ICON_STREAMS_RECORD> > m_records; +}; diff --git a/windows/nsis-plugins/src/tray/trayparser.cpp b/windows/nsis-plugins/src/tray/trayparser.cpp new file mode 100644 index 0000000000..4ff3573c81 --- /dev/null +++ b/windows/nsis-plugins/src/tray/trayparser.cpp @@ -0,0 +1,73 @@ +#include "stdafx.h" +#include "trayparser.h" +#include <stdexcept> +#include <algorithm> + +TrayParser::TrayParser(const std::vector<uint8_t> &blob) +{ + if (blob.size() < sizeof(ICON_STREAMS_HEADER)) + { + throw std::runtime_error("Invalid icon streams header - truncated"); + } + + auto header = reinterpret_cast<const ICON_STREAMS_HEADER *>(&blob[0]); + + if (header->HeaderSize != sizeof(ICON_STREAMS_HEADER)) + { + throw std::runtime_error("Invalid icon streams header - size mismatch"); + } + + memcpy(&m_header, header, sizeof(ICON_STREAMS_HEADER)); + + if (0 == header->NumberRecords) + { + return; + } + + // + // At least one record. + // + + if (blob.size() < sizeof(ICON_STREAMS_HEADER) + sizeof(ICON_STREAMS_RECORD)) + { + throw std::runtime_error("Invalid icon streams - truncated"); + } + + const auto lastValidRecordOffset = blob.size() - sizeof(ICON_STREAMS_RECORD); + + if (header->OffsetFirstRecord < header->HeaderSize + || header->OffsetFirstRecord > lastValidRecordOffset) + { + throw std::runtime_error("Invalid icon streams header - record offset"); + } + + const auto estimatedSize = header->HeaderSize + + (header->OffsetFirstRecord - header->HeaderSize) + + (header->NumberRecords * sizeof(ICON_STREAMS_RECORD)); + + if (blob.size() != estimatedSize) + { + throw std::runtime_error("Invalid icon streams - size mismatch"); + } + + // + // Size checks out. + // + + m_records.reserve(header->NumberRecords); + + auto begin = reinterpret_cast<const ICON_STREAMS_RECORD *>(&blob[0] + header->OffsetFirstRecord); + auto end = reinterpret_cast<const ICON_STREAMS_RECORD *>(&blob[0] + blob.size()); + + std::copy(begin, end, std::back_inserter(m_records)); +} + +const ICON_STREAMS_HEADER &TrayParser::getHeader() const +{ + return m_header; +} + +const std::vector<ICON_STREAMS_RECORD> &TrayParser::getRecords() const +{ + return m_records; +} diff --git a/windows/nsis-plugins/src/tray/trayparser.h b/windows/nsis-plugins/src/tray/trayparser.h new file mode 100644 index 0000000000..6aa3186713 --- /dev/null +++ b/windows/nsis-plugins/src/tray/trayparser.h @@ -0,0 +1,22 @@ +#pragma once + +#include "iconstreams.h" +#include <cstdint> +#include <vector> +#include <memory> + +class TrayParser +{ +public: + + TrayParser(const std::vector<uint8_t> &blob); + + const ICON_STREAMS_HEADER &getHeader() const; + + const std::vector<ICON_STREAMS_RECORD> &getRecords() const; + +private: + + ICON_STREAMS_HEADER m_header; + std::vector<ICON_STREAMS_RECORD> m_records; +}; |
