1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
13 #include "WinEventObserver.h"
15 #include "InputDeviceUtils.h"
16 #include "ScreenHelperWin.h"
17 #include "WindowsUIUtils.h"
18 #include "WinWindowOcclusionTracker.h"
20 #include "gfxDWriteFonts.h"
21 #include "gfxPlatform.h"
22 #include "mozilla/Assertions.h"
23 #include "mozilla/ClearOnShutdown.h"
24 #include "mozilla/Logging.h"
25 #include "mozilla/LookAndFeel.h"
26 #include "mozilla/WindowsVersion.h"
27 #include "nsLookAndFeel.h"
28 #include "nsStringFwd.h"
29 #include "nsWindowDbg.h"
31 #include "nsXULAppAPI.h"
33 // borrowed from devblogs.microsoft.com/oldnewthing/20041025-00/?p=37483, by way
34 // of the Chromium sandboxing code's "current_module.h"
35 extern "C" IMAGE_DOS_HEADER __ImageBase
;
36 #define CURRENT_MODULE() reinterpret_cast<HMODULE>(&__ImageBase)
38 namespace mozilla::widget
{
40 LazyLogModule
gWinEventWindowLog("WinEventWindow");
41 #define OBS_LOG(...) \
42 MOZ_LOG(gWinEventWindowLog, ::mozilla::LogLevel::Info, (__VA_ARGS__))
45 namespace evtwin_details
{
46 static HWND sHiddenWindow
= nullptr;
47 static bool sHiddenWindowShutdown
= false;
48 HDEVNOTIFY sDeviceNotifyHandle
= nullptr;
49 } // namespace evtwin_details
53 void WinEventWindow::Ensure() {
54 MOZ_RELEASE_ASSERT(XRE_IsParentProcess());
55 MOZ_RELEASE_ASSERT(NS_IsMainThread());
57 using namespace evtwin_details
;
59 if (sHiddenWindow
) return;
60 if (sHiddenWindowShutdown
) return;
62 HMODULE
const hSelf
= CURRENT_MODULE();
63 WNDCLASSW
const wc
= {.lpfnWndProc
= WinEventWindow::WndProc
,
65 .lpszClassName
= kClassNameHidden
};
66 ATOM
const atom
= ::RegisterClassW(&wc
);
68 // This is known to be possible when the atom table no longer has free
69 // entries, which unfortunately happens more often than one might expect.
71 auto volatile const err
[[maybe_unused
]] = ::GetLastError();
72 MOZ_CRASH("could not register broadcast-receiver window-class");
76 ::CreateWindowW((LPCWSTR
)(uintptr_t)atom
, L
"WinEventWindow", 0, 0, 0, 0,
77 0, nullptr, nullptr, hSelf
, nullptr);
80 MOZ_CRASH("could not create broadcast-receiver window");
83 sDeviceNotifyHandle
= InputDeviceUtils::RegisterNotification(sHiddenWindow
);
85 // It should be harmless to leak this window until destruction -- but other
86 // parts of Gecko may expect all windows to be destroyed, so do that.
87 mozilla::RunOnShutdown([]() {
88 InputDeviceUtils::UnregisterNotification(sDeviceNotifyHandle
);
90 sHiddenWindowShutdown
= true;
91 ::DestroyWindow(sHiddenWindow
);
92 sHiddenWindow
= nullptr;
97 HWND
WinEventWindow::GetHwndForTestingOnly() {
98 return evtwin_details::sHiddenWindow
;
101 // Callbacks for individual event types. These are private and internal
102 // implementation details of WinEventWindow.
104 namespace evtwin_details
{
106 static void NotifyThemeChanged(ThemeChangeKind aKind
) {
107 LookAndFeel::NotifyChangedAllWindows(aKind
);
110 static void OnSessionChange(WPARAM wParam
, LPARAM lParam
) {
111 if (wParam
== WTS_SESSION_LOCK
|| wParam
== WTS_SESSION_UNLOCK
) {
112 DWORD currentSessionId
;
114 ::ProcessIdToSessionId(::GetCurrentProcessId(), ¤tSessionId
);
116 // A process should always have the relevant access privileges for itself,
117 // but the above call could still fail if, e.g., someone's playing games
118 // with function imports. If so, just assert and/or skip out.
120 // Should this turn out to somehow be a real concern, we could do
122 // DWORD const currentSessionId =
123 // ::NtCurrentTeb()->ProcessEnvironmentBlock->SessionId;
125 // instead, which is actually documented (albeit abjured against).
126 MOZ_ASSERT(false, "::ProcessIdToSessionId() failed");
130 OBS_LOG("WinEventWindow OnSessionChange(): wParam=%zu lParam=%" PRIdLPTR
131 " currentSessionId=%lu",
132 wParam
, lParam
, currentSessionId
);
134 // Ignore lock/unlock messages for other sessions -- which Windows actually
135 // _does_ send in some scenarios; see review of Chromium changeset 1929489:
137 // https://chromium-review.googlesource.com/c/chromium/src/+/1929489
138 if (currentSessionId
!= (DWORD
)lParam
) {
142 if (auto* wwot
= WinWindowOcclusionTracker::Get()) {
143 wwot
->OnSessionChange(wParam
);
148 static void OnPowerBroadcast(WPARAM wParam
, LPARAM lParam
) {
149 if (wParam
== PBT_POWERSETTINGCHANGE
) {
150 POWERBROADCAST_SETTING
* setting
= (POWERBROADCAST_SETTING
*)lParam
;
153 if (::IsEqualGUID(setting
->PowerSetting
, GUID_SESSION_DISPLAY_STATUS
) &&
154 setting
->DataLength
== sizeof(DWORD
)) {
155 MONITOR_DISPLAY_STATE state
{};
157 ::memcpy_s(&state
, sizeof(state
), setting
->Data
, setting
->DataLength
);
159 MOZ_ASSERT(false, "bad data in POWERBROADCAST_SETTING in lParam");
163 bool const displayOn
= MONITOR_DISPLAY_STATE::PowerMonitorOff
!= state
;
165 OBS_LOG("WinEventWindow OnPowerBroadcast(): displayOn=%d",
166 int(displayOn
? 1 : 0));
168 if (auto* wwot
= WinWindowOcclusionTracker::Get()) {
169 wwot
->OnDisplayStateChanged(displayOn
);
175 static void OnSettingsChange(WPARAM wParam
, LPARAM lParam
) {
177 case SPI_SETCLIENTAREAANIMATION
:
178 case SPI_SETKEYBOARDDELAY
:
179 case SPI_SETMOUSEVANISH
:
180 case MOZ_SPI_SETCURSORSIZE
:
181 // These need to update LookAndFeel cached values.
183 // They affect reduced motion settings / caret blink count / show pointer
184 // while typing / tooltip offset, so no need to invalidate style / layout.
185 NotifyThemeChanged(widget::ThemeChangeKind::MediaQueriesOnly
);
188 case SPI_SETFONTSMOOTHING
:
189 case SPI_SETFONTSMOOTHINGTYPE
:
190 gfxDWriteFont::UpdateSystemTextVars();
193 case SPI_SETWORKAREA
:
194 // NB: We also refresh screens on WM_DISPLAYCHANGE, but the rcWork
195 // values are sometimes wrong at that point. This message then arrives
196 // soon afterward, when we can get the right rcWork values.
197 ScreenHelperWin::RefreshScreens();
207 nsDependentString lParamString
{reinterpret_cast<const wchar_t*>(lParam
)};
209 if (lParamString
== u
"ImmersiveColorSet"_ns
) {
210 // This affects system colors (-moz-win-accentcolor), so gotta pass the
212 NotifyThemeChanged(widget::ThemeChangeKind::Style
);
216 // UserInteractionMode, ConvertibleSlateMode, and SystemDockMode may cause
217 // @media(pointer) queries to change, which layout needs to know about.
219 // The former two of those also imply that the current tablet-mode state needs
222 if (lParamString
== u
"UserInteractionMode"_ns
) {
223 // Documentation implies, and testing shows, that this is seen on Win10
225 Unused
<< NS_WARN_IF(mozilla::IsWin11OrLater());
226 WindowsUIUtils::UpdateInWin10TabletMode();
227 NotifyThemeChanged(widget::ThemeChangeKind::MediaQueriesOnly
);
231 if (lParamString
== u
"ConvertibleSlateMode"_ns
) {
232 // Documentation implies, and testing shows, that this is not seen on Win10.
233 Unused
<< NS_WARN_IF(!mozilla::IsWin11OrLater());
234 WindowsUIUtils::UpdateInWin11TabletMode();
235 NotifyThemeChanged(widget::ThemeChangeKind::MediaQueriesOnly
);
239 if (lParamString
== u
"SystemDockMode"_ns
) {
240 NotifyThemeChanged(widget::ThemeChangeKind::MediaQueriesOnly
);
245 static void OnDeviceChange(WPARAM wParam
, LPARAM lParam
) {
246 if (wParam
== DBT_DEVICEARRIVAL
|| wParam
== DBT_DEVICEREMOVECOMPLETE
) {
247 DEV_BROADCAST_HDR
* hdr
= reinterpret_cast<DEV_BROADCAST_HDR
*>(lParam
);
248 // Check dbch_devicetype explicitly since we will get other device types
249 // (e.g. DBT_DEVTYP_VOLUME) for some reason, even if we specify
250 // DBT_DEVTYP_DEVICEINTERFACE in the filter for RegisterDeviceNotification.
251 if (hdr
->dbch_devicetype
== DBT_DEVTYP_DEVICEINTERFACE
) {
252 // This can only change media queries (any-hover/any-pointer).
253 NotifyThemeChanged(widget::ThemeChangeKind::MediaQueriesOnly
);
258 } // namespace evtwin_details
262 LRESULT CALLBACK
WinEventWindow::WndProc(HWND hwnd
, UINT msg
, WPARAM wParam
,
264 NativeEventLogger
eventLogger("WinEventWindow", hwnd
, msg
, wParam
, lParam
);
267 case WM_WINDOWPOSCHANGING
: {
268 // prevent rude external programs from making hidden window visible
269 LPWINDOWPOS info
= (LPWINDOWPOS
)lParam
;
270 info
->flags
&= ~SWP_SHOWWINDOW
;
273 case WM_WTSSESSION_CHANGE
: {
274 evtwin_details::OnSessionChange(wParam
, lParam
);
277 case WM_POWERBROADCAST
: {
278 evtwin_details::OnPowerBroadcast(wParam
, lParam
);
281 case WM_SYSCOLORCHANGE
: {
282 // No need to invalidate layout for system color changes, but we need to
284 evtwin_details::NotifyThemeChanged(widget::ThemeChangeKind::Style
);
287 case WM_THEMECHANGED
: {
288 // We assume pretty much everything could've changed here.
289 evtwin_details::NotifyThemeChanged(
290 widget::ThemeChangeKind::StyleAndLayout
);
293 case WM_FONTCHANGE
: {
294 // update the global font list
295 gfxPlatform::GetPlatform()->UpdateFontList();
298 case WM_SETTINGCHANGE
: {
299 evtwin_details::OnSettingsChange(wParam
, lParam
);
302 case WM_DEVICECHANGE
: {
303 evtwin_details::OnDeviceChange(wParam
, lParam
);
306 case WM_DISPLAYCHANGE
: {
307 ScreenHelperWin::RefreshScreens();
312 LRESULT
const ret
= ::DefWindowProcW(hwnd
, msg
, wParam
, lParam
);
313 eventLogger
.SetResult(ret
, false);
319 } // namespace mozilla::widget