diff --git a/src/InfiniFrame.Native/InfiniFrame.Native.proj b/src/InfiniFrame.Native/InfiniFrame.Native.proj index dc3d1fa3..5d5b7fdf 100644 --- a/src/InfiniFrame.Native/InfiniFrame.Native.proj +++ b/src/InfiniFrame.Native/InfiniFrame.Native.proj @@ -1,4 +1,4 @@ - + @@ -19,6 +19,10 @@ x64 + + x86_64 + arm64 + build/$(CMakeArch)/$(Configuration) windows linux @@ -53,11 +57,11 @@ - + - + diff --git a/src/InfiniFrame.Native/Platform/Linux/Window.cpp b/src/InfiniFrame.Native/Platform/Linux/Window.cpp index 71e0ff5e..c1932413 100644 --- a/src/InfiniFrame.Native/Platform/Linux/Window.cpp +++ b/src/InfiniFrame.Native/Platform/Linux/Window.cpp @@ -537,7 +537,7 @@ void InfiniFrameWindow::GetGrantBrowserPermissions(bool* grant) const { } AutoString InfiniFrameWindow::GetUserAgent() const { - return const_cast(m_impl->_userAgent.c_str()); + return AllocateStringCopy(m_impl->_userAgent); } void InfiniFrameWindow::GetMediaAutoplayEnabled(bool* enabled) const { @@ -616,7 +616,8 @@ void InfiniFrameWindow::GetMinSize(int* width, int* height) const { } AutoString InfiniFrameWindow::GetTitle() const { - return const_cast(gtk_window_get_title(GTK_WINDOW(m_impl->_window))); + const char* title = gtk_window_get_title(GTK_WINDOW(m_impl->_window)); + return g_strdup(title ? title : ""); } void InfiniFrameWindow::GetTopmost(bool* topmost) const { @@ -636,7 +637,7 @@ void InfiniFrameWindow::GetFocused(bool* isFocused) const { } AutoString InfiniFrameWindow::GetIconFileName() const { - return const_cast(m_impl->_iconFileName.c_str()); + return AllocateStringCopy(m_impl->_iconFileName); } // --------------------------------------------------------------------------------------------------------------------- diff --git a/src/InfiniFrame.Native/Platform/Mac/Window.mm b/src/InfiniFrame.Native/Platform/Mac/Window.mm index bbb1a452..f07f3ecd 100644 --- a/src/InfiniFrame.Native/Platform/Mac/Window.mm +++ b/src/InfiniFrame.Native/Platform/Mac/Window.mm @@ -431,7 +431,7 @@ AutoString InfiniFrameWindow::GetUserAgent() const { - return const_cast(m_impl->_userAgent.c_str()); + return AllocateStringCopy(m_impl->_userAgent); } void InfiniFrameWindow::GetMediaAutoplayEnabled(bool* enabled) const @@ -549,7 +549,7 @@ AutoString InfiniFrameWindow::GetTitle() const { - return const_cast(m_impl->_windowTitle.c_str()); + return AllocateStringCopy(m_impl->_windowTitle); } void InfiniFrameWindow::GetTopmost(bool* topmost) const @@ -566,7 +566,7 @@ AutoString InfiniFrameWindow::GetIconFileName() const { - return const_cast(m_impl->_iconFileName.c_str()); + return AllocateStringCopy(m_impl->_iconFileName); } // --------------------------------------------------------------------------------------------------------------------- diff --git a/src/InfiniFrame.Native/Platform/Windows/DarkMode.cpp b/src/InfiniFrame.Native/Platform/Windows/DarkMode.cpp index 2a26764b..0dc18ec5 100644 --- a/src/InfiniFrame.Native/Platform/Windows/DarkMode.cpp +++ b/src/InfiniFrame.Native/Platform/Windows/DarkMode.cpp @@ -198,10 +198,14 @@ bool IsColorSchemeChange(const LPARAM l_param) noexcept { -1, TRUE ) == CSTR_EQUAL) { - RefreshImmersiveColorPolicyState(); + if (RefreshImmersiveColorPolicyState != nullptr) { + RefreshImmersiveColorPolicyState(); + } return_value = true; } - GetIsImmersiveColorUsingHighContrast(IHCM_REFRESH); + if (GetIsImmersiveColorUsingHighContrast != nullptr) { + GetIsImmersiveColorUsingHighContrast(IHCM_REFRESH); + } return return_value; } diff --git a/src/InfiniFrame.Native/Platform/Windows/Window.cpp b/src/InfiniFrame.Native/Platform/Windows/Window.cpp index 62b351d2..0d13c05c 100644 --- a/src/InfiniFrame.Native/Platform/Windows/Window.cpp +++ b/src/InfiniFrame.Native/Platform/Windows/Window.cpp @@ -453,14 +453,16 @@ LRESULT CALLBACK WindowProc(const HWND hwnd, const UINT uMsg, const WPARAM wPara } case WM_ACTIVATE: { InfiniFrameWindow * instance = hwndToInfiniFrame[hwnd]; - if (LOWORD(wParam) == WA_INACTIVE) { - instance->InvokeFocusOut(); - } - else { - instance->FocusWebView2(); - instance->InvokeFocusIn(); + if (instance) { + if (LOWORD(wParam) == WA_INACTIVE) { + instance->InvokeFocusOut(); + } + else { + instance->FocusWebView2(); + instance->InvokeFocusIn(); - return 0; + return 0; + } } break; } @@ -610,8 +612,11 @@ void InfiniFrameWindow::GetContextMenuEnabled(bool* enabled) const { return; } wil::com_ptr settings; - if (SUCCEEDED(m_impl->_webviewWindow->get_Settings(&settings)) && settings) - settings->get_AreDefaultContextMenusEnabled(reinterpret_cast(enabled)); + if (SUCCEEDED(m_impl->_webviewWindow->get_Settings(&settings)) && settings) { + BOOL boolValue = FALSE; + settings->get_AreDefaultContextMenusEnabled(&boolValue); + *enabled = (boolValue != FALSE); + } } void InfiniFrameWindow::GetZoomEnabled(bool* enabled) const { @@ -620,8 +625,11 @@ void InfiniFrameWindow::GetZoomEnabled(bool* enabled) const { return; } wil::com_ptr settings; - if (SUCCEEDED(m_impl->_webviewWindow->get_Settings(&settings)) && settings) - settings->get_IsZoomControlEnabled(reinterpret_cast(enabled)); + if (SUCCEEDED(m_impl->_webviewWindow->get_Settings(&settings)) && settings) { + BOOL boolValue = FALSE; + settings->get_IsZoomControlEnabled(&boolValue); + *enabled = (boolValue != FALSE); + } } void InfiniFrameWindow::GetDevToolsEnabled(bool* enabled) const { @@ -630,8 +638,11 @@ void InfiniFrameWindow::GetDevToolsEnabled(bool* enabled) const { return; } wil::com_ptr settings; - if (SUCCEEDED(m_impl->_webviewWindow->get_Settings(&settings)) && settings) - settings->get_AreDevToolsEnabled(reinterpret_cast(enabled)); + if (SUCCEEDED(m_impl->_webviewWindow->get_Settings(&settings)) && settings) { + BOOL boolValue = FALSE; + settings->get_AreDevToolsEnabled(&boolValue); + *enabled = (boolValue != FALSE); + } } void InfiniFrameWindow::GetFullScreen(bool* fullScreen) const { @@ -644,7 +655,7 @@ void InfiniFrameWindow::GetGrantBrowserPermissions(bool* grant) const { } AutoString InfiniFrameWindow::GetUserAgent() const { - return const_cast(m_impl->_userAgent.c_str()); + return AllocateStringCopy(m_impl->_userAgent); } void InfiniFrameWindow::GetMediaAutoplayEnabled(bool* enabled) const { @@ -684,7 +695,7 @@ void InfiniFrameWindow::GetNotificationsEnabled(bool* enabled) const { } AutoString InfiniFrameWindow::GetIconFileName() const { - return const_cast(m_impl->_iconFileName.c_str()); + return AllocateStringCopy(m_impl->_iconFileName); } void InfiniFrameWindow::GetMaximized(bool* isMaximized) const { @@ -739,7 +750,7 @@ void InfiniFrameWindow::GetMinSize(int* width, int* height) const { } AutoString InfiniFrameWindow::GetTitle() const { - return const_cast(m_impl->_windowTitle.c_str()); + return AllocateStringCopy(m_impl->_windowTitle); } void InfiniFrameWindow::GetTopmost(bool* topmost) const { diff --git a/src/InfiniFrame.Native/Utils/Common.h b/src/InfiniFrame.Native/Utils/Common.h index 54987d9c..f01864e5 100644 --- a/src/InfiniFrame.Native/Utils/Common.h +++ b/src/InfiniFrame.Native/Utils/Common.h @@ -15,6 +15,7 @@ #include #include #include +#include #ifdef _WIN32 #include @@ -152,4 +153,34 @@ template return std::clamp(value, minVal, maxVal); } +#ifdef _WIN32 +inline wchar_t* AllocateStringCopy(const std::wstring& str) { + const size_t len = str.length(); + wchar_t* copy = new wchar_t[len + 1]; + std::memcpy(copy, str.c_str(), (len + 1) * sizeof(wchar_t)); + return copy; +} + +#elif __linux__ +inline char* AllocateStringCopy(const std::string& str) { + return g_strdup(str.c_str()); +} + +#elif __APPLE__ +inline char* AllocateStringCopy(const std::string& str) { + const size_t len = str.length(); + char* copy = static_cast(malloc(len + 1)); + std::memcpy(copy, str.c_str(), len + 1); + return copy; +} + +#else +inline char* AllocateStringCopy(const std::string& str) { + const size_t len = str.length(); + char* copy = static_cast(malloc(len + 1)); + std::memcpy(copy, str.c_str(), len + 1); + return copy; +} +#endif + #endif // INFINIFRAME_COMMON_H diff --git a/src/InfiniFrame.Native/Utils/Event.h b/src/InfiniFrame.Native/Utils/Event.h index bf6f23d5..3fb427af 100644 --- a/src/InfiniFrame.Native/Utils/Event.h +++ b/src/InfiniFrame.Native/Utils/Event.h @@ -85,7 +85,7 @@ class Event { private: mutable std::shared_mutex m_mutex; std::map m_handlers; - Token m_nextToken = 0; + Token m_nextToken = 1; }; // --------------------------------------------------------------------------------------------------------------------- diff --git a/src/InfiniFrame/InfiniFrameWindow.cs b/src/InfiniFrame/InfiniFrameWindow.cs index e7783613..acdafeed 100644 --- a/src/InfiniFrame/InfiniFrameWindow.cs +++ b/src/InfiniFrame/InfiniFrameWindow.cs @@ -449,7 +449,13 @@ public void Close() { if (InstanceHandle == IntPtr.Zero) return; Interlocked.Exchange(ref _shutdownRequested, 1); - Invoke(() => InfiniFrameNative.Close(InstanceHandle)); + Invoke(() => { + if (InstanceHandle == IntPtr.Zero) { + Logger.LogDebug("Window already closed"); + return; + } + InfiniFrameNative.Close(InstanceHandle); + }); } /// @@ -471,7 +477,13 @@ public void SendWebMessage(string message) { } Logger.LogDebug(".SendWebMessage({Message})", message); - Invoke(() => InfiniFrameNative.SendWebMessage(InstanceHandle, message)); + Invoke(() => { + if (Volatile.Read(ref _shutdownStarted) != 0 || InstanceHandle == IntPtr.Zero) { + Logger.LogDebug("Window closed before SendWebMessage could execute"); + return; + } + InfiniFrameNative.SendWebMessage(InstanceHandle, message); + }); } public Task SendWebMessageAsync(string message, CancellationToken ct = default) { @@ -503,8 +515,19 @@ internal byte OnWindowClosing() { /// The title of the notification /// The text of the notification public void SendNotification(string title, string body) { + if (Volatile.Read(ref _shutdownStarted) != 0 || InstanceHandle == IntPtr.Zero) { + Logger.LogDebug("Skipping SendNotification during shutdown"); + return; + } + Logger.LogDebug(".SendNotification({Title}, {Body})", title, body); - Invoke(() => InfiniFrameNative.ShowNotification(InstanceHandle, title, body)); + Invoke(() => { + if (Volatile.Read(ref _shutdownStarted) != 0 || InstanceHandle == IntPtr.Zero) { + Logger.LogDebug("Window closed before SendNotification could execute"); + return; + } + InfiniFrameNative.ShowNotification(InstanceHandle, title, body); + }); } /// @@ -712,8 +735,12 @@ out string? resolvedIconFilePath Invoke(InfiniFrameNative.RegisterMac); Invoke(() => InstanceHandle = InfiniFrameNative.Constructor(in StartupParameters)); + + FreeCustomSchemeNames(); } catch (Exception ex) when (IsNonFatalException(ex)) { + FreeCustomSchemeNames(); + int lastError = 0; if (OperatingSystem.IsWindows()) lastError = Marshal.GetLastWin32Error(); @@ -726,7 +753,24 @@ out string? resolvedIconFilePath } /// - /// Show a native open dialog. + /// Frees unmanaged memory allocated for custom scheme names + /// + /// + /// This method should be called after the native constructor has copied the scheme names. + /// The native side makes its own copies, so the managed side can safely free the memory + /// + private void FreeCustomSchemeNames() { + if (StartupParameters.CustomSchemeNames == null) return; + + foreach (IntPtr ptr in StartupParameters.CustomSchemeNames) { + if (ptr != IntPtr.Zero) { + Marshal.FreeHGlobal(ptr); + } + } + } + + /// + /// Show a native open dialog /// /// Whether files are hidden /// Title of the dialog