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)