forked from chromium/chromium
-
Notifications
You must be signed in to change notification settings - Fork 0
/
hwnd_message_handler.cc
3717 lines (3271 loc) · 144 KB
/
hwnd_message_handler.cc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ui/views/win/hwnd_message_handler.h"
#include <dwmapi.h>
#include <oleacc.h>
#include <shellapi.h>
#include <tchar.h>
#include <wrl/client.h>
#include <utility>
#include "base/auto_reset.h"
#include "base/bind.h"
#include "base/callback_helpers.h"
#include "base/debug/gdi_debug_util_win.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/memory/raw_ptr.h"
#include "base/metrics/histogram_functions.h"
#include "base/strings/string_util_win.h"
#include "base/task/current_thread.h"
#include "base/task/single_thread_task_runner.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/time/time.h"
#include "base/trace_event/trace_event.h"
#include "base/win/dark_mode_support.h"
#include "base/win/scoped_gdi_object.h"
#include "base/win/win_util.h"
#include "base/win/windows_version.h"
#include "services/tracing/public/cpp/perfetto/macros.h"
#include "third_party/perfetto/protos/perfetto/trace/track_event/chrome_window_handle_event_info.pbzero.h"
#include "third_party/skia/include/core/SkPath.h"
#include "ui/accessibility/accessibility_switches.h"
#include "ui/accessibility/platform/ax_fragment_root_win.h"
#include "ui/accessibility/platform/ax_platform_node_win.h"
#include "ui/accessibility/platform/ax_system_caret_win.h"
#include "ui/base/ime/text_input_client.h"
#include "ui/base/ime/text_input_type.h"
#include "ui/base/ui_base_features.h"
#include "ui/base/view_prop.h"
#include "ui/base/win/hwnd_metrics.h"
#include "ui/base/win/internal_constants.h"
#include "ui/base/win/lock_state.h"
#include "ui/base/win/mouse_wheel_util.h"
#include "ui/base/win/session_change_observer.h"
#include "ui/base/win/shell.h"
#include "ui/base/win/touch_input.h"
#include "ui/base/win/win_cursor.h"
#include "ui/display/types/display_constants.h"
#include "ui/display/win/dpi.h"
#include "ui/display/win/screen_win.h"
#include "ui/events/event.h"
#include "ui/events/event_constants.h"
#include "ui/events/event_utils.h"
#include "ui/events/keycodes/keyboard_code_conversion_win.h"
#include "ui/events/types/event_type.h"
#include "ui/events/win/system_event_state_lookup.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/geometry/insets.h"
#include "ui/gfx/geometry/resize_utils.h"
#include "ui/gfx/icon_util.h"
#include "ui/gfx/path_win.h"
#include "ui/gfx/win/hwnd_util.h"
#include "ui/gfx/win/rendering_window_manager.h"
#include "ui/latency/latency_info.h"
#include "ui/native_theme/native_theme_win.h"
#include "ui/views/views_delegate.h"
#include "ui/views/widget/widget_hwnd_utils.h"
#include "ui/views/win/fullscreen_handler.h"
#include "ui/views/win/hwnd_message_handler_delegate.h"
#include "ui/views/win/hwnd_util.h"
#include "ui/views/win/scoped_fullscreen_visibility.h"
namespace views {
namespace {
// MoveLoopMouseWatcher is used to determine if the user canceled or completed a
// move. win32 doesn't appear to offer a way to determine the result of a move,
// so we install hooks to determine if we got a mouse up and assume the move
// completed.
class MoveLoopMouseWatcher {
public:
MoveLoopMouseWatcher(HWNDMessageHandler* host, bool hide_on_escape);
MoveLoopMouseWatcher(const MoveLoopMouseWatcher&) = delete;
MoveLoopMouseWatcher& operator=(const MoveLoopMouseWatcher&) = delete;
~MoveLoopMouseWatcher();
// Returns true if the mouse is up, or if we couldn't install the hook.
bool got_mouse_up() const { return got_mouse_up_; }
private:
// Instance that owns the hook. We only allow one instance to hook the mouse
// at a time.
static MoveLoopMouseWatcher* instance_;
// Key and mouse callbacks from the hook.
static LRESULT CALLBACK MouseHook(int n_code, WPARAM w_param, LPARAM l_param);
static LRESULT CALLBACK KeyHook(int n_code, WPARAM w_param, LPARAM l_param);
void Unhook();
// HWNDMessageHandler that created us.
raw_ptr<HWNDMessageHandler> host_;
// Should the window be hidden when escape is pressed?
const bool hide_on_escape_;
// Did we get a mouse up?
bool got_mouse_up_;
// Hook identifiers.
HHOOK mouse_hook_;
HHOOK key_hook_;
};
// static
MoveLoopMouseWatcher* MoveLoopMouseWatcher::instance_ = nullptr;
MoveLoopMouseWatcher::MoveLoopMouseWatcher(HWNDMessageHandler* host,
bool hide_on_escape)
: host_(host),
hide_on_escape_(hide_on_escape),
got_mouse_up_(false),
mouse_hook_(nullptr),
key_hook_(nullptr) {
// Only one instance can be active at a time.
if (instance_)
instance_->Unhook();
mouse_hook_ =
SetWindowsHookEx(WH_MOUSE, &MouseHook, nullptr, GetCurrentThreadId());
if (mouse_hook_) {
instance_ = this;
// We don't care if setting the key hook succeeded.
key_hook_ =
SetWindowsHookEx(WH_KEYBOARD, &KeyHook, nullptr, GetCurrentThreadId());
}
if (instance_ != this) {
// Failed installation. Assume we got a mouse up in this case, otherwise
// we'll think all drags were canceled.
got_mouse_up_ = true;
}
}
MoveLoopMouseWatcher::~MoveLoopMouseWatcher() {
Unhook();
}
void MoveLoopMouseWatcher::Unhook() {
if (instance_ != this)
return;
DCHECK(mouse_hook_);
UnhookWindowsHookEx(mouse_hook_);
if (key_hook_)
UnhookWindowsHookEx(key_hook_);
key_hook_ = nullptr;
mouse_hook_ = nullptr;
instance_ = nullptr;
}
// static
LRESULT CALLBACK MoveLoopMouseWatcher::MouseHook(int n_code,
WPARAM w_param,
LPARAM l_param) {
DCHECK(instance_);
if (n_code == HC_ACTION && w_param == WM_LBUTTONUP)
instance_->got_mouse_up_ = true;
return CallNextHookEx(instance_->mouse_hook_, n_code, w_param, l_param);
}
// static
LRESULT CALLBACK MoveLoopMouseWatcher::KeyHook(int n_code,
WPARAM w_param,
LPARAM l_param) {
if (n_code == HC_ACTION && w_param == VK_ESCAPE) {
int value = TRUE;
DwmSetWindowAttribute(instance_->host_->hwnd(),
DWMWA_TRANSITIONS_FORCEDISABLED, &value,
sizeof(value));
if (instance_->hide_on_escape_)
instance_->host_->Hide();
}
return CallNextHookEx(instance_->key_hook_, n_code, w_param, l_param);
}
// Called from OnNCActivate.
BOOL CALLBACK EnumChildWindowsForRedraw(HWND hwnd, LPARAM lparam) {
DWORD process_id;
GetWindowThreadProcessId(hwnd, &process_id);
UINT flags = RDW_INVALIDATE | RDW_NOCHILDREN | RDW_FRAME;
if (process_id == GetCurrentProcessId())
flags |= RDW_UPDATENOW;
RedrawWindow(hwnd, nullptr, nullptr, flags);
return TRUE;
}
bool GetMonitorAndRects(const RECT& rect,
HMONITOR* monitor,
gfx::Rect* monitor_rect,
gfx::Rect* work_area) {
DCHECK(monitor);
DCHECK(monitor_rect);
DCHECK(work_area);
*monitor = MonitorFromRect(&rect, MONITOR_DEFAULTTONULL);
if (!*monitor)
return false;
MONITORINFO monitor_info = {0};
monitor_info.cbSize = sizeof(monitor_info);
GetMonitorInfo(*monitor, &monitor_info);
*monitor_rect = gfx::Rect(monitor_info.rcMonitor);
*work_area = gfx::Rect(monitor_info.rcWork);
return true;
}
// Enables or disables the menu item for the specified command and menu.
void EnableMenuItemByCommand(HMENU menu, UINT command, bool enabled) {
UINT flags = MF_BYCOMMAND | (enabled ? MF_ENABLED : MF_DISABLED | MF_GRAYED);
EnableMenuItem(menu, command, flags);
}
// Callback used to notify child windows that the top level window received a
// DWMCompositionChanged message.
BOOL CALLBACK SendDwmCompositionChanged(HWND window, LPARAM param) {
SendMessage(window, WM_DWMCOMPOSITIONCHANGED, 0, 0);
return TRUE;
}
// The thickness of an auto-hide taskbar in pixels.
constexpr int kAutoHideTaskbarThicknessPx = 2;
bool IsTopLevelWindow(HWND window) {
LONG style = ::GetWindowLong(window, GWL_STYLE);
if (!(style & WS_CHILD))
return true;
HWND parent = ::GetParent(window);
return !parent || (parent == ::GetDesktopWindow());
}
ui::EventType GetTouchEventType(POINTER_FLAGS pointer_flags) {
if (pointer_flags & POINTER_FLAG_DOWN)
return ui::ET_TOUCH_PRESSED;
if (pointer_flags & POINTER_FLAG_UPDATE)
return ui::ET_TOUCH_MOVED;
if (pointer_flags & POINTER_FLAG_UP)
return ui::ET_TOUCH_RELEASED;
return ui::ET_TOUCH_MOVED;
}
bool IsHitTestOnResizeHandle(LRESULT hittest) {
return hittest == HTRIGHT || hittest == HTLEFT || hittest == HTTOP ||
hittest == HTBOTTOM || hittest == HTTOPLEFT || hittest == HTTOPRIGHT ||
hittest == HTBOTTOMLEFT || hittest == HTBOTTOMRIGHT;
}
// Convert |param| to the gfx::ResizeEdge used in gfx::SizeRectToAspectRatio().
gfx::ResizeEdge GetWindowResizeEdge(UINT param) {
switch (param) {
case WMSZ_BOTTOM:
return gfx::ResizeEdge::kBottom;
case WMSZ_TOP:
return gfx::ResizeEdge::kTop;
case WMSZ_LEFT:
return gfx::ResizeEdge::kLeft;
case WMSZ_RIGHT:
return gfx::ResizeEdge::kRight;
case WMSZ_TOPLEFT:
return gfx::ResizeEdge::kTopLeft;
case WMSZ_TOPRIGHT:
return gfx::ResizeEdge::kTopRight;
case WMSZ_BOTTOMLEFT:
return gfx::ResizeEdge::kBottomLeft;
case WMSZ_BOTTOMRIGHT:
return gfx::ResizeEdge::kBottomRight;
default:
NOTREACHED();
return gfx::ResizeEdge::kBottomRight;
}
}
int GetFlagsFromRawInputMessage(RAWINPUT* input) {
int flags = ui::EF_NONE;
if (input->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_1_DOWN)
flags |= ui::EF_LEFT_MOUSE_BUTTON;
if (input->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_2_DOWN)
flags |= ui::EF_RIGHT_MOUSE_BUTTON;
if (input->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_3_DOWN)
flags |= ui::EF_MIDDLE_MOUSE_BUTTON;
if (input->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_4_DOWN)
flags |= ui::EF_BACK_MOUSE_BUTTON;
if (input->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_5_DOWN)
flags |= ui::EF_FORWARD_MOUSE_BUTTON;
return ui::GetModifiersFromKeyState() | flags;
}
constexpr auto kTouchDownContextResetTimeout = base::Milliseconds(500);
// Windows does not flag synthesized mouse messages from touch or pen in all
// cases. This causes us grief as we don't want to process touch and mouse
// messages concurrently. Hack as per msdn is to check if the time difference
// between the touch/pen message and the mouse move is within 500 ms and at the
// same location as the cursor.
constexpr int kSynthesizedMouseMessagesTimeDifference = 500;
} // namespace
// A scoping class that prevents a window from being able to redraw in response
// to invalidations that may occur within it for the lifetime of the object.
//
// Why would we want such a thing? Well, it turns out Windows has some
// "unorthodox" behavior when it comes to painting its non-client areas.
// Occasionally, Windows will paint portions of the default non-client area
// right over the top of the custom frame. This is not simply fixed by handling
// WM_NCPAINT/WM_PAINT, with some investigation it turns out that this
// rendering is being done *inside* the default implementation of some message
// handlers and functions:
// . WM_SETTEXT
// . WM_SETICON
// . WM_NCLBUTTONDOWN
// . EnableMenuItem, called from our WM_INITMENU handler
// The solution is to handle these messages and call DefWindowProc ourselves,
// but prevent the window from being able to update itself for the duration of
// the call. We do this with this class, which automatically calls its
// associated Window's lock and unlock functions as it is created and destroyed.
// See documentation in those methods for the technique used.
//
// The lock only has an effect if the window was visible upon lock creation, as
// it doesn't guard against direct visiblility changes, and multiple locks may
// exist simultaneously to handle certain nested Windows messages.
//
// This lock is disabled when DirectComposition is used and there's a child
// rendering window, as the WS_CLIPCHILDREN on the parent window should
// prevent the glitched rendering and making the window contents non-visible
// can cause a them to disappear for a frame.
//
// We normally skip locked updates when Aero is on for two reasons:
// 1. Because it isn't necessary. However, for windows without WS_CAPTION a
// close button may still be drawn, so the resize lock remains enabled for
// them. See http://crrev.com/130323
// 2. Because toggling the WS_VISIBLE flag may occur while the GPU process is
// attempting to present a child window's backbuffer onscreen. When these
// two actions race with one another, the child window will either flicker
// or will simply stop updating entirely.
//
// IMPORTANT: Do not use this scoping object for large scopes or periods of
// time! IT WILL PREVENT THE WINDOW FROM BEING REDRAWN! (duh).
//
// I would love to hear Raymond Chen's explanation for all this. And maybe a
// list of other messages that this applies to ;-)
class HWNDMessageHandler::ScopedRedrawLock {
public:
explicit ScopedRedrawLock(HWNDMessageHandler* owner)
: owner_(owner),
hwnd_(owner_->hwnd()),
cancel_unlock_(false),
should_lock_(owner_->IsVisible() && !owner->HasChildRenderingWindow() &&
::IsWindow(hwnd_) && !owner_->IsHeadless() &&
(!(GetWindowLong(hwnd_, GWL_STYLE) & WS_CAPTION) ||
!ui::win::IsAeroGlassEnabled())) {
if (should_lock_)
owner_->LockUpdates();
}
ScopedRedrawLock(const ScopedRedrawLock&) = delete;
ScopedRedrawLock& operator=(const ScopedRedrawLock&) = delete;
~ScopedRedrawLock() {
if (!cancel_unlock_ && should_lock_ && ::IsWindow(hwnd_))
owner_->UnlockUpdates();
}
// Cancel the unlock operation, call this if the Widget is being destroyed.
void CancelUnlockOperation() { cancel_unlock_ = true; }
private:
// The owner having its style changed.
raw_ptr<HWNDMessageHandler> owner_;
// The owner's HWND, cached to avoid action after window destruction.
HWND hwnd_;
// A flag indicating that the unlock operation was canceled.
bool cancel_unlock_;
// If false, don't use redraw lock.
const bool should_lock_;
};
// static HWNDMessageHandler member initialization.
base::LazyInstance<HWNDMessageHandler::FullscreenWindowMonitorMap>::
DestructorAtExit HWNDMessageHandler::fullscreen_monitor_map_ =
LAZY_INSTANCE_INITIALIZER;
////////////////////////////////////////////////////////////////////////////////
// HWNDMessageHandler, public:
LONG HWNDMessageHandler::last_touch_or_pen_message_time_ = 0;
bool HWNDMessageHandler::is_pen_active_in_client_area_ = false;
HWNDMessageHandler::HWNDMessageHandler(HWNDMessageHandlerDelegate* delegate,
const std::string& debugging_id)
: WindowImpl(debugging_id),
delegate_(delegate),
fullscreen_handler_(new FullscreenHandler),
waiting_for_close_now_(false),
use_system_default_icon_(false),
restored_enabled_(false),
current_cursor_(base::MakeRefCounted<ui::WinCursor>()),
dpi_(0),
called_enable_non_client_dpi_scaling_(false),
active_mouse_tracking_flags_(0),
is_right_mouse_pressed_on_caption_(false),
lock_updates_count_(0),
ignore_window_pos_changes_(false),
last_monitor_(nullptr),
is_first_nccalc_(true),
menu_depth_(0),
id_generator_(0),
pen_processor_(&id_generator_, true),
touch_down_contexts_(0),
last_mouse_hwheel_time_(0),
dwm_transition_desired_(false),
dwm_composition_enabled_(ui::win::IsDwmCompositionEnabled()),
sent_window_size_changing_(false),
did_return_uia_object_(false),
left_button_down_on_caption_(false),
background_fullscreen_hack_(false),
pointer_events_for_touch_(::features::IsUsingWMPointerForTouch()) {}
HWNDMessageHandler::~HWNDMessageHandler() {
// Prevent calls back into this class via WNDPROC now that we've been
// destroyed.
ClearUserData();
}
void HWNDMessageHandler::Init(HWND parent,
const gfx::Rect& bounds,
bool headless_mode) {
TRACE_EVENT0("views", "HWNDMessageHandler::Init");
GetMonitorAndRects(bounds.ToRECT(), &last_monitor_, &last_monitor_rect_,
&last_work_area_);
initial_bounds_valid_ = !bounds.IsEmpty();
// Provide the headless mode window state container.
if (headless_mode)
headless_mode_window_ = absl::make_optional<HeadlessModeWindow>();
// Create the window.
WindowImpl::Init(parent, bounds);
if (!called_enable_non_client_dpi_scaling_ && delegate_->HasFrame() &&
base::win::IsProcessPerMonitorDpiAware()) {
// Derived signature; not available in headers.
// This call gets Windows to scale the non-client area when
// WM_DPICHANGED is fired.
using EnableChildWindowDpiMessagePtr = LRESULT(WINAPI*)(HWND, BOOL);
static const auto enable_child_window_dpi_message_func =
reinterpret_cast<EnableChildWindowDpiMessagePtr>(
base::win::GetUser32FunctionPointer("EnableChildWindowDpiMessage"));
if (enable_child_window_dpi_message_func)
enable_child_window_dpi_message_func(hwnd(), TRUE);
}
prop_window_target_ = std::make_unique<ui::ViewProp>(
hwnd(), ui::WindowEventTarget::kWin32InputEventTarget,
static_cast<ui::WindowEventTarget*>(this));
DCHECK(delegate_->GetHWNDMessageDelegateInputMethod());
observation_.Observe(delegate_->GetHWNDMessageDelegateInputMethod());
// The usual way for UI Automation to obtain a fragment root is through
// WM_GETOBJECT. However, if there's a relation such as "Controller For"
// between element A in one window and element B in another window, UIA might
// call element A to discover the relation, receive a pointer to element B,
// then ask element B for its fragment root, without having sent WM_GETOBJECT
// to element B's window.
// So we create the fragment root now to ensure it's ready if asked for.
if (::switches::IsExperimentalAccessibilityPlatformUIAEnabled())
ax_fragment_root_ = std::make_unique<ui::AXFragmentRootWin>(hwnd(), this);
// Disable pen flicks (http://crbug.com/506977)
base::win::DisableFlicks(hwnd());
}
void HWNDMessageHandler::InitModalType(ui::ModalType modal_type) {
if (modal_type == ui::MODAL_TYPE_NONE)
return;
// We implement modality by crawling up the hierarchy of windows starting
// at the owner, disabling all of them so that they don't receive input
// messages.
HWND start = ::GetWindow(hwnd(), GW_OWNER);
while (start) {
::EnableWindow(start, FALSE);
start = ::GetParent(start);
}
}
void HWNDMessageHandler::Close() {
if (!IsWindow(hwnd()))
return; // No need to do anything.
// Let's hide ourselves right away.
Hide();
// Modal dialog windows disable their owner windows; re-enable them now so
// they can activate as foreground windows upon this window's destruction.
RestoreEnabledIfNecessary();
// Re-enable flicks which removes the window property.
base::win::EnableFlicks(hwnd());
if (!waiting_for_close_now_) {
// And we delay the close so that if we are called from an ATL callback,
// we don't destroy the window before the callback returned (as the caller
// may delete ourselves on destroy and the ATL callback would still
// dereference us when the callback returns).
waiting_for_close_now_ = true;
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(&HWNDMessageHandler::CloseNow,
msg_handler_weak_factory_.GetWeakPtr()));
}
}
void HWNDMessageHandler::CloseNow() {
// We may already have been destroyed if the selection resulted in a tab
// switch which will have reactivated the browser window and closed us, so
// we need to check to see if we're still a window before trying to destroy
// ourself.
waiting_for_close_now_ = false;
if (IsWindow(hwnd()))
DestroyWindow(hwnd());
}
gfx::Rect HWNDMessageHandler::GetWindowBoundsInScreen() const {
RECT r;
GetWindowRect(hwnd(), &r);
return gfx::Rect(r);
}
gfx::Rect HWNDMessageHandler::GetClientAreaBoundsInScreen() const {
RECT r;
GetClientRect(hwnd(), &r);
POINT point = {r.left, r.top};
ClientToScreen(hwnd(), &point);
return gfx::Rect(point.x, point.y, r.right - r.left, r.bottom - r.top);
}
gfx::Rect HWNDMessageHandler::GetRestoredBounds() const {
// Headless mode window never goes fullscreen, so just return an empty
// rectangle here.
if (IsHeadless())
return gfx::Rect();
// If we're in fullscreen mode, we've changed the normal bounds to the monitor
// rect, so return the saved bounds instead.
if (IsFullscreen())
return fullscreen_handler_->GetRestoreBounds();
gfx::Rect bounds;
GetWindowPlacement(&bounds, nullptr);
return bounds;
}
gfx::Rect HWNDMessageHandler::GetClientAreaBounds() const {
if (IsMinimized())
return gfx::Rect();
if (delegate_->WidgetSizeIsClientSize())
return GetClientAreaBoundsInScreen();
return GetWindowBoundsInScreen();
}
void HWNDMessageHandler::GetWindowPlacement(
gfx::Rect* bounds,
ui::WindowShowState* show_state) const {
WINDOWPLACEMENT wp;
wp.length = sizeof(wp);
bool succeeded = !!::GetWindowPlacement(hwnd(), &wp);
DCHECK(succeeded);
if (bounds != nullptr) {
if (wp.showCmd == SW_SHOWNORMAL) {
// GetWindowPlacement can return misleading position if a normalized
// window was resized using Aero Snap feature (see comment 9 in bug
// 36421). As a workaround, using GetWindowRect for normalized windows.
succeeded = GetWindowRect(hwnd(), &wp.rcNormalPosition) != 0;
DCHECK(succeeded);
*bounds = gfx::Rect(wp.rcNormalPosition);
} else {
MONITORINFO mi;
mi.cbSize = sizeof(mi);
succeeded =
GetMonitorInfo(MonitorFromWindow(hwnd(), MONITOR_DEFAULTTONEAREST),
&mi) != 0;
DCHECK(succeeded);
*bounds = gfx::Rect(wp.rcNormalPosition);
// Convert normal position from workarea coordinates to screen
// coordinates.
bounds->Offset(mi.rcWork.left - mi.rcMonitor.left,
mi.rcWork.top - mi.rcMonitor.top);
}
}
if (show_state) {
if (wp.showCmd == SW_SHOWMAXIMIZED)
*show_state = ui::SHOW_STATE_MAXIMIZED;
else if (wp.showCmd == SW_SHOWMINIMIZED)
*show_state = ui::SHOW_STATE_MINIMIZED;
else
*show_state = ui::SHOW_STATE_NORMAL;
}
}
void HWNDMessageHandler::SetBounds(const gfx::Rect& bounds_in_pixels,
bool force_size_changed) {
background_fullscreen_hack_ = false;
SetBoundsInternal(bounds_in_pixels, force_size_changed);
}
void HWNDMessageHandler::SetDwmFrameExtension(DwmFrameState state) {
if (!delegate_->HasFrame() && ui::win::IsAeroGlassEnabled() &&
!is_translucent_) {
MARGINS m = {0, 0, 0, 0};
if (state == DwmFrameState::kOn)
m = {0, 0, 1, 0};
DwmExtendFrameIntoClientArea(hwnd(), &m);
}
}
void HWNDMessageHandler::SetSize(const gfx::Size& size) {
SetWindowPos(hwnd(), nullptr, 0, 0, size.width(), size.height(),
SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOMOVE);
}
void HWNDMessageHandler::CenterWindow(const gfx::Size& size) {
HWND parent = GetParent(hwnd());
if (!IsWindow(hwnd()))
parent = ::GetWindow(hwnd(), GW_OWNER);
gfx::CenterAndSizeWindow(parent, hwnd(), size);
}
void HWNDMessageHandler::SetRegion(HRGN region) {
custom_window_region_.reset(region);
ResetWindowRegion(true, true);
}
void HWNDMessageHandler::StackAbove(HWND other_hwnd) {
// Windows API allows to stack behind another windows only.
DCHECK(other_hwnd);
HWND next_window = GetNextWindow(other_hwnd, GW_HWNDPREV);
SetWindowPos(hwnd(), next_window ? next_window : HWND_TOP, 0, 0, 0, 0,
SWP_NOSIZE | SWP_NOMOVE | SWP_NOACTIVATE);
}
void HWNDMessageHandler::StackAtTop() {
SetWindowPos(hwnd(), HWND_TOP, 0, 0, 0, 0,
SWP_NOSIZE | SWP_NOMOVE | SWP_NOACTIVATE);
}
void HWNDMessageHandler::Show(ui::WindowShowState show_state,
const gfx::Rect& pixel_restore_bounds) {
TRACE_EVENT0("views", "HWNDMessageHandler::Show");
// In headless mode the platform window is always hidden, so instead of
// showing it just maintain a local flag to track the expected headless
// window visibility state.
if (IsHeadless()) {
headless_mode_window_->visibility_state = true;
return;
}
int native_show_state;
if (show_state == ui::SHOW_STATE_MAXIMIZED &&
!pixel_restore_bounds.IsEmpty()) {
WINDOWPLACEMENT placement = {0};
placement.length = sizeof(WINDOWPLACEMENT);
placement.showCmd = SW_SHOWMAXIMIZED;
placement.rcNormalPosition = pixel_restore_bounds.ToRECT();
SetWindowPlacement(hwnd(), &placement);
native_show_state = SW_SHOWMAXIMIZED;
} else {
const bool is_maximized = IsMaximized();
// Use SW_SHOW/SW_SHOWNA instead of SW_SHOWNORMAL/SW_SHOWNOACTIVATE so that
// the window is not restored to its original position if it is maximized.
// This could be used unconditionally for ui::SHOW_STATE_INACTIVE, but
// cross-platform behavior when showing a minimized window is inconsistent,
// some platforms restore the position, some do not. See crbug.com/1296710
switch (show_state) {
case ui::SHOW_STATE_INACTIVE:
native_show_state = is_maximized ? SW_SHOWNA : SW_SHOWNOACTIVATE;
break;
case ui::SHOW_STATE_MAXIMIZED:
native_show_state = SW_SHOWMAXIMIZED;
break;
case ui::SHOW_STATE_MINIMIZED:
native_show_state = SW_SHOWMINIMIZED;
break;
case ui::SHOW_STATE_NORMAL:
if ((GetWindowLong(hwnd(), GWL_EXSTYLE) & WS_EX_TRANSPARENT) ||
(GetWindowLong(hwnd(), GWL_EXSTYLE) & WS_EX_NOACTIVATE)) {
native_show_state = is_maximized ? SW_SHOWNA : SW_SHOWNOACTIVATE;
} else {
native_show_state = is_maximized ? SW_SHOW : SW_SHOWNORMAL;
}
break;
case ui::SHOW_STATE_FULLSCREEN:
native_show_state = SW_SHOWNORMAL;
SetFullscreen(true, display::kInvalidDisplayId);
break;
default:
native_show_state = delegate_->GetInitialShowState();
break;
}
ShowWindow(hwnd(), native_show_state);
// When launched from certain programs like bash and Windows Live
// Messenger, show_state is set to SW_HIDE, so we need to correct that
// condition. We don't just change show_state to SW_SHOWNORMAL because
// MSDN says we must always first call ShowWindow with the specified
// value from STARTUPINFO, otherwise all future ShowWindow calls will be
// ignored (!!#@@#!). Instead, we call ShowWindow again in this case.
if (native_show_state == SW_HIDE) {
native_show_state = SW_SHOWNORMAL;
ShowWindow(hwnd(), native_show_state);
}
}
// We need to explicitly activate the window if we've been shown with a state
// that should activate, because if we're opened from a desktop shortcut while
// an existing window is already running it doesn't seem to be enough to use
// one of these flags to activate the window.
if (native_show_state == SW_SHOWNORMAL ||
native_show_state == SW_SHOWMAXIMIZED)
Activate();
if (!delegate_->HandleInitialFocus(show_state))
SetInitialFocus();
}
void HWNDMessageHandler::Hide() {
// In headless mode the platform window is always hidden, so instead of
// hiding it just maintain a local flag to track the expected headless
// window visibility state.
if (IsHeadless()) {
headless_mode_window_->visibility_state = false;
return;
}
if (IsWindow(hwnd())) {
// NOTE: Be careful not to activate any windows here (for example, calling
// ShowWindow(SW_HIDE) will automatically activate another window). This
// code can be called while a window is being deactivated, and activating
// another window will screw up the activation that is already in progress.
SetWindowPos(hwnd(), nullptr, 0, 0, 0, 0,
SWP_HIDEWINDOW | SWP_NOACTIVATE | SWP_NOMOVE |
SWP_NOREPOSITION | SWP_NOSIZE | SWP_NOZORDER);
}
}
void HWNDMessageHandler::Maximize() {
if (IsHeadless()) {
headless_mode_window_->minmax_state = HeadlessModeWindow::kMaximized;
return;
}
ExecuteSystemMenuCommand(SC_MAXIMIZE);
}
void HWNDMessageHandler::Minimize() {
if (IsHeadless()) {
headless_mode_window_->minmax_state = HeadlessModeWindow::kMinimized;
return;
}
ExecuteSystemMenuCommand(SC_MINIMIZE);
delegate_->HandleNativeBlur(nullptr);
}
void HWNDMessageHandler::Restore() {
if (IsHeadless()) {
headless_mode_window_->minmax_state = HeadlessModeWindow::kNormal;
return;
}
ExecuteSystemMenuCommand(SC_RESTORE);
}
void HWNDMessageHandler::Activate() {
if (IsMinimized()) {
base::AutoReset<bool> restoring_activate(¬ify_restore_on_activate_,
true);
::ShowWindow(hwnd(), SW_RESTORE);
}
::SetWindowPos(hwnd(), HWND_TOP, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE);
SetForegroundWindow(hwnd());
}
void HWNDMessageHandler::Deactivate() {
HWND next_hwnd = ::GetNextWindow(hwnd(), GW_HWNDNEXT);
while (next_hwnd) {
if (::IsWindowVisible(next_hwnd)) {
::SetForegroundWindow(next_hwnd);
return;
}
next_hwnd = ::GetNextWindow(next_hwnd, GW_HWNDNEXT);
}
}
void HWNDMessageHandler::SetAlwaysOnTop(bool on_top) {
::SetWindowPos(hwnd(), on_top ? HWND_TOPMOST : HWND_NOTOPMOST, 0, 0, 0, 0,
SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
}
bool HWNDMessageHandler::IsVisible() const {
// In headless mode the platform window is always hidden, so instead of
// returning the actual window visibility state return the expected visibility
// state maintained by Show/Hide() calls.
return IsHeadless() ? headless_mode_window_->visibility_state
: !!::IsWindowVisible(hwnd());
}
bool HWNDMessageHandler::IsActive() const {
return GetActiveWindow() == hwnd();
}
bool HWNDMessageHandler::IsMinimized() const {
return IsHeadless() ? headless_mode_window_->IsMinimized()
: !!::IsIconic(hwnd());
}
bool HWNDMessageHandler::IsMaximized() const {
return (IsHeadless() ? headless_mode_window_->IsMaximized()
: !!::IsZoomed(hwnd())) &&
!IsFullscreen();
}
bool HWNDMessageHandler::IsFullscreen() const {
// In headless mode report the requested window state instead of the actual
// one.
return IsHeadless() ? headless_mode_window_->fullscreen_state
: fullscreen_handler_->fullscreen();
}
bool HWNDMessageHandler::IsAlwaysOnTop() const {
return (GetWindowLong(hwnd(), GWL_EXSTYLE) & WS_EX_TOPMOST) != 0;
}
bool HWNDMessageHandler::IsHeadless() const {
return headless_mode_window_.has_value();
}
bool HWNDMessageHandler::RunMoveLoop(const gfx::Vector2d& drag_offset,
bool hide_on_escape) {
ReleaseCapture();
MoveLoopMouseWatcher watcher(this, hide_on_escape);
// In Aura, we handle touch events asynchronously. So we need to allow nested
// tasks while in windows move loop.
base::CurrentThread::ScopedNestableTaskAllower allow_nested;
SendMessage(hwnd(), WM_SYSCOMMAND, SC_MOVE | 0x0002,
static_cast<LPARAM>(GetMessagePos()));
// Windows doesn't appear to offer a way to determine whether the user
// canceled the move or not. We assume if the user released the mouse it was
// successful.
return watcher.got_mouse_up();
}
void HWNDMessageHandler::EndMoveLoop() {
SendMessage(hwnd(), WM_CANCELMODE, 0, 0);
}
void HWNDMessageHandler::SendFrameChanged() {
SetWindowPos(hwnd(), nullptr, 0, 0, 0, 0,
SWP_FRAMECHANGED | SWP_NOACTIVATE | SWP_NOCOPYBITS | SWP_NOMOVE |
SWP_NOOWNERZORDER | SWP_NOREPOSITION | SWP_NOSENDCHANGING |
SWP_NOSIZE | SWP_NOZORDER);
}
void HWNDMessageHandler::FlashFrame(bool flash) {
FLASHWINFO fwi;
fwi.cbSize = sizeof(fwi);
fwi.hwnd = hwnd();
if (flash) {
fwi.dwFlags = custom_window_region_.is_valid() ? FLASHW_TRAY : FLASHW_ALL;
fwi.uCount = 4;
fwi.dwTimeout = 0;
} else {
fwi.dwFlags = FLASHW_STOP;
}
FlashWindowEx(&fwi);
}
void HWNDMessageHandler::ClearNativeFocus() {
::SetFocus(hwnd());
}
void HWNDMessageHandler::SetCapture() {
DCHECK(!HasCapture());
::SetCapture(hwnd());
}
void HWNDMessageHandler::ReleaseCapture() {
if (HasCapture())
::ReleaseCapture();
}
bool HWNDMessageHandler::HasCapture() const {
return ::GetCapture() == hwnd();
}
void HWNDMessageHandler::SetVisibilityChangedAnimationsEnabled(bool enabled) {
int dwm_value = enabled ? FALSE : TRUE;
DwmSetWindowAttribute(hwnd(), DWMWA_TRANSITIONS_FORCEDISABLED, &dwm_value,
sizeof(dwm_value));
}
bool HWNDMessageHandler::SetTitle(const std::u16string& title) {
std::wstring current_title;
auto len_with_null = static_cast<size_t>(GetWindowTextLength(hwnd())) + 1;
if (len_with_null == 1 && title.length() == 0)
return false;
if (len_with_null - 1 == title.length() &&
GetWindowText(hwnd(), base::WriteInto(¤t_title, len_with_null),
len_with_null) &&
current_title == base::AsWStringPiece(title))
return false;
SetWindowText(hwnd(), base::as_wcstr(title));
return true;
}
void HWNDMessageHandler::SetCursor(scoped_refptr<ui::WinCursor> cursor) {
DCHECK(cursor);
TRACE_EVENT1("ui,input", "HWNDMessageHandler::SetCursor", "cursor",
static_cast<const void*>(cursor->hcursor()));
::SetCursor(cursor->hcursor());
current_cursor_ = cursor;
}
void HWNDMessageHandler::FrameTypeChanged() {
needs_dwm_frame_clear_ = true;
if (!custom_window_region_.is_valid() && IsFrameSystemDrawn())
dwm_transition_desired_ = true;
if (!dwm_transition_desired_ || !IsFullscreen())
PerformDwmTransition();
}
void HWNDMessageHandler::SetWindowIcons(const gfx::ImageSkia& window_icon,
const gfx::ImageSkia& app_icon) {
if (!window_icon.isNull()) {
base::win::ScopedHICON previous_icon = std::move(window_icon_);
window_icon_ = IconUtil::CreateHICONFromSkBitmap(*window_icon.bitmap());
SendMessage(hwnd(), WM_SETICON, ICON_SMALL,
reinterpret_cast<LPARAM>(window_icon_.get()));
}
if (!app_icon.isNull()) {
base::win::ScopedHICON previous_icon = std::move(app_icon_);
app_icon_ = IconUtil::CreateHICONFromSkBitmap(*app_icon.bitmap());
SendMessage(hwnd(), WM_SETICON, ICON_BIG,
reinterpret_cast<LPARAM>(app_icon_.get()));
}
}
void HWNDMessageHandler::SetFullscreen(bool fullscreen,
int64_t target_display_id) {
// Avoid setting fullscreen mode when in headless mode, but keep track
// of the requested state for IsFullscreen() to report.
if (IsHeadless()) {
headless_mode_window_->fullscreen_state = fullscreen;
return;
}
// Erase any prior reference to this window in the fullscreen window map.
HMONITOR monitor = MonitorFromWindow(hwnd(), MONITOR_DEFAULTTOPRIMARY);
FullscreenWindowMonitorMap::iterator iter =
fullscreen_monitor_map_.Get().find(monitor);
if (iter != fullscreen_monitor_map_.Get().end())
fullscreen_monitor_map_.Get().erase(iter);
background_fullscreen_hack_ = false;
auto ref = msg_handler_weak_factory_.GetWeakPtr();
fullscreen_handler()->SetFullscreen(fullscreen, target_display_id);
if (!ref)
return;
// Add an entry in the fullscreen window map if the window is now fullscreen.
if (fullscreen) {
HMONITOR new_monitor = MonitorFromWindow(hwnd(), MONITOR_DEFAULTTOPRIMARY);
(fullscreen_monitor_map_.Get())[new_monitor] = this;
}
// If we are out of fullscreen and there was a pending DWM transition for the
// window, then go ahead and do it now.
if (!fullscreen && dwm_transition_desired_)
PerformDwmTransition();