diff --git a/.github/workflows/Build.yml b/.github/workflows/Build.yml index 061cbf497..8b5e32ecd 100644 --- a/.github/workflows/Build.yml +++ b/.github/workflows/Build.yml @@ -11,7 +11,7 @@ jobs: configuration: [Debug, Release, Production] env: Solution_Name: src\Notepads.sln - Project_Path: src\Notepads\Notepads.csproj + Project_Path: src\Notepads.Package\Notepads.Package.wapproj runs-on: windows-latest steps: @@ -24,16 +24,27 @@ jobs: - name: Setup MSBuild uses: microsoft/setup-msbuild@v1 + # Add NuGet to the PATH: https://github.com/NuGet/setup-nuget + - name: Setup NuGet + uses: NuGet/setup-nuget@v1.0.5 + - name: Restore the application - run: msbuild $env:Solution_Name /t:Restore /p:Configuration=$env:Configuration - env: - Configuration: ${{ matrix.configuration }} + shell: 'pwsh' + run: | + msbuild $env:Solution_Name /t:Restore + nuget restore $env:Solution_Name - name: Build + shell: 'pwsh' run: | - msbuild $env:Project_Path /p:Configuration=$env:Configuration /p:UapAppxPackageBuildMode=$env:Appx_Package_Build_Mode /p:AppxBundle=$env:Appx_Bundle /p:AppxPackageSigningEnabled=false /p:AppxBundlePlatforms="$env:Appx_Bundle_Platforms" + msbuild $env:Project_Path ` + /p:Configuration=$env:Configuration ` + /p:UapAppxPackageBuildMode=$env:Appx_Package_Build_Mode ` + /p:AppxBundle=$env:Appx_Bundle ` + /p:AppxPackageSigningEnabled=false ` + /p:AppxBundlePlatforms="$env:Appx_Bundle_Platforms" env: Appx_Bundle: Always Appx_Bundle_Platforms: x86|x64 Appx_Package_Build_Mode: StoreUpload - Configuration: ${{ matrix.configuration }} + Configuration: ${{ matrix.configuration }} \ No newline at end of file diff --git a/.gitignore b/.gitignore index d0ac7f165..219b0c34a 100644 --- a/.gitignore +++ b/.gitignore @@ -353,4 +353,4 @@ healthchecksdb .Spotlight-V100 .Trashes ehthumbs.db -Thumbs.db +Thumbs.db \ No newline at end of file diff --git a/README.md b/README.md index cc7868ca7..d1fd1e22c 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,6 @@ So here comes the “Notepads” 🎉 (s stands for Sets). ## Platform limitations (UWP): -* You won't be able to save files to system folders due to UWP restriction (windows, system32, etc.). * You cannot associate potentially harmful file types (.cmd, .bat etc.) with Notepads. * Notepads does not work well with large files; the file size limit is set to 1MB for now. I will add large file support later. diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 1667a62cf..7398a36f5 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -22,10 +22,23 @@ steps: - task: VSBuild@1 inputs: platform: 'x64' - solution: '$(solution)' + solution: '**/*.wapproj' configuration: '$(buildConfiguration)' - msbuildArgs: '/p:AppxBundlePlatforms="$(buildPlatform)" + msbuildArgs: '/t:build;_GenerateAppxPackage + /p:AppxBundlePlatforms="$(buildPlatform)" /p:AppxPackageDir="$(appxPackageDir)" /p:AppxBundle=Always /p:UapAppxPackageBuildMode=StoreUpload /p:AppxPackageSigningEnabled=false' + +- task: CopyFiles@2 + displayName: 'Copy Files to: $(build.artifactstagingdirectory)' + inputs: + SourceFolder: '$(system.defaultworkingdirectory)' + Contents: '**\bin\$(BuildConfiguration)\**' + TargetFolder: '$(build.artifactstagingdirectory)' + +- task: PublishBuildArtifacts@1 + displayName: 'Publish Artifact: drop' + inputs: + PathtoPublish: '$(build.artifactstagingdirectory)' diff --git a/src/Notepads.Controls/Notepads.Controls.csproj b/src/Notepads.Controls/Notepads.Controls.csproj index 29c6bc709..9865d3ac6 100644 --- a/src/Notepads.Controls/Notepads.Controls.csproj +++ b/src/Notepads.Controls/Notepads.Controls.csproj @@ -3,7 +3,7 @@ Debug - AnyCPU + x86 {7AA5E631-B663-420E-A08F-002CD81DF855} Library Properties @@ -17,34 +17,6 @@ 512 {A5A43C5B-DE2A-4C0C-9213-0A381AF9435A};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - - AnyCPU - true - full - false - bin\Debug\ - DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP - prompt - 4 - - - AnyCPU - pdbonly - true - bin\Release\ - TRACE;NETFX_CORE;WINDOWS_UWP - prompt - 4 - - - AnyCPU - pdbonly - true - bin\Production\ - TRACE;NETFX_CORE;WINDOWS_UWP - prompt - 4 - x86 true diff --git a/src/Notepads.Core/CoreKey.cpp b/src/Notepads.Core/CoreKey.cpp new file mode 100644 index 000000000..e5da6b707 --- /dev/null +++ b/src/Notepads.Core/CoreKey.cpp @@ -0,0 +1,76 @@ +#include "pch.h" +#include "CoreKey.h" +#include "CoreKey.g.cpp" + +namespace winrt::Notepads::Core::implementation +{ + hstring CoreKey::PackageSidStr() + { + return PACKAGE_SID_STR; + } + + hstring CoreKey::AppCenterInstallIdStr() + { + return APP_CENTER_INSTALL_ID_STR; + } + + hstring CoreKey::LastChangedSettingsKeyStr() + { + return LAST_CHANGED_SETTINGS_KEY_STR; + } + + hstring CoreKey::LastChangedSettingsAppInstanceIdStr() + { + return LAST_CHANGED_SETTINGS_APP_INSTANCE_ID_STR; + } + + hstring CoreKey::LaunchElevatedProcessSuccessStr() + { + return LAUNCH_ELEVATED_PROCESS_SUCCESS_STR; + } + + hstring CoreKey::LaunchElevatedProcessFailedStr() + { + return LAUNCH_ELEVATED_PROCESS_FAILED_STR; + } + + hstring CoreKey::ExtensionProcessLifetimeObjNameStr() + { + return EXTENSION_PROCESS_LIFETIME_OBJ_NAME_STR; + } + + hstring CoreKey::ElevatedProcessLifetimeObjNameStr() + { + return ELEVATED_PROCESS_LIFETIME_OBJ_NAME_STR; + } + + hstring CoreKey::ExtensionUnblockEventNameStr() + { + return EXTENSION_UNBLOCK_EVENT_NAME_STR; + } + + hstring CoreKey::ElevatedWriteEventNameStr() + { + return ELEVATED_WRITE_EVENT_NAME_STR; + } + + hstring CoreKey::ElevatedRenameEventNameStr() + { + return ELEVATED_RENAME_EVENT_NAME_STR; + } + + hstring CoreKey::ExtensionUnblockPipeConnectionNameStr() + { + return EXTENSION_UNBLOCK_PIPE_CONNECTION_NAME_STR; + } + + hstring CoreKey::ElevatedWritePipeConnectionNameStr() + { + return ELEVATED_WRITE_PIPE_CONNECTION_NAME_STR; + } + + hstring CoreKey::ElevatedRenamePipeConnectionNameStr() + { + return ELEVATED_RENAME_PIPE_CONNECTION_NAME_STR; + } +} \ No newline at end of file diff --git a/src/Notepads.Core/CoreKey.h b/src/Notepads.Core/CoreKey.h new file mode 100644 index 000000000..ad05ed893 --- /dev/null +++ b/src/Notepads.Core/CoreKey.h @@ -0,0 +1,30 @@ +#pragma once +#include "CoreKey.g.h" + +namespace winrt::Notepads::Core::implementation +{ + struct CoreKey : CoreKeyT + { + CoreKey() = default; + + static hstring PackageSidStr(); + static hstring AppCenterInstallIdStr(); + static hstring LastChangedSettingsKeyStr(); + static hstring LastChangedSettingsAppInstanceIdStr(); + static hstring LaunchElevatedProcessSuccessStr(); + static hstring LaunchElevatedProcessFailedStr(); + static hstring ExtensionProcessLifetimeObjNameStr(); + static hstring ElevatedProcessLifetimeObjNameStr(); + static hstring ExtensionUnblockEventNameStr(); + static hstring ElevatedWriteEventNameStr(); + static hstring ElevatedRenameEventNameStr(); + static hstring ExtensionUnblockPipeConnectionNameStr(); + static hstring ElevatedWritePipeConnectionNameStr(); + static hstring ElevatedRenamePipeConnectionNameStr(); + }; +} + +namespace winrt::Notepads::Core::factory_implementation +{ + struct CoreKey : CoreKeyT { }; +} \ No newline at end of file diff --git a/src/Notepads.Core/CoreKey.idl b/src/Notepads.Core/CoreKey.idl new file mode 100644 index 000000000..02ef83077 --- /dev/null +++ b/src/Notepads.Core/CoreKey.idl @@ -0,0 +1,21 @@ +namespace Notepads.Core +{ + [default_interface] + static runtimeclass CoreKey + { + static String PackageSidStr { get; }; + static String AppCenterInstallIdStr { get; }; + static String LastChangedSettingsKeyStr { get; }; + static String LastChangedSettingsAppInstanceIdStr { get; }; + static String LaunchElevatedProcessSuccessStr { get; }; + static String LaunchElevatedProcessFailedStr { get; }; + static String ExtensionProcessLifetimeObjNameStr { get; }; + static String ElevatedProcessLifetimeObjNameStr { get; }; + static String ExtensionUnblockEventNameStr { get; }; + static String ElevatedWriteEventNameStr { get; }; + static String ElevatedRenameEventNameStr { get; }; + static String ExtensionUnblockPipeConnectionNameStr { get; }; + static String ElevatedWritePipeConnectionNameStr { get; }; + static String ElevatedRenamePipeConnectionNameStr { get; }; + } +} \ No newline at end of file diff --git a/src/Notepads.Core/Notepads.Core.def b/src/Notepads.Core/Notepads.Core.def new file mode 100644 index 000000000..b95ee2cd0 --- /dev/null +++ b/src/Notepads.Core/Notepads.Core.def @@ -0,0 +1,3 @@ +EXPORTS +DllCanUnloadNow = WINRT_CanUnloadNow PRIVATE +DllGetActivationFactory = WINRT_GetActivationFactory PRIVATE \ No newline at end of file diff --git a/src/Notepads.Core/Notepads.Core.vcxproj b/src/Notepads.Core/Notepads.Core.vcxproj new file mode 100644 index 000000000..9fe25b5ef --- /dev/null +++ b/src/Notepads.Core/Notepads.Core.vcxproj @@ -0,0 +1,165 @@ + + + + + 14.0 + {022dc661-4fa2-4264-a869-5a560fb4cc92} + Notepads.Core + Notepads.Core + obj\ + $(BaseIntermediateOutputPath)$(Platform)\$(Configuration)\ + $(ProjectDir)bin\$(Platform)\$(Configuration)\ + true + true + true + true + $(BaseIntermediateOutputPath)Generated Files\ + true + Windows Store + 10.0 + 10.0.19041.0 + 10.0.17134.0 + + + + + Debug + ARM + + + Debug + ARM64 + + + Debug + Win32 + + + Debug + x64 + + + Release + ARM + + + Release + ARM64 + + + Release + Win32 + + + Release + x64 + + + Production + ARM + + + Production + ARM64 + + + Production + Win32 + + + Production + x64 + + + + DynamicLibrary + v140 + v141 + v142 + Unicode + false + + + true + true + + + false + true + false + + + + + + + + + + + + Use + pch.h + $(IntDir)pch.pch + Level4 + %(AdditionalOptions) /bigobj + + /DWINRT_NO_MAKE_DETECTION %(AdditionalOptions) + + + _WINRT_DLL;WIN32_LEAN_AND_MEAN;WINRT_LEAN_AND_MEAN;%(PreprocessorDefinitions) + $(WindowsSDK_WindowsMetadata);$(AdditionalUsingDirectories) + + + Console + false + Notepads.Core.def + + + + + _DEBUG;%(PreprocessorDefinitions) + + + + + NDEBUG;%(PreprocessorDefinitions) + + + true + true + + + + + + CoreKey.idl + + + + + CoreKey.idl + + + Create + + + + + + + + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + \ No newline at end of file diff --git a/src/Notepads.Core/packages.config b/src/Notepads.Core/packages.config new file mode 100644 index 000000000..c7a3eef2f --- /dev/null +++ b/src/Notepads.Core/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/src/Notepads.Core/pch.h b/src/Notepads.Core/pch.h new file mode 100644 index 000000000..76e833a70 --- /dev/null +++ b/src/Notepads.Core/pch.h @@ -0,0 +1,17 @@ +#pragma once +#include "winrt/base.h" + +#define PACKAGE_SID_STR L"PackageSidStr" +#define APP_CENTER_INSTALL_ID_STR L"AppCenterInstallIdStr" +#define LAST_CHANGED_SETTINGS_KEY_STR L"LastChangedSettingsKeyStr" +#define LAST_CHANGED_SETTINGS_APP_INSTANCE_ID_STR L"LastChangedSettingsAppInstanceIdStr" +#define LAUNCH_ELEVATED_PROCESS_SUCCESS_STR L"LaunchElevatedProcessSuccess" +#define LAUNCH_ELEVATED_PROCESS_FAILED_STR L"LaunchElevatedProcessFailed" +#define EXTENSION_PROCESS_LIFETIME_OBJ_NAME_STR L"ExtensionProcessLifetimeObj" +#define ELEVATED_PROCESS_LIFETIME_OBJ_NAME_STR L"ElevatedProcessLifetimeObj" +#define EXTENSION_UNBLOCK_EVENT_NAME_STR L"NotepadsExtensionUnblockEvent" +#define ELEVATED_WRITE_EVENT_NAME_STR L"NotepadsElevatedWriteEvent" +#define ELEVATED_RENAME_EVENT_NAME_STR L"NotepadsElevatedRenameEvent" +#define EXTENSION_UNBLOCK_PIPE_CONNECTION_NAME_STR L"NotepadsExtensionUnblockPipe" +#define ELEVATED_WRITE_PIPE_CONNECTION_NAME_STR L"NotepadsElevatedWritePipe" +#define ELEVATED_RENAME_PIPE_CONNECTION_NAME_STR L"NotepadsElevatedRenamePipe" \ No newline at end of file diff --git a/src/Notepads.DesktopExtension/AssemblyInfo.rc b/src/Notepads.DesktopExtension/AssemblyInfo.rc new file mode 100644 index 000000000..3753c120b --- /dev/null +++ b/src/Notepads.DesktopExtension/AssemblyInfo.rc @@ -0,0 +1,72 @@ +// Microsoft Visual C++ generated resource script. +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 101 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 103 +#endif +#endif + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE resource. +// +#include "winres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// Neutral resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_NEU) +LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL +#pragma code_page(1252) + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION 1,0,0,0 + PRODUCTVERSION 1,0,0,0 + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x40004L + FILETYPE 0x1L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "000004b0" + BEGIN +#ifdef _PRODUCTION + VALUE "FileDescription", "Notepads" + VALUE "ProductName", "Notepads" +#else + VALUE "FileDescription", "Notepads-Dev" + VALUE "ProductName", "Notepads-Dev" +#endif + VALUE "FileVersion", "1.0.0.0" + VALUE "InternalName", "Notepads.DesktopExtension" + VALUE "LegalCopyright", "Copyright 2020 Jackie Liu" + VALUE "OriginalFilename", "Notepads.DesktopExtension" + VALUE "ProductVersion", "1.0.0.0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x0, 1200 + END +END + +#endif // Neutral resources +///////////////////////////////////////////////////////////////////////////// \ No newline at end of file diff --git a/src/Notepads.DesktopExtension/Notepads.DesktopExtension.vcxproj b/src/Notepads.DesktopExtension/Notepads.DesktopExtension.vcxproj new file mode 100644 index 000000000..e777aca1c --- /dev/null +++ b/src/Notepads.DesktopExtension/Notepads.DesktopExtension.vcxproj @@ -0,0 +1,234 @@ + + + + + 15.0 + {0a3b698e-6add-4c9e-9741-08f8ae576b2e} + Win32Proj + Notepads.DesktopExtension + obj\ + $(BaseIntermediateOutputPath)$(Platform)\$(Configuration)\ + $(ProjectDir)bin\$(Platform)\$(Configuration)\ + Notepads32 + true + true + true + true + $(BaseIntermediateOutputPath)Generated Files\ + 10.0.19041.0 + 10.0.17763.0 + true + false + false + Windows Store + 10.0 + + + + + Debug + ARM + + + Release + ARM + + + Production + ARM + + + Debug + ARM64 + + + Release + ARM64 + + + Production + ARM64 + + + Debug + Win32 + + + Release + Win32 + + + Production + Win32 + + + Debug + x64 + + + Release + x64 + + + Production + x64 + + + + + _PRODUCTION;%(PreprocessorDefinitions) + + + _UNICODE;UNICODE;_PRODUCTION;%(PreprocessorDefinitions) + + + + Application + v140 + v141 + v142 + Unicode + + + true + true + + + false + true + false + + + + + + + + + + + + + Use + pch.h + $(IntDir)pch.pch + _CONSOLE;WIN32_LEAN_AND_MEAN;WINRT_LEAN_AND_MEAN;%(PreprocessorDefinitions) + Level4 + %(AdditionalOptions) /permissive- /bigobj + stdcpplatest + + + + + Disabled + _DEBUG;%(PreprocessorDefinitions) + + + _UNICODE;UNICODE;_DEBUG;%(PreprocessorDefinitions) + + + Console + false + + + + + WIN32;%(PreprocessorDefinitions) + + + + + MaxSpeed + true + true + NDEBUG;%(PreprocessorDefinitions) + + + Windows + true + true + false + + + + + + + + + + + + + + + + + + + + + Create + + + + + + + + + + + + {022dc661-4fa2-4264-a869-5a560fb4cc92} + false + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + + + + + + + + + + + + + + + + + + + + + +#pragma once +#define APP_CENTER_SDK_VERSION L"$(AppCenterSdkVersion)" +#define APP_CENTER_SECRET L"$(AppCenterSecret)" + + + + + \ No newline at end of file diff --git a/src/Notepads.DesktopExtension/Notepads.DesktopExtension.vcxproj.filters b/src/Notepads.DesktopExtension/Notepads.DesktopExtension.vcxproj.filters new file mode 100644 index 000000000..ba3084f53 --- /dev/null +++ b/src/Notepads.DesktopExtension/Notepads.DesktopExtension.vcxproj.filters @@ -0,0 +1,77 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + Source Files + + + Source Files + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + + + + + + Resource Files + + + \ No newline at end of file diff --git a/src/Notepads.DesktopExtension/appcenter.h b/src/Notepads.DesktopExtension/appcenter.h new file mode 100644 index 000000000..c7803c21c --- /dev/null +++ b/src/Notepads.DesktopExtension/appcenter.h @@ -0,0 +1,80 @@ +#pragma once +#include "pch.h" +#include "error.h" +#include "device.h" +#include "error_report.h" +#include "event_report.h" +#include "settings_key.h" +#include "winrt/Windows.Web.Http.h" +#include "winrt/Windows.Web.Http.Headers.h" + +// Documentation is at https://docs.microsoft.com/en-us/appcenter/diagnostics/upload-crashes +#define APP_CENTER_ENDPOINT L"https://in.appcenter.ms/logs?Api-Version=1.0.0" +const winrt::Windows::Foundation::Uri app_center_uri{ APP_CENTER_ENDPOINT }; + +__declspec(selectany) winrt::Windows::Web::Http::HttpClient client{}; + +struct appcenter +{ + static void start() noexcept + { + if (!APP_CENTER_SECRET || wcslen(APP_CENTER_SECRET) == 0) return; + auto install_id = winrt::unbox_value_or(settings_key::read(APP_CENTER_INSTALL_ID_STR), L""); + if (install_id.empty()) return; + + client.DefaultRequestHeaders().Append(L"app-secret", APP_CENTER_SECRET); + client.DefaultRequestHeaders().Append(L"install-id", install_id); + } + +private: + appcenter() noexcept = default; + appcenter(appcenter&&) noexcept = default; + appcenter(appcenter const& other) noexcept = default; +}; + +struct crashes +{ + static void track_error( + winrt_error const& error, + report::dictionary const& properties, + std::string const& attachment = "" + ) noexcept + { + if (!client.DefaultRequestHeaders().HasKey(L"app-secret") || !client.DefaultRequestHeaders().HasKey(L"install-id")) return; + + auto report = report::json_object(); + report.Insert(L"logs", error_report(error, properties, attachment).to_json()); + auto content = HttpStringContent(report.Stringify()); + auto response = client.TryPostAsync(app_center_uri, content).get(); + } + +private: + using HttpStringContent = winrt::Windows::Web::Http::HttpStringContent; + + crashes() noexcept = default; + crashes(crashes&&) noexcept = default; + crashes(crashes const& other) noexcept = default; +}; + +struct analytics +{ + static void track_event( + winrt::hstring const& name, + report::dictionary const& properties + ) noexcept + { + if (!client.DefaultRequestHeaders().HasKey(L"app-secret") || !client.DefaultRequestHeaders().HasKey(L"install-id")) return; + + auto report = report::json_object(); + report.Insert(L"logs", event_report(name, properties).to_json()); + auto content = HttpStringContent(report.Stringify()); + auto response = client.TryPostAsync(app_center_uri, content).get(); + } + +private: + using HttpStringContent = winrt::Windows::Web::Http::HttpStringContent; + + analytics() noexcept = default; + analytics(analytics&&) noexcept = default; + analytics(analytics const& other) noexcept = default; +}; \ No newline at end of file diff --git a/src/Notepads.DesktopExtension/device.h b/src/Notepads.DesktopExtension/device.h new file mode 100644 index 000000000..e91103471 --- /dev/null +++ b/src/Notepads.DesktopExtension/device.h @@ -0,0 +1,122 @@ +#pragma once +#include "pch.h" +#include "constants.h" + +struct device +{ + device() noexcept + { + auto package_version = Package::Current().Id().Version(); + m_build = m_version = std::format( + L"{}.{}.{}.{}", + package_version.Major, + package_version.Minor, + package_version.Build, + package_version.Revision + ); + + EasClientDeviceInformation oem_info{}; + m_os = oem_info.OperatingSystem(); + + auto version = std::stoull(AnalyticsInfo::VersionInfo().DeviceFamilyVersion().c_str()); + auto major = (version & 0xFFFF000000000000L) >> 48; + auto minor = (version & 0x0000FFFF00000000L) >> 32; + auto build = (version & 0x00000000FFFF0000L) >> 16; + auto revision = (version & 0x000000000000FFFFL); + m_osversion = std::format(L"{}.{}.{}", major, minor, build); + m_osbuild = std::format(L"{}.{}.{}.{}", major, minor, build, revision); + + m_model = oem_info.SystemProductName(); + m_oem = oem_info.SystemManufacturer(); + + RECT desktop; + GetWindowRect(GetDesktopWindow(), &desktop); + m_screen = std::format(L"{}x{}", desktop.right, desktop.bottom); + + m_locale = GlobalizationPreferences::Languages().Size() > 0 + ? GlobalizationPreferences::Languages().First().Current() + : L""; + + TIME_ZONE_INFORMATION timeZoneInfo; + GetTimeZoneInformation(&timeZoneInfo); + m_timezone = -1 * timeZoneInfo.Bias; + } + + device(device const& device) noexcept : + m_version(device.m_version), + m_build(device.m_build), + m_sdkversion(device.m_sdkversion), + m_os(device.m_os), + m_osversion(device.m_osversion), + m_osbuild(device.m_osbuild), + m_model(device.m_model), + m_oem(device.m_oem), + m_screen(device.m_screen), + m_locale(device.m_locale), + m_timezone(device.m_timezone) + {} + + device& operator=(device const& device) noexcept + { + m_namespace = device.m_namespace; + m_version = device.m_version; + m_build = device.m_build; + m_sdk = device.m_sdk; + m_sdkversion = device.m_sdkversion; + m_os = device.m_os; + m_osversion = device.m_osversion; + m_osbuild = device.m_osbuild; + m_model = device.m_model; + m_oem = device.m_oem; + m_screen = device.m_screen; + m_locale = device.m_locale; + m_timezone = device.m_timezone; + return *this; + } + + winrt::Windows::Data::Json::IJsonValue to_json() const noexcept + { + auto json_obj = winrt::Windows::Data::Json::JsonObject(); + json_obj.Insert(L"appNamespace", JsonValue::CreateStringValue(m_namespace)); + json_obj.Insert(L"appVersion", JsonValue::CreateStringValue(m_version)); + json_obj.Insert(L"appBuild", JsonValue::CreateStringValue(m_build)); + json_obj.Insert(L"sdkName", JsonValue::CreateStringValue(m_sdk)); + json_obj.Insert(L"sdkVersion", JsonValue::CreateStringValue(m_sdkversion)); + json_obj.Insert(L"osName", JsonValue::CreateStringValue(m_os)); + json_obj.Insert(L"osVersion", JsonValue::CreateStringValue(m_osversion)); + json_obj.Insert(L"osBuild", JsonValue::CreateStringValue(m_osbuild)); + json_obj.Insert(L"model", JsonValue::CreateStringValue(m_model)); + json_obj.Insert(L"oemName", JsonValue::CreateStringValue(m_oem)); + json_obj.Insert(L"screenSize", JsonValue::CreateStringValue(m_screen)); + json_obj.Insert(L"locale", JsonValue::CreateStringValue(m_locale)); + json_obj.Insert(L"timeZoneOffset", JsonValue::CreateNumberValue(m_timezone)); + return json_obj; + } + + winrt::hstring osbuild() const noexcept + { + return m_osbuild; + } + +private: + using hstring = winrt::hstring; + using Package = winrt::Windows::ApplicationModel::Package; + using JsonValue = winrt::Windows::Data::Json::JsonValue; + using AnalyticsInfo = winrt::Windows::System::Profile::AnalyticsInfo; + using GlobalizationPreferences = winrt::Windows::System::UserProfile::GlobalizationPreferences; + using EasClientDeviceInformation = winrt::Windows::Security::ExchangeActiveSyncProvisioning::EasClientDeviceInformation; + + hstring m_namespace = L"Notepads.DesktopExtension"; + hstring m_version; + hstring m_build; + hstring m_sdk = L"appcenter.uwp"; + hstring m_sdkversion = APP_CENTER_SDK_VERSION; + hstring m_os; + hstring m_osversion; + hstring m_osbuild; + hstring m_model; + hstring m_oem; + hstring m_screen; + hstring m_locale; + unsigned m_timezone; +}; \ No newline at end of file diff --git a/src/Notepads.DesktopExtension/elevated.cpp b/src/Notepads.DesktopExtension/elevated.cpp new file mode 100644 index 000000000..57bd9d872 --- /dev/null +++ b/src/Notepads.DesktopExtension/elevated.cpp @@ -0,0 +1,280 @@ +#include "pch.h" +#include "logger.h" +#include "appcenter.h" + +using namespace std; +using namespace winrt; +using namespace Windows::ApplicationModel; +using namespace Windows::Storage::AccessCache; + +extern DWORD session_id; +extern hstring package_sid; + +hstring write_pipe; +hstring rename_pipe; + +DWORD WINAPI save_file_from_pipe_data(LPVOID /* param */) +{ + try + { + handle elevated_write_event + { + OpenEventW( + SYNCHRONIZE | EVENT_MODIFY_STATE, + false, + std::format( + NAMED_OBJECT_FORMAT, + package_sid.c_str(), + ELEVATED_WRITE_EVENT_NAME_STR + ).c_str() + ) + }; + + check_bool(!WaitForSingleObject(elevated_write_event.get(), INFINITE)); + check_bool(ResetEvent(elevated_write_event.get())); + check_bool(WaitNamedPipeW(write_pipe.c_str(), NMPWAIT_WAIT_FOREVER)); + + handle h_pipe + { + CreateFileW( + write_pipe.c_str(), + GENERIC_READ | GENERIC_WRITE, + 0, + nullptr, + OPEN_EXISTING, + 0, + nullptr + ) + }; + check_bool(bool(h_pipe)); + + CreateThread(nullptr, 0, save_file_from_pipe_data, nullptr, 0, nullptr); + + TCHAR read_buffer[PIPE_READ_BUFFER]; + wstringstream pipe_data{}; + auto byte_read = 0UL; + do + { + fill(begin(read_buffer), end(read_buffer), '\0'); + if (ReadFile(h_pipe.get(), read_buffer, (PIPE_READ_BUFFER - 1) * sizeof(TCHAR), &byte_read, nullptr)) + { + pipe_data << read_buffer; + } + } while (byte_read >= (PIPE_READ_BUFFER - 1) * sizeof(TCHAR)); + + wstring filePath{}; + wstring memory_map_id{}; + wstring data_length_str{}; + getline(pipe_data, filePath, L'|'); + getline(pipe_data, memory_map_id, L'|'); + getline(pipe_data, data_length_str); + + auto data_length = stoi(data_length_str); + auto memory_map = std::format(NAMED_OBJECT_FORMAT, package_sid.c_str(), memory_map_id); + + handle h_memory{ OpenFileMappingW(FILE_MAP_READ, false, memory_map.c_str()) }; + check_bool(bool(h_memory)); + + auto map_view = MapViewOfFile(h_memory.get(), FILE_MAP_READ, 0, 0, data_length); + check_bool(map_view); + + handle h_file + { + CreateFileW( + filePath.c_str(), + GENERIC_READ | GENERIC_WRITE, + 0, + nullptr, + TRUNCATE_EXISTING, + 0, + nullptr + ) + }; + + check_bool(bool(h_file)); + check_bool(WriteFile(h_file.get(), map_view, data_length, nullptr, nullptr)); + check_bool(FlushFileBuffers(h_file.get())); + + UnmapViewOfFile(map_view); + + auto result = L"Success"; + check_bool(WriteFile(h_pipe.get(), result, static_cast(wcslen(result) * sizeof(TCHAR)), nullptr, nullptr)); + check_bool(FlushFileBuffers(h_pipe.get())); + + report::dictionary properties + { + pair(L"Result", result) + }; + logger::log_info(std::format(L"Successfully wrote to \"{}\"", filePath).c_str(), true); + logger::log_info(L"Waiting on uwp app to send data.", true); + + analytics::track_event(L"OnWriteToSystemFileRequested", properties); + return 0; + } + catch (hresult_error const& e) + { + winrt_error error{ e, true, 3 }; + report::dictionary properties + { + pair(L"Result", L"Failed") + }; + report::add_device_metadata(properties); + logger::log_error(error); + crashes::track_error(error, properties); + analytics::track_event(L"OnWriteToSystemFileRequested", properties); + exit_app(e.code()); + return e.code(); + } +} + +DWORD WINAPI rename_file_from_pipe_data(LPVOID /* param */) +{ + hstring old_name{}; + wstring new_name{}; + try + { + handle elevated_rename_event + { + OpenEventW( + SYNCHRONIZE | EVENT_MODIFY_STATE, + false, + std::format( + NAMED_OBJECT_FORMAT, + package_sid.c_str(), + ELEVATED_RENAME_EVENT_NAME_STR + ).c_str() + ) + }; + + check_bool(!WaitForSingleObject(elevated_rename_event.get(), INFINITE)); + check_bool(ResetEvent(elevated_rename_event.get())); + check_bool(WaitNamedPipeW(rename_pipe.c_str(), NMPWAIT_WAIT_FOREVER)); + + handle h_pipe + { + CreateFileW( + rename_pipe.c_str(), + GENERIC_READ | GENERIC_WRITE, + 0, + nullptr, + OPEN_EXISTING, + 0, + nullptr + ) + }; + check_bool(bool(h_pipe)); + + CreateThread(nullptr, 0, rename_file_from_pipe_data, nullptr, 0, nullptr); + + TCHAR read_buffer[PIPE_READ_BUFFER]; + wstringstream pipe_data{}; + auto byte_read = 0UL; + do + { + fill(begin(read_buffer), end(read_buffer), '\0'); + if (ReadFile(h_pipe.get(), read_buffer, (PIPE_READ_BUFFER - 1) * sizeof(TCHAR), &byte_read, nullptr)) + { + pipe_data << read_buffer; + } + } while (byte_read >= (PIPE_READ_BUFFER - 1) * sizeof(TCHAR)); + + wstring file_token{}; + getline(pipe_data, file_token, L'|'); + getline(pipe_data, new_name, L'|'); + + auto file = StorageApplicationPermissions::FutureAccessList().GetFileAsync(file_token).get(); + StorageApplicationPermissions::FutureAccessList().Remove(file_token); + old_name = file.Path(); + file.RenameAsync(new_name).get(); + + auto result = StorageApplicationPermissions::FutureAccessList().Add(file).c_str(); + + check_bool(WriteFile(h_pipe.get(), result, static_cast(wcslen(result) * sizeof(TCHAR)), nullptr, nullptr)); + check_bool(FlushFileBuffers(h_pipe.get())); + + logger::log_info(std::format(L"Successfully renamed \"{}\" to \"{}\"", old_name.c_str(), new_name).c_str(), true); + logger::log_info(L"Waiting on uwp app to send data.", true); + + report::dictionary properties + { + pair(L"Result", L"Success") + }; + + analytics::track_event(L"OnRenameToSystemFileRequested", properties); + return 0; + } + catch (hresult_error const& e) + { + winrt_error error{ e, true, 3 }; + logger::log_info( + old_name.empty() + ? new_name.empty() + ? L"Failed to rename file" + : std::format(L"Failed to rename file to \"{}\"", new_name).c_str() + : std::format(L"Failed to rename \"{}\" to \"{}\"", old_name.c_str(), new_name).c_str(), + true + ); + + report::dictionary properties + { + pair(L"Result", L"Failed") + }; + report::add_device_metadata(properties); + logger::log_error(error); + crashes::track_error(error, properties); + analytics::track_event(L"OnRenameToSystemFileRequested", properties); + exit_app(e.code()); + return e.code(); + } +} + +void initialize_elevated_service() +{ + if (!is_first_instance(ELEVATED_MUTEX_NAME)) return; + + try + { + check_bool(ProcessIdToSessionId(GetCurrentProcessId(), &session_id)); + package_sid = unbox_value(settings_key::read(PACKAGE_SID_STR)); + + write_pipe = std::format(PIPE_NAME_FORMAT, session_id, package_sid.c_str(), ELEVATED_WRITE_PIPE_CONNECTION_NAME_STR); + rename_pipe = std::format(PIPE_NAME_FORMAT, session_id, package_sid.c_str(), ELEVATED_RENAME_PIPE_CONNECTION_NAME_STR); + + logger::log_info(L"Successfully started Elevated Process.", true); + logger::log_info(L"Waiting on uwp app to send data.", true); + + check_bool(CreateThread(nullptr, 0, save_file_from_pipe_data, nullptr, 0, nullptr)); + CreateThread(nullptr, 0, rename_file_from_pipe_data, nullptr, 0, nullptr); + } + catch (hresult_error const& e) + { + winrt_error error{ e, true, 3 }; + report::dictionary properties{}; + report::add_device_metadata(properties); + logger::log_error(error); + crashes::track_error(error, properties); + analytics::track_event(L"OnInitializationForExtensionFailed", properties); + exit_app(e.code()); + } + +LifeTimeCheck: + handle life_time_obj + { + OpenMutexW( + SYNCHRONIZE, + false, + std::format( + L"AppContainerNamedObjects\\{}\\{}", + package_sid.c_str(), + ELEVATED_PROCESS_LIFETIME_OBJ_NAME_STR + ).c_str() + ) + }; + + if (life_time_obj) + { + life_time_obj.close(); + Sleep(1000); + goto LifeTimeCheck; + } +} \ No newline at end of file diff --git a/src/Notepads.DesktopExtension/error.h b/src/Notepads.DesktopExtension/error.h new file mode 100644 index 000000000..af045c96f --- /dev/null +++ b/src/Notepads.DesktopExtension/error.h @@ -0,0 +1,260 @@ +#pragma once +#include "pch.h" +#include "frame.h" +#include "restrictederrorinfo.h" + +#define TRACED_FRAMES_COUNT 5 +#define SKIPPED_FRAMES_COUNT 3 + +struct winrt_error +{ + using from_abi_t = winrt::take_ownership_from_abi_t; + static constexpr auto from_abi{ winrt::take_ownership_from_abi }; + + winrt_error() noexcept = default; + winrt_error(winrt_error&&) = default; + winrt_error& operator=(winrt_error&&) = default; + + winrt_error(winrt_error const& other) noexcept : + m_code(other.m_code), + m_info(other.m_info), + m_trace(other.m_trace), + m_fatal(other.m_fatal) + { + } + + winrt_error& operator=(winrt_error const& other) noexcept + { + m_code = other.m_code; + m_info = other.m_info; + m_trace = other.m_trace; + m_fatal = other.m_fatal; + return *this; + } + + explicit winrt_error(winrt::hresult const code, bool fatal, uint48_t skip = 0) noexcept : + m_code(verify_error(code)), + m_fatal(fatal) + { + originate(code, nullptr, skip + SKIPPED_FRAMES_COUNT); + } + + explicit winrt_error(winrt::hresult_error const& error, bool fatal = true, uint48_t skip = 0) noexcept : + m_code(error.code()), + m_info(error.try_as()), + m_fatal(fatal) + { + trace_stack(skip + SKIPPED_FRAMES_COUNT); + } + + winrt_error(winrt::hresult const code, winrt::param::hstring const& message) noexcept : m_code(verify_error(code)) + { + originate(code, winrt::param::get_abi(message), SKIPPED_FRAMES_COUNT); + } + + winrt_error(winrt::hresult const code, winrt::take_ownership_from_abi_t) noexcept : m_code(verify_error(code)) + { + winrt::com_ptr info; + WINRT_IMPL_GetErrorInfo(0, info.put_void()); + + if ((m_info = info.try_as())) + { + WINRT_VERIFY_(0, m_info->GetReference(m_debug_reference.put())); + + if (auto info2 = m_info.try_as()) + { + WINRT_VERIFY_(0, info2->CapturePropagationContext(nullptr)); + } + } + else + { + winrt::impl::bstr_handle legacy; + + if (info) + { + info->GetDescription(legacy.put()); + } + + winrt::hstring message; + + if (legacy) + { + message = winrt::impl::trim_hresult_message(legacy.get(), WINRT_IMPL_SysStringLen(legacy.get())); + } + + originate(code, get_abi(message), SKIPPED_FRAMES_COUNT); + } + } + + winrt::hresult code() const noexcept + { + return m_code; + } + + winrt::hstring message() const noexcept + { + if (m_info) + { + int32_t code{}; + winrt::impl::bstr_handle fallback; + winrt::impl::bstr_handle message; + winrt::impl::bstr_handle unused; + + if (0 == m_info->GetErrorDetails(fallback.put(), &code, message.put(), unused.put())) + { + if (code == m_code) + { + if (message) + { + return winrt::impl::trim_hresult_message(message.get(), WINRT_IMPL_SysStringLen(message.get())); + } + else + { + return winrt::impl::trim_hresult_message(fallback.get(), WINRT_IMPL_SysStringLen(fallback.get())); + } + } + } + } + + return winrt::impl::message_from_hresult(m_code); + } + + winrt::hstring stacktrace() const noexcept + { + if (!m_info || m_trace.empty()) return L""; + + std::wstringstream stacktrace{ L"" }; + for (auto& trace : m_trace) + { + stacktrace << L" at " << trace.name().c_str() << L" in " << trace.file().c_str() << L" :line" << trace.line() << L"\n"; + } + + return stacktrace.str().c_str(); + } + + winrt_error inner() const noexcept + { + winrt::com_ptr info; + m_info.try_as()->GetPreviousLanguageExceptionErrorInfo(info.put()); + + winrt_error inner; + if (inner.m_info = info.try_as()) + { + inner.trace_stack(0, true); + } + return inner; + } + + bool fatal() const noexcept + { + return m_fatal; + } + + template + auto try_as() const noexcept + { + return m_info.try_as(); + } + + winrt::hresult to_abi() const noexcept + { + if (m_info) + { + WINRT_IMPL_SetErrorInfo(0, m_info.try_as().get()); + } + + return m_code; + } + + winrt::Windows::Data::Json::IJsonValue to_json() const noexcept + { + auto json_obj = winrt::Windows::Data::Json::JsonObject(); + json_obj.Insert(L"type", JsonValue::CreateStringValue(std::format(L"HResult: {}", code().value))); + json_obj.Insert(L"message", JsonValue::CreateStringValue(message())); + //json_obj.Insert(L"stackTrace", JsonValue::CreateStringValue(stacktrace())); + + auto frames = JsonArray(); + for (auto& frame : m_trace) + { + frames.Append(frame.to_json()); + } + json_obj.Insert(L"frames", frames); + + return json_obj; + } + + static winrt_error get_last_error() noexcept + { + return winrt_error(winrt::impl::hresult_from_win32(GetLastError()), false); + } + +private: + using JsonValue = winrt::Windows::Data::Json::JsonValue; + using JsonArray = winrt::Windows::Data::Json::JsonArray; + + static int32_t __stdcall fallback_RoOriginateLanguageException(int32_t error, void* message, void*) noexcept + { + winrt::com_ptr info(new (std::nothrow) winrt::impl::error_info_fallback(error, message), winrt::take_ownership_from_abi); + WINRT_VERIFY_(0, WINRT_IMPL_SetErrorInfo(0, info.get())); + return 1; + } + + void originate(winrt::hresult const code, void* message, uint48_t skip = 0) noexcept + { + static int32_t(__stdcall * handler)(int32_t error, void* message, void* exception) noexcept; + winrt::impl::load_runtime_function("RoOriginateLanguageException", handler, fallback_RoOriginateLanguageException); + WINRT_VERIFY(handler(code, message, nullptr)); + + winrt::com_ptr info; + WINRT_VERIFY_(0, WINRT_IMPL_GetErrorInfo(0, info.put_void())); + WINRT_VERIFY(info.try_as(m_info)); + + trace_stack(skip); + } + + void trace_stack(uint48_t skip = 0, bool original = false) noexcept + { + auto infoWithStackTrace = m_info.try_as(); + uint64_t traces[TRACED_FRAMES_COUNT]{ 0 }; + uint48_t count = TRACED_FRAMES_COUNT; + if (infoWithStackTrace || original) + { + infoWithStackTrace->GetStackBackTrace(TRACED_FRAMES_COUNT, traces, &count); + } + else + { + CaptureStackBackTrace(skip, TRACED_FRAMES_COUNT, (void**)traces, nullptr); + } + + if (count <= 0) return; + frame* frames = new frame[count]; + for (auto i = 0UL; i < count; ++i) + { + frames[i] = traces[i]; + } + + m_trace = winrt::array_view(frames, count); + } + + static winrt::hresult verify_error(winrt::hresult const code) noexcept + { + WINRT_ASSERT(code < 0); + return code; + } + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunused-private-field" +#endif + + winrt::impl::bstr_handle m_debug_reference; + uint32_t m_debug_magic{ 0xAABBCCDD }; + winrt::hresult m_code{ winrt::impl::error_fail }; + winrt::com_ptr m_info; + winrt::array_view m_trace; + bool m_fatal = true; + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif +}; \ No newline at end of file diff --git a/src/Notepads.DesktopExtension/error_attachment_report.h b/src/Notepads.DesktopExtension/error_attachment_report.h new file mode 100644 index 000000000..cc5ef70e3 --- /dev/null +++ b/src/Notepads.DesktopExtension/error_attachment_report.h @@ -0,0 +1,69 @@ +#pragma once +#include "pch.h" +#include "report.h" + +struct error_attachment_report : report +{ + explicit error_attachment_report(hstring const& id, std::string const& attachment) noexcept : + report(), m_errorid(id), m_attachment(winrt::to_hstring(base64_encode(attachment))) + { + } + + error_attachment_report(error_attachment_report const& other) noexcept : + report(other), + m_errorid(other.m_errorid), + m_content(other.m_content), + m_attachment(other.m_attachment) + { + } + + error_attachment_report(error_attachment_report&& other) noexcept : + report(other) + { + } + + bool empty() const noexcept + { + return m_attachment.empty(); + } + +protected: + virtual hstring type() const noexcept + { + return L"errorAttachment"; + } + + virtual void append_additional_data(json_object& json_obj) const noexcept + { + report::append_additional_data(json_obj); + + json_obj.Insert(L"contentType", JsonValue::CreateStringValue(m_content)); + json_obj.Insert(L"data", JsonValue::CreateStringValue(m_attachment)); + json_obj.Insert(L"errorId", JsonValue::CreateStringValue(m_errorid)); + } + + hstring m_errorid; + hstring m_content = L"text/plain"; + hstring m_attachment; + +private: + //From https://stackoverflow.com/a/34571089/5155484 + static std::string base64_encode(std::string const& in) noexcept + { + static const std::string base64_str = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + std::string out; + + int val = 0, valb = -6; + for (auto c : in) { + val = (val << 8) + c; + valb += 8; + while (valb >= 0) { + out.push_back(base64_str[(val >> valb) & 0x3F]); + valb -= 6; + } + } + if (valb > -6) out.push_back(base64_str[((val << 8) >> (valb + 8)) & 0x3F]); + while (out.size() % 4) out.push_back('='); + return out; + } +}; \ No newline at end of file diff --git a/src/Notepads.DesktopExtension/error_report.h b/src/Notepads.DesktopExtension/error_report.h new file mode 100644 index 000000000..bd57421b8 --- /dev/null +++ b/src/Notepads.DesktopExtension/error_report.h @@ -0,0 +1,43 @@ +#pragma once +#include "pch.h" +#include "managed_error_report.h" +#include "handled_error_report.h" + +struct error_report : report +{ + explicit error_report( + winrt_error const& error, + report::dictionary const& properties, + std::string const& attachment = "" + ) noexcept : + m_handled_error_report(error, properties, attachment), m_managed_error_report(error, attachment) + { + } + + error_report(error_report const& other) noexcept : + report(other), + m_managed_error_report(other.m_managed_error_report), + m_handled_error_report(other.m_handled_error_report) + { + } + + error_report(error_report&& other) noexcept : + report(other), + m_managed_error_report(other.m_managed_error_report), + m_handled_error_report(other.m_handled_error_report) + { + } + + virtual winrt::Windows::Data::Json::IJsonValue to_json() const noexcept + { + auto json_obj = json_array(); + json_obj.Append(m_managed_error_report.to_json()); + json_obj.Append(m_handled_error_report.to_json()); + last_error_report_sid = m_managed_error_report.sid(); + return json_obj; + } + +protected: + managed_error_report m_managed_error_report; + handled_error_report m_handled_error_report; +}; \ No newline at end of file diff --git a/src/Notepads.DesktopExtension/event_report.h b/src/Notepads.DesktopExtension/event_report.h new file mode 100644 index 000000000..802c00fcb --- /dev/null +++ b/src/Notepads.DesktopExtension/event_report.h @@ -0,0 +1,56 @@ +#pragma once +#include "pch.h" +#include "error.h" +#include "report.h" + +struct event_report : report +{ + explicit event_report(hstring const& name, report::dictionary const& properties) noexcept : + report(), m_sid(last_error_report_sid), m_name(name), m_properties(properties) + { + last_error_report_sid = L""; + } + + event_report(event_report const& other) noexcept : + report(other), + m_sid(other.m_sid), + m_name(other.m_name), + m_properties(other.m_properties) + { + } + + event_report(event_report&& other) noexcept : + report(other) + { + } + +protected: + virtual hstring type() const noexcept + { + return L"event"; + } + + virtual void append_additional_data(json_object& json_obj) const noexcept + { + report::append_additional_data(json_obj); + + json_obj.Insert(L"sid", JsonValue::CreateStringValue(m_sid)); + json_obj.Insert(L"name", JsonValue::CreateStringValue(m_name)); + + // Write custom properties if available + if (!m_properties.empty()) + { + auto properties = json_object(); + for (auto& property : m_properties) + { + properties.Insert(property.first, JsonValue::CreateStringValue(property.second)); + } + + json_obj.Insert(L"properties", properties); + } + } + + hstring m_sid; + hstring m_name; + report::dictionary m_properties; +}; \ No newline at end of file diff --git a/src/Notepads.DesktopExtension/extension.cpp b/src/Notepads.DesktopExtension/extension.cpp new file mode 100644 index 000000000..7e7be2d81 --- /dev/null +++ b/src/Notepads.DesktopExtension/extension.cpp @@ -0,0 +1,244 @@ +#include "pch.h" +#include "logger.h" +#include "appcenter.h" +#include "settings_key.h" +#include "shellapi.h" + +using namespace std; +using namespace winrt; +using namespace Windows::ApplicationModel; +using namespace Windows::Storage; + +DWORD session_id; +hstring package_sid; +hstring unblock_pipe; + +DWORD WINAPI unblock_file_from_pipe_data(LPVOID /* param */) +{ + hstring file_path{}; + try + { + handle extension_unblock_event + { + OpenEventW( + SYNCHRONIZE | EVENT_MODIFY_STATE, + false, + std::format( + NAMED_OBJECT_FORMAT, + package_sid.c_str(), + EXTENSION_UNBLOCK_EVENT_NAME_STR + ).c_str() + ) + }; + + check_bool(!WaitForSingleObject(extension_unblock_event.get(), INFINITE)); + check_bool(ResetEvent(extension_unblock_event.get())); + check_bool(WaitNamedPipeW(unblock_pipe.c_str(), NMPWAIT_WAIT_FOREVER)); + + CreateThread(nullptr, 0, unblock_file_from_pipe_data, nullptr, 0, nullptr); + + handle h_pipe + { + CreateFileW( + unblock_pipe.c_str(), + GENERIC_READ, + 0, + nullptr, + OPEN_EXISTING, + 0, + nullptr + ) + }; + + check_bool(bool(h_pipe)); + + TCHAR read_buffer[PIPE_READ_BUFFER]; + wstringstream pipe_data{}; + auto byte_read = 0UL; + do + { + fill(begin(read_buffer), end(read_buffer), '\0'); + if (ReadFile(h_pipe.get(), read_buffer, (PIPE_READ_BUFFER - 1) * sizeof(TCHAR), &byte_read, nullptr)) + { + pipe_data << read_buffer; + } + } while (byte_read >= (PIPE_READ_BUFFER - 1) * sizeof(TCHAR)); + + file_path = pipe_data.str(); + auto p_file = create_instance(CLSID_PersistentZoneIdentifier); + check_hresult(p_file->Load(file_path.c_str(), STGM_READWRITE)); + + LPTSTR last_writer_package_family; + check_hresult(p_file.as()->GetLastWriterPackageFamilyName(&last_writer_package_family)); + check_bool(Package::Current().Id().FamilyName() == last_writer_package_family); + check_hresult(p_file.as()->Remove()); + check_hresult(p_file->Save(file_path.c_str(), true)); + + logger::log_info(std::format(L"Successfully unblocked file \"{}\"", file_path.c_str()).c_str(), true); + logger::log_info(L"Waiting on uwp app to send data.", true); + return 0; + } + catch (hresult_error const& e) + { + winrt_error error{ e, true, 3 }; + + logger::log_info( + file_path.empty() + ? L"Failed to unblock file" + : std::format(L"Failed to unblock file \"{}\"", file_path.c_str()).c_str(), + true + ); + + report::dictionary properties{}; + report::add_device_metadata(properties); + logger::log_error(error); + crashes::track_error(error, properties); + analytics::track_event(L"OnFileUnblockFailed", properties); + + exit_app(error.code()); + return error.code(); + } +} + +void launch_elevated_process() +{ + report::dictionary properties{}; + + auto handle_launch_error = [&](winrt_error const& ex) + { + settings_key::write(LAST_CHANGED_SETTINGS_APP_INSTANCE_ID_STR, box_value(L"")); + settings_key::write(LAST_CHANGED_SETTINGS_KEY_STR, box_value(LAUNCH_ELEVATED_PROCESS_FAILED_STR)); + + logger::log_info(L"Launching of Elevated Process was cancelled.", true); + properties.insert( + properties.end(), + { + pair(L"Denied", L"True"), + pair(L"Error Code", to_hstring(ex.code())), + pair(L"Error Message", ex.message()) + } + ); + }; + + try + { + wstring file_name{ MAX_PATH, '\0' }; + + GetModulePath: + auto result = GetModuleFileNameW(nullptr, &file_name[0], static_cast(file_name.size())); + check_bool(result); + if (result == file_name.size() && GetLastError() == ERROR_INSUFFICIENT_BUFFER) + { + file_name.resize(2 * file_name.size(), '\0'); + goto GetModulePath; + } + + SHELLEXECUTEINFO sh_ex_info + { + .cbSize = sizeof(SHELLEXECUTEINFO), + .fMask = SEE_MASK_NOCLOSEPROCESS, + .hwnd = 0, + .lpVerb = L"runas", + .lpFile = file_name.c_str(), + .lpParameters = L"", + .lpDirectory = 0, + .nShow = SW_SHOW, + .hInstApp = 0 + }; + + if (ShellExecuteExW(&sh_ex_info)) + { + settings_key::write(LAST_CHANGED_SETTINGS_APP_INSTANCE_ID_STR, box_value(L"")); + settings_key::write(LAST_CHANGED_SETTINGS_KEY_STR, box_value(LAUNCH_ELEVATED_PROCESS_SUCCESS_STR)); + + logger::log_info(L"Elevated Process has been launched.", true); + properties.push_back(pair(L"Accepted", L"True")); + } + else + { + handle_launch_error(winrt_error::get_last_error()); + } + settings_key::signal_changed(); + } + catch (hresult_error const& e) + { + winrt_error error{ e, true, 3 }; + handle_launch_error(error); + settings_key::signal_changed(); + + report::add_device_metadata(properties); + logger::log_error(error); + crashes::track_error(error, properties); + } + + analytics::track_event(L"OnAdminstratorPrivilageRequested", properties); +} + +void launch_elevated_process_if_requested() +{ + auto n_args = 0; + array_view args{ CommandLineToArgvW(GetCommandLineW(), &n_args), 4 }; + if (n_args < 4) return; + + // Assumed first entry is for uwp app + wstring praid{ Package::Current().GetAppListEntries().GetAt(0).AppUserModelId() }; + praid.erase(0, praid.find(L"!") + 1); + if (wcscmp(args[1], L"/InvokerPRAID:") == 0 && + wcscmp(args[2], praid.c_str()) == 0 && + wcscmp(args[3], L"/admin") == 0 + ) + { + launch_elevated_process(); + } +} + +void initialize_extension_service() +{ + launch_elevated_process_if_requested(); + + if (!is_first_instance(EXTENSION_MUTEX_NAME)) return; + + try + { + check_bool(ProcessIdToSessionId(GetCurrentProcessId(), &session_id)); + package_sid = unbox_value(settings_key::read(PACKAGE_SID_STR)); + + unblock_pipe = std::format(PIPE_NAME_FORMAT, session_id, package_sid.c_str(), EXTENSION_UNBLOCK_PIPE_CONNECTION_NAME_STR); + + logger::log_info(L"Successfully started Desktop Extension.", true); + logger::log_info(L"Waiting on uwp app to send data.", true); + + check_bool(CreateThread(nullptr, 0, unblock_file_from_pipe_data, nullptr, 0, nullptr)); + } + catch (hresult_error const& e) + { + winrt_error error{ e, true, 3 }; + report::dictionary properties{}; + report::add_device_metadata(properties); + logger::log_error(error); + crashes::track_error(error, properties); + analytics::track_event(L"OnInitializationForExtensionFailed", properties); + exit_app(e.code()); + } + +LifeTimeCheck: + handle life_time_obj + { + OpenMutexW( + SYNCHRONIZE, + false, + std::format( + L"AppContainerNamedObjects\\{}\\{}", + package_sid.c_str(), + EXTENSION_PROCESS_LIFETIME_OBJ_NAME_STR + ).c_str() + ) + }; + + if (life_time_obj) + { + life_time_obj.close(); + Sleep(1000); + goto LifeTimeCheck; + } +} \ No newline at end of file diff --git a/src/Notepads.DesktopExtension/frame.h b/src/Notepads.DesktopExtension/frame.h new file mode 100644 index 000000000..65e1958dd --- /dev/null +++ b/src/Notepads.DesktopExtension/frame.h @@ -0,0 +1,162 @@ +#pragma once +#pragma comment(lib, "dbgeng.lib") +#include "pch.h" +#include "dbgeng.h" + +__declspec(selectany) winrt::com_ptr symbols; + +struct frame +{ + using from_abi_t = winrt::take_ownership_from_abi_t; + static constexpr auto from_abi{ winrt::take_ownership_from_abi }; + + frame() noexcept = default; + frame(frame&&) = default; + frame& operator=(frame&&) = default; + + frame(frame const& other) noexcept : + m_offset(other.m_offset) + { + } + + frame& operator=(frame const& other) noexcept + { + m_offset = other.m_offset; + return *this; + } + + frame& operator=(uint64_t addr) noexcept + { + m_offset = addr; + return *this; + } + + explicit frame(uint64_t addr) noexcept + : m_offset(addr) + { + } + + template + explicit frame(T* addr) noexcept + : m_offset(reinterpret_cast(addr)) + { + } + + winrt::hstring name() const noexcept + { + originate(); + + if (!symbols) return winrt::to_hstring(m_offset); + + auto name = std::string(256, '\0'); + auto size = 0UL; + auto result = SUCCEEDED( + symbols->GetNameByOffset( + m_offset, + &name[0], + static_cast(name.size()), + &size, + nullptr + ) + ); + + if (!result && size > name.size()) + { + name.resize(size); + result = SUCCEEDED( + symbols->GetNameByOffset( + m_offset, + &name[0], + static_cast(name.size()), + &size, + nullptr + ) + ); + } + + return winrt::to_hstring(name.data()); + } + + winrt::hstring file() const noexcept + { + originate(); + + if (!symbols) return L""; + + auto file = std::string(256, '\0'); + auto size = 0UL; + auto result = SUCCEEDED( + symbols->GetLineByOffset( + m_offset, + nullptr, + &file[0], + static_cast(file.size()), + &size, + nullptr + ) + ); + + if (!result && size > file.size()) + { + file.resize(size); + result = SUCCEEDED( + symbols->GetLineByOffset( + m_offset, + nullptr, + &file[0], + static_cast(file.size()), + &size, + nullptr + ) + ); + } + + return winrt::to_hstring(file.data()); + } + + uint48_t line() const noexcept + { + originate(); + + if (!symbols) return 0; + + auto line = 0UL; + auto result = SUCCEEDED( + symbols->GetLineByOffset(m_offset, &line, 0, 0, 0, 0) + ); + + return (result ? line : 0); + } + + winrt::Windows::Data::Json::IJsonValue to_json() const noexcept + { + std::wstring full_name = name().c_str(); + auto delimiter = full_name.find_first_of('!'); + auto class_name = delimiter == std::wstring::npos ? full_name : full_name.substr(0, delimiter); + auto method_name = delimiter == std::wstring::npos ? L"" : full_name.substr(delimiter + 1, full_name.length()); + + auto json_obj = winrt::Windows::Data::Json::JsonObject(); + json_obj.Insert(L"className", JsonValue::CreateStringValue(class_name)); + json_obj.Insert(L"methodName", JsonValue::CreateStringValue(method_name)); + json_obj.Insert(L"fileName", JsonValue::CreateStringValue(file())); + json_obj.Insert(L"lineNumber", JsonValue::CreateNumberValue(line())); + return json_obj; + } + +private: + using JsonValue = winrt::Windows::Data::Json::JsonValue; + + static void originate() noexcept + { + if (!symbols) + { + winrt::com_ptr client = nullptr; + DebugCreate(winrt::guid_of(), client.put_void()); + client->AttachProcess(0, GetCurrentProcessId(), DEBUG_ATTACH_NONINVASIVE | DEBUG_ATTACH_NONINVASIVE_NO_SUSPEND); + client.as()->WaitForEvent(DEBUG_WAIT_DEFAULT, INFINITE); + client.as(symbols); + } + } + + uint64_t m_offset{ 0 }; +}; \ No newline at end of file diff --git a/src/Notepads.DesktopExtension/handled_error_report.h b/src/Notepads.DesktopExtension/handled_error_report.h new file mode 100644 index 000000000..c3ce03755 --- /dev/null +++ b/src/Notepads.DesktopExtension/handled_error_report.h @@ -0,0 +1,52 @@ +#pragma once +#include "pch.h" +#include "error.h" +#include "managed_error_report.h" + +struct handled_error_report : managed_error_report +{ + explicit handled_error_report( + winrt_error const& error, + report::dictionary const& properties, + std::string const& attachment = "" + ) noexcept : + managed_error_report(error, attachment), m_properties(properties) + { + } + + handled_error_report(handled_error_report const& other) noexcept : + managed_error_report(other), + m_properties(other.m_properties) + { + } + + handled_error_report(handled_error_report&& other) noexcept : + managed_error_report(other) + { + } + +protected: + virtual hstring type() const noexcept + { + return L"handledError"; + } + + virtual void append_additional_data(json_object& json_obj) const noexcept + { + managed_error_report::append_additional_data(json_obj); + + // Write custom properties if available + if (!m_properties.empty()) + { + auto properties = json_object(); + for (auto& property : m_properties) + { + properties.Insert(property.first, JsonValue::CreateStringValue(property.second)); + } + + json_obj.Insert(L"properties", properties); + } + } + + report::dictionary m_properties; +}; \ No newline at end of file diff --git a/src/Notepads.DesktopExtension/logger.h b/src/Notepads.DesktopExtension/logger.h new file mode 100644 index 000000000..7001fb49b --- /dev/null +++ b/src/Notepads.DesktopExtension/logger.h @@ -0,0 +1,91 @@ +#pragma once +#include "pch.h" +#include "error.h" + +#define LOG_FILE_FORMAT L"{year.full}{month.integer(2)}{day.integer(2)}" \ + "T{hour.integer(2)}{minute.integer(2)}{second.integer(2)}" +#define LOG_FORMAT L"{month.integer(2)}/{day.integer(2)}/{year.full} " \ + "{hour.integer(2)}:{minute.integer(2)}:{second.integer(2)}" + +__declspec(selectany) winrt::Windows::Storage::StorageFile log_file = nullptr; + +struct logger +{ + static winrt::fire_and_forget start(bool elevated) noexcept + { + auto local_folder = ApplicationData::Current().LocalFolder(); + auto log_folder = co_await local_folder.CreateFolderAsync(L"Logs", CreationCollisionOption::OpenIfExists); + log_file = co_await log_folder.CreateFileAsync( + get_time_stamp(LOG_FILE_FORMAT) + + winrt::to_hstring(elevated ? L"-extension.log" : L"-elevated-extension.log"), + CreationCollisionOption::OpenIfExists + ); + } + + static void log_error(winrt_error const& error) noexcept + { + std::wstring formatted_message = std::format( + L"{} [Error] HResult Error {}: {}\n{}\n", + get_time_stamp(LOG_FORMAT).c_str(), + error.code().value, + error.message().c_str(), + error.stacktrace().c_str() + ); + + logger::print(formatted_message.c_str()); + + if (log_file) + { + FileIO::AppendTextAsync(log_file, formatted_message).get(); + } + } + + static void log_info(winrt::hstring const& message, bool console_only = false) noexcept + { + std::wstring formatted_message = std::format( + L"{} [Info] {}\n", + get_time_stamp(LOG_FORMAT).c_str(), + message.c_str() + ); + + logger::print(formatted_message.c_str()); + + if (!console_only && log_file) + { + FileIO::AppendTextAsync(log_file, formatted_message).get(); + } + } + +#ifdef _DEBUG + static void print(winrt::hstring const& message, uint48_t sleep = 0) noexcept + { + wprintf_s(L"%ls", message.c_str()); + Sleep(sleep); +#else + static void print(winrt::hstring const& message, uint48_t /* sleep */ = 0) noexcept + { +#endif + OutputDebugStringW(message.c_str()); + } + + static winrt::hstring get_time_stamp(winrt::hstring const& format) noexcept + { + return DateTimeFormatter(format).Format(winrt::clock::now()); + } + + static winrt::hstring get_utc_time_stamp(winrt::hstring const& format) noexcept + { + return DateTimeFormatter(format).Format(winrt::clock::now(), L"UTC"); + } + +private: + using FileIO = winrt::Windows::Storage::FileIO; + using StorageFile = winrt::Windows::Storage::StorageFile; + using ApplicationData = winrt::Windows::Storage::ApplicationData; + using CreationCollisionOption = winrt::Windows::Storage::CreationCollisionOption; + using DateTimeFormatter = winrt::Windows::Globalization::DateTimeFormatting::DateTimeFormatter; + + logger() noexcept = default; + logger(logger&&) noexcept = default; + logger(logger const& other) noexcept = default; +}; \ No newline at end of file diff --git a/src/Notepads.DesktopExtension/main.cpp b/src/Notepads.DesktopExtension/main.cpp new file mode 100644 index 000000000..0b8134721 --- /dev/null +++ b/src/Notepads.DesktopExtension/main.cpp @@ -0,0 +1,97 @@ +#include "pch.h" +#include "logger.h" +#include "appcenter.h" + +using namespace std; +using namespace winrt; + +void exit_app(int code) +{ + uninit_apartment(); + exit(code); +} + +bool is_first_instance(hstring mutex_name) +{ + auto result = true; + + handle h_mutex{ OpenMutexW(MUTEX_ALL_ACCESS, false, mutex_name.c_str()) }; + if (!h_mutex) + { + try + { + check_bool(CreateMutexW(nullptr, false, mutex_name.c_str())); + } + catch (hresult_error const& e) + { + winrt_error error{ e, true, 3 }; + report::dictionary properties{}; + report::add_device_metadata(properties); + logger::log_error(error); + crashes::track_error(error, properties); + analytics::track_event(L"OnExtensionInstanceDetectionFailed", properties); + exit_app(e.code()); + } + +#ifdef _DEBUG + logger::start(mutex_name == EXTENSION_MUTEX_NAME); +#endif + } + else + { + result = false; + logger::log_info(L"Closing this instance as another instance is already running.", true); + logger::print(L"", 3000); + exit_app(); + } + + return result; +} + +bool is_elevated_process() +{ + try + { + handle h_token; + check_bool(OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, h_token.put())); + TOKEN_ELEVATION elevation; + auto size = static_cast(sizeof(elevation)); + check_bool(GetTokenInformation(h_token.get(), TokenElevation, &elevation, sizeof(elevation), &size)); + return elevation.TokenIsElevated; + } + catch (hresult_error const& e) + { + winrt_error error{ e, true, 3 }; + report::dictionary properties{}; + report::add_device_metadata(properties); + crashes::track_error(error, properties); + analytics::track_event(L"OnPrivilageDetectionFailed", properties); + return false; + } +} + +#ifndef _DEBUG +int APIENTRY wWinMain( + _In_ HINSTANCE /* hInstance */, + _In_opt_ HINSTANCE /* hPrevInstance */, + _In_ LPWSTR /* lpCmdLine */, + _In_ int /* nCmdShow */ +) +#else +int main() +#endif +{ + init_apartment(); + appcenter::start(); + + if (is_elevated_process()) + { + initialize_elevated_service(); + } + else + { + initialize_extension_service(); + } + + uninit_apartment(); +} \ No newline at end of file diff --git a/src/Notepads.DesktopExtension/managed_error_report.h b/src/Notepads.DesktopExtension/managed_error_report.h new file mode 100644 index 000000000..93be9567a --- /dev/null +++ b/src/Notepads.DesktopExtension/managed_error_report.h @@ -0,0 +1,77 @@ +#pragma once +#include "pch.h" +#include "error.h" +#include "error_attachment_report.h" + +struct managed_error_report : report +{ + explicit managed_error_report(winrt_error const& error, std::string const& attachment = "") noexcept : + report(), m_error(error), m_attachment(m_id, attachment) + { + std::wstring sid = winrt::to_hstring(winrt::Windows::Foundation::GuidHelper::CreateNewGuid()).c_str(); + sid.erase(0, sid.find_first_not_of('{')).erase(sid.find_last_not_of('}') + 1); + m_sid = sid; + } + + managed_error_report(managed_error_report const& other) noexcept : + report(other), + m_pid(other.m_pid), + m_thread(other.m_thread), + m_sid(other.m_sid), + m_process(other.m_process), + m_error(other.m_error), + m_attachment(other.m_attachment) + { + } + + managed_error_report(managed_error_report&& other) noexcept : + report(other), + m_error(other.m_error), + m_attachment(other.m_attachment) + { + } + + hstring sid() const noexcept + { + return m_sid; + } + + virtual winrt::Windows::Data::Json::IJsonValue to_json() const noexcept + { + auto json_obj = json_array(); + + json_obj.Append(report::to_json()); + + if (!m_attachment.empty()) + { + json_obj.Append(m_attachment.to_json()); + } + + return json_obj; + } + +protected: + virtual hstring type() const noexcept + { + return L"managedError"; + } + + virtual void append_additional_data(json_object& json_obj) const noexcept + { + report::append_additional_data(json_obj); + + json_obj.Insert(L"sid", JsonValue::CreateStringValue(m_sid)); + json_obj.Insert(L"processId", JsonValue::CreateNumberValue(m_pid)); + json_obj.Insert(L"fatal", JsonValue::CreateBooleanValue(m_error.fatal())); + json_obj.Insert(L"processName", JsonValue::CreateStringValue(m_process)); + json_obj.Insert(L"errorThreadId", JsonValue::CreateNumberValue(m_thread)); + json_obj.Insert(L"exception", m_error.to_json()); + } + + unsigned m_pid = GetCurrentProcessId(); + unsigned m_thread = GetCurrentThreadId(); + hstring m_sid; + hstring m_process = L"Notepads32.exe"; + error_attachment_report m_attachment; + winrt_error m_error; +}; \ No newline at end of file diff --git a/src/Notepads.DesktopExtension/packages.config b/src/Notepads.DesktopExtension/packages.config new file mode 100644 index 000000000..c7a3eef2f --- /dev/null +++ b/src/Notepads.DesktopExtension/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/src/Notepads.DesktopExtension/pch.h b/src/Notepads.DesktopExtension/pch.h new file mode 100644 index 000000000..af10e8df0 --- /dev/null +++ b/src/Notepads.DesktopExtension/pch.h @@ -0,0 +1,49 @@ +#pragma once +#pragma comment(lib, "shell32") +#include "format" +#include "windows.h" +#include "shlobj_core.h" +#include "winrt/Windows.ApplicationModel.h" +#include "winrt/Windows.ApplicationModel.Core.h" +#include "winrt/Windows.Data.Json.h" +#include "winrt/Windows.Foundation.h" +#include "winrt/Windows.Foundation.Collections.h" +#include "winrt/Windows.Globalization.DateTimeFormatting.h" +#include "winrt/Windows.Security.ExchangeActiveSyncProvisioning.h" +#include "winrt/Windows.Storage.h" +#include "winrt/Windows.Storage.AccessCache.h" +#include "winrt/Windows.System.h" +#include "winrt/Windows.System.Profile.h" +#include "winrt/Windows.System.UserProfile.h" +#include "winrt/Notepads.Core.h" + +#define EXTENSION_MUTEX_NAME L"ExtensionMutex" +#define ELEVATED_MUTEX_NAME L"ElevatedMutex" + +#define PIPE_READ_BUFFER 2 * MAX_PATH + 10 +#define PIPE_NAME_FORMAT L"\\\\.\\pipe\\Sessions\\{}\\AppContainerNamedObjects\\{}\\{}" +#define NAMED_OBJECT_FORMAT L"AppContainerNamedObjects\\{}\\{}" + +typedef unsigned long uint48_t; +typedef winrt::Notepads::Core::CoreKey CoreKey; + +#define PACKAGE_SID_STR CoreKey::PackageSidStr().c_str() +#define APP_CENTER_INSTALL_ID_STR CoreKey::AppCenterInstallIdStr().c_str() +#define LAST_CHANGED_SETTINGS_KEY_STR CoreKey::LastChangedSettingsKeyStr().c_str() +#define LAST_CHANGED_SETTINGS_APP_INSTANCE_ID_STR CoreKey::LastChangedSettingsAppInstanceIdStr().c_str() +#define LAUNCH_ELEVATED_PROCESS_SUCCESS_STR CoreKey::LaunchElevatedProcessSuccessStr().c_str() +#define LAUNCH_ELEVATED_PROCESS_FAILED_STR CoreKey::LaunchElevatedProcessFailedStr().c_str() +#define EXTENSION_PROCESS_LIFETIME_OBJ_NAME_STR CoreKey::ExtensionProcessLifetimeObjNameStr().c_str() +#define ELEVATED_PROCESS_LIFETIME_OBJ_NAME_STR CoreKey::ElevatedProcessLifetimeObjNameStr().c_str() +#define EXTENSION_UNBLOCK_EVENT_NAME_STR CoreKey::ExtensionUnblockEventNameStr().c_str() +#define ELEVATED_WRITE_EVENT_NAME_STR CoreKey::ElevatedWriteEventNameStr().c_str() +#define ELEVATED_RENAME_EVENT_NAME_STR CoreKey::ElevatedRenameEventNameStr().c_str() +#define EXTENSION_UNBLOCK_PIPE_CONNECTION_NAME_STR CoreKey::ExtensionUnblockPipeConnectionNameStr().c_str() +#define ELEVATED_WRITE_PIPE_CONNECTION_NAME_STR CoreKey::ElevatedWritePipeConnectionNameStr().c_str() +#define ELEVATED_RENAME_PIPE_CONNECTION_NAME_STR CoreKey::ElevatedRenamePipeConnectionNameStr().c_str() + +bool is_elevated_process(); +bool is_first_instance(winrt::hstring mutexName); +void initialize_extension_service(); +void initialize_elevated_service(); +void exit_app(int code = 0); \ No newline at end of file diff --git a/src/Notepads.DesktopExtension/report.h b/src/Notepads.DesktopExtension/report.h new file mode 100644 index 000000000..f4a3c3704 --- /dev/null +++ b/src/Notepads.DesktopExtension/report.h @@ -0,0 +1,111 @@ +#pragma once +#include "pch.h" +#include "logger.h" + +#define APP_CENTER_FORMAT L"{year.full}-{month.integer(2)}-{day.integer(2)}" \ + "T{hour.integer(2)}:{minute.integer(2)}:{second.integer(2)}Z" + +const winrt::hstring launch_time_stamp = logger::get_utc_time_stamp(APP_CENTER_FORMAT); + +__declspec(selectany) winrt::hstring last_error_report_sid = L""; +__declspec(selectany) device device_info {}; + +struct report +{ + using json_object = winrt::Windows::Data::Json::JsonObject; + using dictionary = std::vector>; + + virtual winrt::Windows::Data::Json::IJsonValue to_json() const noexcept + { + auto json_obj = json_object(); + json_obj.Insert(L"type", JsonValue::CreateStringValue(type())); + json_obj.Insert(L"id", JsonValue::CreateStringValue(m_id)); + json_obj.Insert(L"timestamp", JsonValue::CreateStringValue(m_timestamp)); + json_obj.Insert(L"appLaunchTimestamp", JsonValue::CreateStringValue(launch_time_stamp)); + json_obj.Insert(L"architecture", JsonValue::CreateStringValue(m_arch)); + + // Write report specific data + append_additional_data(json_obj); + + json_obj.Insert(L"device", device_info.to_json()); + + return json_obj; + } + + static void add_device_metadata(dictionary& properties) noexcept + { + properties.insert( + properties.end(), + { + std::pair(L"AvailableMemory", winrt::to_hstring((float)MemoryManager::AppMemoryUsageLimit() / 1024 / 1024)), + std::pair(L"OSArchitecture", to_hstring(Package::Current().Id().Architecture())), + std::pair(L"OSVersion", device_info.osbuild()), + std::pair(L"IsDesktopExtension", L"True"), + std::pair(L"IsElevated", is_elevated_process() ? L"True" : L"False") + } + ); + } + +protected: + using hstring = winrt::hstring; + using json_array = winrt::Windows::Data::Json::JsonArray; + + report() noexcept + { + std::wstring id = winrt::to_hstring(winrt::Windows::Foundation::GuidHelper::CreateNewGuid()).c_str(); + id.erase(0, id.find_first_not_of('{')).erase(id.find_last_not_of('}') + 1); + m_id = id; + } + + report(report const& other) noexcept : + m_id(other.m_id), + m_process(other.m_process), + m_arch(other.m_arch), + m_timestamp(other.m_timestamp) + { + } + + report(report&&) noexcept = default; + + virtual hstring type() const noexcept + { + return L""; + } + + virtual void append_additional_data(json_object& /* writer */) const noexcept + { + // override in child classes + } + + hstring m_id; + hstring m_process = L"Notepads32.exe"; + hstring m_arch = to_hstring(Package::Current().Id().Architecture()); + hstring m_timestamp = logger::get_utc_time_stamp(APP_CENTER_FORMAT); + +private: + using Package = winrt::Windows::ApplicationModel::Package; + using JsonValue = winrt::Windows::Data::Json::JsonValue; + using MemoryManager = winrt::Windows::System::MemoryManager; + using ProcessorArchitecture = winrt::Windows::System::ProcessorArchitecture; + + static winrt::hstring to_hstring(ProcessorArchitecture arch) noexcept + { + switch (arch) + { + case ProcessorArchitecture::Arm: + return L"Arm"; + case ProcessorArchitecture::Arm64: + return L"Arm64"; + case ProcessorArchitecture::X86OnArm64: + return L"X86OnArm64"; + case ProcessorArchitecture::X86: + return L"X86"; + case ProcessorArchitecture::X64: + return L"X64"; + case ProcessorArchitecture::Neutral: + return L"Neutral"; + default: + return L"Unknown"; + } + } +}; \ No newline at end of file diff --git a/src/Notepads.DesktopExtension/settings_key.h b/src/Notepads.DesktopExtension/settings_key.h new file mode 100644 index 000000000..e669ed41a --- /dev/null +++ b/src/Notepads.DesktopExtension/settings_key.h @@ -0,0 +1,28 @@ +#pragma once +#include "pch.h" + +struct settings_key +{ + using IInspectable = winrt::Windows::Foundation::IInspectable; + using ApplicationData = winrt::Windows::Storage::ApplicationData; + + static IInspectable read(winrt::hstring key) noexcept + { + return ApplicationData::Current().LocalSettings().Values().TryLookup(key); + } + + static void write(winrt::hstring key, IInspectable data) noexcept + { + ApplicationData::Current().LocalSettings().Values().Insert(key, data); + } + + static void signal_changed() noexcept + { + ApplicationData::Current().SignalDataChanged(); + } + +private: + settings_key() noexcept = default; + settings_key(settings_key&&) noexcept = default; + settings_key(settings_key const& other) noexcept = default; +}; \ No newline at end of file diff --git a/src/Notepads.Package/Notepads.Package.wapproj b/src/Notepads.Package/Notepads.Package.wapproj new file mode 100644 index 000000000..4441e7297 --- /dev/null +++ b/src/Notepads.Package/Notepads.Package.wapproj @@ -0,0 +1,92 @@ + + + + 15.0 + + + + Debug + x86 + + + Release + x86 + + + Production + x86 + + + Debug + x64 + + + Release + x64 + + + Production + x64 + + + Debug + ARM + + + Release + ARM + + + Production + ARM + + + Debug + ARM64 + + + Release + ARM64 + + + Production + ARM64 + + + + $(MSBuildExtensionsPath)\Microsoft\DesktopBridge\ + + + + f451c0eb-4f7f-47d4-bcce-b0e265909dd9 + 10.0.19041.0 + 10.0.17763.0 + en-US + ..\Notepads\Notepads.csproj + False + Always + 1 + OnApplicationRun + False + True + x86|x64|arm64|arm + + + + Designer + + + + + + + + + + AppCenterSecret=$(AppCenterSecret) + + + AppCenterSecret=$(AppCenterSecret) + + + \ No newline at end of file diff --git a/src/Notepads.sln b/src/Notepads.sln index fad9f03e9..46cea285d 100644 --- a/src/Notepads.sln +++ b/src/Notepads.sln @@ -15,64 +15,160 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution .editorconfig = .editorconfig EndProjectSection EndProject +Project("{C7167F0D-BC9F-4E6E-AFE1-012C56B48DB5}") = "Notepads.Package", "Notepads.Package\Notepads.Package.wapproj", "{F451C0EB-4F7F-47D4-BCCE-B0E265909DD9}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Notepads.DesktopExtension", "Notepads.DesktopExtension\Notepads.DesktopExtension.vcxproj", "{0A3B698E-6ADD-4C9E-9741-08F8AE576B2E}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Notepads.Core", "Notepads.Core\Notepads.Core.vcxproj", "{022DC661-4FA2-4264-A869-5A560FB4CC92}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|ARM = Debug|ARM Debug|ARM64 = Debug|ARM64 Debug|x64 = Debug|x64 Debug|x86 = Debug|x86 - Release|ARM64 = Release|ARM64 - Release|x64 = Release|x64 - Release|x86 = Release|x86 + Production|ARM = Production|ARM Production|ARM64 = Production|ARM64 Production|x64 = Production|x64 Production|x86 = Production|x86 + Release|ARM = Release|ARM + Release|ARM64 = Release|ARM64 + Release|x64 = Release|x64 + Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution + {99274932-9E86-480C-8142-38525F80007D}.Debug|ARM.ActiveCfg = Debug|ARM + {99274932-9E86-480C-8142-38525F80007D}.Debug|ARM.Build.0 = Debug|ARM {99274932-9E86-480C-8142-38525F80007D}.Debug|ARM64.ActiveCfg = Debug|ARM64 {99274932-9E86-480C-8142-38525F80007D}.Debug|ARM64.Build.0 = Debug|ARM64 - {99274932-9E86-480C-8142-38525F80007D}.Debug|ARM64.Deploy.0 = Debug|ARM64 {99274932-9E86-480C-8142-38525F80007D}.Debug|x64.ActiveCfg = Debug|x64 {99274932-9E86-480C-8142-38525F80007D}.Debug|x64.Build.0 = Debug|x64 - {99274932-9E86-480C-8142-38525F80007D}.Debug|x64.Deploy.0 = Debug|x64 {99274932-9E86-480C-8142-38525F80007D}.Debug|x86.ActiveCfg = Debug|x86 {99274932-9E86-480C-8142-38525F80007D}.Debug|x86.Build.0 = Debug|x86 - {99274932-9E86-480C-8142-38525F80007D}.Debug|x86.Deploy.0 = Debug|x86 - {99274932-9E86-480C-8142-38525F80007D}.Release|ARM64.ActiveCfg = Release|ARM64 - {99274932-9E86-480C-8142-38525F80007D}.Release|ARM64.Build.0 = Release|ARM64 - {99274932-9E86-480C-8142-38525F80007D}.Release|ARM64.Deploy.0 = Release|ARM64 - {99274932-9E86-480C-8142-38525F80007D}.Release|x64.ActiveCfg = Release|x64 - {99274932-9E86-480C-8142-38525F80007D}.Release|x64.Build.0 = Release|x64 - {99274932-9E86-480C-8142-38525F80007D}.Release|x64.Deploy.0 = Release|x64 - {99274932-9E86-480C-8142-38525F80007D}.Release|x86.ActiveCfg = Release|x86 - {99274932-9E86-480C-8142-38525F80007D}.Release|x86.Build.0 = Release|x86 - {99274932-9E86-480C-8142-38525F80007D}.Release|x86.Deploy.0 = Release|x86 + {99274932-9E86-480C-8142-38525F80007D}.Production|ARM.ActiveCfg = Production|ARM + {99274932-9E86-480C-8142-38525F80007D}.Production|ARM.Build.0 = Production|ARM {99274932-9E86-480C-8142-38525F80007D}.Production|ARM64.ActiveCfg = Production|ARM64 {99274932-9E86-480C-8142-38525F80007D}.Production|ARM64.Build.0 = Production|ARM64 - {99274932-9E86-480C-8142-38525F80007D}.Production|ARM64.Deploy.0 = Production|ARM64 {99274932-9E86-480C-8142-38525F80007D}.Production|x64.ActiveCfg = Production|x64 {99274932-9E86-480C-8142-38525F80007D}.Production|x64.Build.0 = Production|x64 - {99274932-9E86-480C-8142-38525F80007D}.Production|x64.Deploy.0 = Production|x64 {99274932-9E86-480C-8142-38525F80007D}.Production|x86.ActiveCfg = Production|x86 {99274932-9E86-480C-8142-38525F80007D}.Production|x86.Build.0 = Production|x86 - {99274932-9E86-480C-8142-38525F80007D}.Production|x86.Deploy.0 = Production|x86 + {99274932-9E86-480C-8142-38525F80007D}.Release|ARM.ActiveCfg = Release|ARM + {99274932-9E86-480C-8142-38525F80007D}.Release|ARM.Build.0 = Release|ARM + {99274932-9E86-480C-8142-38525F80007D}.Release|ARM64.ActiveCfg = Release|ARM64 + {99274932-9E86-480C-8142-38525F80007D}.Release|ARM64.Build.0 = Release|ARM64 + {99274932-9E86-480C-8142-38525F80007D}.Release|x64.ActiveCfg = Release|x64 + {99274932-9E86-480C-8142-38525F80007D}.Release|x64.Build.0 = Release|x64 + {99274932-9E86-480C-8142-38525F80007D}.Release|x86.ActiveCfg = Release|x86 + {99274932-9E86-480C-8142-38525F80007D}.Release|x86.Build.0 = Release|x86 + {7AA5E631-B663-420E-A08F-002CD81DF855}.Debug|ARM.ActiveCfg = Debug|ARM + {7AA5E631-B663-420E-A08F-002CD81DF855}.Debug|ARM.Build.0 = Debug|ARM {7AA5E631-B663-420E-A08F-002CD81DF855}.Debug|ARM64.ActiveCfg = Debug|ARM64 {7AA5E631-B663-420E-A08F-002CD81DF855}.Debug|ARM64.Build.0 = Debug|ARM64 {7AA5E631-B663-420E-A08F-002CD81DF855}.Debug|x64.ActiveCfg = Debug|x64 {7AA5E631-B663-420E-A08F-002CD81DF855}.Debug|x64.Build.0 = Debug|x64 {7AA5E631-B663-420E-A08F-002CD81DF855}.Debug|x86.ActiveCfg = Debug|x86 {7AA5E631-B663-420E-A08F-002CD81DF855}.Debug|x86.Build.0 = Debug|x86 - {7AA5E631-B663-420E-A08F-002CD81DF855}.Release|ARM64.ActiveCfg = Release|ARM64 - {7AA5E631-B663-420E-A08F-002CD81DF855}.Release|ARM64.Build.0 = Release|ARM64 - {7AA5E631-B663-420E-A08F-002CD81DF855}.Release|x64.ActiveCfg = Release|x64 - {7AA5E631-B663-420E-A08F-002CD81DF855}.Release|x64.Build.0 = Release|x64 - {7AA5E631-B663-420E-A08F-002CD81DF855}.Release|x86.ActiveCfg = Release|x86 - {7AA5E631-B663-420E-A08F-002CD81DF855}.Release|x86.Build.0 = Release|x86 + {7AA5E631-B663-420E-A08F-002CD81DF855}.Production|ARM.ActiveCfg = Production|ARM + {7AA5E631-B663-420E-A08F-002CD81DF855}.Production|ARM.Build.0 = Production|ARM {7AA5E631-B663-420E-A08F-002CD81DF855}.Production|ARM64.ActiveCfg = Production|ARM64 {7AA5E631-B663-420E-A08F-002CD81DF855}.Production|ARM64.Build.0 = Production|ARM64 {7AA5E631-B663-420E-A08F-002CD81DF855}.Production|x64.ActiveCfg = Production|x64 {7AA5E631-B663-420E-A08F-002CD81DF855}.Production|x64.Build.0 = Production|x64 {7AA5E631-B663-420E-A08F-002CD81DF855}.Production|x86.ActiveCfg = Production|x86 {7AA5E631-B663-420E-A08F-002CD81DF855}.Production|x86.Build.0 = Production|x86 + {7AA5E631-B663-420E-A08F-002CD81DF855}.Release|ARM.ActiveCfg = Release|ARM + {7AA5E631-B663-420E-A08F-002CD81DF855}.Release|ARM.Build.0 = Release|ARM + {7AA5E631-B663-420E-A08F-002CD81DF855}.Release|ARM64.ActiveCfg = Release|ARM64 + {7AA5E631-B663-420E-A08F-002CD81DF855}.Release|ARM64.Build.0 = Release|ARM64 + {7AA5E631-B663-420E-A08F-002CD81DF855}.Release|x64.ActiveCfg = Release|x64 + {7AA5E631-B663-420E-A08F-002CD81DF855}.Release|x64.Build.0 = Release|x64 + {7AA5E631-B663-420E-A08F-002CD81DF855}.Release|x86.ActiveCfg = Release|x86 + {7AA5E631-B663-420E-A08F-002CD81DF855}.Release|x86.Build.0 = Release|x86 + {F451C0EB-4F7F-47D4-BCCE-B0E265909DD9}.Debug|ARM.ActiveCfg = Debug|ARM + {F451C0EB-4F7F-47D4-BCCE-B0E265909DD9}.Debug|ARM.Build.0 = Debug|ARM + {F451C0EB-4F7F-47D4-BCCE-B0E265909DD9}.Debug|ARM.Deploy.0 = Debug|ARM + {F451C0EB-4F7F-47D4-BCCE-B0E265909DD9}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {F451C0EB-4F7F-47D4-BCCE-B0E265909DD9}.Debug|ARM64.Build.0 = Debug|ARM64 + {F451C0EB-4F7F-47D4-BCCE-B0E265909DD9}.Debug|ARM64.Deploy.0 = Debug|ARM64 + {F451C0EB-4F7F-47D4-BCCE-B0E265909DD9}.Debug|x64.ActiveCfg = Debug|x64 + {F451C0EB-4F7F-47D4-BCCE-B0E265909DD9}.Debug|x64.Build.0 = Debug|x64 + {F451C0EB-4F7F-47D4-BCCE-B0E265909DD9}.Debug|x64.Deploy.0 = Debug|x64 + {F451C0EB-4F7F-47D4-BCCE-B0E265909DD9}.Debug|x86.ActiveCfg = Debug|x86 + {F451C0EB-4F7F-47D4-BCCE-B0E265909DD9}.Debug|x86.Build.0 = Debug|x86 + {F451C0EB-4F7F-47D4-BCCE-B0E265909DD9}.Debug|x86.Deploy.0 = Debug|x86 + {F451C0EB-4F7F-47D4-BCCE-B0E265909DD9}.Production|ARM.ActiveCfg = Production|ARM + {F451C0EB-4F7F-47D4-BCCE-B0E265909DD9}.Production|ARM.Build.0 = Production|ARM + {F451C0EB-4F7F-47D4-BCCE-B0E265909DD9}.Production|ARM.Deploy.0 = Production|ARM + {F451C0EB-4F7F-47D4-BCCE-B0E265909DD9}.Production|ARM64.ActiveCfg = Production|ARM64 + {F451C0EB-4F7F-47D4-BCCE-B0E265909DD9}.Production|ARM64.Build.0 = Production|ARM64 + {F451C0EB-4F7F-47D4-BCCE-B0E265909DD9}.Production|ARM64.Deploy.0 = Production|ARM64 + {F451C0EB-4F7F-47D4-BCCE-B0E265909DD9}.Production|x64.ActiveCfg = Production|x64 + {F451C0EB-4F7F-47D4-BCCE-B0E265909DD9}.Production|x64.Build.0 = Production|x64 + {F451C0EB-4F7F-47D4-BCCE-B0E265909DD9}.Production|x64.Deploy.0 = Production|x64 + {F451C0EB-4F7F-47D4-BCCE-B0E265909DD9}.Production|x86.ActiveCfg = Production|x86 + {F451C0EB-4F7F-47D4-BCCE-B0E265909DD9}.Production|x86.Build.0 = Production|x86 + {F451C0EB-4F7F-47D4-BCCE-B0E265909DD9}.Production|x86.Deploy.0 = Production|x86 + {F451C0EB-4F7F-47D4-BCCE-B0E265909DD9}.Release|ARM.ActiveCfg = Release|ARM + {F451C0EB-4F7F-47D4-BCCE-B0E265909DD9}.Release|ARM.Build.0 = Release|ARM + {F451C0EB-4F7F-47D4-BCCE-B0E265909DD9}.Release|ARM.Deploy.0 = Release|ARM + {F451C0EB-4F7F-47D4-BCCE-B0E265909DD9}.Release|ARM64.ActiveCfg = Release|ARM64 + {F451C0EB-4F7F-47D4-BCCE-B0E265909DD9}.Release|ARM64.Build.0 = Release|ARM64 + {F451C0EB-4F7F-47D4-BCCE-B0E265909DD9}.Release|ARM64.Deploy.0 = Release|ARM64 + {F451C0EB-4F7F-47D4-BCCE-B0E265909DD9}.Release|x64.ActiveCfg = Release|x64 + {F451C0EB-4F7F-47D4-BCCE-B0E265909DD9}.Release|x64.Build.0 = Release|x64 + {F451C0EB-4F7F-47D4-BCCE-B0E265909DD9}.Release|x64.Deploy.0 = Release|x64 + {F451C0EB-4F7F-47D4-BCCE-B0E265909DD9}.Release|x86.ActiveCfg = Release|x86 + {F451C0EB-4F7F-47D4-BCCE-B0E265909DD9}.Release|x86.Build.0 = Release|x86 + {F451C0EB-4F7F-47D4-BCCE-B0E265909DD9}.Release|x86.Deploy.0 = Release|x86 + {0A3B698E-6ADD-4C9E-9741-08F8AE576B2E}.Debug|ARM.ActiveCfg = Debug|ARM + {0A3B698E-6ADD-4C9E-9741-08F8AE576B2E}.Debug|ARM.Build.0 = Debug|ARM + {0A3B698E-6ADD-4C9E-9741-08F8AE576B2E}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {0A3B698E-6ADD-4C9E-9741-08F8AE576B2E}.Debug|ARM64.Build.0 = Debug|ARM64 + {0A3B698E-6ADD-4C9E-9741-08F8AE576B2E}.Debug|x64.ActiveCfg = Debug|x64 + {0A3B698E-6ADD-4C9E-9741-08F8AE576B2E}.Debug|x64.Build.0 = Debug|x64 + {0A3B698E-6ADD-4C9E-9741-08F8AE576B2E}.Debug|x86.ActiveCfg = Debug|Win32 + {0A3B698E-6ADD-4C9E-9741-08F8AE576B2E}.Debug|x86.Build.0 = Debug|Win32 + {0A3B698E-6ADD-4C9E-9741-08F8AE576B2E}.Production|ARM.ActiveCfg = Production|ARM + {0A3B698E-6ADD-4C9E-9741-08F8AE576B2E}.Production|ARM.Build.0 = Production|ARM + {0A3B698E-6ADD-4C9E-9741-08F8AE576B2E}.Production|ARM64.ActiveCfg = Production|ARM64 + {0A3B698E-6ADD-4C9E-9741-08F8AE576B2E}.Production|ARM64.Build.0 = Production|ARM64 + {0A3B698E-6ADD-4C9E-9741-08F8AE576B2E}.Production|x64.ActiveCfg = Production|x64 + {0A3B698E-6ADD-4C9E-9741-08F8AE576B2E}.Production|x64.Build.0 = Production|x64 + {0A3B698E-6ADD-4C9E-9741-08F8AE576B2E}.Production|x86.ActiveCfg = Production|Win32 + {0A3B698E-6ADD-4C9E-9741-08F8AE576B2E}.Production|x86.Build.0 = Production|Win32 + {0A3B698E-6ADD-4C9E-9741-08F8AE576B2E}.Release|ARM.ActiveCfg = Release|ARM + {0A3B698E-6ADD-4C9E-9741-08F8AE576B2E}.Release|ARM.Build.0 = Release|ARM + {0A3B698E-6ADD-4C9E-9741-08F8AE576B2E}.Release|ARM64.ActiveCfg = Release|ARM64 + {0A3B698E-6ADD-4C9E-9741-08F8AE576B2E}.Release|ARM64.Build.0 = Release|ARM64 + {0A3B698E-6ADD-4C9E-9741-08F8AE576B2E}.Release|x64.ActiveCfg = Release|x64 + {0A3B698E-6ADD-4C9E-9741-08F8AE576B2E}.Release|x64.Build.0 = Release|x64 + {0A3B698E-6ADD-4C9E-9741-08F8AE576B2E}.Release|x86.ActiveCfg = Release|Win32 + {0A3B698E-6ADD-4C9E-9741-08F8AE576B2E}.Release|x86.Build.0 = Release|Win32 + {022DC661-4FA2-4264-A869-5A560FB4CC92}.Debug|ARM.ActiveCfg = Debug|ARM + {022DC661-4FA2-4264-A869-5A560FB4CC92}.Debug|ARM.Build.0 = Debug|ARM + {022DC661-4FA2-4264-A869-5A560FB4CC92}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {022DC661-4FA2-4264-A869-5A560FB4CC92}.Debug|ARM64.Build.0 = Debug|ARM64 + {022DC661-4FA2-4264-A869-5A560FB4CC92}.Debug|x64.ActiveCfg = Debug|x64 + {022DC661-4FA2-4264-A869-5A560FB4CC92}.Debug|x64.Build.0 = Debug|x64 + {022DC661-4FA2-4264-A869-5A560FB4CC92}.Debug|x86.ActiveCfg = Debug|Win32 + {022DC661-4FA2-4264-A869-5A560FB4CC92}.Debug|x86.Build.0 = Debug|Win32 + {022DC661-4FA2-4264-A869-5A560FB4CC92}.Production|ARM.ActiveCfg = Production|ARM + {022DC661-4FA2-4264-A869-5A560FB4CC92}.Production|ARM.Build.0 = Production|ARM + {022DC661-4FA2-4264-A869-5A560FB4CC92}.Production|ARM64.ActiveCfg = Production|ARM64 + {022DC661-4FA2-4264-A869-5A560FB4CC92}.Production|ARM64.Build.0 = Production|ARM64 + {022DC661-4FA2-4264-A869-5A560FB4CC92}.Production|x64.ActiveCfg = Production|x64 + {022DC661-4FA2-4264-A869-5A560FB4CC92}.Production|x64.Build.0 = Production|x64 + {022DC661-4FA2-4264-A869-5A560FB4CC92}.Production|x86.ActiveCfg = Production|Win32 + {022DC661-4FA2-4264-A869-5A560FB4CC92}.Production|x86.Build.0 = Production|Win32 + {022DC661-4FA2-4264-A869-5A560FB4CC92}.Release|ARM.ActiveCfg = Release|ARM + {022DC661-4FA2-4264-A869-5A560FB4CC92}.Release|ARM.Build.0 = Release|ARM + {022DC661-4FA2-4264-A869-5A560FB4CC92}.Release|ARM64.ActiveCfg = Release|ARM64 + {022DC661-4FA2-4264-A869-5A560FB4CC92}.Release|ARM64.Build.0 = Release|ARM64 + {022DC661-4FA2-4264-A869-5A560FB4CC92}.Release|x64.ActiveCfg = Release|x64 + {022DC661-4FA2-4264-A869-5A560FB4CC92}.Release|x64.Build.0 = Release|x64 + {022DC661-4FA2-4264-A869-5A560FB4CC92}.Release|x86.ActiveCfg = Release|Win32 + {022DC661-4FA2-4264-A869-5A560FB4CC92}.Release|x86.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/Notepads/App.xaml.cs b/src/Notepads/App.xaml.cs index 11e2edf97..bf713de47 100644 --- a/src/Notepads/App.xaml.cs +++ b/src/Notepads/App.xaml.cs @@ -31,6 +31,7 @@ sealed partial class App : Application public static bool IsPrimaryInstance = false; public static bool IsGameBarWidget = false; + // Notepads GitHub CD workflow will swap null with production value getting from Github Secrets private const string AppCenterSecret = null; public static Mutex InstanceHandlerMutex { get; set; } @@ -65,6 +66,7 @@ public App() InitializeComponent(); Suspending += OnSuspending; + LeavingBackground += OnLeavingBackground; } /// @@ -107,7 +109,6 @@ private async Task ActivateAsync(IActivatedEventArgs e) { { "OSArchitecture", SystemInformation.OperatingSystemArchitecture.ToString() }, { "OSVersion", $"{SystemInformation.OperatingSystemVersion.Major}.{SystemInformation.OperatingSystemVersion.Minor}.{SystemInformation.OperatingSystemVersion.Build}" }, - { "UseWindowsTheme", ThemeSettingsService.UseWindowsTheme.ToString() }, { "ThemeMode", ThemeSettingsService.ThemeMode.ToString() }, { "UseWindowsAccentColor", ThemeSettingsService.UseWindowsAccentColor.ToString() }, { "AppBackgroundTintOpacity", $"{(int) (ThemeSettingsService.AppBackgroundPanelTintOpacity * 10.0) * 10}" }, @@ -219,6 +220,18 @@ private void OnSuspending(object sender, SuspendingEventArgs e) deferral.Complete(); } + /// + /// Occurs when the app moves to foreground from background. + /// Pending changes to the UI is made before app comes to focus. + /// + /// The source of the leaving background request. + /// Details about the leaving background request. + private void OnLeavingBackground(object sender, LeavingBackgroundEventArgs e) + { + ThemeSettingsService.Initialize(true); + AppSettingsService.Initialize(true); + } + // Occurs when an exception is not handled on the UI thread. private static void OnUnhandledException(object sender, Windows.UI.Xaml.UnhandledExceptionEventArgs e) { @@ -241,7 +254,8 @@ private static void OnUnhandledException(object sender, Windows.UI.Xaml.Unhandle $"Exception: {e.Exception}, " + $"Message: {e.Message}, " + $"InnerException: {e.Exception?.InnerException}, " + - $"InnerExceptionMessage: {e.Exception?.InnerException?.Message}", + $"InnerExceptionMessage: {e.Exception?.InnerException?.Message}, " + + $"IsDesktopExtension: {false}", "UnhandledException"); Analytics.TrackEvent("OnUnhandledException", diagnosticInfo); diff --git a/src/Notepads/Controls/Dialog/LaunchElevatedExtensionDialog.cs b/src/Notepads/Controls/Dialog/LaunchElevatedExtensionDialog.cs new file mode 100644 index 000000000..64f6c7fe6 --- /dev/null +++ b/src/Notepads/Controls/Dialog/LaunchElevatedExtensionDialog.cs @@ -0,0 +1,30 @@ +namespace Notepads.Controls.Dialog +{ + using System; + using Notepads.Services; + using Windows.ApplicationModel.Resources; + + public class LaunchElevatedProcessDialog : NotepadsDialog + { + public LaunchElevatedProcessDialog(ElevatedOperationType type, string fileName, Action confirmedAction, Action closeAction) + { + if (type == ElevatedOperationType.Save) + { + Title = ResourceLoader.GetString("LaunchElevatedProcessDialog_SaveFailed_Title"); + Content = string.Format(ResourceLoader.GetString("LaunchElevatedProcessDialog_SaveFailed_Content"), fileName); + CloseButtonText = ResourceLoader.GetString("LaunchElevatedProcessDialog_SaveFailed_CloseButtonText"); + } + else + { + Title = ResourceLoader.GetString("LaunchElevatedProcessDialog_RenameFailed_Title"); + Content = string.Format(ResourceLoader.GetString("LaunchElevatedProcessDialog_RenameFailed_Content"), fileName); + CloseButtonText = ResourceLoader.GetString("LaunchElevatedProcessDialog_RenameFailed_CloseButtonText"); + } + + PrimaryButtonText = ResourceLoader.GetString("LaunchElevatedProcessDialog_PrimaryButtonText"); + + if (confirmedAction != null) PrimaryButtonClick += (dialog, args) => { confirmedAction(); }; + if (closeAction != null) CloseButtonClick += (dialog, args) => { closeAction(); }; + } + } +} diff --git a/src/Notepads/Controls/Dialog/NotepadsDialog.cs b/src/Notepads/Controls/Dialog/NotepadsDialog.cs index bb27e1b9c..73c82d856 100644 --- a/src/Notepads/Controls/Dialog/NotepadsDialog.cs +++ b/src/Notepads/Controls/Dialog/NotepadsDialog.cs @@ -17,8 +17,8 @@ public class NotepadsDialog : ContentDialog public NotepadsDialog() { - RequestedTheme = ThemeSettingsService.ThemeMode; - Background = ThemeSettingsService.ThemeMode == ElementTheme.Dark + RequestedTheme = ThemeSettingsService.GetActualTheme(ThemeSettingsService.ThemeMode); + Background = RequestedTheme == ElementTheme.Dark ? _darkModeBackgroundBrush : _lightModeBackgroundBrush; diff --git a/src/Notepads/Controls/TextEditor/TextEditor.xaml.cs b/src/Notepads/Controls/TextEditor/TextEditor.xaml.cs index b85923151..fd505950f 100644 --- a/src/Notepads/Controls/TextEditor/TextEditor.xaml.cs +++ b/src/Notepads/Controls/TextEditor/TextEditor.xaml.cs @@ -294,18 +294,40 @@ await Dispatcher.CallOnUIThreadAsync(() => public async Task RenameAsync(string newFileName) { + var result = false; + if (EditingFile == null) { FileNamePlaceholder = newFileName; + result = true; } else { - await EditingFile.RenameAsync(newFileName); + try + { + await EditingFile.RenameAsync(newFileName); + result = true; + } + catch (UnauthorizedAccessException ex) + { + if (!DesktopExtensionService.ShouldUseDesktopExtension) throw ex; + + var file = await DesktopExtensionService.RenameFileAsAdmin(EditingFile, newFileName); + if (file != null) + { + EditingFile = file; + result = true; + } + } } - UpdateDocumentInfo(); + if (result) + { + UpdateDocumentInfo(); - FileRenamed?.Invoke(this, EventArgs.Empty); + FileRenamed?.Invoke(this, EventArgs.Empty); + NotificationCenter.Instance.PostNotification(_resourceLoader.GetString("TextEditor_NotificationMsg_FileRenamed"), 1500); + } } public string GetText() diff --git a/src/Notepads/Core/TabContextFlyout.cs b/src/Notepads/Core/TabContextFlyout.cs index 1c3a61917..0eabf669a 100644 --- a/src/Notepads/Core/TabContextFlyout.cs +++ b/src/Notepads/Core/TabContextFlyout.cs @@ -257,7 +257,6 @@ private MenuFlyoutItem Rename { await _textEditor.RenameAsync(newFilename); _notepadsCore.FocusOnSelectedTextEditor(); - NotificationCenter.Instance.PostNotification(_resourceLoader.GetString("TextEditor_NotificationMsg_FileRenamed"), 1500); } catch (Exception ex) { diff --git a/src/Notepads/Notepads.csproj b/src/Notepads/Notepads.csproj index c8624d420..ea86e0e0c 100644 --- a/src/Notepads/Notepads.csproj +++ b/src/Notepads/Notepads.csproj @@ -23,7 +23,7 @@ OnApplicationRun False True - x86|x64|arm64 + x86|x64|arm64|arm Resource\MiddleClickScrolling-CursorType.res -dev @@ -73,7 +73,6 @@ false prompt true - true bin\ARM64\Release\ @@ -99,6 +98,41 @@ true true + + true + bin\ARM\Debug\ + DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP;DISABLE_XAML_GENERATED_MAIN + ;2008 + full + ARM + false + prompt + true + + + bin\ARM\Release\ + TRACE;NETFX_CORE;WINDOWS_UWP;DISABLE_XAML_GENERATED_MAIN + true + ;2008 + pdbonly + ARM + false + prompt + true + true + + + bin\ARM\Production\ + TRACE;NETFX_CORE;WINDOWS_UWP;DISABLE_XAML_GENERATED_MAIN + true + ;2008 + pdbonly + ARM + false + prompt + true + true + true bin\x64\Debug\ @@ -148,6 +182,7 @@ + @@ -174,7 +209,9 @@ + + @@ -443,6 +480,10 @@ {7aa5e631-b663-420e-a08f-002cd81df855} Notepads.Controls + + {022dc661-4fa2-4264-a869-5a560fb4cc92} + Notepads.Core + @@ -450,16 +491,21 @@ + + + Windows Desktop Extensions for the UWP + + 14.0 - - + \ No newline at end of file diff --git a/src/Notepads/Package.appxmanifest b/src/Notepads/Package.appxmanifest index bf8c3c847..6d50d24be 100644 --- a/src/Notepads/Package.appxmanifest +++ b/src/Notepads/Package.appxmanifest @@ -6,28 +6,30 @@ xmlns:uap4="http://schemas.microsoft.com/appx/manifest/uap/windows10/4" xmlns:uap5="http://schemas.microsoft.com/appx/manifest/uap/windows10/5" xmlns:uap10="http://schemas.microsoft.com/appx/manifest/uap/windows10/10" + xmlns:desktop="http://schemas.microsoft.com/appx/manifest/desktop/windows10" xmlns:desktop4="http://schemas.microsoft.com/appx/manifest/desktop/windows10/4" xmlns:iot2="http://schemas.microsoft.com/appx/manifest/iot/windows10/2" xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities" - IgnorableNamespaces="mp uap uap5 uap10 desktop4 iot2 rescap"> - + IgnorableNamespaces="mp uap uap5 uap10 desktop desktop4 iot2 rescap"> + - + Notepads App Jackie Liu Assets\StoreLogo.png - + + - + - + - + @@ -64,6 +66,12 @@ + + + + + + @@ -1175,9 +1183,12 @@ - + + + + \ No newline at end of file diff --git a/src/Notepads/Services/AppSettingsService.cs b/src/Notepads/Services/AppSettingsService.cs index eb7bab0aa..967a9206a 100644 --- a/src/Notepads/Services/AppSettingsService.cs +++ b/src/Notepads/Services/AppSettingsService.cs @@ -125,8 +125,7 @@ public static Encoding EditorDefaultEncoding if (value is UTF8Encoding) { - ApplicationSettingsStore.Write(SettingsKey.EditorDefaultUtf8EncoderShouldEmitByteOrderMarkBool, - Equals(value, new UTF8Encoding(true))); + ApplicationSettingsStore.Write(SettingsKey.EditorDefaultUtf8EncoderShouldEmitByteOrderMarkBool, Equals(value, new UTF8Encoding(true))); } OnDefaultEncodingChanged?.Invoke(null, value); @@ -284,36 +283,41 @@ public static bool IsSmartCopyEnabled } } - public static void Initialize() + public static void Initialize(bool shouldInvokeChangedEvent = false) { - InitializeFontSettings(); + InitializeFontFamilySettings(shouldInvokeChangedEvent); + InitializeFontSizeSettings(shouldInvokeChangedEvent); + InitializeFontStyleSettings(shouldInvokeChangedEvent); + InitializeFontWeightSettings(shouldInvokeChangedEvent); - InitializeTextWrappingSettings(); + InitializeTextWrappingSettings(shouldInvokeChangedEvent); - InitializeSpellingSettings(); + InitializeSpellingSettings(shouldInvokeChangedEvent); - InitializeDisplaySettings(); + InitializeDisplayLineHighlighterSettings(shouldInvokeChangedEvent); + InitializeDisplayLineNumbersSettings(shouldInvokeChangedEvent); - InitializeSmartCopySettings(); + InitializeSmartCopySettings(shouldInvokeChangedEvent); - InitializeLineEndingSettings(); + InitializeLineEndingSettings(shouldInvokeChangedEvent); - InitializeEncodingSettings(); + InitializeEncodingSettings(shouldInvokeChangedEvent); - InitializeDecodingSettings(); + InitializeDecodingSettings(shouldInvokeChangedEvent); - InitializeTabIndentsSettings(); + InitializeTabIndentsSettings(shouldInvokeChangedEvent); - InitializeSearchEngineSettings(); + InitializeSearchEngineSettings(shouldInvokeChangedEvent); + InitializeCustomSearchUrlSettings(shouldInvokeChangedEvent); - InitializeStatusBarSettings(); + InitializeStatusBarSettings(shouldInvokeChangedEvent); - InitializeSessionSnapshotSettings(); + InitializeSessionSnapshotSettings(shouldInvokeChangedEvent); - InitializeAppOpeningPreferencesSettings(); + InitializeAppOpeningPreferencesSettings(shouldInvokeChangedEvent); } - private static void InitializeStatusBarSettings() + public static void InitializeStatusBarSettings(bool invokeChangedEvent = false) { if (ApplicationSettingsStore.Read(SettingsKey.EditorShowStatusBarBool) is bool showStatusBar) { @@ -323,9 +327,11 @@ private static void InitializeStatusBarSettings() { _showStatusBar = true; } + + if (invokeChangedEvent) OnStatusBarVisibilityChanged?.Invoke(null, _showStatusBar); } - private static void InitializeSessionSnapshotSettings() + private static void InitializeSessionSnapshotSettings(bool invokeChangedEvent = false) { // We should disable session snapshot feature on multi instances if (!App.IsPrimaryInstance) @@ -349,7 +355,7 @@ private static void InitializeSessionSnapshotSettings() } } - private static void InitializeLineEndingSettings() + public static void InitializeLineEndingSettings(bool invokeChangedEvent = false) { if (ApplicationSettingsStore.Read(SettingsKey.EditorDefaultLineEndingStr) is string lineEndingStr && Enum.TryParse(typeof(LineEnding), lineEndingStr, out var lineEnding)) @@ -360,9 +366,11 @@ private static void InitializeLineEndingSettings() { _editorDefaultLineEnding = LineEnding.Crlf; } + + if (invokeChangedEvent) OnDefaultLineEndingChanged?.Invoke(null, _editorDefaultLineEnding); } - private static void InitializeTextWrappingSettings() + public static void InitializeTextWrappingSettings(bool invokeChangedEvent = false) { if (ApplicationSettingsStore.Read(SettingsKey.EditorDefaultTextWrappingStr) is string textWrappingStr && Enum.TryParse(typeof(TextWrapping), textWrappingStr, out var textWrapping)) @@ -373,9 +381,11 @@ private static void InitializeTextWrappingSettings() { _editorDefaultTextWrapping = TextWrapping.NoWrap; } + + if (invokeChangedEvent) OnDefaultTextWrappingChanged?.Invoke(null, _editorDefaultTextWrapping); } - private static void InitializeSpellingSettings() + public static void InitializeSpellingSettings(bool invokeChangedEvent = false) { if (ApplicationSettingsStore.Read(SettingsKey.EditorHighlightMisspelledWordsBool) is bool highlightMisspelledWords) { @@ -385,9 +395,11 @@ private static void InitializeSpellingSettings() { _isHighlightMisspelledWordsEnabled = false; } + + if (invokeChangedEvent) OnHighlightMisspelledWordsChanged?.Invoke(null, _isHighlightMisspelledWordsEnabled); } - private static void InitializeDisplaySettings() + public static void InitializeDisplayLineHighlighterSettings(bool invokeChangedEvent = false) { if (ApplicationSettingsStore.Read(SettingsKey.EditorDefaultLineHighlighterViewStateBool) is bool displayLineHighlighter) { @@ -398,6 +410,11 @@ private static void InitializeDisplaySettings() _editorDisplayLineHighlighter = true; } + if (invokeChangedEvent) OnDefaultLineHighlighterViewStateChanged?.Invoke(null, _editorDisplayLineHighlighter); + } + + public static void InitializeDisplayLineNumbersSettings(bool invokeChangedEvent = false) + { if (ApplicationSettingsStore.Read(SettingsKey.EditorDefaultDisplayLineNumbersBool) is bool displayLineNumbers) { _displayLineNumbers = displayLineNumbers; @@ -406,9 +423,11 @@ private static void InitializeDisplaySettings() { _displayLineNumbers = true; } + + if (invokeChangedEvent) OnDefaultDisplayLineNumbersViewStateChanged?.Invoke(null, _displayLineNumbers); } - private static void InitializeSmartCopySettings() + public static void InitializeSmartCopySettings(bool invokeChangedEvent = false) { if (ApplicationSettingsStore.Read(SettingsKey.EditorEnableSmartCopyBool) is bool enableSmartCopy) { @@ -420,7 +439,7 @@ private static void InitializeSmartCopySettings() } } - private static void InitializeEncodingSettings() + public static void InitializeEncodingSettings(bool invokeChangedEvent = false) { Encoding.RegisterProvider(System.Text.CodePagesEncodingProvider.Instance); @@ -446,9 +465,11 @@ private static void InitializeEncodingSettings() { _editorDefaultEncoding = new UTF8Encoding(false); } + + if (invokeChangedEvent) OnDefaultEncodingChanged?.Invoke(null, _editorDefaultEncoding); } - private static void InitializeDecodingSettings() + public static void InitializeDecodingSettings(bool invokeChangedEvent = false) { if (ApplicationSettingsStore.Read(SettingsKey.EditorDefaultDecodingCodePageInt) is int decodingCodePage) { @@ -480,7 +501,7 @@ private static void InitializeDecodingSettings() } } - private static void InitializeTabIndentsSettings() + public static void InitializeTabIndentsSettings(bool invokeChangedEvent = false) { if (ApplicationSettingsStore.Read(SettingsKey.EditorDefaultTabIndentsInt) is int tabIndents) { @@ -490,9 +511,11 @@ private static void InitializeTabIndentsSettings() { _editorDefaultTabIndents = -1; } + + if (invokeChangedEvent) OnDefaultTabIndentsChanged?.Invoke(null, _editorDefaultTabIndents); } - private static void InitializeSearchEngineSettings() + public static void InitializeSearchEngineSettings(bool invokeChangedEvent = false) { if (ApplicationSettingsStore.Read(SettingsKey.EditorDefaultSearchEngineStr) is string searchEngineStr && Enum.TryParse(typeof(SearchEngine), searchEngineStr, out var searchEngine)) @@ -503,7 +526,10 @@ private static void InitializeSearchEngineSettings() { _editorDefaultSearchEngine = SearchEngine.Bing; } + } + public static void InitializeCustomSearchUrlSettings(bool invokeChangedEvent = false) + { if (ApplicationSettingsStore.Read(SettingsKey.EditorCustomMadeSearchUrlStr) is string customMadeSearchUrl) { _editorCustomMadeSearchUrl = customMadeSearchUrl; @@ -514,7 +540,7 @@ private static void InitializeSearchEngineSettings() } } - private static void InitializeFontSettings() + public static void InitializeFontFamilySettings(bool invokeChangedEvent = false) { if (ApplicationSettingsStore.Read(SettingsKey.EditorFontFamilyStr) is string fontFamily) { @@ -525,6 +551,11 @@ private static void InitializeFontSettings() _editorFontFamily = "Consolas"; } + if (invokeChangedEvent) OnFontFamilyChanged?.Invoke(null, _editorFontFamily); + } + + public static void InitializeFontSizeSettings(bool invokeChangedEvent = false) + { if (ApplicationSettingsStore.Read(SettingsKey.EditorFontSizeInt) is int fontSize) { _editorFontSize = fontSize; @@ -534,6 +565,11 @@ private static void InitializeFontSettings() _editorFontSize = 14; } + if (invokeChangedEvent) OnFontSizeChanged?.Invoke(null, _editorFontSize); + } + + public static void InitializeFontStyleSettings(bool invokeChangedEvent = false) + { if (ApplicationSettingsStore.Read(SettingsKey.EditorFontStyleStr) is string fontStyleStr && Enum.TryParse(typeof(FontStyle), fontStyleStr, out var fontStyle)) { @@ -544,6 +580,11 @@ private static void InitializeFontSettings() _editorFontStyle = FontStyle.Normal; } + if (invokeChangedEvent) OnFontStyleChanged?.Invoke(null, _editorFontStyle); + } + + public static void InitializeFontWeightSettings(bool invokeChangedEvent = false) + { if (ApplicationSettingsStore.Read(SettingsKey.EditorFontWeightUshort) is ushort fontWeight) { _editorFontWeight = new FontWeight() @@ -555,9 +596,11 @@ private static void InitializeFontSettings() { _editorFontWeight = FontWeights.Normal; } + + if (invokeChangedEvent) OnFontWeightChanged?.Invoke(null, _editorFontWeight); } - private static void InitializeAppOpeningPreferencesSettings() + public static void InitializeAppOpeningPreferencesSettings(bool invokeChangedEvent = false) { if (ApplicationSettingsStore.Read(SettingsKey.AlwaysOpenNewWindowBool) is bool alwaysOpenNewWindow) { diff --git a/src/Notepads/Services/DesktopExtensionService.cs b/src/Notepads/Services/DesktopExtensionService.cs new file mode 100644 index 000000000..43fa88931 --- /dev/null +++ b/src/Notepads/Services/DesktopExtensionService.cs @@ -0,0 +1,284 @@ +namespace Notepads.Services +{ + using Microsoft.AppCenter; + using Notepads.Controls.Dialog; + using Notepads.Core; + using Notepads.Settings; + using Notepads.Utilities; + using System; + using System.IO; + using System.IO.MemoryMappedFiles; + using System.IO.Pipes; + using System.Security.Principal; + using System.Threading; + using System.Threading.Tasks; + using Windows.ApplicationModel; + using Windows.ApplicationModel.Resources; + using Windows.Foundation.Metadata; + using Windows.Security.Authentication.Web; + using Windows.Storage; + using Windows.Storage.AccessCache; + + public enum ElevatedOperationType + { + Save, + Rename + } + + public class AdminstratorAccessException : Exception + { + public AdminstratorAccessException():base("Failed to save due to no Adminstration access") { } + + public AdminstratorAccessException(string message) : base(message) { } + public AdminstratorAccessException(string message, Exception innerException) : base(message, innerException) { } + } + + public static class DesktopExtensionService + { + public static readonly bool ShouldUseDesktopExtension = + ApiInformation.IsApiContractPresent("Windows.ApplicationModel.FullTrustAppContract", 1, 0) && + !new WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator); + + private static readonly ResourceLoader _resourceLoader = ResourceLoader.GetForCurrentView(); + + private static Mutex ExtensionProcessLifetimeObject = null; + private static Mutex ElevatedProcessLifetimeObject = null; + + private static EventWaitHandle _extensionUnblockEvent = null; + private static EventWaitHandle _elevatedWriteEvent = null; + private static EventWaitHandle _elevatedRenameEvent = null; + + /// + /// Initializes desktop processes. + /// + public static async void Initialize() + { + if (!ShouldUseDesktopExtension) return; + + if (ExtensionProcessLifetimeObject == null) + { + ExtensionProcessLifetimeObject = new Mutex(false, CoreKey.ExtensionProcessLifetimeObjNameStr); + } + + if (ElevatedProcessLifetimeObject == null) + { + ElevatedProcessLifetimeObject = new Mutex(false, CoreKey.ElevatedProcessLifetimeObjNameStr); + } + + if (_extensionUnblockEvent == null) + { + _extensionUnblockEvent = new EventWaitHandle(false, EventResetMode.ManualReset, CoreKey.ExtensionUnblockEventNameStr); + } + + if (_elevatedWriteEvent == null) + { + _elevatedWriteEvent = new EventWaitHandle(false, EventResetMode.ManualReset, CoreKey.ElevatedWriteEventNameStr); + } + + if (_elevatedRenameEvent == null) + { + _elevatedRenameEvent = new EventWaitHandle(false, EventResetMode.ManualReset, CoreKey.ElevatedRenameEventNameStr); + } + + ApplicationSettingsStore.Write(CoreKey.PackageSidStr, WebAuthenticationBroker.GetCurrentApplicationCallbackUri().Host.ToUpper()); + ApplicationSettingsStore.Write(CoreKey.AppCenterInstallIdStr, (await AppCenter.GetInstallIdAsync())?.ToString()); + + await FullTrustProcessLauncher.LaunchFullTrustProcessForCurrentAppAsync(); + } + + /// + /// Releases resources and closes desktop processes. + /// + public static void Dispose() + { + _extensionUnblockEvent?.Dispose(); + _elevatedWriteEvent?.Dispose(); + _elevatedRenameEvent?.Dispose(); + ExtensionProcessLifetimeObject?.Dispose(); + ElevatedProcessLifetimeObject?.Dispose(); + } + + /// + /// Unblock UWP edited file with desktop component. + /// + /// + /// Only available for legacy Windows 10 desktop. + /// + public static async void UnblockFile(string filePath) + { + if (!ShouldUseDesktopExtension) return; + + using (var pipeStream = new NamedPipeServerStream( + $"Local\\{CoreKey.ExtensionUnblockPipeConnectionNameStr}", + PipeDirection.Out, + NamedPipeServerStream.MaxAllowedServerInstances, + PipeTransmissionMode.Message, + PipeOptions.Asynchronous)) + { + _extensionUnblockEvent.Set(); + // Wait for 100 ms for desktop extension to accept request. + if (!pipeStream.WaitForConnectionAsync().Wait(100)) + { + // If the connection fails, desktop extension is not launched. + // In that case, launch desktop extension. + _extensionUnblockEvent.Reset(); + await FullTrustProcessLauncher.LaunchFullTrustProcessForCurrentAppAsync(); + } + else + { + var pipeWriter = new StreamWriter(pipeStream, new System.Text.UnicodeEncoding(!BitConverter.IsLittleEndian, false)); + + await pipeWriter.WriteAsync(filePath); + await pipeWriter.FlushAsync(); + } + } + } + + /// + /// Launch desktop extension with elevated privilages. + /// + /// + /// Only available for legacy Windows 10 desktop. + /// + public static async Task LaunchElevetedProcess() + { + if (!ShouldUseDesktopExtension) return; + + // Pass the group id that describes the correct parameter for prompting elevated process launch + await FullTrustProcessLauncher.LaunchFullTrustProcessForCurrentAppAsync("Elevate"); + } + + /// + /// Notify user whether launching of eleveted process succeeded. + /// + /// + /// Only available for legacy Windows 10 desktop. + /// + /// Is eleveted process launched. + public static void OnElevetedProcessLaunchRequested(bool isLaunched) + { + if (isLaunched) + { + NotificationCenter.Instance.PostNotification(_resourceLoader.GetString("TextEditor_NotificationMsg_ElevatedProcessLaunched"), 1500); + } + else + { + NotificationCenter.Instance.PostNotification(_resourceLoader.GetString("TextEditor_NotificationMsg_ElevatedProcessLaunchFailed"), 1500); + } + } + + /// + /// Write data to system file. + /// + /// + /// Only available for legacy Windows 10 desktop. + /// + /// Absolute path of the file to write. + /// Data to write. + public static async Task SaveFileAsAdmin(string filePath, byte[] data) + { + if (!ShouldUseDesktopExtension) return; + + using (var pipeStream = new NamedPipeServerStream( + $"Local\\{CoreKey.ElevatedWritePipeConnectionNameStr}", + PipeDirection.InOut, + NamedPipeServerStream.MaxAllowedServerInstances, + PipeTransmissionMode.Message, + PipeOptions.Asynchronous)) + { + _elevatedWriteEvent.Set(); + // Wait for 100 ms for desktop extension to accept request. + if (!pipeStream.WaitForConnectionAsync().Wait(100)) + { + // If the connection fails, desktop extension is not launched with elevated privilages. + // In that case, prompt user to launch desktop extension with elevated privilages. + _elevatedWriteEvent.Reset(); + pipeStream?.Dispose(); + throw new AdminstratorAccessException(); + } + + var pipeReader = new StreamReader(pipeStream, new System.Text.UnicodeEncoding(!BitConverter.IsLittleEndian, false)); + var pipeWriter = new StreamWriter(pipeStream, new System.Text.UnicodeEncoding(!BitConverter.IsLittleEndian, false)); + + var mapName = filePath.Replace(Path.DirectorySeparatorChar, '-'); + + // Create the memory-mapped file and write original file data to be written. + using (var mmf = MemoryMappedFile.CreateOrOpen(mapName, data.Length > 0 ? data.Length : 1, MemoryMappedFileAccess.ReadWrite)) + { + using (var writer = new BinaryWriter(mmf.CreateViewStream())) + { + writer.Write(data); + writer.Flush(); + } + + await pipeWriter.WriteAsync($"{filePath}|{mapName}|{data.Length}"); + await pipeWriter.FlushAsync(); + + // Wait for desktop extension to send response. + if (!"Success".Equals(await pipeReader.ReadLineAsync())) + { + // Promt user to "Save As" if extension failed to save data. + mmf?.Dispose(); + pipeStream?.Dispose(); + throw new UnauthorizedAccessException(); + } + } + } + } + + /// + /// Rename system file. + /// + /// + /// Only available for legacy Windows 10 desktop. + /// + /// File to rename + /// New name + public static async Task RenameFileAsAdmin(StorageFile file, string newName) + { + if (!ShouldUseDesktopExtension) return null; + + using (var pipeStream = new NamedPipeServerStream( + $"Local\\{CoreKey.ElevatedRenamePipeConnectionNameStr}", + PipeDirection.InOut, + NamedPipeServerStream.MaxAllowedServerInstances, + PipeTransmissionMode.Message, + PipeOptions.Asynchronous)) + { + _elevatedRenameEvent.Set(); + // Wait for 100 ms for desktop extension to accept request. + if (!pipeStream.WaitForConnectionAsync().Wait(100)) + { + // If the connection fails, desktop extension is not launched with elevated privilages. + // In that case, prompt user to launch desktop extension with elevated privilages. + _elevatedRenameEvent.Reset(); + + var launchElevatedProcessDialog = new LaunchElevatedProcessDialog( + ElevatedOperationType.Rename, + file.Path, + async () => { await LaunchElevetedProcess(); }, + null); + + await DialogManager.OpenDialogAsync(launchElevatedProcessDialog, awaitPreviousDialog: false); + file = null; + } + else + { + var pipeReader = new StreamReader(pipeStream, new System.Text.UnicodeEncoding(!BitConverter.IsLittleEndian, false)); + var pipeWriter = new StreamWriter(pipeStream, new System.Text.UnicodeEncoding(!BitConverter.IsLittleEndian, false)); + + var token = StorageApplicationPermissions.FutureAccessList.Add(file); + await pipeWriter.WriteAsync($"{token}|{newName}"); + await pipeWriter.FlushAsync(); + + // Wait for desktop extension to send response. + token = await pipeReader.ReadLineAsync(); + file = await StorageApplicationPermissions.FutureAccessList.GetFileAsync(token); + StorageApplicationPermissions.FutureAccessList.Remove(token); + } + } + + return file; + } + } +} diff --git a/src/Notepads/Services/InterInstanceSyncService.cs b/src/Notepads/Services/InterInstanceSyncService.cs new file mode 100644 index 000000000..af595dc10 --- /dev/null +++ b/src/Notepads/Services/InterInstanceSyncService.cs @@ -0,0 +1,84 @@ +namespace Notepads.Services +{ + using Notepads.Core; + using Notepads.Extensions; + using Notepads.Settings; + using Notepads.Views.MainPage; + using System; + using System.Collections.Generic; + using Windows.Storage; + + public static class InterInstanceSyncService + { + private static NotepadsMainPage _notepadsMainPage = null; + + public static readonly string RecentFilesListKey = "BuildOpenRecentButtonSubItems"; + + public static readonly IReadOnlyDictionary> SyncManager = new Dictionary> + { + { SettingsKey.AppBackgroundTintOpacityDouble, ThemeSettingsService.InitializeAppBackgroundPanelTintOpacity }, + { SettingsKey.RequestedThemeStr, ThemeSettingsService.InitializeThemeMode }, + { SettingsKey.UseWindowsAccentColorBool, ThemeSettingsService.InitializeAppAccentColor }, + { SettingsKey.AppAccentColorHexStr, ThemeSettingsService.InitializeAppAccentColor }, + { SettingsKey.CustomAccentColorHexStr, ThemeSettingsService.InitializeCustomAccentColor }, + { SettingsKey.EditorDefaultLineHighlighterViewStateBool, AppSettingsService.InitializeDisplayLineHighlighterSettings }, + { SettingsKey.EditorDefaultDisplayLineNumbersBool, AppSettingsService.InitializeDisplayLineNumbersSettings }, + { SettingsKey.EditorDefaultTabIndentsInt, AppSettingsService.InitializeTabIndentsSettings }, + { SettingsKey.EditorDefaultTextWrappingStr, AppSettingsService.InitializeTextWrappingSettings }, + { SettingsKey.EditorFontFamilyStr, AppSettingsService.InitializeFontFamilySettings }, + { SettingsKey.EditorFontSizeInt, AppSettingsService.InitializeFontSizeSettings }, + { SettingsKey.EditorFontStyleStr, AppSettingsService.InitializeFontStyleSettings }, + { SettingsKey.EditorFontWeightUshort, AppSettingsService.InitializeFontWeightSettings }, + { SettingsKey.EditorHighlightMisspelledWordsBool, AppSettingsService.InitializeSpellingSettings }, + { SettingsKey.EditorDefaultEncodingCodePageInt, AppSettingsService.InitializeEncodingSettings }, + { SettingsKey.EditorDefaultLineEndingStr, AppSettingsService.InitializeLineEndingSettings }, + { SettingsKey.EditorShowStatusBarBool, AppSettingsService.InitializeStatusBarSettings }, + { SettingsKey.EditorCustomMadeSearchUrlStr, AppSettingsService.InitializeCustomSearchUrlSettings }, + { SettingsKey.EditorDefaultDecodingCodePageInt, AppSettingsService.InitializeDecodingSettings }, + { SettingsKey.EditorDefaultSearchEngineStr, AppSettingsService.InitializeSearchEngineSettings }, + { SettingsKey.EditorEnableSmartCopyBool, AppSettingsService.InitializeSmartCopySettings }, + { SettingsKey.AlwaysOpenNewWindowBool, AppSettingsService.InitializeAppOpeningPreferencesSettings }, + { CoreKey.LaunchElevatedProcessFailedStr, DesktopExtensionService.OnElevetedProcessLaunchRequested }, + { CoreKey.LaunchElevatedProcessSuccessStr, DesktopExtensionService.OnElevetedProcessLaunchRequested }, + { RecentFilesListKey, async (permission) => await _notepadsMainPage.BuildOpenRecentButtonSubItems(!permission) } + }; + + public static void Initialize(NotepadsMainPage page) + { + _notepadsMainPage = page; + ApplicationData.Current.DataChanged += Application_OnDataChanged; + } + + private static async void Application_OnDataChanged(ApplicationData sender, object args) + { + if (ApplicationSettingsStore.Read(CoreKey.LastChangedSettingsAppInstanceIdStr) is string lastChangedSettingsAppInstanceIdStr && + lastChangedSettingsAppInstanceIdStr == App.Id.ToString()) + { + return; + } + + if (ApplicationSettingsStore.Read(CoreKey.LastChangedSettingsKeyStr) is string lastChangedSettingsKeyStr && + SyncManager.ContainsKey(lastChangedSettingsKeyStr) && _notepadsMainPage != null) + { + await DispatcherExtensions.CallOnUIThreadAsync(_notepadsMainPage.Dispatcher, () => + { + if (lastChangedSettingsKeyStr != RecentFilesListKey && + lastChangedSettingsKeyStr != CoreKey.LaunchElevatedProcessSuccessStr && + lastChangedSettingsKeyStr != CoreKey.LaunchElevatedProcessFailedStr) + { + _notepadsMainPage.CloseSettingsPane(); + } + + if (lastChangedSettingsKeyStr == CoreKey.LaunchElevatedProcessFailedStr) + { + SyncManager[lastChangedSettingsKeyStr].Invoke(false); + } + else + { + SyncManager[lastChangedSettingsKeyStr].Invoke(true); + } + }); + } + } + } +} diff --git a/src/Notepads/Services/ThemeSettingsService.cs b/src/Notepads/Services/ThemeSettingsService.cs index c1a438b81..b6761feb1 100644 --- a/src/Notepads/Services/ThemeSettingsService.cs +++ b/src/Notepads/Services/ThemeSettingsService.cs @@ -18,32 +18,22 @@ public static class ThemeSettingsService public static event EventHandler OnBackgroundChanged; public static event EventHandler OnAccentColorChanged; - public static ElementTheme ThemeMode { get; set; } - private static readonly UISettings UISettings = new UISettings(); private static readonly ThemeListener ThemeListener = new ThemeListener(); private static Brush _currentAppBackgroundBrush; - private static bool _useWindowsTheme; + private static ElementTheme _themeMode; - public static bool UseWindowsTheme + public static ElementTheme ThemeMode { - get => _useWindowsTheme; + get => _themeMode; set { - if (value != _useWindowsTheme) + if (value != _themeMode) { - _useWindowsTheme = value; - if (value) - { - var currentWindowsTheme = Application.Current.RequestedTheme.ToElementTheme(); - if (ThemeMode != currentWindowsTheme) - { - ThemeMode = currentWindowsTheme; - OnThemeChanged?.Invoke(null, ThemeMode); - } - } - ApplicationSettingsStore.Write(SettingsKey.UseWindowsThemeBool, _useWindowsTheme); + _themeMode = value; + OnThemeChanged?.Invoke(null, value); + ApplicationSettingsStore.Write(SettingsKey.RequestedThemeStr, value.ToString()); } } } @@ -102,18 +92,18 @@ public static Color CustomAccentColor } } - public static void Initialize() + public static void Initialize(bool shouldInvokeChangedEvent = false) { - InitializeThemeMode(); + InitializeThemeMode(shouldInvokeChangedEvent); - InitializeAppAccentColor(); + InitializeAppAccentColor(shouldInvokeChangedEvent); - InitializeCustomAccentColor(); + InitializeCustomAccentColor(shouldInvokeChangedEvent); - InitializeAppBackgroundPanelTintOpacity(); + InitializeAppBackgroundPanelTintOpacity(shouldInvokeChangedEvent); } - private static void InitializeAppAccentColor() + public static void InitializeAppAccentColor(bool invokeChangedEvent = false) { if (ApplicationSettingsStore.Read(SettingsKey.UseWindowsAccentColorBool) is bool useWindowsAccentColor) { @@ -135,9 +125,11 @@ private static void InitializeAppAccentColor() _appAccentColor = accentColorHexStr.ToColor(); } } + + if (invokeChangedEvent) OnAccentColorChanged?.Invoke(null, _appAccentColor); } - private static void InitializeCustomAccentColor() + public static void InitializeCustomAccentColor(bool invokeChangedEvent = false) { if (ApplicationSettingsStore.Read(SettingsKey.CustomAccentColorHexStr) is string customAccentColorHexStr) { @@ -157,7 +149,7 @@ private static void UiSettings_ColorValuesChanged(UISettings sender, object args } } - private static void InitializeAppBackgroundPanelTintOpacity() + public static void InitializeAppBackgroundPanelTintOpacity(bool invokeChangedEvent = false) { if (ApplicationSettingsStore.Read(SettingsKey.AppBackgroundTintOpacityDouble) is double tintOpacity) { @@ -167,51 +159,32 @@ private static void InitializeAppBackgroundPanelTintOpacity() { _appBackgroundPanelTintOpacity = 0.75; } + + if (invokeChangedEvent) OnBackgroundChanged?.Invoke(null, GetAppBackgroundBrush(ThemeMode)); } - private static void InitializeThemeMode() + public static void InitializeThemeMode(bool invokeChangedEvent = false) { - if (ApplicationSettingsStore.Read(SettingsKey.UseWindowsThemeBool) is bool useWindowsTheme) - { - _useWindowsTheme = useWindowsTheme; - } - else - { - _useWindowsTheme = true; - } - ThemeListener.ThemeChanged += ThemeListener_ThemeChanged; - ThemeMode = Application.Current.RequestedTheme.ToElementTheme(); - - if (!UseWindowsTheme) + if (ApplicationSettingsStore.Read(SettingsKey.RequestedThemeStr) is string themeModeStr) { - if (ApplicationSettingsStore.Read(SettingsKey.RequestedThemeStr) is string themeModeStr) + if (Enum.TryParse(typeof(ElementTheme), themeModeStr, out var theme)) { - if (Enum.TryParse(typeof(ElementTheme), themeModeStr, out var theme)) - { - ThemeMode = (ElementTheme)theme; - } + _themeMode = (ElementTheme)theme; } } - } - - private static void ThemeListener_ThemeChanged(ThemeListener sender) - { - if (UseWindowsTheme) + else { - SetTheme(sender.CurrentTheme.ToElementTheme()); + _themeMode = ElementTheme.Default; } + + if (invokeChangedEvent) OnThemeChanged?.Invoke(null, ThemeMode); } - public static void SetTheme(ElementTheme theme) + private static void ThemeListener_ThemeChanged(ThemeListener sender) { - if (ThemeMode != theme) - { - ThemeMode = theme; - ApplicationSettingsStore.Write(SettingsKey.RequestedThemeStr, ThemeMode.ToString()); - OnThemeChanged?.Invoke(null, theme); - } + _themeMode = sender.CurrentTheme.ToElementTheme(); } public static void SetRequestedTheme(Panel backgroundPanel, UIElement currentContent, ApplicationViewTitleBar titleBar) @@ -234,8 +207,10 @@ public static void SetRequestedTheme(Panel backgroundPanel, UIElement currentCon } // Set ContentDialog background dimming color + var themeMode = ThemeMode; + themeMode = GetActualTheme(themeMode); ((SolidColorBrush)Application.Current.Resources["SystemControlPageBackgroundMediumAltMediumBrush"]).Color = - ThemeMode == ElementTheme.Dark ? Color.FromArgb(153, 0, 0, 0) : Color.FromArgb(153, 255, 255, 255); + themeMode == ElementTheme.Dark ? Color.FromArgb(153, 0, 0, 0) : Color.FromArgb(153, 255, 255, 255); if (DialogManager.ActiveDialog != null) { @@ -264,11 +239,20 @@ public static ElementTheme ToElementTheme(this ApplicationTheme theme) } } + public static ElementTheme GetActualTheme(ElementTheme theme) + { + if (theme == ElementTheme.Default) + return Application.Current.RequestedTheme.ToElementTheme(); + else + return theme; + } + private static Brush GetAppBackgroundBrush(ElementTheme theme) { var darkModeBaseColor = Color.FromArgb(255, 46, 46, 46); var lightModeBaseColor = Color.FromArgb(255, 240, 240, 240); + theme = GetActualTheme(theme); var baseColor = theme == ElementTheme.Light ? lightModeBaseColor : darkModeBaseColor; if (AppBackgroundPanelTintOpacity > 0.99f || @@ -291,6 +275,7 @@ private static Brush GetAppBackgroundBrush(ElementTheme theme) public static void ApplyThemeForTitleBarButtons(ApplicationViewTitleBar titleBar, ElementTheme theme) { + theme = GetActualTheme(theme); if (theme == ElementTheme.Dark) { // Set active window colors @@ -379,5 +364,13 @@ private static void UpdateSystemAccentColorAndBrushes(Color color) // ignore } } + + public static void UpdateAllSettings() + { + Initialize(); + OnThemeChanged?.Invoke(null, _themeMode); + OnBackgroundChanged?.Invoke(null, GetAppBackgroundBrush(_themeMode)); + OnAccentColorChanged?.Invoke(null, _appAccentColor); + } } } \ No newline at end of file diff --git a/src/Notepads/Settings/ApplicationSettings.cs b/src/Notepads/Settings/ApplicationSettings.cs index c0e682984..537231590 100644 --- a/src/Notepads/Settings/ApplicationSettings.cs +++ b/src/Notepads/Settings/ApplicationSettings.cs @@ -1,19 +1,21 @@ namespace Notepads.Settings { using System; + using Notepads.Core; using Notepads.Services; using Windows.Storage; public static class ApplicationSettingsStore { + private static readonly ApplicationDataContainer _localSettings = ApplicationData.Current.LocalSettings; + public static object Read(string key) { object obj = null; - ApplicationDataContainer localSettings = Windows.Storage.ApplicationData.Current.LocalSettings; - if (localSettings.Values.ContainsKey(key)) + if (_localSettings.Values.ContainsKey(key)) { - obj = localSettings.Values[key]; + obj = _localSettings.Values[key]; } return obj; @@ -21,16 +23,27 @@ public static object Read(string key) public static void Write(string key, object obj) { - ApplicationDataContainer localSettings = Windows.Storage.ApplicationData.Current.LocalSettings; - localSettings.Values[key] = obj; + if (_localSettings.Values.ContainsKey(key) && _localSettings.Values[key].Equals(obj)) return; + + _localSettings.Values[key] = obj; + SignalDataChanged(key); + } + + public static void SignalDataChanged(string key) + { + if (InterInstanceSyncService.SyncManager.ContainsKey(key)) + { + _localSettings.Values[CoreKey.LastChangedSettingsKeyStr] = key; + _localSettings.Values[CoreKey.LastChangedSettingsAppInstanceIdStr] = App.Id.ToString(); + ApplicationData.Current.SignalDataChanged(); + } } public static bool Remove(string key) { try { - ApplicationDataContainer localSettings = Windows.Storage.ApplicationData.Current.LocalSettings; - return localSettings.Values.Remove(key); + return _localSettings.Values.Remove(key); } catch (Exception ex) { diff --git a/src/Notepads/Settings/SettingsKey.cs b/src/Notepads/Settings/SettingsKey.cs index 245afdf1b..3738f8288 100644 --- a/src/Notepads/Settings/SettingsKey.cs +++ b/src/Notepads/Settings/SettingsKey.cs @@ -3,37 +3,36 @@ internal static class SettingsKey { // App related - internal static string AppVersionStr = "AppVersionStr"; - internal static string IsJumpListOutOfDateBool = "IsJumpListOutOfDateBool"; - internal static string ActiveInstanceIdStr = "ActiveInstanceIdStr"; - internal static string AlwaysOpenNewWindowBool = "AlwaysOpenNewWindowBool"; + internal const string AppVersionStr = "AppVersionStr"; + internal const string IsJumpListOutOfDateBool = "IsJumpListOutOfDateBool"; + internal const string ActiveInstanceIdStr = "ActiveInstanceIdStr"; + internal const string AlwaysOpenNewWindowBool = "AlwaysOpenNewWindowBool"; // Theme related - internal static string RequestedThemeStr = "RequestedThemeStr"; - internal static string UseWindowsThemeBool = "UseWindowsThemeBool"; - internal static string AppBackgroundTintOpacityDouble = "AppBackgroundTintOpacityDouble"; - internal static string AppAccentColorHexStr = "AppAccentColorHexStr"; - internal static string CustomAccentColorHexStr = "CustomAccentColorHexStr"; - internal static string UseWindowsAccentColorBool = "UseWindowsAccentColorBool"; + internal const string RequestedThemeStr = "RequestedThemeStr"; + internal const string AppBackgroundTintOpacityDouble = "AppBackgroundTintOpacityDouble"; + internal const string AppAccentColorHexStr = "AppAccentColorHexStr"; + internal const string CustomAccentColorHexStr = "CustomAccentColorHexStr"; + internal const string UseWindowsAccentColorBool = "UseWindowsAccentColorBool"; // Editor related - internal static string EditorFontFamilyStr = "EditorFontFamilyStr"; - internal static string EditorFontSizeInt = "EditorFontSizeInt"; - internal static string EditorFontStyleStr = "EditorFontStyleStr"; - internal static string EditorFontWeightUshort = "EditorFontWeightUshort"; - internal static string EditorDefaultTextWrappingStr = "EditorDefaultTextWrappingStr"; - internal static string EditorDefaultLineHighlighterViewStateBool = "EditorDefaultLineHighlighterViewStateBool"; - internal static string EditorDefaultLineEndingStr = "EditorDefaultLineEndingStr"; - internal static string EditorDefaultEncodingCodePageInt = "EditorDefaultEncodingCodePageInt"; - internal static string EditorDefaultDecodingCodePageInt = "EditorDefaultDecodingCodePageInt"; - internal static string EditorDefaultUtf8EncoderShouldEmitByteOrderMarkBool = "EditorDefaultUtf8EncoderShouldEmitByteOrderMarkBool"; - internal static string EditorDefaultTabIndentsInt = "EditorDefaultTabIndentsInt"; - internal static string EditorDefaultSearchEngineStr = "EditorDefaultSearchUrlStr"; - internal static string EditorCustomMadeSearchUrlStr = "EditorCustomMadeSearchUrlStr"; - internal static string EditorShowStatusBarBool = "EditorShowStatusBarBool"; - internal static string EditorEnableSessionBackupAndRestoreBool = "EditorEnableSessionBackupAndRestoreBool"; - internal static string EditorHighlightMisspelledWordsBool = "EditorHighlightMisspelledWordsBool"; - internal static string EditorDefaultDisplayLineNumbersBool = "EditorDefaultDisplayLineNumbersBool"; - internal static string EditorEnableSmartCopyBool = "EditorEnableSmartCopyBool"; + internal const string EditorFontFamilyStr = "EditorFontFamilyStr"; + internal const string EditorFontSizeInt = "EditorFontSizeInt"; + internal const string EditorFontStyleStr = "EditorFontStyleStr"; + internal const string EditorFontWeightUshort = "EditorFontWeightUshort"; + internal const string EditorDefaultTextWrappingStr = "EditorDefaultTextWrappingStr"; + internal const string EditorDefaultLineHighlighterViewStateBool = "EditorDefaultLineHighlighterViewStateBool"; + internal const string EditorDefaultLineEndingStr = "EditorDefaultLineEndingStr"; + internal const string EditorDefaultEncodingCodePageInt = "EditorDefaultEncodingCodePageInt"; + internal const string EditorDefaultDecodingCodePageInt = "EditorDefaultDecodingCodePageInt"; + internal const string EditorDefaultUtf8EncoderShouldEmitByteOrderMarkBool = "EditorDefaultUtf8EncoderShouldEmitByteOrderMarkBool"; + internal const string EditorDefaultTabIndentsInt = "EditorDefaultTabIndentsInt"; + internal const string EditorDefaultSearchEngineStr = "EditorDefaultSearchUrlStr"; + internal const string EditorCustomMadeSearchUrlStr = "EditorCustomMadeSearchUrlStr"; + internal const string EditorShowStatusBarBool = "EditorShowStatusBarBool"; + internal const string EditorEnableSessionBackupAndRestoreBool = "EditorEnableSessionBackupAndRestoreBool"; + internal const string EditorHighlightMisspelledWordsBool = "EditorHighlightMisspelledWordsBool"; + internal const string EditorDefaultDisplayLineNumbersBool = "EditorDefaultDisplayLineNumbersBool"; + internal const string EditorEnableSmartCopyBool = "EditorEnableSmartCopyBool"; } } \ No newline at end of file diff --git a/src/Notepads/Strings/en-US/Resources.resw b/src/Notepads/Strings/en-US/Resources.resw index a5f169f1c..44ea44cef 100644 --- a/src/Notepads/Strings/en-US/Resources.resw +++ b/src/Notepads/Strings/en-US/Resources.resw @@ -438,7 +438,7 @@ App: DragAndDrop UIOverride Caption: "Open with Notepads" display text - This is a shadow window of Notepads. Session snapshot and settings are disabled. + This is a shadow window of Notepads. Session snapshot is disabled. App: ShadowWindowIndicator Description display text. @@ -693,4 +693,40 @@ File extension "{0}" is not supported at this moment FileRenameError: Extension is not currently supported. {0} stands for the file extension string. + + Save As + LaunchElevatedProcessDialog: Save Failed CloseButtonText. + + + Notepads needs Adminstrative Privilage to modify file "{0}" + LaunchElevatedProcessDialog: Save Failed "Content" display text. + + + Save Failed + LaunchElevatedProcessDialog: Save Failed "Title" display text. + + + Now system files can be directly modified + TextEditor: Notification message when launching of elevated process is successful. + + + Failed to start elevated process + TextEditor: Notification message when launching of elevated process is failed. + + + Give Access + LaunchElevatedProcessDialog: PrimaryButtonText. + + + Cancel + LaunchElevatedProcessDialog: Rename Failed CloseButtonText. + + + Notepads needs Adminstrative Privilage to rename file "{0}" + LaunchElevatedProcessDialog: Rename Failed "Content" display text. + + + Rename Failed + LaunchElevatedProcessDialog: Rename Failed "Title" display text. + \ No newline at end of file diff --git a/src/Notepads/Utilities/FileSystemUtility.cs b/src/Notepads/Utilities/FileSystemUtility.cs index 833693334..4f4e02064 100644 --- a/src/Notepads/Utilities/FileSystemUtility.cs +++ b/src/Notepads/Utilities/FileSystemUtility.cs @@ -206,7 +206,7 @@ public static string GetAbsolutePathFromCommandLine(string dir, string args, str } } - // Replace all forward slash with platform supported directory separator + // Replace all forward slash with platform supported directory separator path = path.Trim('/').Replace('/', Path.DirectorySeparatorChar); if (IsFullPath(path)) @@ -573,9 +573,19 @@ public static async Task WriteToFile(string text, Encoding encoding, StorageFile // In case the file is actually read-only, WriteBytesAsync will throw UnauthorizedAccessException var content = encoding.GetBytes(text); var result = encoding.GetPreamble().Concat(content).ToArray(); - await PathIO.WriteBytesAsync(file.Path, result); + try + { + await PathIO.WriteBytesAsync(file.Path, result); + DesktopExtensionService.UnblockFile(file.Path); + } + catch (UnauthorizedAccessException ex) // Try to save as admin if fullTrust api is supported + { + if (!DesktopExtensionService.ShouldUseDesktopExtension) throw ex; + + await DesktopExtensionService.SaveFileAsAdmin(file.Path, result); + } } - else // Use StorageFile API to save + else // Use StorageFile API to save { using (var stream = await file.OpenStreamForWriteAsync()) using (var writer = new StreamWriter(stream, encoding)) @@ -585,6 +595,7 @@ public static async Task WriteToFile(string text, Encoding encoding, StorageFile await writer.FlushAsync(); stream.SetLength(stream.Position); // Truncate } + DesktopExtensionService.UnblockFile(file.Path); } } finally diff --git a/src/Notepads/Views/MainPage/NotepadsMainPage.IO.cs b/src/Notepads/Views/MainPage/NotepadsMainPage.IO.cs index 81d0e8b2f..79d247e89 100644 --- a/src/Notepads/Views/MainPage/NotepadsMainPage.IO.cs +++ b/src/Notepads/Views/MainPage/NotepadsMainPage.IO.cs @@ -152,6 +152,46 @@ private async Task SaveInternal(ITextEditor textEditor, StorageFile file, bool r } } + private async Task TrySaveFile(ITextEditor textEditor, StorageFile file, bool rebuildOpenRecentItems) + { + var isSaveSuccess = true; + + try + { + await SaveInternal(textEditor, file, rebuildOpenRecentItems); + } + catch (UnauthorizedAccessException) // Happens when the file we are saving is read-only + { + isSaveSuccess = false; + } + catch (FileNotFoundException) // Happens when the file not found or storage media is removed + { + isSaveSuccess = false; + } + catch (AdminstratorAccessException) // Happens when the file we are saving is read-only, ask user for permission to write + { + var launchElevatedProcessDialog = new LaunchElevatedProcessDialog( + ElevatedOperationType.Save, file.Path, + async () => + { + await DesktopExtensionService.LaunchElevetedProcess(); + }, + () => + { + isSaveSuccess = false; + }); + + var dialogResult = await DialogManager.OpenDialogAsync(launchElevatedProcessDialog, awaitPreviousDialog: false); + + if (dialogResult == null || launchElevatedProcessDialog.IsAborted) + { + isSaveSuccess = false; + } + } + + return isSaveSuccess; + } + private async Task Save(ITextEditor textEditor, bool saveAs, bool ignoreUnmodifiedDocument = false, bool rebuildOpenRecentItems = true) { if (textEditor == null) return false; @@ -175,21 +215,7 @@ private async Task Save(ITextEditor textEditor, bool saveAs, bool ignoreUn file = textEditor.EditingFile; } - bool promptSaveAs = false; - try - { - await SaveInternal(textEditor, file, rebuildOpenRecentItems); - } - catch (UnauthorizedAccessException) // Happens when the file we are saving is read-only - { - promptSaveAs = true; - } - catch (FileNotFoundException) // Happens when the file not found or storage media is removed - { - promptSaveAs = true; - } - - if (promptSaveAs) + if (!await TrySaveFile(textEditor, file, rebuildOpenRecentItems)) { file = await OpenFileUsingFileSavePicker(textEditor); if (file == null) return false; // User cancelled @@ -245,7 +271,6 @@ private async Task RenameFileAsync(ITextEditor textEditor) { await textEditor.RenameAsync(newFilename); NotepadsCore.FocusOnSelectedTextEditor(); - NotificationCenter.Instance.PostNotification(_resourceLoader.GetString("TextEditor_NotificationMsg_FileRenamed"), 1500); } catch (Exception ex) { diff --git a/src/Notepads/Views/MainPage/NotepadsMainPage.MainMenu.cs b/src/Notepads/Views/MainPage/NotepadsMainPage.MainMenu.cs index df903eb93..4591dc0d2 100644 --- a/src/Notepads/Views/MainPage/NotepadsMainPage.MainMenu.cs +++ b/src/Notepads/Views/MainPage/NotepadsMainPage.MainMenu.cs @@ -11,6 +11,7 @@ using Windows.UI.Xaml.Controls.Primitives; using Windows.UI.Xaml.Media; using Notepads.Services; + using Notepads.Settings; public sealed partial class NotepadsMainPage { @@ -34,8 +35,7 @@ private void InitializeMainMenu() if (!App.IsPrimaryInstance) { - MainMenuButton.Foreground = new SolidColorBrush(ThemeSettingsService.AppAccentColor); - MenuSettingsButton.IsEnabled = false; + MainMenuButton.Foreground = (SolidColorBrush)Application.Current.Resources["SystemControlForegroundAccentBrush"]; } if (App.IsGameBarWidget) @@ -106,7 +106,7 @@ private void MainMenuButtonFlyout_Opening(object sender, object e) MenuSaveAllButton.IsEnabled = NotepadsCore.HaveUnsavedTextEditor(); } - private async Task BuildOpenRecentButtonSubItems() + public async Task BuildOpenRecentButtonSubItems(bool invokeAfterChanged = true) { var openRecentSubItem = new MenuFlyoutSubItem { @@ -168,6 +168,8 @@ private async Task BuildOpenRecentButtonSubItems() var indexToInsert = MainMenuButtonFlyout.Items.IndexOf(MenuOpenFileButton) + 1; MainMenuButtonFlyout.Items.Insert(indexToInsert, openRecentSubItem); } + + if (invokeAfterChanged) ApplicationSettingsStore.SignalDataChanged(InterInstanceSyncService.RecentFilesListKey); } } } \ No newline at end of file diff --git a/src/Notepads/Views/MainPage/NotepadsMainPage.xaml.cs b/src/Notepads/Views/MainPage/NotepadsMainPage.xaml.cs index 7badfc983..2fd78e078 100644 --- a/src/Notepads/Views/MainPage/NotepadsMainPage.xaml.cs +++ b/src/Notepads/Views/MainPage/NotepadsMainPage.xaml.cs @@ -274,6 +274,9 @@ private async void Sets_Loaded(object sender, RoutedEventArgs e) Window.Current.CoreWindow.Activated -= CoreWindow_Activated; Window.Current.CoreWindow.Activated += CoreWindow_Activated; } + + InterInstanceSyncService.Initialize(this); + DesktopExtensionService.Initialize(); } private async void App_EnteredBackground(object sender, Windows.ApplicationModel.EnteredBackgroundEventArgs e) @@ -350,6 +353,7 @@ private async void MainPage_CloseRequested(object sender, Windows.UI.Core.Previe // Save session before app exit await SessionManager.SaveSessionAsync(() => { SessionManager.IsBackupEnabled = false; }); App.InstanceHandlerMutex?.Dispose(); + DesktopExtensionService.Dispose(); deferral.Complete(); return; } @@ -357,6 +361,7 @@ private async void MainPage_CloseRequested(object sender, Windows.UI.Core.Previe if (!NotepadsCore.HaveUnsavedTextEditor()) { App.InstanceHandlerMutex?.Dispose(); + DesktopExtensionService.Dispose(); deferral.Complete(); return; } @@ -390,6 +395,7 @@ private async void MainPage_CloseRequested(object sender, Windows.UI.Core.Previe else { App.InstanceHandlerMutex?.Dispose(); + DesktopExtensionService.Dispose(); } deferral.Complete(); @@ -397,6 +403,7 @@ private async void MainPage_CloseRequested(object sender, Windows.UI.Core.Previe discardAndExitAction: () => { App.InstanceHandlerMutex?.Dispose(); + DesktopExtensionService.Dispose(); deferral.Complete(); }, cancelAction: () => @@ -430,6 +437,11 @@ private void HideAllOpenFlyouts() } } + public void CloseSettingsPane() + { + RootSplitView.IsPaneOpen = false; + } + private async void OnSessionBackupAndRestoreOptionChanged(object sender, bool isSessionBackupAndRestoreEnabled) { await Dispatcher.CallOnUIThreadAsync(async () => diff --git a/src/Notepads/Views/Settings/PersonalizationSettingsPage.xaml.cs b/src/Notepads/Views/Settings/PersonalizationSettingsPage.xaml.cs index 4ec25a495..10d0d2913 100644 --- a/src/Notepads/Views/Settings/PersonalizationSettingsPage.xaml.cs +++ b/src/Notepads/Views/Settings/PersonalizationSettingsPage.xaml.cs @@ -18,21 +18,17 @@ public PersonalizationSettingsPage() { InitializeComponent(); - if (ThemeSettingsService.UseWindowsTheme) + switch (ThemeSettingsService.ThemeMode) { - ThemeModeDefaultButton.IsChecked = true; - } - else - { - switch (ThemeSettingsService.ThemeMode) - { - case ElementTheme.Light: - ThemeModeLightButton.IsChecked = true; - break; - case ElementTheme.Dark: - ThemeModeDarkButton.IsChecked = true; - break; - } + case ElementTheme.Light: + ThemeModeLightButton.IsChecked = true; + break; + case ElementTheme.Dark: + ThemeModeDarkButton.IsChecked = true; + break; + default: + ThemeModeDefaultButton.IsChecked = true; + break; } AccentColorToggle.IsOn = ThemeSettingsService.UseWindowsAccentColor; @@ -124,15 +120,13 @@ private void ThemeRadioButton_OnChecked(object sender, RoutedEventArgs e) switch (radioButton.Tag) { case "Light": - ThemeSettingsService.UseWindowsTheme = false; - ThemeSettingsService.SetTheme(ElementTheme.Light); + ThemeSettingsService.ThemeMode = ElementTheme.Light; break; case "Dark": - ThemeSettingsService.UseWindowsTheme = false; - ThemeSettingsService.SetTheme(ElementTheme.Dark); + ThemeSettingsService.ThemeMode = ElementTheme.Dark; break; case "Default": - ThemeSettingsService.UseWindowsTheme = true; + ThemeSettingsService.ThemeMode = ElementTheme.Default; break; } } @@ -142,8 +136,12 @@ private void AccentColorPicker_OnColorChanged(ColorPicker sender, ColorChangedEv { if (AccentColorPicker.IsEnabled) { - ThemeSettingsService.AppAccentColor = args.NewColor; if (!AccentColorToggle.IsOn) ThemeSettingsService.CustomAccentColor = args.NewColor; + ThemeSettingsService.AppAccentColor = args.NewColor; + if (!AccentColorToggle.IsOn) + { + ThemeSettingsService.CustomAccentColor = args.NewColor; + } } } diff --git a/src/Notepads/Views/Settings/TextAndEditorSettingsPage.xaml.cs b/src/Notepads/Views/Settings/TextAndEditorSettingsPage.xaml.cs index 68d68adda..75c3ebe65 100644 --- a/src/Notepads/Views/Settings/TextAndEditorSettingsPage.xaml.cs +++ b/src/Notepads/Views/Settings/TextAndEditorSettingsPage.xaml.cs @@ -365,8 +365,7 @@ private void FontFamilyPicker_OnSelectionChanged(object sender, SelectionChanged { var fontFamily = new FontFamily((string)e.AddedItems.First()); AppSettingsService.EditorFontFamily = fontFamily.Source; - FontStylePicker.FontFamily = fontFamily; - FontWeightPicker.FontFamily = fontFamily; + FontStylePicker.FontFamily = FontWeightPicker.FontFamily = fontFamily; } private void FontSizePicker_OnSelectionChanged(object sender, SelectionChangedEventArgs e)