/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include #include #include #include #include #include "WinEventObserver.h" #include "InputDeviceUtils.h" #include "ScreenHelperWin.h" #include "WindowsUIUtils.h" #include "WinWindowOcclusionTracker.h" #include "gfxDWriteFonts.h" #include "gfxPlatform.h" #include "mozilla/Assertions.h" #include "mozilla/ClearOnShutdown.h" #include "mozilla/Logging.h" #include "mozilla/LookAndFeel.h" #include "mozilla/WindowsVersion.h" #include "nsLookAndFeel.h" #include "nsStringFwd.h" #include "nsWindowDbg.h" #include "nsdefs.h" #include "nsXULAppAPI.h" // borrowed from devblogs.microsoft.com/oldnewthing/20041025-00/?p=37483, by way // of the Chromium sandboxing code's "current_module.h" extern "C" IMAGE_DOS_HEADER __ImageBase; #define CURRENT_MODULE() reinterpret_cast(&__ImageBase) namespace mozilla::widget { LazyLogModule gWinEventWindowLog("WinEventWindow"); #define OBS_LOG(...) \ MOZ_LOG(gWinEventWindowLog, ::mozilla::LogLevel::Info, (__VA_ARGS__)) namespace { namespace evtwin_details { static HWND sHiddenWindow = nullptr; static bool sHiddenWindowShutdown = false; HDEVNOTIFY sDeviceNotifyHandle = nullptr; } // namespace evtwin_details } // namespace /* static */ void WinEventWindow::Ensure() { MOZ_RELEASE_ASSERT(XRE_IsParentProcess()); MOZ_RELEASE_ASSERT(NS_IsMainThread()); using namespace evtwin_details; if (sHiddenWindow) return; if (sHiddenWindowShutdown) return; HMODULE const hSelf = CURRENT_MODULE(); WNDCLASSW const wc = {.lpfnWndProc = WinEventWindow::WndProc, .hInstance = hSelf, .lpszClassName = kClassNameHidden}; ATOM const atom = ::RegisterClassW(&wc); if (!atom) { // This is known to be possible when the atom table no longer has free // entries, which unfortunately happens more often than one might expect. // See bug 1571516. auto volatile const err [[maybe_unused]] = ::GetLastError(); MOZ_CRASH("could not register broadcast-receiver window-class"); } sHiddenWindow = ::CreateWindowW((LPCWSTR)(uintptr_t)atom, L"WinEventWindow", 0, 0, 0, 0, 0, nullptr, nullptr, hSelf, nullptr); if (!sHiddenWindow) { MOZ_CRASH("could not create broadcast-receiver window"); } sDeviceNotifyHandle = InputDeviceUtils::RegisterNotification(sHiddenWindow); // It should be harmless to leak this window until destruction -- but other // parts of Gecko may expect all windows to be destroyed, so do that. mozilla::RunOnShutdown([]() { InputDeviceUtils::UnregisterNotification(sDeviceNotifyHandle); sHiddenWindowShutdown = true; ::DestroyWindow(sHiddenWindow); sHiddenWindow = nullptr; }); }; /* static */ HWND WinEventWindow::GetHwndForTestingOnly() { return evtwin_details::sHiddenWindow; } // Callbacks for individual event types. These are private and internal // implementation details of WinEventWindow. namespace { namespace evtwin_details { static void NotifyThemeChanged(ThemeChangeKind aKind) { LookAndFeel::NotifyChangedAllWindows(aKind); } static void OnSessionChange(WPARAM wParam, LPARAM lParam) { if (wParam == WTS_SESSION_LOCK || wParam == WTS_SESSION_UNLOCK) { DWORD currentSessionId; BOOL const rv = ::ProcessIdToSessionId(::GetCurrentProcessId(), ¤tSessionId); if (!rv) { // A process should always have the relevant access privileges for itself, // but the above call could still fail if, e.g., someone's playing games // with function imports. If so, just assert and/or skip out. // // Should this turn out to somehow be a real concern, we could do // ``` // DWORD const currentSessionId = // ::NtCurrentTeb()->ProcessEnvironmentBlock->SessionId; // ``` // instead, which is actually documented (albeit abjured against). MOZ_ASSERT(false, "::ProcessIdToSessionId() failed"); return; } OBS_LOG("WinEventWindow OnSessionChange(): wParam=%zu lParam=%" PRIdLPTR " currentSessionId=%lu", wParam, lParam, currentSessionId); // Ignore lock/unlock messages for other sessions -- which Windows actually // _does_ send in some scenarios; see review of Chromium changeset 1929489: // // https://chromium-review.googlesource.com/c/chromium/src/+/1929489 if (currentSessionId != (DWORD)lParam) { return; } if (auto* wwot = WinWindowOcclusionTracker::Get()) { wwot->OnSessionChange(wParam); } } } static void OnPowerBroadcast(WPARAM wParam, LPARAM lParam) { if (wParam == PBT_POWERSETTINGCHANGE) { POWERBROADCAST_SETTING* setting = (POWERBROADCAST_SETTING*)lParam; MOZ_ASSERT(setting); if (::IsEqualGUID(setting->PowerSetting, GUID_SESSION_DISPLAY_STATUS) && setting->DataLength == sizeof(DWORD)) { MONITOR_DISPLAY_STATE state{}; errno_t const err = ::memcpy_s(&state, sizeof(state), setting->Data, setting->DataLength); if (err) { MOZ_ASSERT(false, "bad data in POWERBROADCAST_SETTING in lParam"); return; } bool const displayOn = MONITOR_DISPLAY_STATE::PowerMonitorOff != state; OBS_LOG("WinEventWindow OnPowerBroadcast(): displayOn=%d", int(displayOn ? 1 : 0)); if (auto* wwot = WinWindowOcclusionTracker::Get()) { wwot->OnDisplayStateChanged(displayOn); } } } } static void OnSettingsChange(WPARAM wParam, LPARAM lParam) { switch (wParam) { case SPI_SETCLIENTAREAANIMATION: case SPI_SETKEYBOARDDELAY: case SPI_SETMOUSEVANISH: case MOZ_SPI_SETCURSORSIZE: // These need to update LookAndFeel cached values. // // They affect reduced motion settings / caret blink count / show pointer // while typing / tooltip offset, so no need to invalidate style / layout. NotifyThemeChanged(widget::ThemeChangeKind::MediaQueriesOnly); return; case SPI_SETFONTSMOOTHING: case SPI_SETFONTSMOOTHINGTYPE: gfxDWriteFont::UpdateSystemTextVars(); return; case SPI_SETWORKAREA: // NB: We also refresh screens on WM_DISPLAYCHANGE, but the rcWork // values are sometimes wrong at that point. This message then arrives // soon afterward, when we can get the right rcWork values. ScreenHelperWin::RefreshScreens(); return; default: break; } if (lParam == 0) { return; } nsDependentString lParamString{reinterpret_cast(lParam)}; if (lParamString == u"ImmersiveColorSet"_ns) { // This affects system colors (-moz-win-accentcolor), so gotta pass the // style flag. NotifyThemeChanged(widget::ThemeChangeKind::Style); return; } // UserInteractionMode, ConvertibleSlateMode, and SystemDockMode may cause // @media(pointer) queries to change, which layout needs to know about. // // The former two of those also imply that the current tablet-mode state needs // to be updated. if (lParamString == u"UserInteractionMode"_ns) { // Documentation implies, and testing shows, that this is seen on Win10 // only. Unused << NS_WARN_IF(mozilla::IsWin11OrLater()); WindowsUIUtils::UpdateInWin10TabletMode(); NotifyThemeChanged(widget::ThemeChangeKind::MediaQueriesOnly); return; } if (lParamString == u"ConvertibleSlateMode"_ns) { // Documentation implies, and testing shows, that this is not seen on Win10. Unused << NS_WARN_IF(!mozilla::IsWin11OrLater()); WindowsUIUtils::UpdateInWin11TabletMode(); NotifyThemeChanged(widget::ThemeChangeKind::MediaQueriesOnly); return; } if (lParamString == u"SystemDockMode"_ns) { NotifyThemeChanged(widget::ThemeChangeKind::MediaQueriesOnly); return; } } static void OnDeviceChange(WPARAM wParam, LPARAM lParam) { if (wParam == DBT_DEVICEARRIVAL || wParam == DBT_DEVICEREMOVECOMPLETE) { DEV_BROADCAST_HDR* hdr = reinterpret_cast(lParam); // Check dbch_devicetype explicitly since we will get other device types // (e.g. DBT_DEVTYP_VOLUME) for some reason, even if we specify // DBT_DEVTYP_DEVICEINTERFACE in the filter for RegisterDeviceNotification. if (hdr->dbch_devicetype == DBT_DEVTYP_DEVICEINTERFACE) { // This can only change media queries (any-hover/any-pointer). NotifyThemeChanged(widget::ThemeChangeKind::MediaQueriesOnly); } } } } // namespace evtwin_details } // namespace // static LRESULT CALLBACK WinEventWindow::WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { NativeEventLogger eventLogger("WinEventWindow", hwnd, msg, wParam, lParam); switch (msg) { case WM_WINDOWPOSCHANGING: { // prevent rude external programs from making hidden window visible LPWINDOWPOS info = (LPWINDOWPOS)lParam; info->flags &= ~SWP_SHOWWINDOW; } break; case WM_WTSSESSION_CHANGE: { evtwin_details::OnSessionChange(wParam, lParam); } break; case WM_POWERBROADCAST: { evtwin_details::OnPowerBroadcast(wParam, lParam); } break; case WM_SYSCOLORCHANGE: { // No need to invalidate layout for system color changes, but we need to // invalidate style. evtwin_details::NotifyThemeChanged(widget::ThemeChangeKind::Style); } break; case WM_THEMECHANGED: { // We assume pretty much everything could've changed here. evtwin_details::NotifyThemeChanged( widget::ThemeChangeKind::StyleAndLayout); } break; case WM_FONTCHANGE: { // update the global font list gfxPlatform::GetPlatform()->UpdateFontList(); } break; case WM_SETTINGCHANGE: { evtwin_details::OnSettingsChange(wParam, lParam); } break; case WM_DEVICECHANGE: { evtwin_details::OnDeviceChange(wParam, lParam); } break; case WM_DISPLAYCHANGE: { ScreenHelperWin::RefreshScreens(); break; } } LRESULT const ret = ::DefWindowProcW(hwnd, msg, wParam, lParam); eventLogger.SetResult(ret, false); return ret; } #undef OBS_LOG } // namespace mozilla::widget