#include #include #include #include #include // KNOWNFOLDERID, SHGetKnownFolderPath #include // OPENFILENAME #include // _com_error #include // WinHTTP #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "module.h" #include "themeinfo.h" #include "mpstr.h" #include "initfile.h" #include "vemani.h" #include "ieshell.h" #include "localeex.h" #include "download.h" #include "bridge.h" #include "rctools.h" #include "nstring.h" #include "resmap.h" #include "nstring.h" #include "raii.h" using namespace System; using namespace System::Runtime::InteropServices; using namespace System::IO::Compression; using namespace System::Text; #ifdef _DEBUG #define DEBUGMODE true #else #define DEBUGMODE false #endif #define JS_SAFE [MarshalAs (UnmanagedType::SafeArray, SafeArraySubType = VarEnum::VT_VARIANT)] struct iconhandle { HICON hIcon = nullptr; iconhandle (HICON hIcon = nullptr): hIcon (hIcon) {} ~iconhandle () { try { if (hIcon) DestroyIcon (hIcon); hIcon = nullptr; } catch (...) {} } }; LPCWSTR g_lpAppId = L"WindowsModern.PracticalToolsProject!Settings"; LPCWSTR g_idInVe = L"Settings"; LPCWSTR g_wndclass = L"Win32_WebUI_WindowsModern_Settings"; iconhandle g_hIconMain (LoadRCIcon (IDI_ICON_MAIN)); initfile g_initfile (CombinePath (GetProgramRootDirectoryW (), L"config.ini")); vemanifest g_vemani ( IsFileExists (CombinePath (GetProgramRootDirectoryW (), L"VisualElementsManifest.xml")) ? CombinePath (GetProgramRootDirectoryW (), L"VisualElementsManifest.xml") : CombinePath (GetProgramRootDirectoryW (), L"Settings.VisualElementsManifest.xml") ); resxmldoc g_scaleres ( IsFileExists (CombinePath (GetProgramRootDirectoryW (), L"VisualElements\\scale.xml")) ? CombinePath (GetProgramRootDirectoryW (), L"VisualElements\\scale.xml") : CombinePath (GetProgramRootDirectoryW (), L"VisualElementsManifest.xml") ); ref class MainHtmlWnd; msclr::gcroot g_mainwnd; std::wstring g_lastfile; inline std::wstring ToStdWString (const std::wstring &str) { return str; } std::string GetSuitableLanguageValue (const std::map &map, const std::nstring &localename) { for (auto &it : map) if (it.first == localename) return it.second; for (auto &it : map) if (LocaleNameCompare (pugi::as_wide (it.first), pugi::as_wide (localename))) return it.second; for (auto &it : map) if (IsNormalizeStringEquals (GetLocaleRestrictedCodeA (it.first), GetLocaleRestrictedCodeA (localename))) return it.second; for (auto &it : map) if (LocaleNameCompare (pugi::as_wide (GetLocaleRestrictedCodeA (it.first)), pugi::as_wide (GetLocaleRestrictedCodeA (localename)))) return it.second; return ""; } std::string GetSuitableLanguageValue (const std::map &map) { if (map.empty ()) return ""; std::string ret = GetSuitableLanguageValue (map, pugi::as_utf8 (GetComputerLocaleCodeW ())); if (ret.empty ()) ret = GetSuitableLanguageValue (map, "en-US"); if (ret.empty ()) ret = map.begin ()->second; return ret; } struct xmlstrres { pugi::xml_document doc; bool isvalid = false; void destroy () { if (isvalid) doc.reset (); isvalid = false; } bool create (const std::wstring &filepath) { destroy (); auto res = doc.load_file (filepath.c_str ()); return isvalid = res; } xmlstrres (const std::wstring &filepath) { create (filepath); } ~xmlstrres () { destroy (); } std::string get (const std::string &id) const { auto root = doc.first_child (); auto nodes = root.children (); for (auto &it : nodes) { if (IsNormalizeStringEquals (std::string (it.attribute ("id").as_string ()), id)) { auto strings = it.children (); std::map lang_value; for (auto &sub : strings) { std::nstring lang = sub.attribute ("name").as_string (); if (!lang.empty ()) lang_value [lang] = sub.text ().get (); } return GetSuitableLanguageValue (lang_value); } } return ""; } std::wstring get (const std::wstring &id) const { return pugi::as_wide (get (pugi::as_utf8 (id))); } std::wstring operator [] (const std::wstring &id) const { return get (id); } std::wstring operator [] (const std::wstring &id) { return get (id); } std::string operator [] (const std::string &id) const { return get (id); } std::string operator [] (const std::string &id) { return get (id); } }; xmlstrres g_winjspri (CombinePath (GetProgramRootDirectoryW (), L"locale\\resources.xml")); std::vector g_cmdargs; size_t ExploreFile (HWND hParent, std::vector &results, LPWSTR lpFilter = L"Windows Store App Package (*.appx; *.appxbundle)\0*.appx;*.appxbundle", DWORD dwFlags = OFN_EXPLORER | OFN_ALLOWMULTISELECT | OFN_PATHMUSTEXIST, const std::wstring &swWndTitle = std::wstring (L"Please select the file(-s): "), const std::wstring &swInitDir = GetFileDirectoryW (g_lastfile)) { results.clear (); const DWORD BUFFER_SIZE = 65536; // 64KB std::vector buffer (BUFFER_SIZE, 0); OPENFILENAME ofn; ZeroMemory (&ofn, sizeof (ofn)); ofn.hwndOwner = hParent; ofn.lpstrFile = (LPWSTR)buffer.data (); ofn.nMaxFile = BUFFER_SIZE; ofn.lpstrFilter = lpFilter; ofn.nFilterIndex = 1; ofn.lpstrTitle = swWndTitle.c_str (); ofn.Flags = dwFlags; ofn.lpstrInitialDir = swInitDir.c_str (); ofn.lStructSize = sizeof (ofn); if (GetOpenFileNameW (&ofn)) { LPCWSTR p = buffer.data (); std::wstring dir = p; p += dir.length () + 1; if (*p == 0) results.push_back (dir); else { while (*p) { std::wstring fullPath = dir + L"\\" + p; results.push_back (fullPath); p += wcslen (p) + 1; } } if (!results.empty ()) g_lastfile = results.back (); } return results.size (); } HRESULT GetWebBrowser2Interface (System::Windows::Forms::WebBrowser ^fwb, IWebBrowser2 **output) { if (fwb == nullptr || output == nullptr) return E_INVALIDARG; *output = nullptr; Object ^activeX = fwb->ActiveXInstance; if (activeX == nullptr) return E_FAIL; IntPtr pUnk = Marshal::GetIUnknownForObject (activeX); if (pUnk == IntPtr::Zero) return E_FAIL; HRESULT hr = ((IUnknown *)pUnk.ToPointer ())->QueryInterface (IID_IWebBrowser2, (void **)output); Marshal::Release (pUnk); return hr; } bool IsWindows10 () { #pragma warning(push) #pragma warning(disable:4996) OSVERSIONINFOEX osvi = {0}; osvi.dwOSVersionInfoSize = sizeof (OSVERSIONINFOEX); osvi.dwMajorVersion = 10; DWORDLONG conditionMask = 0; VER_SET_CONDITION (conditionMask, VER_MAJORVERSION, VER_GREATER_EQUAL); if (VerifyVersionInfoW (&osvi, VER_MAJORVERSION, conditionMask)) return TRUE; DWORD error = GetLastError (); return (error == ERROR_OLD_WIN_VERSION) ? FALSE : FALSE; #pragma warning(pop) } [ComVisible (true)] public ref class _I_InitConfig { public: Win32::InitConfig ^Create (String ^filepath) { return gcnew Win32::InitConfig (filepath); } Win32::InitConfig ^GetConfig () { return Create (CStringToMPString (g_initfile.filepath)); } }; [ComVisible (true)] public ref class _I_VisualElement { protected: String ^appid; public: _I_VisualElement (String ^appid): appid (appid) {} _I_VisualElement (): appid (String::Empty) {} property String ^Id { String ^get () { return appid; } void set (String ^value) { appid = value; } } #define PROPERTY_VELEMENT(_PropertyName_, _MethodName_) \ property String ^_PropertyName_ { \ String ^get() { \ return CStringToMPString(g_vemani._MethodName_(MPStringToStdW(appid))); \ } \ } PROPERTY_VELEMENT (DisplayName, display_name) PROPERTY_VELEMENT (Logo, logo) PROPERTY_VELEMENT (SmallLogo, small_logo) property String ^ForegroundText { String ^get () { return g_vemani.foreground_text (MPStringToStdW (appid)) == vemanifest::TextColor::light ? "light" : "dark"; }} PROPERTY_VELEMENT (Lnk32x32Logo, lnk_32x32_logo) PROPERTY_VELEMENT (ItemDisplayLogo, item_display_logo) property bool ShowNameOnTile { bool get () { return g_vemani.show_name_on_tile (MPStringToStdW (appid)); }} PROPERTY_VELEMENT (BackgroundColor, background_color) PROPERTY_VELEMENT (SplashScreenImage, splash_screen_image) PROPERTY_VELEMENT (SplashScreenBackgroundColor, splash_screen_backgroundcolor) PROPERTY_VELEMENT (SplashScreenBackgroundColorDarkMode, splash_screen_backgroundcolor_darkmode) #ifdef PROPERTY_VELEMENT #undef PROPERTY_VELEMENT #endif Object ^Get (String ^propertyName) { String ^str = propertyName->ToLower ()->Trim (); if (str == "displayname") return DisplayName; else if (str == "logo") return Logo; else if (str == "smalllogo") return SmallLogo; else if (str == "foregroundtext") return ForegroundText; else if (str == "lnk32x32logo") return Lnk32x32Logo; else if (str == "shownameontile") return ShowNameOnTile; else if (str == "backgroundcolor") return BackgroundColor; else if (str == "splashscreenimage") return SplashScreenImage; else if (str == "splashscreenbackgroundcolor") return SplashScreenBackgroundColor; else if (str == "splashscreenbackgroundcolordarkmode") return SplashScreenBackgroundColorDarkMode; return String::Empty; } Object ^operator [] (String ^propertyName) { return Get (propertyName); } }; [ComVisible (true)] public ref class _I_VisualElements { public: array ^GetIds () { std::vector res; g_vemani.app_ids (res); auto ret = gcnew array (res.size ()); for (size_t i = 0; i < res.size (); i ++) ret [i] = CStringToMPString (res [i]); return ret; } String ^GetIdsToJson () { return StringArrayToJson (GetIds ()); } _I_VisualElement ^Get (String ^id) { return gcnew _I_VisualElement (id); } _I_VisualElement ^operator [] (String ^id) { return Get (id); } #define ATTRIBUTE_METHODS(_FunctionName_, _MethodName_) \ String^ _FunctionName_(String^ appid) { \ return CStringToMPString(g_vemani._MethodName_(MPStringToStdW(appid))); \ } ATTRIBUTE_METHODS (DisplayName, display_name) ATTRIBUTE_METHODS (Logo, logo) ATTRIBUTE_METHODS (SmallLogo, small_logo) String ^ForegroundText (String ^appid) { return g_vemani.foreground_text (MPStringToStdW (appid)) == vemanifest::TextColor::light ? "light" : "dark"; } ATTRIBUTE_METHODS (Lnk32x32Logo, lnk_32x32_logo) ATTRIBUTE_METHODS (ItemDisplayLogo, item_display_logo) bool ShowNameOnTile (String ^appid) { return g_vemani.show_name_on_tile (MPStringToStdW (appid)); } ATTRIBUTE_METHODS (BackgroundColor, background_color) ATTRIBUTE_METHODS (SplashScreenImage, splash_screen_image) ATTRIBUTE_METHODS (SplashScreenBackgroundColor, splash_screen_backgroundcolor) ATTRIBUTE_METHODS (SplashScreenBackgroundColorDarkMode, splash_screen_backgroundcolor_darkmode) #ifdef ATTRIBUTE_METHODS #undef ATTRIBUTE_METHODS #endif Object ^GetValue (String ^appid, String ^attributeName) { auto attr = attributeName->ToLower ()->Trim (); if (attr == "displayname") return DisplayName (appid); else if (attr == "logo") return Logo (appid); else if (attr == "smalllogo") return SmallLogo (appid); else if (attr == "foregroundtext") return ForegroundText (appid); else if (attr == "lnk32x32logo") return Lnk32x32Logo (appid); else if (attr == "itemdisplaylogo") return ItemDisplayLogo (appid); else if (attr == "shownameontile") return ShowNameOnTile (appid); else if (attr == "backgroundcolor") return BackgroundColor (appid); else if (attr == "splashscreenimage") return SplashScreenImage (appid); else if (attr == "splashscreenbackgroundcolor") return SplashScreenBackgroundColor (appid); else if (attr == "splashscreenbackgroundcolordarkmode") return SplashScreenBackgroundColorDarkMode (appid); else return String::Empty; } }; [ComVisible (true)] public ref class _I_Bridge_Base { protected: _I_String ^str = gcnew _I_String (); _I_InitConfig ^initconfig = gcnew _I_InitConfig (); _I_Storage ^storage = gcnew _I_Storage (); public: property _I_String ^String { _I_String ^get () { return str; }} property _I_InitConfig ^Config { _I_InitConfig ^get () { return initconfig; }} property _I_Storage ^Storage { _I_Storage ^get () { return storage; }} }; [ComVisible (true)] public interface class IScriptBridge { public: virtual Object ^CallEvent (String ^funcName, Object ^e) = 0; }; [ComVisible (true)] public ref class _I_Window { private: IScriptBridge ^wndinst = nullptr; public: _I_Window (IScriptBridge ^wnd): wndinst (wnd) {} Object ^CallEvent (String ^name, ... array ^args) { return wndinst->CallEvent (name, args [0]); } }; [ComVisible (true)] public ref class _I_UI { private: System::Windows::Forms::Form ^wndinst = nullptr; public: ref struct _I_UI_Size { private: int m_width = 0; int m_height = 0; public: property int width { int get () { return m_width; } } property int height { int get () { return m_height; }} property int Width { int get () { return m_width; } } property int Height { int get () { return m_height; }} int getWidth () { return m_width; } int getHeight () { return m_height; } _I_UI_Size (int w, int h): m_width (w), m_height (h) {} }; _I_UI (System::Windows::Forms::Form ^wnd): wndinst (wnd) {} property int DPIPercent { int get () { return GetDPI (); }} property double DPI { double get () { return DPIPercent * 0.01; }} property _I_UI_Size ^WndSize { _I_UI_Size ^get () { return gcnew _I_UI_Size (wndinst->Width, wndinst->Height); } } property _I_UI_Size ^ClientSize { _I_UI_Size ^get () { auto cs = wndinst->ClientSize; return gcnew _I_UI_Size (cs.Width, cs.Height); } } property String ^ThemeColor { String ^get () { return ColorToHtml (GetDwmThemeColor ()); } } property bool DarkMode { bool get () { return IsAppInDarkMode (); }} property String ^HighContrast { String ^get () { auto highc = GetHighContrastTheme (); switch (highc) { case HighContrastTheme::None: return "none"; break; case HighContrastTheme::Black: return "black"; break; case HighContrastTheme::White: return "white"; break; case HighContrastTheme::Other: return "high"; break; default: return "none"; break; } return "none"; } } }; [ComVisible (true)] public ref class _I_Locale { public: property String ^CurrentLocale { String ^get () { return CStringToMPString (GetComputerLocaleCodeW ()); } } property LCID CurrentLCID { LCID get () { return LocaleCodeToLcid (GetComputerLocaleCodeW ()); } } String ^ToLocaleName (LCID lcid) { return CStringToMPString (LcidToLocaleCodeW (lcid)); } LCID ToLCID (String ^localename) { return LocaleCodeToLcidW (MPStringToStdW (localename)); } Object ^LocaleInfo (LCID lcid, LCTYPE lctype) { return CStringToMPString (GetLocaleInfoW (lcid, lctype)); } Object ^LocaleInfoEx (String ^localeName, LCTYPE lctype) { std::wstring output = L""; int ret = GetLocaleInfoEx (MPStringToStdW (localeName), lctype, output); if (output.empty ()) return ret; else return CStringToMPString (output); } }; [ComVisible (true)] public ref class _I_System { private: _I_Resources ^ires = gcnew _I_Resources (); _I_Locale ^ilocale = gcnew _I_Locale (); public: property _I_Resources ^Resources { _I_Resources ^get () { return ires; } } property _I_Locale ^Locale { _I_Locale ^get () { return ilocale; } } property bool IsWindows10 { bool get () { OSVERSIONINFOEX osvi = {0}; osvi.dwOSVersionInfoSize = sizeof (OSVERSIONINFOEX); osvi.dwMajorVersion = 10; DWORDLONG conditionMask = 0; VER_SET_CONDITION (conditionMask, VER_MAJORVERSION, VER_GREATER_EQUAL); if (VerifyVersionInfoW (&osvi, VER_MAJORVERSION, conditionMask)) return TRUE; DWORD error = GetLastError (); return (error == ERROR_OLD_WIN_VERSION) ? FALSE : FALSE; } } }; public ref class _I_System2: public _I_System { protected: _I_UI ^ui; public: _I_System2 (System::Windows::Forms::Form ^wnd) { ui = gcnew _I_UI (wnd); } property _I_UI ^UI { _I_UI ^get () { return ui; } } }; [ComVisible (true)] public ref class _I_Bridge_Base2: public _I_Bridge_Base { protected: _I_Window ^window; public: _I_Bridge_Base2 (IScriptBridge ^iscr) { window = gcnew _I_Window (iscr); } property _I_Window ^Window { _I_Window ^get () { return window; }} }; [ComVisible (true)] public ref class _I_Bridge_Base3: public _I_Bridge_Base2 { protected: _I_System2 ^system; public: _I_Bridge_Base3 (IScriptBridge ^iscr, System::Windows::Forms::Form ^form): _I_Bridge_Base2 (iscr) { system = gcnew _I_System2 (form); } property _I_System2 ^System { _I_System2 ^get () { return system; }} }; [ComVisible (true)] public ref class _I_IEFrame_Base { public: property int Version { int get () { return GetInternetExplorerVersionMajor (); }} String ^ParseHtmlColor (String ^color) { auto dcolor = Drawing::ColorTranslator::FromHtml (color); { rapidjson::Document doc; doc.SetObject (); auto &alloc = doc.GetAllocator (); doc.AddMember ("r", (uint16_t)dcolor.R, alloc); doc.AddMember ("g", (uint16_t)dcolor.G, alloc); doc.AddMember ("b", (uint16_t)dcolor.B, alloc); doc.AddMember ("a", (uint16_t)dcolor.A, alloc); rapidjson::StringBuffer buffer; rapidjson::Writer writer (buffer); doc.Accept (writer); std::string utf8 = buffer.GetString (); std::wstring_convert > conv; return CStringToMPString (conv.from_bytes (utf8)); } return "{}"; } }; int ExecuteProgram ( const std::wstring &cmdline, const std::wstring &file, int wndshowmode, bool wait, const std::wstring &execdir = L"") { STARTUPINFOW si; PROCESS_INFORMATION pi; ZeroMemory (&si, sizeof (si)); ZeroMemory (&pi, sizeof (pi)); si.cb = sizeof (si); si.dwFlags = STARTF_USESHOWWINDOW; si.wShowWindow = static_cast (wndshowmode); std::vector buf (cmdline.capacity () + 1); wcscpy (buf.data (), cmdline.c_str ()); LPCWSTR workdir = IsNormalizeStringEmpty (execdir) ? NULL : execdir.c_str (); BOOL ok = CreateProcessW ( IsNormalizeStringEmpty (file) ? NULL : file.c_str (), // 应用程序路径 IsNormalizeStringEmpty (cmdline) ? NULL : buf.data (), // 命令行必须可写 NULL, // 进程安全属性 NULL, // 线程安全属性 FALSE, // 不继承句柄 0, // 创建标志 NULL, // 使用父进程环境变量 workdir, // 工作目录 &si, &pi ); if (!ok) return static_cast (GetLastError ()); if (wait) WaitForSingleObject (pi.hProcess, INFINITE); CloseHandle (pi.hThread); CloseHandle (pi.hProcess); return 0; } bool KillProcessByFilePath ( const std::wstring &filepath, bool multiple = false, bool isonlyname = false ) { if (filepath.empty ()) return false; std::wstring targetPath = filepath; std::wstring targetName; if (isonlyname) { size_t pos = filepath.find_last_of (L"\\/"); if (pos != std::wstring::npos) targetName = filepath.substr (pos + 1); else targetName = filepath; // 直接是文件名 } HANDLE hSnap = CreateToolhelp32Snapshot (TH32CS_SNAPPROCESS, 0); if (hSnap == INVALID_HANDLE_VALUE) return false; PROCESSENTRY32W pe; pe.dwSize = sizeof (pe); bool killed = false; if (Process32FirstW (hSnap, &pe)) { do { bool match = false; if (isonlyname) { if (PathEquals (pe.szExeFile, targetName.c_str ())) match = true; } else { // 比较完整路径,需要 QueryFullProcessImageNameW HANDLE hProc = OpenProcess (PROCESS_QUERY_INFORMATION | PROCESS_TERMINATE, FALSE, pe.th32ProcessID); if (hProc) { wchar_t exePath [MAX_PATH] = {0}; DWORD sz = MAX_PATH; if (QueryFullProcessImageNameW (hProc, 0, exePath, &sz)) { if (_wcsicmp (exePath, targetPath.c_str ()) == 0) match = true; } CloseHandle (hProc); } } if (match) { HANDLE hProc = OpenProcess (PROCESS_TERMINATE, FALSE, pe.th32ProcessID); if (hProc) { TerminateProcess (hProc, 1); CloseHandle (hProc); killed = true; } if (!multiple) break; } } while (Process32NextW (hSnap, &pe)); } CloseHandle (hSnap); return killed; } static std::wstring QueryVersionString (const BYTE *data, const std::wstring &langCode, const wchar_t *key) { wchar_t query [256] = {0}; wsprintfW (query, L"\\StringFileInfo\\%s\\%s", langCode.c_str (), key); LPWSTR value = nullptr; UINT len = 0; if (VerQueryValueW (data, query, (LPVOID *)&value, &len) && value) return std::wstring (value ? value : L"", len); return L""; } rapidjson::Document GetFileVersionAsJson (const std::wstring &filePath) { rapidjson::Document doc; doc.SetObject (); auto &alloc = doc.GetAllocator (); DWORD dummy = 0; DWORD size = GetFileVersionInfoSizeW (filePath.c_str (), &dummy); if (size == 0) { doc.AddMember ("error", "No version info", alloc); return doc; } std::vector data (size); if (!GetFileVersionInfoW (filePath.c_str (), 0, size, data.data ())) { doc.AddMember ("error", "GetFileVersionInfoW failed", alloc); return doc; } struct LANGANDCODEPAGE { WORD wLanguage; WORD wCodePage; }; LANGANDCODEPAGE *lpTranslate = nullptr; UINT cbTranslate = 0; if (!VerQueryValueW (data.data (), L"\\VarFileInfo\\Translation", (LPVOID *)&lpTranslate, &cbTranslate)) { doc.AddMember ("error", "No Translation", alloc); return doc; } wchar_t langCode [20] = {}; wsprintfW (langCode, L"%04x%04x", lpTranslate [0].wLanguage, lpTranslate [0].wCodePage); std::wstring lc = langCode; const wchar_t *keys [] = { L"CompanyName", L"FileDescription", L"FileVersion", L"InternalName", L"OriginalFilename", L"ProductName", L"ProductVersion", L"LegalCopyright" }; for (auto key : keys) { std::wstring val = QueryVersionString (data.data (), lc, key); if (!val.empty ()) { rapidjson::Value k; k.SetString (WStringToString (key, CP_UTF8).c_str (), alloc); rapidjson::Value v; v.SetString (WStringToString (val, CP_UTF8).c_str (), alloc); doc.AddMember (k, v, alloc); } } VS_FIXEDFILEINFO *ffi = nullptr; UINT ffiLen = 0; if (VerQueryValueW (data.data (), L"\\", (LPVOID *)&ffi, &ffiLen)) { if (ffi && ffiLen) { wchar_t ver [64]; wsprintfW (ver, L"%u.%u.%u.%u", HIWORD (ffi->dwFileVersionMS), LOWORD (ffi->dwFileVersionMS), HIWORD (ffi->dwFileVersionLS), LOWORD (ffi->dwFileVersionLS)); doc.AddMember ("FileVersionRaw", rapidjson::Value ().SetString (WStringToString (ver, CP_UTF8).c_str (), alloc), alloc); } } return doc; } size_t ExploreSaveFile ( HWND hParent, std::vector &results, LPWSTR lpFilter = L"Windows Store App Package (*.appx; *.appxbundle)\0*.appx;*.appxbundle", const std::wstring &lpDefExt = L"appx", DWORD dwFlags = OFN_OVERWRITEPROMPT | OFN_PATHMUSTEXIST | OFN_HIDEREADONLY, const std::wstring &swWndTitle = std::wstring (L"Please select the file to save: "), const std::wstring &swInitDir = GetFileDirectoryW (g_lastfile)) { results.clear (); const DWORD BUFFER_SIZE = 65536; // 64KB std::vector buffer (BUFFER_SIZE, 0); OPENFILENAME ofn; ZeroMemory (&ofn, sizeof (ofn)); ofn.hwndOwner = hParent; ofn.lpstrFile = buffer.data (); ofn.nMaxFile = BUFFER_SIZE; ofn.lpstrFilter = lpFilter; ofn.nFilterIndex = 1; ofn.lpstrTitle = swWndTitle.c_str (); ofn.lpstrDefExt = lpDefExt.c_str (); ofn.Flags = dwFlags; ofn.lpstrInitialDir = swInitDir.c_str (); ofn.lStructSize = sizeof (ofn); if (GetSaveFileNameW (&ofn)) { std::wstring filePath = buffer.data (); results.push_back (filePath); g_lastfile = filePath; } return results.size (); } [ComVisible (true)] public ref class SplashForm: public System::Windows::Forms::Form { public: using PictureBox = System::Windows::Forms::PictureBox; using Timer = System::Windows::Forms::Timer; private: PictureBox ^picbox; Timer ^timer; System::Drawing::Image ^splashimg = nullptr; System::Drawing::Color background = System::Drawing::Color::Transparent; // System::Windows::Forms ^parent = nullptr; double opastep = 0.05; void InitForm () { this->DoubleBuffered = true; InitializeComponent (); this->FormBorderStyle = System::Windows::Forms::FormBorderStyle::None; this->StartPosition = System::Windows::Forms::FormStartPosition::Manual; this->ShowInTaskbar = false; this->AllowTransparency = true; this->Opacity = 1.0; } void InitializeComponent () { double dDpi = GetDPI () * 0.01; this->picbox = gcnew System::Windows::Forms::PictureBox (); this->picbox->Size = System::Drawing::Size (620 * dDpi, 300 * dDpi); this->picbox->BackColor = System::Drawing::Color::Transparent; picbox->Anchor = System::Windows::Forms::AnchorStyles::None; picbox->SizeMode = System::Windows::Forms::PictureBoxSizeMode::Zoom; } void OnFadeTimer (Object ^sender, EventArgs ^e) { auto fadeTimer = timer; auto opacityStep = opastep; if (this->Opacity > 0) { this->Opacity -= opacityStep; } else { fadeTimer->Stop (); this->Close (); } } void OnLoad (Object ^sender, EventArgs ^e) { this->ChangePosAndSize (); this->Visible = true; } void OnResize (Object ^sender, EventArgs ^e) { if (IsHandleCreated && picbox->IsHandleCreated) { Drawing::Size sz = this->ClientSize; this->picbox->Location = Drawing::Point ( (sz.Width - picbox->Width) * 0.5, (sz.Height - picbox->Height) * 0.5 ); } } void OnResizeOwner (Object ^sender, EventArgs ^e) { this->ChangePosAndSize (); } void OnLocationChangedOwner (Object ^sender, EventArgs ^e) { this->ChangePosAndSize (); } protected: virtual void OnHandleCreated (EventArgs^ e) override { Form::OnHandleCreated (e); if (Environment::OSVersion->Version->Major >= 6) { INT mr = 0; MARGINS margins = {mr, mr, mr, mr}; HRESULT hr = DwmExtendFrameIntoClientArea ((HWND)this->Handle.ToPointer (), &margins); } } public: SplashForm (System::String ^imgpath, System::Drawing::Color backcolor, System::Windows::Forms::Form ^owner) { if (owner != nullptr) this->Owner = owner; InitForm (); std::wstring filefullpath = MPStringToStdW (imgpath); if (filefullpath.find (L'%') != filefullpath.npos) filefullpath = ProcessEnvVars (filefullpath); filefullpath = GetFullPathName (imgpath ? MPStringToStdW (imgpath) : L""); try { auto img = System::Drawing::Image::FromFile (gcnew System::String (filefullpath.c_str ())); if (img != nullptr) { splashimg = img; picbox->Image = img; } } catch (...) {} if (splashimg) picbox->Image = splashimg; try { if (backcolor != Drawing::Color::Transparent) { background = backcolor; picbox->BackColor = backcolor; this->BackColor = backcolor; } else { picbox->BackColor = background; this->BackColor = background; } } catch (...) {} if (this->Owner != nullptr) { this->Owner->Resize += gcnew System::EventHandler (this, &SplashForm::OnResizeOwner); this->Owner->LocationChanged += gcnew System::EventHandler (this, &SplashForm::OnLocationChangedOwner); } this->Controls->Add (picbox); this->Resize += gcnew EventHandler (this, &SplashForm::OnResize); timer = gcnew System::Windows::Forms::Timer (); timer->Interval = 15; timer->Tick += gcnew System::EventHandler (this, &SplashForm::OnFadeTimer); this->Load += gcnew EventHandler (this, &SplashForm::OnLoad); } void ReInit () { InitForm (); picbox = gcnew System::Windows::Forms::PictureBox (); picbox->BackColor = background; if (splashimg) picbox->Image = splashimg; picbox->SizeMode = System::Windows::Forms::PictureBoxSizeMode::Zoom; picbox->Anchor = System::Windows::Forms::AnchorStyles::None; double dDpi = GetDPI () * 0.01; picbox->Size = Drawing::Size (620 * dDpi, 300 * dDpi); this->BackColor = background; this->Controls->Clear (); this->Controls->Add (picbox); timer = gcnew System::Windows::Forms::Timer (); timer->Interval = 15; timer->Tick += gcnew EventHandler (this, &SplashForm::OnFadeTimer); this->Resize += gcnew EventHandler (this, &SplashForm::OnResize); this->Load += gcnew EventHandler (this, &SplashForm::OnLoad); ChangePosAndSize (); this->Opacity = 1.0; } void ChangePosAndSize () { if (this->Owner && this->Owner->IsHandleCreated) { this->Owner->Update (); System::Drawing::Point pt = this->Owner->PointToScreen (this->Owner->ClientRectangle.Location); this->Location = pt; this->Size = this->Owner->ClientSize; } else if (this->Parent && this->Parent->IsHandleCreated) { this->Parent->Update (); System::Drawing::Point pt = this->Parent->PointToScreen (this->Parent->ClientRectangle.Location); this->Location = pt; this->Size = this->Parent->ClientSize; } if (IsHandleCreated && picbox->IsHandleCreated) { Drawing::Size sz = this->ClientSize; this->picbox->Location = Drawing::Point ( (sz.Width - picbox->Width) * 0.5, (sz.Height - picbox->Height) * 0.5 ); } } void SetSplashImage (System::Drawing::Image ^img) { if (picbox && picbox->IsHandleCreated) { splashimg = img; picbox->Image = splashimg; } else splashimg = img; } void SetSplashImage (System::String ^imgpath) { try { SetSplashImage (System::Drawing::Image::FromFile (imgpath)); } catch (...) {} } void SetSplashImage (const std::wstring &imgpath) { SetSplashImage (CStringToMPString (imgpath)); } void SetSplashBackgroundColor (System::Drawing::Color color) { background = color; picbox->BackColor = color; this->BackColor = color; } // 渐变消失 void FadeOut () { timer->Start (); } // 立即消失 void FadeAway () { timer->Start (); } ~SplashForm () { if (this->Owner != nullptr) { this->Owner->Resize -= gcnew System::EventHandler (this, &SplashForm::OnResizeOwner); this->Owner->LocationChanged -= gcnew System::EventHandler (this, &SplashForm::OnLocationChangedOwner); } } }; uint64_t GetFileSize (String ^filepath) { FileInfo ^fi = gcnew FileInfo (filepath); if (!fi->Exists) return -1; return fi->Length; } bool CopyFileWithDialog (const std::wstring &src, const std::wstring &dst) { HRESULT hr = CoInitializeEx (NULL, COINIT_APARTMENTTHREADED); if (FAILED (hr)) return false; CComPtr pfo; hr = pfo.CoCreateInstance (CLSID_FileOperation); if (FAILED (hr)) return false; pfo->SetOperationFlags ( FOF_NOCONFIRMMKDIR | FOF_ALLOWUNDO ); CComPtr psiSrc; CComPtr psiDst; hr = SHCreateItemFromParsingName ( src.c_str (), NULL, IID_PPV_ARGS (&psiSrc) ); if (FAILED (hr)) return false; std::wstring dstDir = GetFileDirectoryW (dst); hr = SHCreateItemFromParsingName ( dstDir.c_str (), NULL, IID_PPV_ARGS (&psiDst) ); if (FAILED (hr)) return false; hr = pfo->CopyItem (psiSrc, psiDst, NULL, NULL); if (FAILED (hr)) return false; hr = pfo->PerformOperations (); bool ok = SUCCEEDED (hr); return ok; } // pkginfo 中的内容以 UTF8 保存为 themeinfo.json 文件,并位于压缩包根目录。 // 该文件仅在压缩包中存在,是为了方便读取主题文件信息的文件。当压缩或解压完毕后该文件会在磁盘中删除。 void CompressToThemePackage (String ^srcfolder, String ^destfile, String ^pkginfo, String ^id) { if (!Directory::Exists (srcfolder)) throw gcnew DirectoryNotFoundException (srcfolder); if (File::Exists (destfile)) File::Delete (destfile); FileStream^ fs = gcnew FileStream (destfile, FileMode::CreateNew); ZipArchive^ zip = gcnew ZipArchive (fs, ZipArchiveMode::Create); try { array^ files = Directory::GetFiles (srcfolder, "*", SearchOption::AllDirectories); for each (String^ file in files) { String^ relativePath = file->Substring (srcfolder->Length); // 去掉开头的分隔符 if (relativePath->StartsWith (Path::DirectorySeparatorChar.ToString ()) || relativePath->StartsWith (Path::AltDirectorySeparatorChar.ToString ())) { relativePath = relativePath->Substring (1); } // 如果是 custom.css 就改名为 {id}.css if (relativePath->Equals ("custom.css", StringComparison::OrdinalIgnoreCase)) { relativePath = id->Trim () + ".css"; } ZipArchiveEntry^ entry = zip->CreateEntry (relativePath, CompressionLevel::Optimal); Stream^ entryStream = entry->Open (); FileStream^ fileStream = gcnew FileStream (file, FileMode::Open, FileAccess::Read); array^ buffer = gcnew array (4096); int bytesRead; while ((bytesRead = fileStream->Read (buffer, 0, buffer->Length)) > 0) { entryStream->Write (buffer, 0, bytesRead); } fileStream->Close (); entryStream->Close (); } // 添加 themeinfo.json ZipArchiveEntry^ infoEntry = zip->CreateEntry ("themeinfo.json", CompressionLevel::Optimal); Stream^ infoStream = infoEntry->Open (); array^ infoBytes = Encoding::UTF8->GetBytes (pkginfo); infoStream->Write (infoBytes, 0, infoBytes->Length); infoStream->Close (); } finally { delete zip; delete fs; } } String ^GetThemePackageInfo (String ^pkgfile) { if (!File::Exists (pkgfile)) throw gcnew FileNotFoundException ("Theme package not found.", pkgfile); FileStream^ fs = gcnew FileStream (pkgfile, FileMode::Open, FileAccess::Read); ZipArchive^ zip = gcnew ZipArchive (fs, ZipArchiveMode::Read); try { ZipArchiveEntry^ infoEntry = zip->GetEntry ("themeinfo.json"); if (infoEntry == nullptr) throw gcnew InvalidDataException ("themeinfo.json not found in theme package."); Stream^ stream = infoEntry->Open (); StreamReader^ reader = gcnew StreamReader (stream, Encoding::UTF8); String^ json = reader->ReadToEnd (); reader->Close (); stream->Close (); return json; } finally { delete zip; delete fs; } } void ExtractThemePackage (String ^pkgfile, String ^destfolder) { if (!File::Exists (pkgfile)) throw gcnew FileNotFoundException ("Theme package not found.", pkgfile); if (Directory::Exists (destfolder)) Directory::Delete (destfolder); if (!Directory::Exists (destfolder)) Directory::CreateDirectory (destfolder); FileStream^ fs = gcnew FileStream (pkgfile, FileMode::Open, FileAccess::Read); ZipArchive^ zip = gcnew ZipArchive (fs, ZipArchiveMode::Read); try { for each (ZipArchiveEntry^ entry in zip->Entries) { String^ outPath = Path::Combine (destfolder, entry->FullName); // 目录项 if (String::IsNullOrEmpty (entry->Name)) { if (!Directory::Exists (outPath)) Directory::CreateDirectory (outPath); continue; } // 确保目录存在 String^ dir = Path::GetDirectoryName (outPath); if (!String::IsNullOrEmpty (dir) && !Directory::Exists (dir)) Directory::CreateDirectory (dir); // 写文件 Stream^ inStream = entry->Open (); FileStream^ outStream = gcnew FileStream ( outPath, FileMode::Create, FileAccess::Write ); array^ buffer = gcnew array (4096); int bytesRead; while ((bytesRead = inStream->Read (buffer, 0, buffer->Length)) > 0) { outStream->Write (buffer, 0, bytesRead); } outStream->Close (); inStream->Close (); } } finally { delete zip; delete fs; } } [ComVisible (true)] public ref class MainHtmlWnd: public System::Windows::Forms::Form, public IScriptBridge { public: using WebBrowser = System::Windows::Forms::WebBrowser; private: WebBrowser ^webui; SplashForm ^splash; ITaskbarList3 *taskbar = nullptr; public: [ComVisible (true)] ref class _I_UI2: public _I_UI { private: MainHtmlWnd ^wndinst = nullptr; public: _I_UI2 (MainHtmlWnd ^wnd): wndinst (wnd), _I_UI (wnd) {} property String ^SplashImage { String ^get () { auto uri = gcnew Uri (CStringToMPString (wndinst->GetSuitSplashImage ())); return uri->AbsoluteUri; } } property String ^SplashBackgroundColor { String ^get () { std::wnstring ret = L""; auto personal = g_initfile [L"Personalization"]; auto thememode = personal [L"AppInstaller:ThemeMode"]; auto custommode = personal [L"AppInstaller:CustomThemeMode"]; bool nowdark = IsNormalizeStringEquals (thememode.read_wstring ().c_str (), L"dark") || IsNormalizeStringEquals (thememode.read_wstring ().c_str (), L"auto") && IsAppInDarkMode () || IsNormalizeStringEquals (thememode.read_wstring ().c_str (), L"custom") && IsNormalizeStringEquals (custommode.read_wstring ().c_str (), L"dark") || IsNormalizeStringEquals (thememode.read_wstring ().c_str (), L"custom") && IsNormalizeStringEquals (custommode.read_wstring ().c_str (), L"auto") && IsAppInDarkMode (); if (nowdark) ret = g_vemani.splash_screen_backgroundcolor_darkmode (g_idInVe); else ret = g_vemani.splash_screen_backgroundcolor (g_idInVe); if (ret.empty ()) ret = g_vemani.splash_screen_backgroundcolor (g_idInVe); if (ret.empty ()) ret = g_vemani.background_color (g_idInVe); return CStringToMPString (ret); } } void ShowSplash () { if (wndinst->SplashScreen->IsHandleCreated) wndinst->SplashScreen->Show (); else wndinst->SplashScreen->ReInit (); } void FadeAwaySplash () { wndinst->SplashScreen->FadeAway (); } void FadeOutSplash () { wndinst->SplashScreen->FadeOut (); } }; [ComVisible (true)] ref class _I_VisualElements2: public _I_VisualElements { public: property _I_VisualElement ^Current {_I_VisualElement ^get () { return Get (CStringToMPString (g_idInVe)); }} }; [ComVisible (true)] ref class IBridge: public _I_Bridge_Base2 { private: MainHtmlWnd ^wndinst = nullptr; bool hasjump1 = false, hasjump2 = false, hasexec = false; public: using String = System::String; [ComVisible (true)] ref class _I_IEFrame: public _I_IEFrame_Base { private: MainHtmlWnd ^wndinst = nullptr; public: _I_IEFrame (MainHtmlWnd ^wnd): wndinst (wnd) {} property int Scale { int get () { return wndinst->PageScale; } void set (int value) { return wndinst->PageScale = value; } } }; [ComVisible (true)] ref class _I_System3: public _I_System { protected: _I_UI2 ^ui2; public: _I_System3 (MainHtmlWnd ^wnd) { ui2 = gcnew _I_UI2 (wnd); } property _I_UI2 ^UI { _I_UI2 ^get () { return ui2; } } }; [ComVisible (true)] ref class _I_Process { public: using String = System::String; ref class ProcessWorker { _I_Process ^parent; public: ProcessWorker (_I_Process ^parent): parent (parent) {} ProcessWorker (): parent (gcnew _I_Process ()) {} String ^cmdline = String::Empty; String ^filepath = String::Empty; int wndtype = SW_NORMAL; String ^runpath = String::Empty; Object ^callback = nullptr; void Work () { int ret = parent->Run (cmdline, filepath, wndtype, true, runpath); if (callback) { try { callback->GetType ()->InvokeMember ( "call", BindingFlags::InvokeMethod, nullptr, callback, gcnew array{ 1, ret } ); } catch (...) {} } } }; public: int Run (String ^cmdline, String ^filepath, int wndtype, bool wait, String ^runpath) { return ExecuteProgram ( MPStringToStdW (cmdline), MPStringToStdW (filepath), wndtype, wait, MPStringToStdW (runpath) ); } void RunAsync (String ^cmdline, String ^filepath, int wndtype, String ^runpath, Object ^callback) { auto worker = gcnew ProcessWorker (this); worker->cmdline = cmdline; worker->filepath = filepath; worker->wndtype = wndtype; worker->runpath = runpath; worker->callback = callback; Thread^ th = gcnew Thread (gcnew ThreadStart (worker, &ProcessWorker::Work)); th->IsBackground = true; th->Start (); } bool Kill (String ^filename, bool allproc, bool onlyname) { return KillProcessByFilePath (MPStringToStdW (filename), allproc, onlyname); } }; [ComVisible (true)] ref class _I_ResourcePri { public: using String = System::String; String ^GetString (String ^uri) { auto ret = g_winjspri.get (MPStringToStdW (uri)); auto retstr = CStringToMPString (ret); return retstr; } }; private: _I_IEFrame ^ieframe; _I_System3 ^sys; _I_VisualElements2 ^ve; _I_Download ^download; _I_Process ^proc; _I_ResourcePri ^winjs_res; public: IBridge (MainHtmlWnd ^wnd): wndinst (wnd), _I_Bridge_Base2 (wnd) { ieframe = gcnew _I_IEFrame (wnd); sys = gcnew _I_System3 (wnd); storage = gcnew _I_Storage (); ve = gcnew _I_VisualElements2 (); download = gcnew _I_Download (); proc = gcnew _I_Process (); winjs_res = gcnew _I_ResourcePri (); } property _I_IEFrame ^IEFrame { _I_IEFrame ^get () { return ieframe; }} property _I_System3 ^System { _I_System3 ^get () { return sys; }} property _I_VisualElements2 ^VisualElements { _I_VisualElements2 ^get () { return ve; } } property _I_Download ^Download { _I_Download ^get () { return download; }} property _I_Process ^Process { _I_Process ^get () { return proc; }} property _I_ResourcePri ^WinJsStringRes { _I_ResourcePri ^get () { return winjs_res; }} property _I_ResourcePri ^StringResources { _I_ResourcePri ^get () { return winjs_res; }} String ^FormatDateTime (String ^fmt, String ^jsDate) { return FormatString (fmt, Convert::ToDateTime (jsDate)); } property String ^CmdArgs { String ^get () { return CStringToMPString (StringArrayToJson (g_cmdargs)); }} property bool Jump1 { bool get () { return hasjump1; } void set (bool value) { hasjump1 = value; } } property bool Jump2 { bool get () { return hasjump2; } void set (bool value) { hasjump2 = value; } } property bool Exec1 { bool get () { return hasexec; } void set (bool value) { hasexec = value; } } property double TBProgress { void set (double value) { wndinst->TProgressPercent = value; }} property DWORD TBState { void set (DWORD value) { wndinst->TProgressState = (TBPFLAG)value; }} String ^GetVersionInfoToJSON (String ^filepath) { auto doc = GetFileVersionAsJson (MPStringToStdW (filepath)); rapidjson::StringBuffer buffer; rapidjson::Writer writer (buffer); doc.Accept (writer); return CStringToMPString (StringToWString (buffer.GetString (), CP_UTF8)); } String ^SelectFilesToJSON (String ^filter, DWORD flags, String ^wndtitle, String ^initdir) { std::vector ret; std::wstring filterbuf = MPStringToStdW (filter) + L'|||'; for (auto &it : filterbuf) if (it == L'|') it = L'\0'; ExploreFile (wndinst->InvokeGetHWND (), ret, (LPWSTR)filterbuf.c_str (), flags, MPStringToStdW (wndtitle), MPStringToStdW (initdir)); return CStringToMPString (StringArrayToJson (ret)); } uint64_t GetFileSize (String ^filepath) { return ::GetFileSize (filepath); } bool ShellCopyFile (String ^src, String ^desc) { return CopyFileWithDialog (MPStringToStdW (src), MPStringToStdW (desc)); } String ^GetSavePath (String ^filter, String ^defext, DWORD flags, String ^wndtitle, String ^initdir) { std::vector ret; std::wstring filterbuf = MPStringToStdW (filter) + L'|||'; for (auto &it : filterbuf) if (it == L'|') it = L'\0'; ExploreSaveFile (wndinst->InvokeGetHWND (), ret, (LPWSTR)filterbuf.data (), MPStringToStdW (defext), flags, MPStringToStdW (wndtitle), MPStringToStdW (initdir)); for (auto &it : ret) if (!IsNormalizeStringEmpty (it)) return CStringToMPString (it); return ""; } void CompressTheme (String ^srcfolder, String ^destfile, String ^pkginfo, String ^id) { CompressToThemePackage (srcfolder, destfile, pkginfo, id); } String ^GetThemePkgInfo (String ^themepkg) { return GetThemePackageInfo (themepkg); } void ExtraceThemePkg (String ^src, String ^desc) { ExtractThemePackage (src, desc); } bool CopyFile (String^ srcFile, String^ destFile, bool cover) { try { if (String::IsNullOrEmpty (srcFile) || String::IsNullOrEmpty (destFile)) return false; if (!File::Exists (srcFile)) throw gcnew FileNotFoundException ("Source file not found.", srcFile); // 确保目标目录存在 String^ destDir = Path::GetDirectoryName (destFile); if (!String::IsNullOrEmpty (destDir) && !Directory::Exists (destDir)) { Directory::CreateDirectory (destDir); } // overwrite = true File::Copy (srcFile, destFile, cover); return true; } catch (Exception^) { return false; } } void CloseWindow () { if (wndinst && wndinst->IsHandleCreated) wndinst->Close (); } }; protected: property WebBrowser ^WebUI { WebBrowser ^get () { return this->webui; } } property SplashForm ^SplashScreen { SplashForm ^get () { return this->splash; } } property int DPIPercent { int get () { return GetDPI (); }} property double DPI { double get () { return DPIPercent * 0.01; }} virtual void OnHandleCreated (EventArgs ^e) override { ::SetClassLongPtrW ((HWND)this->Handle.ToPointer (), GCLP_HBRBACKGROUND, (LONG_PTR)g_wndclass); Form::OnHandleCreated (e); } void InitSize () { unsigned ww = 0, wh = 0; auto &ini = g_initfile; auto setsect = ini ["Settings"]; auto savepos = setsect [L"Settings:SavePosAndSizeBeforeCancel"]; auto lastw = setsect [L"Settings:LastWidth"]; auto lasth = setsect [L"Settings:LastHeight"]; auto defw = setsect [L"Settings:DefaultWidth"]; auto defh = setsect [L"Settings:DefaultHeight"]; auto minw = setsect [L"Settings:MinimumWidth"]; auto minh = setsect [L"Settings:MinimumHeight"]; auto lasts = setsect [L"Settings:LastWndState"]; if (savepos.read_bool ()) { ww = lastw.read_uint (defw.read_uint (rcInt (IDS_DEFAULTWIDTH))); wh = lasth.read_uint (defh.read_uint (rcInt (IDS_DEFAULTHEIGHT))); } else { ww = defw.read_uint (rcInt (IDS_DEFAULTWIDTH)); wh = defh.read_uint (rcInt (IDS_DEFAULTHEIGHT)); } this->ClientSize = System::Drawing::Size (ww * DPI, wh * DPI); int hborder = this->Size.Width - this->ClientSize.Width, vborder = this->Size.Height - this->ClientSize.Height; this->MinimumSize = System::Drawing::Size ( minw.read_uint (rcInt (IDS_MINWIDTH)) * DPI + hborder, minh.read_uint (rcInt (IDS_MINHIEHGT)) * DPI + vborder ); this->WindowState = (System::Windows::Forms::FormWindowState)lasts.read_int ((int)System::Windows::Forms::FormWindowState::Normal); } void Init () { this->Visible = false; this->webui = gcnew System::Windows::Forms::WebBrowser (); this->SuspendLayout (); this->webui->Dock = System::Windows::Forms::DockStyle::Fill; this->webui->IsWebBrowserContextMenuEnabled = DEBUGMODE; this->webui->AllowWebBrowserDrop = false; this->Controls->Add (this->webui); if (g_hIconMain.hIcon) { try { this->Icon = System::Drawing::Icon::FromHandle (IntPtr (g_hIconMain.hIcon)); } catch (...) {} } this->Text = GetRCStringCli (IDS_WINTITLE); this->ResumeLayout (false); webui->ObjectForScripting = gcnew IBridge (this); this->webui->DocumentCompleted += gcnew System::Windows::Forms::WebBrowserDocumentCompletedEventHandler (this, &MainHtmlWnd::OnDocumentCompleted); this->webui->PreviewKeyDown += gcnew System::Windows::Forms::PreviewKeyDownEventHandler (this, &MainHtmlWnd::OnPreviewKeyDown_WebBrowser); this->Resize += gcnew System::EventHandler (this, &MainHtmlWnd::OnResize); this->Load += gcnew EventHandler (this, &MainHtmlWnd::OnCreate); this->ResizeEnd += gcnew EventHandler (this, &MainHtmlWnd::OnResizeEnd); } void OnDocumentCompleted (Object ^sender, System::Windows::Forms::WebBrowserDocumentCompletedEventArgs ^e) { static bool issetdpi = false; if (!issetdpi) { issetdpi = true; ExecScript ("Bridge.Frame.scale = Bridge.Frame.scale * Bridge.UI.dpi"); } ExecScript ("Windows.UI.DPI.mode = 1"); if (e->Url->ToString () == webui->Url->ToString ()) { auto &ini = g_initfile; splash->FadeAway (); } } void OnCreate (System::Object ^sender, System::EventArgs ^e) { splash->Owner = this; splash->ChangePosAndSize (); splash->Show (); splash->Update (); splash->SetSplashImage (GetSuitSplashImage ()); System::Windows::Forms::Application::DoEvents (); auto htmlpath = CombinePath (GetProgramRootDirectoryW (), L"html\\settings.html"); webui->Navigate (CStringToMPString (htmlpath)); if (!IsFileExists (htmlpath)) { std::wstring msg = L"Error: cannot find file \"" + htmlpath + L"\"."; MessageBoxW (InvokeGetHWND (), msg.c_str (), GetRCStringSW (IDS_WINTITLE).c_str (), MB_ICONERROR); this->Close (); return; } } void OnPreviewKeyDown_WebBrowser (System::Object ^sender, System::Windows::Forms::PreviewKeyDownEventArgs ^e) { if (e->KeyCode == System::Windows::Forms::Keys::F5 || (e->KeyCode == System::Windows::Forms::Keys::R && e->Control)) e->IsInputKey = true; } void ResponseSplashChange () { splash->SetSplashImage (GetSuitSplashImage ()); //ExecScript ( // "(function () {" // "var splash = Page.splash;" // "if (!splash) return null;" // "splash.imagesrc = Bridge.UI.Splash.imageurl;" // "splash.background = Bridge.UI.Splash.backcolor;" // "var progress = splash.content.querySelector (\"progress\");" // "if (Bridge.Frame.WindowSize.height / Bridge.UI.dpi < 500) {" // "if (progress.classList.contains(\"win-ring\")) progress.classList.remove(\"win-ring\");}" // "else { if (!progress.classList.contains(\"win-ring\")) progress.classList.add(\"win-ring\"); }" // "}) ();" //); } void OnResize (Object ^sender, EventArgs ^e) { ResizeEvent (); } void OnResizeEnd (Object ^sender, EventArgs ^e) {} std::wstring GetSuitSplashImage () { int limitw = 800 * DPI; int limith = 600 * DPI; std::wstring tag = L"settings-splash"; int noww = this->Width; int nowh = this->Height; if (noww >= limitw && nowh >= limith) tag = L"settings-splashlarge"; std::wstring path = g_scaleres [tag]; if (IsNormalizeStringEmpty (path)) path = g_scaleres [L"settings-splash"]; if (IsNormalizeStringEmpty (path)) path = g_vemani.splash_screen_image (g_idInVe); return path; } void ResizeEvent () { auto &ini = g_initfile; auto setsect = ini ["Settings"]; auto lasts = setsect [L"Settings:LastWndState"]; auto savepos = setsect [L"Settings:SavePosAndSizeBeforeCancel"]; auto lastw = setsect [L"Settings:LastWidth"]; auto lasth = setsect [L"Settings:LastHeight"]; switch (this->WindowState) { case System::Windows::Forms::FormWindowState::Normal: case System::Windows::Forms::FormWindowState::Maximized: lasts = (int)this->WindowState; } if (this->WindowState == System::Windows::Forms::FormWindowState::Normal && savepos) { lastw = (int)(this->ClientSize.Width / DPI); lasth = (int)(this->ClientSize.Height / DPI); } ResponseSplashChange (); } void InvokeClose () { if (this->InvokeRequired) this->Invoke (gcnew Action (this, &MainHtmlWnd::Close)); else this->Close (); } IntPtr GetHWnd () { return this->Handle; } delegate IntPtr GetHwndDelegate (); HWND InvokeGetHWND () { if (this->InvokeRequired) { GetHwndDelegate ^del = gcnew GetHwndDelegate (this, &MainHtmlWnd::GetHWnd); IntPtr result = safe_cast (this->Invoke (del)); return static_cast (result.ToPointer ()); } else return static_cast (this->Handle.ToPointer ()); } bool BackPage () { return this->webui->GoBack (); } bool InvokeBackPage () { if (this->InvokeRequired) return (bool)this->Invoke (gcnew Func (this, &MainHtmlWnd::BackPage)); else return BackPage (); // 当前线程就是 UI 线程 } public: MainHtmlWnd () { InitSize (); System::Windows::Forms::Application::DoEvents (); splash = gcnew SplashForm ( CStringToMPString (GetSuitSplashImage ()), StringToColor (CStringToMPString (g_vemani.splash_screen_backgroundcolor (g_idInVe))), this ); System::Windows::Forms::Application::DoEvents (); Init (); ITaskbarList3 *ptr = nullptr; HRESULT hr = CoCreateInstance (CLSID_TaskbarList, nullptr, CLSCTX_INPROC_SERVER, IID_ITaskbarList3, (void **)&ptr); if (SUCCEEDED (hr)) { taskbar = ptr; taskbar->HrInit (); } else { taskbar = nullptr; if (ptr) ptr->Release (); } } Object ^CallScriptFunction (String ^lpFuncName, ... array ^alpParams) { try { return this->webui->Document->InvokeScript (lpFuncName, alpParams); } catch (Exception ^e) { try { this->webui->Document->InvokeScript ("messageBoxAsync", gcnew array { e->Message, e->Source, 0, CStringToMPString (g_vemani.background_color (g_idInVe)) }); } catch (Exception ^ex) { MessageBoxW (InvokeGetHWND (), MPStringToStdW (e->Message).c_str (), MPStringToStdW (e->Source).c_str (), 0); } } return nullptr; } Object ^CallScriptFunction (String ^lpScriptName) { try { return this->webui->Document->InvokeScript (lpScriptName); } catch (Exception ^ex) { System::Windows::Forms::MessageBox::Show ("Error calling JavaScript function: " + ex->Message); } return nullptr; } Object ^InvokeCallScriptFunction (String ^lpFuncName, ... array ^alpParams) { try { if (this->InvokeRequired) return (Object ^)this->Invoke (gcnew Func ^, Object ^> (this, &MainHtmlWnd::CallScriptFunction), lpFuncName, alpParams); else return CallScriptFunction (lpFuncName, alpParams); } catch (Exception ^e) {} return nullptr; } Object ^InvokeCallScriptFunction (String ^lpScriptName) { try { if (this->InvokeRequired) return (Object ^)this->Invoke (gcnew Func (this, &MainHtmlWnd::CallScriptFunction), lpScriptName); else return CallScriptFunction (lpScriptName); } catch (Exception ^e) {} return nullptr; } Object ^ExecScript (... array ^alpScript) { return InvokeCallScriptFunction ("eval", alpScript); } Object ^CallEvent (String ^funcName, Object ^e) override { std::wstring fname = MPStringToStdW (funcName); if (fname == L"InvokeBackPage") InvokeBackPage (); return nullptr; } property int PageScale { int get () { CComPtr web2; HRESULT hr = GetWebBrowser2Interface (webui, &web2); if (FAILED (hr)) return 0; VARIANT v; VariantInit (&v); hr = web2->ExecWB (OLECMDID_OPTICAL_ZOOM, OLECMDEXECOPT_DODEFAULT, nullptr, &v); if (FAILED (hr) || v.vt != VT_I4) return 0; int val = v.lVal; VariantClear (&v); return val; } void set (int value) { CComPtr web2; HRESULT hr = GetWebBrowser2Interface (webui, &web2); if (FAILED (hr)) return; VARIANT v; VariantInit (&v); v.vt = VT_I4; v.lVal = value; web2->ExecWB (OLECMDID_OPTICAL_ZOOM, OLECMDEXECOPT_DONTPROMPTUSER, &v, nullptr); } } // 只能设置。0 - 100 property double TProgressPercent { void set (double progressPercent) { if (taskbar) taskbar->SetProgressValue (InvokeGetHWND (), progressPercent * 100, 100 * 100); }} property TBPFLAG TProgressState { void set (TBPFLAG state) { if (taskbar) taskbar->SetProgressState (InvokeGetHWND (), state); }} ~MainHtmlWnd () { if (taskbar) taskbar->Release (); taskbar = nullptr; } }; using MainWnd = MainHtmlWnd; HRESULT SetCurrentAppUserModelID (PCWSTR appID) { typedef HRESULT (WINAPI *SetAppUserModelIDFunc)(PCWSTR); HMODULE shell32 = LoadLibraryW (L"shell32.dll"); destruct freelib ([&] () { if (shell32) FreeLibrary (shell32); }); try { if (!shell32) return E_FAIL; auto SetAppUserModelID = (SetAppUserModelIDFunc)GetProcAddress (shell32, "SetCurrentProcessExplicitAppUserModelID"); if (!SetAppUserModelID) { FreeLibrary (shell32); return E_FAIL; } return SetAppUserModelID (appID); } catch (...) { return E_FAIL; } return E_FAIL; } void SetProgramSingleInstance ( const std::wstring &mutexName, std::function repeatCallback = nullptr, bool focusMain = true) { HANDLE hMutex = CreateMutexW (NULL, TRUE, mutexName.c_str ()); if (hMutex == NULL) return; destruct _mutexFree ([&] () { CloseHandle (hMutex); }); if (GetLastError () != ERROR_ALREADY_EXISTS) return; if (repeatCallback) repeatCallback (); wchar_t pathBuf [MAX_PATH] = {0}; GetModuleFileNameW (NULL, pathBuf, MAX_PATH); std::wstring exeName = pathBuf; exeName = exeName.substr (exeName.find_last_of (L"\\/") + 1); DWORD existingPid = 0; HANDLE snap = CreateToolhelp32Snapshot (TH32CS_SNAPPROCESS, 0); if (snap != INVALID_HANDLE_VALUE) { PROCESSENTRY32W pe = {sizeof (pe)}; if (Process32FirstW (snap, &pe)) { do { if (_wcsicmp (pe.szExeFile, exeName.c_str ()) == 0 && pe.th32ProcessID != GetCurrentProcessId ()) { existingPid = pe.th32ProcessID; break; } } while (Process32NextW (snap, &pe)); } CloseHandle (snap); } HWND targetHwnd = NULL; if (existingPid) { EnumWindows ([] (HWND hwnd, LPARAM lParam) -> BOOL { DWORD pid = 0; GetWindowThreadProcessId (hwnd, &pid); if (pid == (DWORD)lParam && IsWindowVisible (hwnd)) { *((HWND *)(&lParam)) = hwnd; return FALSE; } return TRUE; }, (LPARAM)&targetHwnd); } if (focusMain && targetHwnd) { if (IsIconic (targetHwnd)) ShowWindow (targetHwnd, SW_RESTORE); DWORD thisThread = GetCurrentThreadId (); DWORD wndThread = GetWindowThreadProcessId (targetHwnd, NULL); AttachThreadInput (thisThread, wndThread, TRUE); SetActiveWindow (targetHwnd); SetForegroundWindow (targetHwnd); AttachThreadInput (thisThread, wndThread, FALSE); BringWindowToTop (targetHwnd); } ExitProcess (0); } [STAThread] int APIENTRY wWinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nCmdShow) { SetCurrentProcessExplicitAppUserModelID (g_lpAppId); SetProgramSingleInstance (g_lpAppId); SetProcessDPIAware (); { // 设置当前目录为程序所在目录 std::wnstring currdir = GetCurrentDirectoryW (); std::wnstring rootdir = GetProgramRootDirectoryW (); if (!PathEquals (currdir, rootdir)) SetCurrentDirectoryW (rootdir.c_str ()); } CoInitializeEx (NULL, COINIT_MULTITHREADED | COINIT_APARTMENTTHREADED); SetupInstanceEnvironment (); destruct relco ([] () { CoUninitialize (); }); { auto cmdline = GetCommandLineW (); int argc = 0; auto argv = CommandLineToArgvW (cmdline, &argc); destruct relt ([&argv] () { if (argv) LocalFree (argv); }); for (size_t i = 1; i < argc; i ++) g_cmdargs.push_back (argv [i]); } SetWebBrowserEmulation (); System::Windows::Forms::Application::EnableVisualStyles (); System::Windows::Forms::Application::SetCompatibleTextRenderingDefault (false); auto mwnd = gcnew MainHtmlWnd (); g_mainwnd = mwnd; System::Windows::Forms::Application::Run (mwnd); return 0; }