diff --git a/.github/workflows/Build.yml b/.github/workflows/Build.yml index 44df7c1a6..54dd12abc 100644 --- a/.github/workflows/Build.yml +++ b/.github/workflows/Build.yml @@ -11,7 +11,7 @@ jobs: configuration: [Debug, Release] 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: @@ -29,6 +29,12 @@ jobs: env: Configuration: ${{ matrix.configuration }} + - name: Setup NuGet + uses: NuGet/setup-nuget@v1.0.5 + + - name: Restore NuGet packages + run: nuget restore $env:Solution_Name + - name: Build 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" 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 ea3fabed1..a731559ff 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 64990b151..46771142f 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/Notepad/Notepad.vcxproj b/src/Notepad/Notepad.vcxproj new file mode 100644 index 000000000..3d702ec3b --- /dev/null +++ b/src/Notepad/Notepad.vcxproj @@ -0,0 +1,144 @@ + + + + + true + true + true + true + 15.0 + {0f39c2eb-f8c6-473e-b26a-0714385798f5} + Win32Proj + Notepad + $(ProjectDir)bin\$(Platform)\$(Configuration)\ + obj\$(Platform)\$(Configuration)\ + 10.0.19041.0 + 10.0.17134.0 + true + false + false + Windows Store + 10.0 + + + + + Debug + ARM + + + Release + ARM + + + Debug + ARM64 + + + Release + ARM64 + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 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 + + + + + Disabled + _DEBUG;%(PreprocessorDefinitions) + + + Console + false + + + + + WIN32;%(PreprocessorDefinitions) + + + + + MaxSpeed + true + true + NDEBUG;%(PreprocessorDefinitions) + + + Windows + true + true + false + + + + + + + + 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/Notepad/Notepad.vcxproj.filters b/src/Notepad/Notepad.vcxproj.filters new file mode 100644 index 000000000..de7f7ff47 --- /dev/null +++ b/src/Notepad/Notepad.vcxproj.filters @@ -0,0 +1,33 @@ + + + + + {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 + + + + + Header Files + + + + + Source Files + + + Source Files + + + + + + \ No newline at end of file diff --git a/src/Notepad/main.cpp b/src/Notepad/main.cpp new file mode 100644 index 000000000..7ca00897f --- /dev/null +++ b/src/Notepad/main.cpp @@ -0,0 +1,73 @@ +#include "pch.h" +#include "iostream" + +#ifndef _DEBUG +#define AUMID L"19282JackieLiu.Notepads-Beta_echhpq9pdbte8!App" +#else +#define AUMID L"Notepads-Dev_ezhh5fms182ha!App" +#endif + +#define OPEN L"open" +#define PRINT L"print" + +#define NOTEPADPRINTCOMMAND L"/p" +#define NOTEPADOPENWITHANSIENCODINGCOMMAND L"/a" +#define NOTEPADOPENWITHUTF16ENCODINGCOMMAND L"/w" + +using namespace std; +using namespace winrt; + +#ifndef _DEBUG +INT APIENTRY wWinMain(_In_ HINSTANCE /* hInstance */, _In_opt_ HINSTANCE /* hPrevInstance */, _In_ LPWSTR /* lpCmdLine */, _In_ INT /* nCmdShow */) +#else +INT main() +#endif +{ + init_apartment(); + + LPWSTR* szArglist = NULL; + INT nArgs; + szArglist = CommandLineToArgvW(GetCommandLineW(), &nArgs); + if (szArglist && nArgs > 1) + { + LPCTSTR verb = OPEN; + if (wcscmp(NOTEPADPRINTCOMMAND, szArglist[1]) == 0) verb = PRINT; + bool isOpenRequested = (wcscmp(OPEN, verb) == 0); + + INT ct = nArgs - isOpenRequested ? 1 : 2; + HRESULT hr = E_OUTOFMEMORY; + com_ptr< IShellItemArray> ppsia = NULL; + PIDLIST_ABSOLUTE* rgpidl = new(std::nothrow) PIDLIST_ABSOLUTE[ct]; + if (rgpidl) + { + hr = S_OK; + INT cpidl; + for (cpidl = 0; SUCCEEDED(hr) && cpidl < ct; cpidl++) + { + hr = SHParseDisplayName(szArglist[cpidl + isOpenRequested ? 1 : 2], NULL, &rgpidl[cpidl], 0, NULL); + } + + if (cpidl > 0 && SUCCEEDED(SHCreateShellItemArrayFromIDLists(cpidl, rgpidl, ppsia.put()))) + { + com_ptr appActivationMgr = NULL; + if (SUCCEEDED(CoCreateInstance(CLSID_ApplicationActivationManager, NULL, CLSCTX_LOCAL_SERVER, __uuidof(appActivationMgr), appActivationMgr.put_void()))) + { + DWORD pid = 0; + appActivationMgr->ActivateForFile(AUMID, ppsia.get(), verb, &pid); + } + appActivationMgr.~com_ptr(); + } + + for (INT i = 0; i < cpidl; i++) + { + CoTaskMemFree(rgpidl[i]); + } + } + + ppsia.~com_ptr(); + delete[] rgpidl; + } + + LocalFree(szArglist); + uninit_apartment(); +} diff --git a/src/Notepad/packages.config b/src/Notepad/packages.config new file mode 100644 index 000000000..1c90c5bae --- /dev/null +++ b/src/Notepad/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/src/Notepad/pch.h b/src/Notepad/pch.h new file mode 100644 index 000000000..13872ee19 --- /dev/null +++ b/src/Notepad/pch.h @@ -0,0 +1,9 @@ +#pragma once +#pragma comment(lib, "Shell32") +#define STRICT +#define STRICT_TYPED_ITEMIDS +#include +#include +#include +#include +#include diff --git a/src/Notepads.Controls/Notepads.Controls.csproj b/src/Notepads.Controls/Notepads.Controls.csproj index 6e9078829..df869baf2 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,25 +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 - x86 true diff --git a/src/Notepads.DesktopExtension/.gitignore b/src/Notepads.DesktopExtension/.gitignore new file mode 100644 index 000000000..b44ff4fd5 --- /dev/null +++ b/src/Notepads.DesktopExtension/.gitignore @@ -0,0 +1,7 @@ +# vcpkg generated files # +######################### +**/vcpkg_installed/ + +# AppCenter version file # +########################## +appcenter-sdk-version.txt \ No newline at end of file diff --git a/src/Notepads.DesktopExtension/App.xaml b/src/Notepads.DesktopExtension/App.xaml new file mode 100644 index 000000000..0d1ebdd7a --- /dev/null +++ b/src/Notepads.DesktopExtension/App.xaml @@ -0,0 +1,8 @@ + + + + + diff --git a/src/Notepads.DesktopExtension/AssemblyInfo.rc b/src/Notepads.DesktopExtension/AssemblyInfo.rc new file mode 100644 index 000000000..b55f9362e --- /dev/null +++ b/src/Notepads.DesktopExtension/AssemblyInfo.rc @@ -0,0 +1,105 @@ +// Microsoft Visual C++ generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (India) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENN) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_INDIA +#pragma code_page(1252) + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// 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 + VALUE "FileDescription", "Notepads" + VALUE "FileVersion", "1.0.0.0" + VALUE "InternalName", "Notepads.DesktopExtension" + VALUE "LegalCopyright", "Copyright 2020 Jackie Liu" + VALUE "OriginalFilename", "Notepads.DesktopExtension" + VALUE "ProductName", "Notepads" + VALUE "ProductVersion", "1.0.0.0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x0, 1200 + END +END + +#endif // English (India) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// TEXTFILE +// +APPCENTER_SDK_VERSION TEXTFILE "appcenter-sdk-version.txt" \ 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..2f6fb9f8d --- /dev/null +++ b/src/Notepads.DesktopExtension/Notepads.DesktopExtension.vcxproj @@ -0,0 +1,184 @@ + + + + + true + true + true + true + 15.0 + {0a3b698e-6add-4c9e-9741-08f8ae576b2e} + Win32Proj + Notepads.DesktopExtension + $(ProjectDir)bin\$(Platform)\$(Configuration)\ + obj\$(Platform)\$(Configuration)\ + Notepads32 + 10.0.19041.0 + 10.0.17763.0 + true + false + false + Windows Store + 10.0 + + + + + Debug + ARM + + + Debug + ARM64 + + + Debug + Win32 + + + Release + ARM + + + Release + ARM64 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + Application + v140 + v141 + v142 + Unicode + + + true + true + + + false + true + false + + + + + + + + + + + + + + + true + + + + Use + pch.h + $(IntDir)pch.pch + _CONSOLE;WIN32_LEAN_AND_MEAN;WINRT_LEAN_AND_MEAN;%(PreprocessorDefinitions) + Level4 + %(AdditionalOptions) /permissive- /bigobj + stdcpplatest + + + powershell -ExecutionPolicy Unrestricted $(ProjectDir)build.ps1 -project_dir $(ProjectDir) + + + + + Disabled + _DEBUG;%(PreprocessorDefinitions) + + + Console + false + + + + + WIN32;%(PreprocessorDefinitions) + + + + + MaxSpeed + true + true + NDEBUG;%(PreprocessorDefinitions) + + + Windows + true + true + false + + + + + + + + + + + + + + + + Create + + + + + + + + + + + + + + + + + + + + + + + + + + This project uses vcpkg that is missing on this computer. Manually download from $(VcpkgRepo) and place the contents in {0}. Or install git and add it to your path, for more information see https://git-scm.com. The missing file is {1}. + + + + + + + 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.DesktopExtension/Notepads.DesktopExtension.vcxproj.filters b/src/Notepads.DesktopExtension/Notepads.DesktopExtension.vcxproj.filters new file mode 100644 index 000000000..e840983df --- /dev/null +++ b/src/Notepads.DesktopExtension/Notepads.DesktopExtension.vcxproj.filters @@ -0,0 +1,67 @@ + + + + + {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 + + + {de4d9f33-9346-4bce-97fe-469f12bf39a5} + + + + + Source Files + + + Source Files + + + Source Files + + + AppCenter + + + + + Header Files + + + Header Files + + + AppCenter + + + AppCenter + + + AppCenter + + + AppCenter + + + + + + + Resource Files + + + + + + Resource Files + + + \ No newline at end of file diff --git a/src/Notepads.DesktopExtension/PropertySheet.props b/src/Notepads.DesktopExtension/PropertySheet.props new file mode 100644 index 000000000..0346d53a1 --- /dev/null +++ b/src/Notepads.DesktopExtension/PropertySheet.props @@ -0,0 +1,22 @@ + + + + + + + https://github.com/microsoft/vcpkg + + 331f87775cd53d6ab36545b0a1ba55e051b090f9 + + windows + + + \ No newline at end of file diff --git a/src/Notepads.DesktopExtension/appcenter.cpp b/src/Notepads.DesktopExtension/appcenter.cpp new file mode 100644 index 000000000..de87e757a --- /dev/null +++ b/src/Notepads.DesktopExtension/appcenter.cpp @@ -0,0 +1,163 @@ +// Documentation is at https://docs.microsoft.com/en-us/appcenter/diagnostics/upload-crashes +#include "pch.h" +#include "appcenter.h" +#include "log.h" +#include "rapidjson/stringbuffer.h" + +#define APPCENTER_ENDPOINT "https://in.appcenter.ms/logs?Api-Version=1.0.0" + +using namespace fmt; +using namespace rapidjson; +using namespace std; +using namespace winrt; +using namespace AppCenter; +using namespace Windows::ApplicationModel; +using namespace Windows::Foundation; +using namespace Windows::Security::ExchangeActiveSyncProvisioning; +using namespace Windows::Storage; +using namespace Windows::System; +using namespace Windows::System::Profile; + +VOID AppCenter::start() +{ + if (!AppCenterSecret) return; + + if (!headerList) + { + hstring installId = unbox_value_or(readSettingsKey(AppCenterInstallIdStr), L""); + if (installId.empty()) return; + + headerList = curl_slist_append(headerList, "Content-Type: application/json"); + headerList = curl_slist_append(headerList, format("app-secret: {}", to_string(AppCenterSecret)).c_str()); + headerList = curl_slist_append(headerList, format("install-id: {}", to_string(installId)).c_str()); + } + + if (!deviceInfo) deviceInfo = new Device(); +} + +VOID AppCenter::exit() +{ + if (headerList) LocalFree(headerList); + if (deviceInfo) LocalFree(deviceInfo); +} + +VOID AppCenter::trackError(bool isFatal, DWORD errorCode, const string& message, const stacktrace& stackTrace) +{ + if (!headerList || !deviceInfo) return; + + string crashReportId = to_string(to_hstring(GuidHelper::CreateNewGuid())); + crashReportId.erase(0, crashReportId.find_first_not_of('{')).erase(crashReportId.find_last_not_of('}') + 1); + string crashReportSid = to_string(to_hstring(GuidHelper::CreateNewGuid())); + crashReportSid.erase(0, crashReportSid.find_first_not_of('{')).erase(crashReportSid.find_last_not_of('}') + 1); + string errorAttachmentId = to_string(to_hstring(GuidHelper::CreateNewGuid())); + errorAttachmentId.erase(0, errorAttachmentId.find_first_not_of('{')).erase(errorAttachmentId.find_last_not_of('}') + 1); + string eventReportId = to_string(to_hstring(GuidHelper::CreateNewGuid())); + eventReportId.erase(0, eventReportId.find_first_not_of('{')).erase(eventReportId.find_last_not_of('}') + 1); + + TCHAR locale[LOCALE_NAME_MAX_LENGTH + 1]; + LCIDToLocaleName(GetThreadLocale(), locale, LOCALE_NAME_MAX_LENGTH, 0); + TCHAR localeDisplayName[LOCALE_NAME_MAX_LENGTH + 1]; + GetLocaleInfoEx(locale, LOCALE_SENGLISHDISPLAYNAME, localeDisplayName, LOCALE_NAME_MAX_LENGTH); + + string isElevated = isElevatedProcess() ? "True" : "False"; + string eventType = isFatal ? "OnWin32UnhandledException" : "OnWin32UnexpectedException"; + + vector> properties + { + pair("Exception", format("Win32Exception: Exception of code no. {} was thrown.", errorCode)), + pair("Message", message), + pair("Culture", to_string(localeDisplayName)), + pair("AvailableMemory", to_string((FLOAT)MemoryManager::AppMemoryUsageLimit() / 1024 / 1024)), + pair("FirstUseTimeUTC", getTimeStamp("%m/%d/%Y %T")), + pair("OSArchitecture", to_string(Package::Current().Id().Architecture())), + pair("OSVersion", deviceInfo->getOsVersion()), + pair("IsDesktopExtension", "True"), + pair("IsElevated", isElevated) + }; + + vector errorReportSet + { + Log(LogType::managedError, crashReportId, crashReportSid, isFatal, new Exception(message, stackTrace), properties), + Log(LogType::errorAttachment, errorAttachmentId, crashReportId, base64_encode( + format("Exception: Win32Exception code no. {}, Message: {}, IsDesktopExtension: True, IsElevated: {}", + errorCode, message, isElevated))), + Log(LogType::event, eventReportId, crashReportSid, eventType, properties) + }; + + StringBuffer errorReport; +#ifdef _DEBUG + PrettyWriter writer(errorReport); +#else + Writer writer(errorReport); +#endif + + writer.StartObject(); + writer.String("logs"); + writer.StartArray(); + for (vector::const_iterator report = errorReportSet.begin(); report != errorReportSet.end(); ++report) + { + report->Serialize(writer); + } + writer.EndArray(); + writer.EndObject(); + + CURL* curl = curl_easy_init(); + if (curl) + { + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headerList); + curl_easy_setopt(curl, CURLOPT_URL, APPCENTER_ENDPOINT); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, errorReport.GetString()); + +#ifdef _DEBUG + curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); +#endif + + CURLcode res = curl_easy_perform(curl); + if (res != CURLE_OK) + { + printDebugMessage(format(L"curl_easy_perform() failed: {}", to_hstring(curl_easy_strerror(res))).c_str()); + } + } + curl_easy_cleanup(curl); +} + +VOID AppCenter::trackEvent(const string& name, const vector>& properties, const string& sid) +{ + if (!headerList || !deviceInfo) return; + + string eventReportId = to_string(to_hstring(GuidHelper::CreateNewGuid())); + eventReportId.erase(0, eventReportId.find_first_not_of('{')).erase(eventReportId.find_last_not_of('}') + 1); + + StringBuffer eventReport; +#ifdef _DEBUG + PrettyWriter writer(eventReport); +#else + Writer writer(eventReport); +#endif + + writer.StartObject(); + writer.String("logs"); + writer.StartArray(); + Log(LogType::event, eventReportId, sid, name, properties).Serialize(writer); + writer.EndArray(); + writer.EndObject(); + + CURL* curl = curl_easy_init(); + if (curl) + { + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headerList); + curl_easy_setopt(curl, CURLOPT_URL, APPCENTER_ENDPOINT); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, eventReport.GetString()); + +#ifdef _DEBUG + curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); +#endif + + CURLcode res = curl_easy_perform(curl); + if (res != CURLE_OK) + { + printDebugMessage(format(L"curl_easy_perform() failed: {}", to_hstring(curl_easy_strerror(res))).c_str()); + } + } + curl_easy_cleanup(curl); +} \ 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..c2099b8f2 --- /dev/null +++ b/src/Notepads.DesktopExtension/appcenter.h @@ -0,0 +1,25 @@ +#pragma once +#include "pch.h" +#include "curl/curl.h" + +#ifdef _DEBUG +#include "rapidjson/prettywriter.h" +#else +#include "rapidjson/writer.h" +#endif + +namespace AppCenter +{ + using namespace boost::stacktrace; + using namespace std; + + namespace + { + static struct curl_slist* headerList = NULL; + } + + VOID start(); + VOID exit(); + VOID trackError(bool isFatal, DWORD errorCode, const string& message, const stacktrace& stackTrace); + VOID trackEvent(const string& name, const vector>& properties, const string& sid = ""); +} \ No newline at end of file diff --git a/src/Notepads.DesktopExtension/build.ps1 b/src/Notepads.DesktopExtension/build.ps1 new file mode 100644 index 000000000..616b1c351 --- /dev/null +++ b/src/Notepads.DesktopExtension/build.ps1 @@ -0,0 +1,11 @@ +param ( + [string]$project_dir = $(throw "-project_dir= is required") +) + +$project_dir = Resolve-Path $project_dir.TrimEnd("\") +$appcenter_file = "${project_dir}\appcenter-sdk-version.txt" +$uwp_project_file = "${project_dir}\..\Notepads\Notepads.csproj" + +# Keep appcenter sdk version same as AppCenter nuget package version +[xml]$sdk_target = Get-Content $uwp_project_file +$sdk_target.Project.ItemGroup.PackageReference | ForEach-Object {if($_.Include -eq 'Microsoft.AppCenter.Crashes'){$_.Version | Set-Content $appcenter_file -NoNewline}} \ 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..1573f65d4 --- /dev/null +++ b/src/Notepads.DesktopExtension/device.h @@ -0,0 +1,140 @@ +#pragma once +#include "pch.h" +#include "resource.h" + +namespace AppCenter +{ + using namespace fmt; + using namespace rapidjson; + using namespace std; + using namespace winrt; + using namespace Windows::ApplicationModel; + using namespace Windows::Security::ExchangeActiveSyncProvisioning; + using namespace Windows::System::Profile; + + class Device + { + public: + Device() + { + auto packageVersion = Package::Current().Id().Version(); + _appVersion = format("{}.{}.{}.{}", packageVersion.Major, packageVersion.Minor, packageVersion.Build, packageVersion.Revision); + _appBuild = _appVersion; + + HMODULE appModule = GetModuleHandle(NULL); + _sdkVersion = static_cast(LockResource(LoadResource(appModule, + FindResource(appModule, MAKEINTRESOURCE(APPCENTER_SDK_VERSION), MAKEINTRESOURCE(TEXTFILE))))); + + EasClientDeviceInformation oemInfo = EasClientDeviceInformation(); + _osName = to_string(oemInfo.OperatingSystem()); + + ULONGLONG version = stoull(AnalyticsInfo::VersionInfo().DeviceFamilyVersion().c_str()); + ULONGLONG major = (version & 0xFFFF000000000000L) >> 48; + ULONGLONG minor = (version & 0x0000FFFF00000000L) >> 32; + ULONGLONG build = (version & 0x00000000FFFF0000L) >> 16; + ULONGLONG revision = (version & 0x000000000000FFFFL); + _osVersion = format("{}.{}.{}", major, minor, build); + _osBuild = format("{}.{}.{}.{}", major, minor, build, revision); + + _model = to_string(oemInfo.SystemProductName()); + _oemName = to_string(oemInfo.SystemManufacturer()); + + RECT desktop; + GetWindowRect(GetDesktopWindow(), &desktop); + _screenSize = format("{}x{}", desktop.right, desktop.bottom); + + TCHAR locale[LOCALE_NAME_MAX_LENGTH + 1]; + LCIDToLocaleName(GetThreadLocale(), locale, LOCALE_NAME_MAX_LENGTH, 0); + TCHAR localeDisplayName[LOCALE_NAME_MAX_LENGTH + 1]; + GetLocaleInfoEx(locale, LOCALE_SENGLISHDISPLAYNAME, localeDisplayName, LOCALE_NAME_MAX_LENGTH); + _locale = to_string(locale); + + TIME_ZONE_INFORMATION timeZoneInfo; + GetTimeZoneInformation(&timeZoneInfo); + _timeZoneOffset = -1 * timeZoneInfo.Bias; + } + + Device(const Device& device) : + _appVersion(device._appVersion), _appBuild(device._appBuild), _sdkVersion(device._sdkVersion), + _osName(device._osName), _osVersion(device._osVersion), _osBuild(device._osBuild), + _model(device._model), _oemName(device._oemName), _screenSize(device._screenSize), + _locale(device._locale), _timeZoneOffset(device._timeZoneOffset) {} + + ~Device() {} + + Device& operator=(const Device& device) + { + if (this == &device) return *this; + + _appNamespace = device._appNamespace; + _appVersion = device._appVersion; + _appBuild = device._appBuild; + _sdkName = device._sdkName; + _sdkVersion = device._sdkVersion; + _osName = device._osName; + _osVersion = device._osVersion; + _osBuild = device._osBuild; + _model = device._model; + _oemName = device._oemName; + _screenSize = device._screenSize; + _locale = device._locale; + _timeZoneOffset = device._timeZoneOffset; + return *this; + } + + template + VOID Serialize(Writer& writer) const + { + writer.StartObject(); + + writer.String("appNamespace"); + writer.String(_appNamespace.c_str(), static_cast(_appNamespace.length())); + writer.String("appVersion"); + writer.String(_appVersion.c_str(), static_cast(_appVersion.length())); + writer.String("appBuild"); + writer.String(_appBuild.c_str(), static_cast(_appBuild.length())); + writer.String("sdkName"); + writer.String(_sdkName.c_str(), static_cast(_sdkName.length())); + writer.String("sdkVersion"); + writer.String(_sdkVersion.c_str(), static_cast(_sdkVersion.length())); + writer.String("osName"); + writer.String(_osName.c_str(), static_cast(_osName.length())); + writer.String("osVersion"); + writer.String(_osVersion.c_str(), static_cast(_osVersion.length())); + writer.String("osBuild"); + writer.String(_osBuild.c_str(), static_cast(_osBuild.length())); + writer.String("model"); + writer.String(_model.c_str(), static_cast(_model.length())); + writer.String("oemName"); + writer.String(_oemName.c_str(), static_cast(_oemName.length())); + writer.String("screenSize"); + writer.String(_screenSize.c_str(), static_cast(_screenSize.length())); + writer.String("locale"); + writer.String(_locale.c_str(), static_cast(_locale.length())); + writer.String("timeZoneOffset"); + writer.Uint(_timeZoneOffset); + + writer.EndObject(); + } + + string getOsVersion() + { + return _osBuild; + } + + private: + string _appNamespace = "Notepads.DesktopExtension"; + string _appVersion; + string _appBuild; + string _sdkName = "appcenter.uwp"; + string _sdkVersion; + string _osName; + string _osVersion; + string _osBuild; + string _model; + string _oemName; + string _screenSize; + string _locale; + unsigned _timeZoneOffset; + }; +} \ 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..4b1075c03 --- /dev/null +++ b/src/Notepads.DesktopExtension/elevated.cpp @@ -0,0 +1,227 @@ +#include "pch.h" +#include "appcenter.h" +#include "fmt/format.h" + +using namespace fmt; +using namespace std; +using namespace winrt; +using namespace Windows::ApplicationModel; +using namespace Windows::ApplicationModel::DataTransfer; + +constexpr INT PIPE_READ_BUFFER = 2 * MAX_PATH + 10; +constexpr LPCTSTR pipeNameFormat = L"\\\\.\\pipe\\Sessions\\{}\\AppContainerNamedObjects\\{}\\{}"; +constexpr LPCTSTR namedObjectFormat = L"AppContainerNamedObjects\\{}\\{}"; + +DWORD sessionId; +hstring packageSid; + +HANDLE adminWriteEvent = NULL; +HANDLE adminRenameEvent = NULL; + +wstring writePipeName; +wstring renamePipeName; + +DWORD WINAPI saveFileFromPipeData(LPVOID /* param */) +{ + setExceptionHandling(); + + vector> properties; + LPCTSTR result = L"Failed"; + + HANDLE hPipe = INVALID_HANDLE_VALUE; + if (!WaitForSingleObject(adminWriteEvent, INFINITE) && ResetEvent(adminWriteEvent) && WaitNamedPipe(writePipeName.c_str(), NMPWAIT_WAIT_FOREVER)) + { + hPipe = CreateFile(writePipeName.c_str(), GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL); + } + + if (hPipe) + { + CreateThread(NULL, 0, saveFileFromPipeData, NULL, 0, NULL); + + TCHAR readBuffer[PIPE_READ_BUFFER]; + wstringstream pipeData; + DWORD byteRead; + do + { + fill(begin(readBuffer), end(readBuffer), '\0'); + if (ReadFile(hPipe, readBuffer, (PIPE_READ_BUFFER - 1) * sizeof(TCHAR), &byteRead, NULL)) + { + pipeData << readBuffer; + } + } while (byteRead >= (PIPE_READ_BUFFER - 1) * sizeof(TCHAR)); + + wstring filePath; + wstring memoryMapId; + wstring dataArrayLengthStr; + getline(pipeData, filePath, L'|'); + getline(pipeData, memoryMapId, L'|'); + getline(pipeData, dataArrayLengthStr); + + INT dataArrayLength = stoi(dataArrayLengthStr); + wstring memoryMapName = format(namedObjectFormat, packageSid, memoryMapId); + HANDLE hMemory = OpenFileMapping(FILE_MAP_READ, FALSE, memoryMapName.c_str()); + if (hMemory) + { + LPVOID mapView = MapViewOfFile(hMemory, FILE_MAP_READ, 0, 0, dataArrayLength); + if (mapView) + { + HANDLE hFile = CreateFile( + filePath.c_str(), + GENERIC_READ | GENERIC_WRITE, + 0, + NULL, + TRUNCATE_EXISTING, + 0, + NULL); + + if (hFile) + { + if (WriteFile(hFile, mapView, dataArrayLength, NULL, NULL) && FlushFileBuffers(hFile)) + { + result = L"Success"; + } + + CloseHandle(hFile); + } + + UnmapViewOfFile(mapView); + } + + CloseHandle(hMemory); + } + + if (WriteFile(hPipe, result, wcslen(result) * sizeof(TCHAR), NULL, NULL)) FlushFileBuffers(hPipe); + CloseHandle(hPipe); + + properties.push_back(pair("Result", to_string(result))); + if (wcscmp(result, L"Success") == 0) + { + printDebugMessage(format(L"Successfully wrote to \"{}\"", filePath).c_str()); + } + else + { + pair ex = getLastErrorDetails(); + properties.insert(properties.end(), + { + pair("Error Code", to_string(ex.first)), + pair("Error Message", winrt::to_string(ex.second)) + }); + printDebugMessage(format(L"Failed to write to \"{}\"", filePath).c_str()); + } + printDebugMessage(L"Waiting on uwp app to send data."); + + AppCenter::trackEvent("OnWriteToSystemFileRequested", properties); + } + else + { + pair ex = getLastErrorDetails(); + properties.insert(properties.end(), + { + pair("Result", to_string(result)), + pair("Error Code", to_string(ex.first)), + pair("Error Message", winrt::to_string(ex.second)) + }); + AppCenter::trackEvent("OnWriteToSystemFileRequested", properties); + exitApp(); + } + + return 0; +} + +DWORD WINAPI renameFileFromPipeData(LPVOID /* param */) +{ + setExceptionHandling(); + + vector> properties; + LPCTSTR result = L"Failed"; + + HANDLE hPipe = INVALID_HANDLE_VALUE; + if (!WaitForSingleObject(adminRenameEvent, INFINITE) && ResetEvent(adminRenameEvent) && WaitNamedPipe(renamePipeName.c_str(), NMPWAIT_WAIT_FOREVER)) + { + hPipe = CreateFile(renamePipeName.c_str(), GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL); + } + + if (hPipe) + { + CreateThread(NULL, 0, renameFileFromPipeData, NULL, 0, NULL); + + TCHAR readBuffer[PIPE_READ_BUFFER]; + wstringstream pipeData; + DWORD byteRead; + do + { + fill(begin(readBuffer), end(readBuffer), '\0'); + if (ReadFile(hPipe, readBuffer, (PIPE_READ_BUFFER - 1) * sizeof(TCHAR), &byteRead, NULL)) + { + pipeData << readBuffer; + } + } while (byteRead >= (PIPE_READ_BUFFER - 1) * sizeof(TCHAR)); + + wstring fileToken; + wstring newName; + getline(pipeData, fileToken, L'|'); + getline(pipeData, newName, L'|'); + + auto file = SharedStorageAccessManager::RedeemTokenForFileAsync(fileToken).get(); + SharedStorageAccessManager::RemoveFile(fileToken); + auto oldName = file.Path(); + file.RenameAsync(newName).get(); + result = SharedStorageAccessManager::AddFile(file).c_str(); + + if (WriteFile(hPipe, result, wcslen(result) * sizeof(TCHAR), NULL, NULL)) FlushFileBuffers(hPipe); + CloseHandle(hPipe); + + if (wcscmp(result, L"Failed") == 0) + { + pair ex = getLastErrorDetails(); + properties.insert(properties.end(), + { + pair("Result", to_string(result)), + pair("Error Code", to_string(ex.first)), + pair("Error Message", winrt::to_string(ex.second)) + }); + printDebugMessage(format(L"Failed to rename \"{}\" to \"{}\"", oldName, newName).c_str()); + } + else + { + properties.push_back(pair("Result", "Success")); + printDebugMessage(format(L"Successfully renamed \"{}\" to \"{}\"", oldName, newName).c_str()); + } + printDebugMessage(L"Waiting on uwp app to send data."); + + AppCenter::trackEvent("OnRenameToSystemFileRequested", properties); + } + else + { + pair ex = getLastErrorDetails(); + properties.insert(properties.end(), + { + pair("Result", to_string(result)), + pair("Error Code", to_string(ex.first)), + pair("Error Message", winrt::to_string(ex.second)) + }); + AppCenter::trackEvent("OnRenameToSystemFileRequested", properties); + exitApp(); + } + + return 0; +} + +VOID initializeAdminService() +{ + ProcessIdToSessionId(GetCurrentProcessId(), &sessionId); + packageSid = unbox_value_or(readSettingsKey(PackageSidStr), L""); + + adminWriteEvent = OpenEvent(SYNCHRONIZE | EVENT_MODIFY_STATE, FALSE, format(namedObjectFormat, packageSid, AdminWriteEventNameStr).c_str()); + adminRenameEvent = OpenEvent(SYNCHRONIZE | EVENT_MODIFY_STATE, FALSE, format(namedObjectFormat, packageSid, AdminRenameEventNameStr).c_str()); + + auto packageFamilyName = Package::Current().Id().FamilyName(); + writePipeName = format(pipeNameFormat, sessionId, packageSid, AdminWritePipeConnectionNameStr); + renamePipeName = format(pipeNameFormat, sessionId, packageSid, AdminRenamePipeConnectionNameStr); + + printDebugMessage(L"Successfully started Adminstrator Extension."); + printDebugMessage(L"Waiting on uwp app to send data."); + + CreateThread(NULL, 0, saveFileFromPipeData, NULL, 0, NULL); + CreateThread(NULL, 0, renameFileFromPipeData, NULL, 0, NULL); +} \ No newline at end of file diff --git a/src/Notepads.DesktopExtension/exception.h b/src/Notepads.DesktopExtension/exception.h new file mode 100644 index 000000000..aff0f4c0e --- /dev/null +++ b/src/Notepads.DesktopExtension/exception.h @@ -0,0 +1,115 @@ +#pragma once +#include "pch.h" + +namespace AppCenter +{ + using namespace boost::stacktrace; + using namespace std; + + class Frame + { + public: + Frame(const frame& frame) : _methodName(frame.name()), _fileName(frame.source_file()), _lineNumber(frame.source_line()) {} + Frame(const Frame& frame) : _methodName(frame._methodName), _fileName(frame._fileName), _lineNumber(frame._lineNumber) {} + + ~Frame() {} + + Frame& operator=(const Frame& frame) + { + if (this == &frame) return *this; + + _methodName = frame._methodName; + _fileName = frame._fileName; + _lineNumber = frame._lineNumber; + return *this; + } + + template + VOID Serialize(Writer& writer) const + { + if (_lineNumber <= 0 && _fileName.empty()) return; + + writer.StartObject(); + + writer.String("methodName"); + writer.String(_methodName.c_str(), static_cast(_methodName.length())); + writer.String("fileName"); + writer.String(_fileName.c_str(), static_cast(_fileName.length())); + writer.String("lineNumber"); + writer.Uint(_lineNumber); + + writer.EndObject(); + } + + private: + string _methodName; + string _fileName; + unsigned _lineNumber; + }; + + class Exception + { + public: + Exception(const string& message) : _message(message) {} + Exception(const string& message, const string& stackTrace) : _message(message), _stackTrace(stackTrace) {} + Exception(const Exception& exception) : _message(exception._message), _stackTrace(exception._stackTrace), _frames(exception._frames) {} + Exception(const exception& exception) : _message(exception.what()) {} + + Exception(const string& message, const stacktrace& stackTrace) : _message(message) + { + for (frame frame : stackTrace.as_vector()) + { + _frames.push_back(Frame(frame)); + } + } + + ~Exception() {} + + Exception& operator=(const Exception& exception) + { + if (this == &exception) return *this; + + _type = exception._type; + _message = exception._message; + _stackTrace = exception._stackTrace; + _frames = exception._frames; + return *this; + } + + template + VOID Serialize(Writer& writer) const + { + writer.StartObject(); + + writer.String("type"); + writer.String(_type.c_str(), static_cast(_type.length())); + writer.String("message"); + writer.String(_message.c_str(), static_cast(_message.length())); + + if (!_stackTrace.empty()) + { + writer.String("stackTrace"); + writer.String(_stackTrace.c_str(), static_cast(_stackTrace.length())); + } + + if (!_frames.empty()) + { + writer.String("frames"); + writer.StartArray(); + for (Frame frame : _frames) + { + frame.Serialize(writer); + } + writer.EndArray(); + } + + writer.EndObject(); + } + + private: + string _type = "Win32Exception"; + string _message; + string _stackTrace = ""; + vector _frames; + }; +} \ No newline at end of file diff --git a/src/Notepads.DesktopExtension/log.h b/src/Notepads.DesktopExtension/log.h new file mode 100644 index 000000000..88374b76b --- /dev/null +++ b/src/Notepads.DesktopExtension/log.h @@ -0,0 +1,201 @@ +#pragma once +#include "pch.h" +#include "device.h" +#include "exception.h" + +namespace AppCenter +{ + using namespace rapidjson; + using namespace std; + + enum LogType + { + managedError, + errorAttachment, + event + }; + + namespace + { + static const string logTypes[] = { "managedError", "errorAttachment", "event" }; + static string launchTimeStamp = getTimeStamp(); + static Device* deviceInfo = NULL; + } + + class Log + { + public: + #pragma region Constructors for error report + + Log(LogType type, const string& id, const string& sid, bool isFatal, Exception* exception) : + _type(type), _id(id), _sid(sid), _fatal(isFatal), _exception(exception) + { + InitializeLog(); + } + + Log(LogType type, const string& id, const string& sid, bool isFatal, Exception* exception, const vector>& properties) : + _type(type), _id(id), _sid(sid), _fatal(isFatal), _exception(exception), _properties(properties) + { + InitializeLog(); + } + + #pragma endregion + + #pragma region Constructor for error attachment + + Log(LogType type, const string& id, const string& errorId, const string& data) : + _type(type), _id(id), _errorId(errorId), _data(data) + { + InitializeLog(); + } + + #pragma endregion + + #pragma region Constructor for event report + + Log(LogType type, const string& id, const string& sid, const string& name, const vector>& properties) : + _type(type), _id(id), _sid(sid), _name(name), _properties(properties) + { + InitializeLog(); + } + + #pragma endregion + + Log(const Log& log) : + _type(log._type), _name(log._name), _timestamp(log._timestamp), _processId(log._processId), _id(log._id), _sid(log._sid), _fatal(log._fatal), + _processName(log._processName), _errorThreadId(log._errorThreadId), _data(log._data), _errorId(log._errorId), + _exception(NULL), _properties(log._properties) + { + _exception = (log._exception == 0) ? 0 : new Exception(*log._exception); + InitializeLog(); + } + + ~Log() + { + delete _exception; + } + + Log& operator=(const Log& log) + { + if (this == &log) return *this; + + delete _exception; + _type = log._type; + _timestamp = log._timestamp; + _id = log._id; + _sid = log._sid; + _processId = log._processId; + _fatal = log._fatal; + _processName = log._processName; + _errorThreadId = log._errorThreadId; + _exception = (log._exception == 0) ? 0 : new Exception(*log._exception); + _contentType = log._contentType; + _data = log._data; + _errorId = log._errorId; + _name = log._name; + _properties = log._properties; + return *this; + } + + template + VOID Serialize(Writer& writer) const + { + writer.StartObject(); + + writer.String("type"); + writer.String(logTypes[_type].c_str(), static_cast(logTypes[_type].length())); + writer.String("timestamp"); + writer.String(_timestamp.c_str(), static_cast(_timestamp.length())); + writer.String("appLaunchTimestamp"); + writer.String(launchTimeStamp.c_str(), static_cast(launchTimeStamp.length())); + writer.String("id"); + writer.String(_id.c_str(), static_cast(_id.length())); + + if (!_sid.empty()) + { + writer.String("sid"); + writer.String(_sid.c_str(), static_cast(_sid.length())); + } + + switch (_type) + { + case LogType::managedError: + writer.String("processId"); + writer.Uint(_processId); + writer.String("fatal"); + writer.Bool(_fatal); + writer.String("processName"); + writer.String(_processName.c_str(), static_cast(_processName.length())); + writer.String("errorThreadId"); + writer.Uint(_errorThreadId); + + // Write exception data + writer.String("exception"); + _exception->Serialize(writer); + break; + case LogType::errorAttachment: + writer.String("contentType"); + writer.String(_contentType.c_str(), static_cast(_contentType.length())); + writer.String("data"); + writer.String(_data.c_str(), static_cast(_data.length())); + writer.String("errorId"); + writer.String(_errorId.c_str(), static_cast(_errorId.length())); + break; + case LogType::event: + writer.String("name"); + writer.String(_name.c_str(), static_cast(_name.length())); + break; + } + + // Write device specific data + writer.String("device"); + deviceInfo->Serialize(writer); + + // Write custom properties if available + if (!_properties.empty()) + { + writer.String("properties"); + writer.StartObject(); + for (vector>::const_iterator property = _properties.begin(); property != _properties.end(); ++property) + { + writer.String(property->first); + writer.String(property->second.c_str(), static_cast(property->second.length())); + } + writer.EndObject(); + } + + writer.EndObject(); + } + + private: + LogType _type; + string _contentType = "text/plain"; + string _name; + string _timestamp = getTimeStamp(); + unsigned _processId; + string _id; + string _sid; + bool _fatal = false; + string _processName = "Notepads32.exe"; + unsigned _errorThreadId = 0; + string _data; + string _errorId; + Exception* _exception = NULL; + vector> _properties; + + VOID InitializeLog() + { + switch (_type) + { + case LogType::managedError: + _processId = GetCurrentProcessId(); + _errorThreadId = GetCurrentThreadId(); + break; + case LogType::errorAttachment: + break; + case LogType::event: + break; + } + } + }; +} \ 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..43bf25449 --- /dev/null +++ b/src/Notepads.DesktopExtension/main.cpp @@ -0,0 +1,257 @@ +#include "pch.h" +#include "appcenter.h" +#include "shlobj_core.h" + +using namespace fmt; +using namespace std; +using namespace winrt; +using namespace Windows::ApplicationModel; +using namespace Windows::Foundation; +using namespace Windows::Storage; + +constexpr LPCTSTR DesktopExtensionMutexName = L"DesktopExtensionMutexName"; +constexpr LPCTSTR AdminExtensionMutexName = L"AdminExtensionMutexName"; + +wstring adminParams; + +extern hstring packageSid; +extern HANDLE adminWriteEvent; +extern HANDLE adminRenameEvent; + +IInspectable readSettingsKey(hstring key) +{ + return ApplicationData::Current().LocalSettings().Values().TryLookup(key); +} + +INT releaseResources() +{ + CloseHandle(adminWriteEvent); + CloseHandle(adminRenameEvent); + return 0; +} + +VOID exitApp() +{ + exit(0); +} + +VOID onUnhandledException() +{ + logLastError(true).get(); + Sleep(5000); + exitApp(); +} + +VOID onUnexpectedException() +{ + logLastError(false).get(); + Sleep(5000); + exitApp(); +} + +VOID setExceptionHandling() +{ + set_terminate(onUnhandledException); + set_unexpected(onUnexpectedException); +} + +VOID launchElevatedProcess() +{ + TCHAR fileName[MAX_PATH]; + GetModuleFileName(NULL, fileName, MAX_PATH); + + SHELLEXECUTEINFO shExInfo + { + .cbSize = sizeof(shExInfo), + .fMask = SEE_MASK_NOCLOSEPROCESS, + .hwnd = 0, + .lpVerb = L"runas", + .lpFile = fileName, + .lpParameters = adminParams.c_str(), + .lpDirectory = 0, + .nShow = SW_SHOW, + .hInstApp = 0 + }; + + vector> properties; + if (ShellExecuteEx(&shExInfo)) + { + ApplicationData::Current().LocalSettings().Values().Insert(LastChangedSettingsAppInstanceIdStr, box_value(L"")); + ApplicationData::Current().LocalSettings().Values().Insert(LastChangedSettingsKeyStr, box_value(LaunchElevetedProcessSuccessStr)); + + printDebugMessage(L"Adminstrator Extension has been launched."); + properties.push_back(pair("Accepted", "True")); + } + else + { + ApplicationData::Current().LocalSettings().Values().Insert(LastChangedSettingsAppInstanceIdStr, box_value(L"")); + ApplicationData::Current().LocalSettings().Values().Insert(LastChangedSettingsKeyStr, box_value(LaunchElevetedProcessSuccessStr)); + + printDebugMessage(L"Launching of Adminstrator Extension was cancelled."); + pair ex = getLastErrorDetails(); + properties.insert(properties.end(), + { + pair("Denied", "True"), + pair("Error Code", to_string(ex.first)), + pair("Error Message", to_string(ex.second)) + }); + } + + ApplicationData::Current().SignalDataChanged(); + AppCenter::trackEvent("OnAdminstratorPrivilageRequested", properties); +} + +bool isFirstInstance(LPCTSTR mutexName) +{ + auto result = true; + + HANDLE hMutex = OpenMutex(MUTEX_ALL_ACCESS, FALSE, mutexName); + if (!hMutex) + { + CreateMutex(NULL, FALSE, mutexName); + +#ifdef _DEBUG + initializeLogging(wcscmp(mutexName, DesktopExtensionMutexName) == 0 ? L"-extension.log" : L"-elevated-extension.log"); +#endif + } + else + { + result = false; + printDebugMessage(L"Closing this instance as another instance is already running.", 3000); + exitApp(); + } + if (hMutex) ReleaseMutex(hMutex); + + return result; +} + +bool isElevatedProcess() +{ + auto result = false; + + HANDLE hToken = NULL; + if (OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken)) + { + TOKEN_ELEVATION Elevation; + DWORD cbSize = sizeof(TOKEN_ELEVATION); + if (GetTokenInformation(hToken, TokenElevation, &Elevation, sizeof(Elevation), &cbSize)) + { + result = Elevation.TokenIsElevated; + } + } + + if (hToken) + { + CloseHandle(hToken); + } + + return result; +} + +bool isFileLaunchRequested() +{ + bool isFileLaunchRequested = true; + LPWSTR* szArglist = NULL; + INT nArgs; + szArglist = CommandLineToArgvW(GetCommandLineW(), &nArgs); + if (szArglist) + { + // Assumed first entry is for uwp app + auto aumid = Package::Current().GetAppListEntries().GetAt(0).AppUserModelId(); + wstring praid(aumid); + praid.erase(0, praid.find(L"!") + 1); + if (nArgs > 2 && wcscmp(szArglist[1], L"/InvokerPRAID:") == 0 && wcscmp(szArglist[2], praid.c_str()) == 0) + { + adminParams = format(L"{} {}", szArglist[1], szArglist[2]); + isFileLaunchRequested = false; + } + else + { + INT ct = nArgs - 1; + HRESULT hr = E_OUTOFMEMORY; + com_ptr< IShellItemArray> ppsia = NULL; + PIDLIST_ABSOLUTE* rgpidl = new(std::nothrow) PIDLIST_ABSOLUTE[ct]; + if (rgpidl) + { + hr = S_OK; + INT cpidl; + for (cpidl = 0; SUCCEEDED(hr) && cpidl < ct; cpidl++) + { + hr = SHParseDisplayName(szArglist[cpidl + 1], nullptr, &rgpidl[cpidl], 0, nullptr); + } + + if (cpidl > 0 && SUCCEEDED(SHCreateShellItemArrayFromIDLists(cpidl, rgpidl, ppsia.put()))) + { + com_ptr appActivationMgr = NULL; + if (SUCCEEDED(CoCreateInstance(CLSID_ApplicationActivationManager, NULL, CLSCTX_LOCAL_SERVER, __uuidof(appActivationMgr), appActivationMgr.put_void()))) + { + DWORD pid = 0; + appActivationMgr->ActivateForFile(aumid.c_str(), ppsia.get(), NULL, &pid); + printDebugMessage(format(L"Launched files with process id: {}", pid).c_str(), 5000); + } + appActivationMgr.~com_ptr(); + } + + for (INT i = 0; i < cpidl; i++) + { + CoTaskMemFree(rgpidl[i]); + } + } + + ppsia.~com_ptr(); + delete[] rgpidl; + } + } + + LocalFree(szArglist); + + return isFileLaunchRequested; +} + +#ifndef _DEBUG +INT APIENTRY wWinMain(_In_ HINSTANCE /* hInstance */, _In_opt_ HINSTANCE /* hPrevInstance */, _In_ LPWSTR /* lpCmdLine */, _In_ INT /* nCmdShow */) +#else +INT main() +#endif +{ + setExceptionHandling(); + SetErrorMode(SEM_NOGPFAULTERRORBOX); + _onexit(releaseResources); + + init_apartment(); + if (isFileLaunchRequested()) + { + uninit_apartment(); + return 0; + } + + AppCenter::start(); + + if (isElevatedProcess()) + { + if (!isFirstInstance(AdminExtensionMutexName)) return 0; + + initializeAdminService(); + } + else + { + if (!isFirstInstance(DesktopExtensionMutexName)) return 0; + + launchElevatedProcess(); + AppCenter::exit(); + uninit_apartment(); + return 0; + } + + LifeTimeCheck: + HANDLE lifeTimeObj = OpenMutex(SYNCHRONIZE, FALSE, format(L"AppContainerNamedObjects\\{}\\{}", packageSid, DesktopExtensionLifetimeObjNameStr).c_str()); + if (lifeTimeObj) + { + CloseHandle(lifeTimeObj); + Sleep(1000); + goto LifeTimeCheck; + } + AppCenter::exit(); + uninit_apartment(); + exit(0); +} \ 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..1c90c5bae --- /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.cpp b/src/Notepads.DesktopExtension/pch.cpp new file mode 100644 index 000000000..849a1cb4e --- /dev/null +++ b/src/Notepads.DesktopExtension/pch.cpp @@ -0,0 +1,134 @@ +#include "pch.h" +#include "appcenter.h" + +using namespace boost::stacktrace; +using namespace boost::stacktrace::detail; +using namespace fmt; +using namespace std; +using namespace winrt; +using namespace Windows::Foundation; +using namespace Windows::Storage; +using namespace Windows::System; + +constexpr INT MAX_TIME_STR = 20; +constexpr INT MAX_DATE_STR = 20; +constexpr INT MAX_DATETIME_STR = 100; + +StorageFile logFile = NULL; + +VOID printDebugMessage([[maybe_unused]] LPCTSTR message, [[maybe_unused]] DWORD sleepTime) { +#ifdef _DEBUG + wcout << message << endl; + Sleep(sleepTime); +#endif +} + +string to_string(ProcessorArchitecture arch) +{ + switch (arch) + { + case ProcessorArchitecture::Arm: + return "Arm"; + case ProcessorArchitecture::Arm64: + return "Arm64"; + case ProcessorArchitecture::X86OnArm64: + return "X86OnArm64"; + case ProcessorArchitecture::X86: + return "X86"; + case ProcessorArchitecture::X64: + return "X64"; + case ProcessorArchitecture::Neutral: + return "Neutral"; + default: + return "Unknown"; + } +} + +string getTimeStamp() +{ + SYSTEMTIME systemTime; + GetSystemTime(&systemTime); + return format("{}.{}Z", getTimeStamp("%FT%T"), systemTime.wMilliseconds); +} + +string getTimeStamp(const CHAR* format) +{ + time_t timePast = time(NULL); + tm utcTime; + gmtime_s(&utcTime, &timePast); + CHAR timeStamp[MAX_DATETIME_STR]; + strftime(timeStamp, sizeof(timeStamp), format, &utcTime); + return timeStamp; +} + +//From https://stackoverflow.com/a/34571089/5155484 +static const string b = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; +string base64_encode(const string& in) +{ + string out; + + INT val = 0, valb = -6; + for (UCHAR c : in) { + val = (val << 8) + c; + valb += 8; + while (valb >= 0) { + out.push_back(b[(val >> valb) & 0x3F]); + valb -= 6; + } + } + if (valb > -6) out.push_back(b[((val << 8) >> (valb + 8)) & 0x3F]); + while (out.size() % 4) out.push_back('='); + return out; +} + +pair getLastErrorDetails() +{ + LPVOID msgBuf; + DWORD errorCode = GetLastError(); + FormatMessage( + FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, + errorCode, + 0, + (LPTSTR)&msgBuf, + 0, + NULL); + + wstring msg; + getline(wstringstream((LPCTSTR)msgBuf), msg, L'\r'); + LocalFree(msgBuf); + + return pair { errorCode, msg }; +} + +IAsyncAction logLastError(bool isFatal) +{ + stacktrace st = stacktrace(); + hstring stackTrace = to_hstring(to_string(&st.as_vector()[0], st.size())); + + pair ex = getLastErrorDetails(); + + printDebugMessage(stackTrace.c_str(), 5000); + AppCenter::trackError(isFatal, ex.first, to_string(ex.second), st); + + if (logFile) + { + SYSTEMTIME systemTime; + GetSystemTime(&systemTime); + TCHAR timeStr[MAX_TIME_STR]; + GetTimeFormatEx(LOCALE_NAME_INVARIANT, 0, &systemTime, NULL, timeStr, MAX_TIME_STR); + TCHAR dateStr[MAX_DATE_STR]; + GetDateFormatEx(LOCALE_NAME_INVARIANT, 0, &systemTime, NULL, dateStr, MAX_DATE_STR, NULL); + wstring debugMsg = format(L"{} {} [Error] [Error Code: {}] {}{}\n{}\n", + dateStr, timeStr, ex.first, isFatal ? L"OnUnhandledException: " : L"OnUnexpectedException: ", ex.second, stackTrace); + + co_await PathIO::AppendTextAsync(logFile.Path(), debugMsg); + } +} + +fire_and_forget initializeLogging(LPCTSTR trailStr) +{ + auto localFolder = ApplicationData::Current().LocalFolder(); + auto logFolder = co_await localFolder.CreateFolderAsync(L"Logs", CreationCollisionOption::OpenIfExists); + logFile = co_await logFolder.CreateFileAsync(to_hstring(getTimeStamp("%Y%m%dT%H%M%S")) + to_hstring(trailStr), CreationCollisionOption::OpenIfExists); +} \ 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..416c063f5 --- /dev/null +++ b/src/Notepads.DesktopExtension/pch.h @@ -0,0 +1,53 @@ +#pragma once +#pragma comment(lib, "shell32") +#define STRICT +#define STRICT_TYPED_ITEMIDS +#include "iostream" +#include "sstream" +#include "windows.h" +#include "shellapi.h" +#include "winrt/Windows.ApplicationModel.h" +#include "winrt/Windows.ApplicationModel.Core.h" +#include "winrt/Windows.ApplicationModel.DataTransfer.h" +#include "winrt/Windows.Foundation.h" +#include "winrt/Windows.Foundation.Collections.h" +#include "winrt/Windows.Security.ExchangeActiveSyncProvisioning.h" +#include "winrt/Windows.Storage.h" +#include "winrt/Windows.System.h" +#include "winrt/Windows.System.Profile.h" +#include "boost/stacktrace.hpp" +#include "fmt/core.h" + +// These values depend upon constant fields described in ..\Notepads\Settings\SettingsKey.cs. +// Changing value in one place require changing variable with similar name in another. +///////////////////////////////////////////////////////////////////////////////////////////// +constexpr LPCTSTR AppCenterSecret = NULL; +constexpr LPCTSTR AppCenterInstallIdStr = L"AppCenterInstallIdStr"; +constexpr LPCTSTR LastChangedSettingsKeyStr = L"LastChangedSettingsKeyStr"; +constexpr LPCTSTR LastChangedSettingsAppInstanceIdStr = L"LastChangedSettingsAppInstanceIdStr"; +constexpr LPCTSTR LaunchElevetedProcessSuccessStr = L"LaunchElevetedProcessSuccess"; +constexpr LPCTSTR LaunchElevetedProcessFailedStr = L"LaunchElevetedProcessFailed"; +constexpr LPCTSTR DesktopExtensionLifetimeObjNameStr = L"DesktopExtensionLifetimeObj"; +constexpr LPCTSTR PackageSidStr = L"PackageSidStr"; +constexpr LPCTSTR AdminWriteEventNameStr = L"NotepadsAdminWriteEvent"; +constexpr LPCTSTR AdminWritePipeConnectionNameStr = L"NotepadsAdminWritePipe"; +constexpr LPCTSTR AdminRenameEventNameStr = L"NotepadsAdminRenameEvent"; +constexpr LPCTSTR AdminRenamePipeConnectionNameStr = L"NotepadsAdminRenamePipe"; +///////////////////////////////////////////////////////////////////////////////////////////// + +bool isElevatedProcess(); +VOID setExceptionHandling(); +VOID exitApp(); + +VOID printDebugMessage([[maybe_unused]] LPCTSTR message, [[maybe_unused]] DWORD sleepTime = 0); +std::string getTimeStamp(); +std::string getTimeStamp(const CHAR* format); +std::string to_string(winrt::Windows::System::ProcessorArchitecture arch); +std::string base64_encode(const std::string& in); + +VOID initializeAdminService(); +winrt::Windows::Foundation::IInspectable readSettingsKey(winrt::hstring key); + +std::pair getLastErrorDetails(); +winrt::Windows::Foundation::IAsyncAction logLastError(bool isFatal); +winrt::fire_and_forget initializeLogging(LPCTSTR trailStr); \ No newline at end of file diff --git a/src/Notepads.DesktopExtension/resource.h b/src/Notepads.DesktopExtension/resource.h new file mode 100644 index 000000000..72d6e0329 --- /dev/null +++ b/src/Notepads.DesktopExtension/resource.h @@ -0,0 +1,17 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by AssemblyInfo.rc +// +#define APPCENTER_SDK_VERSION 101 +#define TEXTFILE 256 + +// Next default values for new objects +// +#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 diff --git a/src/Notepads.DesktopExtension/vcpkg.json b/src/Notepads.DesktopExtension/vcpkg.json new file mode 100644 index 000000000..c2d3f2669 --- /dev/null +++ b/src/Notepads.DesktopExtension/vcpkg.json @@ -0,0 +1,24 @@ +{ + "name": "notepads-desktopextension", + "version-string": "1.0.0.0", + "dependencies": [ + { + "name": "boost-stacktrace", + "$version": "1.74.0" + }, + { + "name": "curl", + "$version": "7.74.0", + "$port-version": 1 + }, + { + "name": "fmt", + "$version": "7.1.3" + }, + { + "name": "rapidjson", + "$version-date": "2020-09-14" + } + ], + "$x-default-baseline": "331f87775cd53d6ab36545b0a1ba55e051b090f9" +} \ 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..8033b3539 --- /dev/null +++ b/src/Notepads.Package/Notepads.Package.wapproj @@ -0,0 +1,69 @@ + + + + 15.0 + + + + Debug + x86 + + + Release + x86 + + + Debug + x64 + + + Release + x64 + + + Debug + ARM + + + Release + ARM + + + Debug + ARM64 + + + Release + 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 + + + + + + + + + \ No newline at end of file diff --git a/src/Notepads.Package/Package.appxmanifest b/src/Notepads.Package/Package.appxmanifest new file mode 100644 index 000000000..57562b083 --- /dev/null +++ b/src/Notepads.Package/Package.appxmanifest @@ -0,0 +1,1211 @@ + + + + + + + + + + Notepads App + Jackie Liu + Assets\StoreLogo.png + + + + + + + + + + + + + + + + + + + + + + + + + + + Assets\appicon_w.png + Notepads URI Scheme + + + + + + + + + + + + .txt + + ms-resource:Manifest/TextFileDisplayName + + Assets\FileIcons\txt.png + + + + + + .cfg + .config + .cnf + .conf + .properties + .directory + + ms-resource:Manifest/ConfigFileDisplayName + Assets\FileIcons\cfg.png + + + + + + + .ini + + ms-resource:Manifest/InitializationFileDisplayName + Assets\FileIcons\ini.png + + + + + + + .md + .markdown + .mkd + .mdwn + .mdown + .markdn + .mdtxt + .workbook + + ms-resource:Manifest/MarkdownFileDisplayName + Assets\FileIcons\md.png + + + + + + + .log + + ms-resource:Manifest/LogFileDisplayName + Assets\FileIcons\log.png + + + + + + + .bib + + ms-resource:Manifest/BibTeXFileDisplayName + Assets\FileIcons\bib.png + + + + + + + .json + .jsonc + .jsonld + .hintrc + .babelrc + .eslintrc + .jslintrc + .bowerrc + .jscsrc + .webmanifest + .har + + ms-resource:Manifest/JsonFileDisplayName + Assets\FileIcons\json.png + + + + + + + .yml + .yaml + + ms-resource:Manifest/YamlFileDisplayName + Assets\FileIcons\yml.png + + + + + + + .xml + .xsd + .ascx + .atom + .axml + .bpmn + .cpt + .csl + + ms-resource:Manifest/XmlFileDisplayName + Assets\FileIcons\xml.png + + + + + + + .html + .htm + .shtml + .xhtml + .mdoc + .jshtm + .volt + + ms-resource:Manifest/HtmlFileDisplayName + Assets\FileIcons\html.png + + + + + + + .asp + .aspx + + ms-resource:Manifest/AspFileDisplayName + Assets\FileIcons\asp.png + + + + + + + .jsp + .jspx + + ms-resource:Manifest/JspFileDisplayName + Assets\FileIcons\jsp.png + + + + + + + .css + .scss + + ms-resource:Manifest/CssFileDisplayName + Assets\FileIcons\css.png + + + + + + + .sh + + ms-resource:Manifest/ShellScriptFileDisplayName + Assets\FileIcons\sh.png + + + + + + + .bashrc + .vimrc + .rc + + ms-resource:Manifest/RunCommandsFileDisplayName + Assets\FileIcons\rc.png + + + + + + + .bash + + ms-resource:Manifest/BashScriptFileDisplayName + Assets\FileIcons\bash.png + + + + + + + .js + .jsx + .es6 + .mjs + .cjs + .pac + + ms-resource:Manifest/JavascriptFileDisplayName + Assets\FileIcons\js.png + + + + + + + .ts + .tsx + + ms-resource:Manifest/TypeScriptFileDisplayName + Assets\FileIcons\file.png + + + + + + + .lua + + ms-resource:Manifest/LuaFileDisplayName + Assets\FileIcons\file.png + + + + + + + .c + .m + .i + + ms-resource:Manifest/CFileDisplayName + Assets\FileIcons\c.png + + + + + + + .cc + .mm + .cpp + .cxx + .ii + .ino + + ms-resource:Manifest/CppFileDisplayName + Assets\FileIcons\cpp.png + + + + + + + .h + .hh + .hpp + .hxx + + ms-resource:Manifest/HeaderFileDisplayName + Assets\FileIcons\h.png + + + + + + + .cmake + + ms-resource:Manifest/CmakeFileDisplayName + Assets\FileIcons\file.png + + + + + + + .cs + .csx + .cake + + ms-resource:Manifest/CSharpFileDisplayName + Assets\FileIcons\cs.png + + + + + + + .php + .php4 + .php5 + .phtml + .ctp + + ms-resource:Manifest/PhpFileDisplayName + Assets\FileIcons\php.png + + + + + + + .py + + ms-resource:Manifest/PythonFileDisplayName + Assets\FileIcons\py.png + + + + + + + .rb + .rbx + .rjs + .gemspec + .rake + .ru + .erb + .rbi + .arb + + ms-resource:Manifest/RubyFileDisplayName + + Assets\FileIcons\rb.png + + + + + + .java + .jav + + ms-resource:Manifest/JavaFileDisplayName + Assets\FileIcons\java.png + + + + + + + .vb + .vbs + .brs + .bas + + ms-resource:Manifest/VisualBasicFileDisplayName + Assets\FileIcons\vb.png + + + + + + + .go + + ms-resource:Manifest/GoFileDisplayName + Assets\FileIcons\file.png + + + + + + + .srt + + ms-resource:Manifest/SubtitleFileDisplayName + Assets\FileIcons\srt.png + + + + + + + .ass + + ms-resource:Manifest/AegisubAdvancedSubtitleFileDisplayName + Assets\FileIcons\ass.png + + + + + + + .ssa + + ms-resource:Manifest/SubStationAlphaSubtitleFileDisplayName + Assets\FileIcons\ssa.png + + + + + + + .vue + + ms-resource:Manifest/VueFileDisplayName + Assets\FileIcons\vue.png + + + + + + + .vuerc + + ms-resource:Manifest/VueConfigFileDisplayName + Assets\FileIcons\vue.png + + + + + + + .gitconfig + + ms-resource:Manifest/GitConfigFileDisplayName + Assets\FileIcons\file.png + + + + + + + .gitignore + + ms-resource:Manifest/GitIgnoreFileDisplayName + Assets\FileIcons\file.png + + + + + + + .gitattributes + + ms-resource:Manifest/GitAttributesFileDisplayName + Assets\FileIcons\file.png + + + + + + + .project + .prj + + ms-resource:Manifest/ProjectFileDisplayName + Assets\FileIcons\file.png + + + + + + + .npmrc + + ms-resource:Manifest/NpmConfigFileDisplayName + Assets\FileIcons\file.png + + + + + + + .bash_history + + ms-resource:Manifest/BashHistoryFileDisplayName + Assets\FileIcons\file.png + + + + + + + .bash_aliases + + ms-resource:Manifest/BashAliasesFileDisplayName + Assets\FileIcons\file.png + + + + + + + .bash_profile + + ms-resource:Manifest/BashProfileFileDisplayName + Assets\FileIcons\file.png + + + + + + + .bash_login + + ms-resource:Manifest/BashLoginFileDisplayName + Assets\FileIcons\file.png + + + + + + + .bash_logout + + ms-resource:Manifest/BashLogoutFileDisplayName + Assets\FileIcons\file.png + + + + + + + .cgi + + ms-resource:Manifest/CgiFileDisplayName + Assets\FileIcons\file.png + + + + + + + .pl + .pm + .psgi + + ms-resource:Manifest/PerlFileDisplayName + Assets\FileIcons\perl.png + + + + + + + .buildpath + + ms-resource:Manifest/BuildPathFileDisplayName + Assets\FileIcons\file.png + + + + + + + .sql + .dsql + + ms-resource:Manifest/SqlFileDisplayName + Assets\FileIcons\sql.png + + + + + + + .nfo + + ms-resource:Manifest/NfoFileDisplayName + Assets\FileIcons\file.png + + + + + + + .ahk + + ms-resource:Manifest/AutoHotkeyScriptFileDisplayName + Assets\FileIcons\file.png + + + + + + + .zshrc + + ms-resource:Manifest/ZshellConfigFileDisplayName + Assets\FileIcons\file.png + + + + + + + .zsh_history + + ms-resource:Manifest/ZshellHistoryFileDisplayName + Assets\FileIcons\file.png + + + + + + + .lrc + + ms-resource:Manifest/LrcFileDisplayName + Assets\FileIcons\file.png + + + + + + + .v + + ms-resource:Manifest/VerilogFileDisplayName + Assets\FileIcons\file.png + + + + + + + .dat + + ms-resource:Manifest/DatFileDisplayName + Assets\FileIcons\file.png + + + + + + + .glsp + + ms-resource:Manifest/GlspFileDisplayName + Assets\FileIcons\file.png + + + + + + + .asm + + ms-resource:Manifest/AsmFileDisplayName + Assets\FileIcons\file.png + + + + + + + .clj + .cljs + .cljc + .cljx + .clojure + .edn + + ms-resource:Manifest/ClojureFileDisplayName + Assets\FileIcons\file.png + + + + + + + .coffee + .cson + .iced + + ms-resource:Manifest/CoffeeScriptFileDisplayName + Assets\FileIcons\file.png + + + + + + + .dockerfile + + ms-resource:Manifest/DockerFileDisplayName + Assets\FileIcons\file.png + + + + + + + .fs + .fsi + .fsx + .fsscript + + ms-resource:Manifest/FSharpFileDisplayName + Assets\FileIcons\file.png + + + + + + + .patch + .diff + .rej + + ms-resource:Manifest/DiffFileDisplayName + Assets\FileIcons\file.png + + + + + + + .groovy + .gvy + .gradle + + ms-resource:Manifest/GroovyFileDisplayName + Assets\FileIcons\file.png + + + + + + + .handlebars + .hbs + .hjs + + ms-resource:Manifest/HandlebarsFileDisplayName + Assets\FileIcons\file.png + + + + + + + .hlsl + .hlsli + .fx + .fxh + .vsh + .psh + .cginc + .compute + + ms-resource:Manifest/HlslFileDisplayName + Assets\FileIcons\file.png + + + + + + + .map + + ms-resource:Manifest/MapFileDisplayName + Assets\FileIcons\file.png + + + + + + + .less + + ms-resource:Manifest/LessFileDisplayName + Assets\FileIcons\file.png + + + + + + + .t + + ms-resource:Manifest/TFileDisplayName + Assets\FileIcons\file.png + + + + + + + .pod + .podspec + + ms-resource:Manifest/PodFileDisplayName + Assets\FileIcons\file.png + + + + + + + .p6 + .pl6 + .pm6 + .nqp + + ms-resource:Manifest/Perl6FileDisplayName + Assets\FileIcons\perl.png + + + + + + + .cshtml + + ms-resource:Manifest/RazorFileDisplayName + Assets\FileIcons\file.png + + + + + + + .rs + + ms-resource:Manifest/RustFileDisplayName + Assets\FileIcons\file.png + + + + + + + .shader + + ms-resource:Manifest/ShaderFileDisplayName + Assets\FileIcons\file.png + + + + + + + .jade + .pug + + ms-resource:Manifest/PugFileDisplayName + Assets\FileIcons\file.png + + + + + + + .r + .rhistory + .rprofile + .rt + + ms-resource:Manifest/RFileDisplayName + Assets\FileIcons\file.png + + + + + + + .install + + ms-resource:Manifest/InstallFileDisplayName + Assets\FileIcons\file.png + + + + + + + .profile + + ms-resource:Manifest/ProfileFileDisplayName + Assets\FileIcons\file.png + + + + + + + .ebuild + + ms-resource:Manifest/EBuildFileDisplayName + Assets\FileIcons\file.png + + + + + + + .swift + + ms-resource:Manifest/SwiftFileDisplayName + Assets\FileIcons\file.png + + + + + + + .ps1 + .psm1 + .psd1 + .pssc + .psrc + + ms-resource:Manifest/PowerShellFileDisplayName + Assets\FileIcons\file.png + + + + + + + .xaml + + ms-resource:Manifest/XamlFileDisplayName + Assets\FileIcons\file.png + + + + + + + .user + + ms-resource:Manifest/UserFileDisplayName + Assets\FileIcons\file.png + + + + + + + .bond + + ms-resource:Manifest/BondFileDisplayName + Assets\FileIcons\file.png + + + + + + + .htaccess + + ms-resource:Manifest/HypertextAccessFileDisplayName + Assets\FileIcons\file.png + + + + + + + .csv + + ms-resource:Manifest/CommaSeparatedValuesFileDisplayName + Assets\FileIcons\file.png + + + + + + + .m3u + .m3u8 + + ms-resource:Manifest/M3uFileDisplayName + Assets\FileIcons\file.png + + + + + + + .glslp + + ms-resource:Manifest/GlslFileDisplayName + Assets\FileIcons\file.png + + + + + + + .cht + + ms-resource:Manifest/CheatFileDisplayName + Assets\FileIcons\file.png + + + + + + + .opt + + ms-resource:Manifest/OptFileDisplayName + Assets\FileIcons\file.png + + + + + + + .env + + ms-resource:Manifest/EnvironmentFileDisplayName + Assets\FileIcons\file.png + + + + + + + .toml + + ms-resource:Manifest/TomlFileDisplayName + Assets\FileIcons\file.png + + + + + + + .pvd + + ms-resource:Manifest/PvdFileDisplayName + Assets\FileIcons\file.png + + + + + + + .xsl + + ms-resource:Manifest/XslFileDisplayName + Assets\FileIcons\file.png + + + + + + + .lic + + ms-resource:Manifest/LicenseFileDisplayName + Assets\FileIcons\file.png + + + + + + + .csproj + + ms-resource:Manifest/CSharpeProjectFileDisplayName + Assets\FileIcons\file.png + + + + + + + .resx + + ms-resource:Manifest/ResourceFileDisplayName + Assets\FileIcons\file.png + + + + + + + .plist + + ms-resource:Manifest/PropertyListFileDisplayName + Assets\FileIcons\file.png + + + + + + + + .randomextension + * + + Assets\FileIcons\file.png + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Notepads.sln b/src/Notepads.sln index ad5a84593..22d8c8a18 100644 --- a/src/Notepads.sln +++ b/src/Notepads.sln @@ -15,46 +15,112 @@ 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}") = "Notepad", "Notepad\Notepad.vcxproj", "{0F39C2EB-F8C6-473E-B26A-0714385798F5}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|ARM = Debug|ARM Debug|ARM64 = Debug|ARM64 Debug|x64 = Debug|x64 Debug|x86 = Debug|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|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|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 + {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|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}.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}.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 + {0F39C2EB-F8C6-473E-B26A-0714385798F5}.Debug|ARM.ActiveCfg = Debug|ARM + {0F39C2EB-F8C6-473E-B26A-0714385798F5}.Debug|ARM.Build.0 = Debug|ARM + {0F39C2EB-F8C6-473E-B26A-0714385798F5}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {0F39C2EB-F8C6-473E-B26A-0714385798F5}.Debug|ARM64.Build.0 = Debug|ARM64 + {0F39C2EB-F8C6-473E-B26A-0714385798F5}.Debug|x64.ActiveCfg = Debug|x64 + {0F39C2EB-F8C6-473E-B26A-0714385798F5}.Debug|x64.Build.0 = Debug|x64 + {0F39C2EB-F8C6-473E-B26A-0714385798F5}.Debug|x86.ActiveCfg = Debug|Win32 + {0F39C2EB-F8C6-473E-B26A-0714385798F5}.Debug|x86.Build.0 = Debug|Win32 + {0F39C2EB-F8C6-473E-B26A-0714385798F5}.Release|ARM.ActiveCfg = Release|ARM + {0F39C2EB-F8C6-473E-B26A-0714385798F5}.Release|ARM.Build.0 = Release|ARM + {0F39C2EB-F8C6-473E-B26A-0714385798F5}.Release|ARM64.ActiveCfg = Release|ARM64 + {0F39C2EB-F8C6-473E-B26A-0714385798F5}.Release|ARM64.Build.0 = Release|ARM64 + {0F39C2EB-F8C6-473E-B26A-0714385798F5}.Release|x64.ActiveCfg = Release|x64 + {0F39C2EB-F8C6-473E-B26A-0714385798F5}.Release|x64.Build.0 = Release|x64 + {0F39C2EB-F8C6-473E-B26A-0714385798F5}.Release|x86.ActiveCfg = Release|Win32 + {0F39C2EB-F8C6-473E-B26A-0714385798F5}.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..591b0f496 100644 --- a/src/Notepads/App.xaml.cs +++ b/src/Notepads/App.xaml.cs @@ -31,8 +31,6 @@ sealed partial class App : Application public static bool IsPrimaryInstance = false; public static bool IsGameBarWidget = false; - private const string AppCenterSecret = null; - public static Mutex InstanceHandlerMutex { get; set; } /// @@ -45,7 +43,7 @@ public App() TaskScheduler.UnobservedTaskException += OnUnobservedException; var services = new Type[] { typeof(Crashes), typeof(Analytics) }; - AppCenter.Start(AppCenterSecret, services); + AppCenter.Start(SettingsKey.AppCenterSecret, services); InstanceHandlerMutex = new Mutex(true, App.ApplicationName, out bool isNew); if (isNew) @@ -65,6 +63,7 @@ public App() InitializeComponent(); Suspending += OnSuspending; + LeavingBackground += OnLeavingBackground; } /// @@ -107,7 +106,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 +217,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 +251,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..a4a2ffe7b --- /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 LaunchElevatedExtensionDialog : NotepadsDialog + { + public LaunchElevatedExtensionDialog(AdminOperationType type, string fileName, Action confirmedAction, Action closeAction) + { + if (type == AdminOperationType.Save) + { + Title = ResourceLoader.GetString("LaunchElevatedExtensionDialog_SaveFailed_Title"); + Content = string.Format(ResourceLoader.GetString("LaunchElevatedExtensionDialog_SaveFailed_Content"), fileName); + CloseButtonText = ResourceLoader.GetString("LaunchElevatedExtensionDialog_SaveFailed_CloseButtonText"); + } + else + { + Title = ResourceLoader.GetString("LaunchElevatedExtensionDialog_RenameFailed_Title"); + Content = string.Format(ResourceLoader.GetString("LaunchElevatedExtensionDialog_RenameFailed_Content"), fileName); + CloseButtonText = ResourceLoader.GetString("LaunchElevatedExtensionDialog_RenameFailed_CloseButtonText"); + } + + PrimaryButtonText = ResourceLoader.GetString("LaunchElevatedExtensionDialog_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 c408c9458..15e5a909b 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 @@ -59,7 +59,6 @@ false prompt true - true bin\ARM64\Release\ @@ -73,6 +72,29 @@ 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 + true bin\x64\Debug\ @@ -110,6 +132,7 @@ + @@ -136,7 +159,9 @@ + + @@ -856,6 +881,11 @@ + + + Windows Desktop Extensions for the UWP + + 14.0 diff --git a/src/Notepads/Package.appxmanifest b/src/Notepads/Package.appxmanifest index 88e6e9a9b..b9296a118 100644 --- a/src/Notepads/Package.appxmanifest +++ b/src/Notepads/Package.appxmanifest @@ -4,12 +4,14 @@ xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10" xmlns:mp="http://schemas.microsoft.com/appx/2014/phone/manifest" xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10" + xmlns:uap3="http://schemas.microsoft.com/appx/manifest/uap/windows10/3" 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: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 uap3 uap5 uap10 desktop desktop4 iot2 rescap"> + @@ -35,7 +38,7 @@ + @@ -181,7 +185,7 @@ .axml .bpmn .cpt - .csl + .csl ms-resource:Manifest/XmlFileDisplayName Assets\FileIcons\xml.png @@ -197,7 +201,7 @@ .xhtml .mdoc .jshtm - .volt + .volt ms-resource:Manifest/HtmlFileDisplayName Assets\FileIcons\html.png @@ -402,7 +406,7 @@ .ru .erb .rbi - .arb + .arb ms-resource:Manifest/RubyFileDisplayName @@ -657,7 +661,7 @@ - + .zshrc @@ -1176,10 +1180,31 @@ + + + + - + \ 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..6e3c2beee --- /dev/null +++ b/src/Notepads/Services/DesktopExtensionService.cs @@ -0,0 +1,214 @@ +namespace Notepads.Services +{ + using Microsoft.AppCenter; + using Notepads.Controls.Dialog; + 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.DataTransfer; + using Windows.ApplicationModel.Resources; + using Windows.Foundation.Metadata; + using Windows.Security.Authentication.Web; + using Windows.Storage; + + public enum AdminOperationType + { + 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); + + public static Mutex ExtensionLifetimeObject = null; + + private static readonly ResourceLoader _resourceLoader = ResourceLoader.GetForCurrentView(); + + private static EventWaitHandle _adminWriteEvent = null; + private static EventWaitHandle _adminRenameEvent = null; + + public static void Initialize() + { + if (!ShouldUseDesktopExtension) return; + + if (ExtensionLifetimeObject == null) + { + ExtensionLifetimeObject = new Mutex(false, SettingsKey.DesktopExtensionLifetimeObjNameStr); + } + + if (_adminWriteEvent == null) + { + _adminWriteEvent = new EventWaitHandle(false, EventResetMode.ManualReset, SettingsKey.AdminWriteEventNameStr); + } + + if (_adminRenameEvent == null) + { + _adminRenameEvent = new EventWaitHandle(false, EventResetMode.ManualReset, SettingsKey.AdminRenameEventNameStr); + } + } + + /// + /// Launch desktop extension with elevated privilages. + /// + /// + /// Only available for legacy Windows 10 desktop. + /// + public static async Task LaunchElevetedProcess() + { + if (!ShouldUseDesktopExtension) return; + + ApplicationSettingsStore.Write(SettingsKey.PackageSidStr, WebAuthenticationBroker.GetCurrentApplicationCallbackUri().Host.ToUpper()); + ApplicationSettingsStore.Write(SettingsKey.AppCenterInstallIdStr, (await AppCenter.GetInstallIdAsync())?.ToString()); + await FullTrustProcessLauncher.LaunchFullTrustProcessForCurrentAppAsync(); + } + + /// + /// 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_AdminExtensionCreated"), 1500); + } + else + { + NotificationCenter.Instance.PostNotification(_resourceLoader.GetString("TextEditor_NotificationMsg_AdminExtensionCreationFailed"), 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 adminConnectionPipeStream = new NamedPipeServerStream( + $"Local\\{SettingsKey.AdminWritePipeConnectionNameStr}", + PipeDirection.InOut, + NamedPipeServerStream.MaxAllowedServerInstances, + PipeTransmissionMode.Message, + PipeOptions.Asynchronous)) + { + _adminWriteEvent.Set(); + // Wait for 250 ms for desktop extension to accept request. + if (!adminConnectionPipeStream.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. + _adminWriteEvent.Reset(); + throw new AdminstratorAccessException(); + } + + var pipeReader = new StreamReader(adminConnectionPipeStream, new System.Text.UnicodeEncoding(!BitConverter.IsLittleEndian, false)); + var pipeWriter = new StreamWriter(adminConnectionPipeStream, 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())) + { + return; + } + else + { + // Promt user to "Save As" if extension failed to save data. + 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 adminConnectionPipeStream = new NamedPipeServerStream( + $"Local\\{SettingsKey.AdminRenamePipeConnectionNameStr}", + PipeDirection.InOut, + NamedPipeServerStream.MaxAllowedServerInstances, + PipeTransmissionMode.Message, + PipeOptions.Asynchronous)) + { + _adminRenameEvent.Set(); + // Wait for 250 ms for desktop extension to accept request. + if (!adminConnectionPipeStream.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. + _adminRenameEvent.Reset(); + var launchElevatedExtensionDialog = new LaunchElevatedExtensionDialog( + AdminOperationType.Rename, + file.Path, + async () => { await DesktopExtensionService.LaunchElevetedProcess(); }, + null); + + var dialogResult = await DialogManager.OpenDialogAsync(launchElevatedExtensionDialog, awaitPreviousDialog: false); + + return null; + } + + var pipeReader = new StreamReader(adminConnectionPipeStream, new System.Text.UnicodeEncoding(!BitConverter.IsLittleEndian, false)); + var pipeWriter = new StreamWriter(adminConnectionPipeStream, new System.Text.UnicodeEncoding(!BitConverter.IsLittleEndian, false)); + + var token = SharedStorageAccessManager.AddFile(file); + await pipeWriter.WriteAsync($"{token}|{newName}"); + await pipeWriter.FlushAsync(); + + // Wait for desktop extension to send response. + token = await pipeReader.ReadLineAsync(); + file = await SharedStorageAccessManager.RedeemTokenForFileAsync(token); + SharedStorageAccessManager.RemoveFile(token); + return file; + } + } + } +} diff --git a/src/Notepads/Services/InterInstanceSyncService.cs b/src/Notepads/Services/InterInstanceSyncService.cs new file mode 100644 index 000000000..5210592b5 --- /dev/null +++ b/src/Notepads/Services/InterInstanceSyncService.cs @@ -0,0 +1,83 @@ +namespace Notepads.Services +{ + 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 }, + { SettingsKey.LaunchElevetedProcessFailedStr, DesktopExtensionService.OnElevetedProcessLaunchRequested }, + { SettingsKey.LaunchElevetedProcessSuccessStr, 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(SettingsKey.LastChangedSettingsAppInstanceIdStr) is string lastChangedSettingsAppInstanceIdStr && + lastChangedSettingsAppInstanceIdStr == App.Id.ToString()) + { + return; + } + + if (ApplicationSettingsStore.Read(SettingsKey.LastChangedSettingsKeyStr) is string lastChangedSettingsKeyStr && + SyncManager.ContainsKey(lastChangedSettingsKeyStr) && _notepadsMainPage != null) + { + await DispatcherExtensions.CallOnUIThreadAsync(_notepadsMainPage.Dispatcher, () => + { + if (lastChangedSettingsKeyStr != RecentFilesListKey && + lastChangedSettingsKeyStr != SettingsKey.LaunchElevetedProcessSuccessStr && + lastChangedSettingsKeyStr != SettingsKey.LaunchElevetedProcessFailedStr) + { + _notepadsMainPage.CloseSettingsPane(); + } + + if (lastChangedSettingsKeyStr == SettingsKey.LaunchElevetedProcessFailedStr) + { + 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..8c5b785ef 100644 --- a/src/Notepads/Settings/ApplicationSettings.cs +++ b/src/Notepads/Settings/ApplicationSettings.cs @@ -6,14 +6,15 @@ 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 +22,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[SettingsKey.LastChangedSettingsKeyStr] = key; + _localSettings.Values[SettingsKey.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..9c9ceeab3 100644 --- a/src/Notepads/Settings/SettingsKey.cs +++ b/src/Notepads/Settings/SettingsKey.cs @@ -3,37 +3,52 @@ 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"; + + // Interop related + // These values depend upon constant fields described in ..\Notepads.DesktopExtension\pch.h. + // Changing value in one place require changing variable with similar name in another. + internal const string AppCenterSecret = null; + internal const string AppCenterInstallIdStr = "AppCenterInstallIdStr"; + internal const string LastChangedSettingsKeyStr = "LastChangedSettingsKeyStr"; + internal const string LastChangedSettingsAppInstanceIdStr = "LastChangedSettingsAppInstanceIdStr"; + internal const string LaunchElevetedProcessSuccessStr = "LaunchElevetedProcessSuccess"; + internal const string LaunchElevetedProcessFailedStr = "LaunchElevetedProcessFailed"; + internal const string DesktopExtensionLifetimeObjNameStr = "DesktopExtensionLifetimeObj"; + internal const string PackageSidStr = "PackageSidStr"; + internal const string AdminWriteEventNameStr = "NotepadsAdminWriteEvent"; + internal const string AdminWritePipeConnectionNameStr = "NotepadsAdminWritePipe"; + internal const string AdminRenameEventNameStr = "NotepadsAdminRenameEvent"; + internal const string AdminRenamePipeConnectionNameStr = "NotepadsAdminRenamePipe"; } } \ 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..7ba5acad3 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 + LaunchElevatedExtensionDialog: Save Failed CloseButtonText. + + + Notepads needs Adminstrative Privilage to modify file "{0}" + LaunchElevatedExtensionDialog: Save Failed "Content" display text. + + + Save Failed + LaunchElevatedExtensionDialog: Save Failed "Title" display text. + + + Now system files can be directly modified + TextEditor: Notification message when launching of admin process is successful. + + + Failed to start admin process + TextEditor: Notification message when launching of admin process is failed. + + + Give Access + LaunchElevatedExtensionDialog: PrimaryButtonText. + + + Cancel + LaunchElevatedExtensionDialog: Rename Failed CloseButtonText. + + + Notepads needs Adminstrative Privilage to rename file "{0}" + LaunchElevatedExtensionDialog: Rename Failed "Content" display text. + + + Rename Failed + LaunchElevatedExtensionDialog: 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..073e8588c 100644 --- a/src/Notepads/Utilities/FileSystemUtility.cs +++ b/src/Notepads/Utilities/FileSystemUtility.cs @@ -573,7 +573,16 @@ 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); + } + 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 { diff --git a/src/Notepads/Views/MainPage/NotepadsMainPage.IO.cs b/src/Notepads/Views/MainPage/NotepadsMainPage.IO.cs index 81d0e8b2f..2ccc2ae49 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 launchElevatedExtensionDialog = new LaunchElevatedExtensionDialog( + AdminOperationType.Save, file.Path, + async () => + { + await DesktopExtensionService.LaunchElevetedProcess(); + }, + () => + { + isSaveSuccess = false; + }); + + var dialogResult = await DialogManager.OpenDialogAsync(launchElevatedExtensionDialog, awaitPreviousDialog: false); + + if (dialogResult == null || launchElevatedExtensionDialog.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..b4aaf6e87 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.ExtensionLifetimeObject?.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.ExtensionLifetimeObject?.Dispose(); deferral.Complete(); return; } @@ -390,6 +395,7 @@ private async void MainPage_CloseRequested(object sender, Windows.UI.Core.Previe else { App.InstanceHandlerMutex?.Dispose(); + DesktopExtensionService.ExtensionLifetimeObject?.Dispose(); } deferral.Complete(); @@ -397,6 +403,7 @@ private async void MainPage_CloseRequested(object sender, Windows.UI.Core.Previe discardAndExitAction: () => { App.InstanceHandlerMutex?.Dispose(); + DesktopExtensionService.ExtensionLifetimeObject?.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)