blob: e89d838483ccca6b9e25eed9465e1af50cec2d53 [file] [log] [blame]
// Copyright 2016 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 "components/mus/ws/window_manager_state.h"
#include <queue>
#include "base/memory/weak_ptr.h"
#include "components/mus/common/event_matcher_util.h"
#include "components/mus/ws/accelerator.h"
#include "components/mus/ws/display_manager.h"
#include "components/mus/ws/platform_display.h"
#include "components/mus/ws/server_window.h"
#include "components/mus/ws/user_display_manager.h"
#include "components/mus/ws/user_id_tracker.h"
#include "components/mus/ws/window_server.h"
#include "components/mus/ws/window_tree.h"
#include "services/shell/public/interfaces/connector.mojom.h"
#include "ui/events/event.h"
namespace mus {
namespace ws {
namespace {
// Debug accelerator IDs start far above the highest valid Windows command ID
// (0xDFFF) and Chrome's highest IDC command ID.
const uint32_t kPrintWindowsDebugAcceleratorId = 1u << 31;
base::TimeDelta GetDefaultAckTimerDelay() {
#if defined(NDEBUG)
return base::TimeDelta::FromMilliseconds(100);
#else
return base::TimeDelta::FromMilliseconds(1000);
#endif
}
bool EventsCanBeCoalesced(const ui::Event& one, const ui::Event& two) {
if (one.type() != two.type() || one.flags() != two.flags())
return false;
// TODO(sad): wheel events can also be merged.
if (one.type() != ui::ET_POINTER_MOVED)
return false;
return one.AsPointerEvent()->pointer_id() ==
two.AsPointerEvent()->pointer_id();
}
std::unique_ptr<ui::Event> CoalesceEvents(std::unique_ptr<ui::Event> first,
std::unique_ptr<ui::Event> second) {
DCHECK(first->type() == ui::ET_POINTER_MOVED)
<< " Non-move events cannot be merged yet.";
// For mouse moves, the new event just replaces the old event.
return second;
}
} // namespace
class WindowManagerState::ProcessedEventTarget {
public:
ProcessedEventTarget(ServerWindow* window,
bool in_nonclient_area,
Accelerator* accelerator)
: in_nonclient_area_(in_nonclient_area) {
tracker_.Add(window);
if (accelerator)
accelerator_ = accelerator->GetWeakPtr();
}
~ProcessedEventTarget() {}
// Return true if the event is still valid. The event becomes invalid if
// the window is destroyed while waiting to dispatch.
bool IsValid() const { return !tracker_.windows().empty(); }
ServerWindow* window() {
DCHECK(IsValid());
return tracker_.windows().front();
}
bool in_nonclient_area() const { return in_nonclient_area_; }
base::WeakPtr<Accelerator> accelerator() { return accelerator_; }
private:
ServerWindowTracker tracker_;
const bool in_nonclient_area_;
base::WeakPtr<Accelerator> accelerator_;
DISALLOW_COPY_AND_ASSIGN(ProcessedEventTarget);
};
WindowManagerState::QueuedEvent::QueuedEvent() {}
WindowManagerState::QueuedEvent::~QueuedEvent() {}
WindowManagerState::WindowManagerState(Display* display,
PlatformDisplay* platform_display)
: WindowManagerState(display,
platform_display,
false,
shell::mojom::kRootUserID) {}
WindowManagerState::WindowManagerState(Display* display,
PlatformDisplay* platform_display,
const UserId& user_id)
: WindowManagerState(display, platform_display, true, user_id) {}
WindowManagerState::~WindowManagerState() {}
void WindowManagerState::SetFrameDecorationValues(
mojom::FrameDecorationValuesPtr values) {
got_frame_decoration_values_ = true;
frame_decoration_values_ = values.Clone();
display_->display_manager()
->GetUserDisplayManager(user_id_)
->OnFrameDecorationValuesChanged(this);
}
bool WindowManagerState::SetCapture(ServerWindow* window,
bool in_nonclient_area) {
// TODO(sky): capture should be a singleton. Need to route to WindowServer
// so that all other EventDispatchers are updated.
DCHECK(IsActive());
if (capture_window() == window)
return true;
DCHECK(!window || root_->Contains(window));
return event_dispatcher_.SetCaptureWindow(window, in_nonclient_area);
}
void WindowManagerState::ReleaseCaptureBlockedByModalWindow(
const ServerWindow* modal_window) {
event_dispatcher_.ReleaseCaptureBlockedByModalWindow(modal_window);
}
void WindowManagerState::ReleaseCaptureBlockedByAnyModalWindow() {
event_dispatcher_.ReleaseCaptureBlockedByAnyModalWindow();
}
void WindowManagerState::AddSystemModalWindow(ServerWindow* window) {
DCHECK(!window->transient_parent());
event_dispatcher_.AddSystemModalWindow(window);
}
mojom::DisplayPtr WindowManagerState::ToMojomDisplay() const {
mojom::DisplayPtr display_ptr = display_->ToMojomDisplay();
// TODO(sky): set work area.
display_ptr->work_area = display_ptr->bounds.Clone();
display_ptr->frame_decoration_values = frame_decoration_values_.Clone();
return display_ptr;
}
void WindowManagerState::OnWillDestroyTree(WindowTree* tree) {
if (tree_awaiting_input_ack_ != tree)
return;
// The WindowTree is dying. So it's not going to ack the event.
// If the dying tree matches the root |tree_| marked as handled so we don't
// notify it of accelerators.
OnEventAck(tree_awaiting_input_ack_, tree == tree_
? mojom::EventResult::HANDLED
: mojom::EventResult::UNHANDLED);
}
WindowManagerState::WindowManagerState(Display* display,
PlatformDisplay* platform_display,
bool is_user_id_valid,
const UserId& user_id)
: display_(display),
platform_display_(platform_display),
is_user_id_valid_(is_user_id_valid),
user_id_(user_id),
event_dispatcher_(this) {
frame_decoration_values_ = mojom::FrameDecorationValues::New();
frame_decoration_values_->normal_client_area_insets = mojo::Insets::New();
frame_decoration_values_->maximized_client_area_insets = mojo::Insets::New();
frame_decoration_values_->max_title_bar_button_width = 0u;
root_.reset(window_server()->CreateServerWindow(
window_server()->display_manager()->GetAndAdvanceNextRootId(),
ServerWindow::Properties()));
// Our root is always a child of the Display's root. Do this
// before the WindowTree has been created so that the client doesn't get
// notified of the add, bounds change and visibility change.
root_->SetBounds(gfx::Rect(display->root_window()->bounds().size()));
root_->SetVisible(true);
display->root_window()->Add(root_.get());
event_dispatcher_.set_root(root_.get());
AddDebugAccelerators();
}
bool WindowManagerState::IsActive() const {
return display()->GetActiveWindowManagerState() == this;
}
void WindowManagerState::Activate(const gfx::Point& mouse_location_on_screen) {
root_->SetVisible(true);
event_dispatcher_.Reset();
event_dispatcher_.SetMousePointerScreenLocation(mouse_location_on_screen);
}
void WindowManagerState::Deactivate() {
root_->SetVisible(false);
event_dispatcher_.Reset();
// The tree is no longer active, so no point in dispatching any further
// events.
std::queue<std::unique_ptr<QueuedEvent>> event_queue;
event_queue.swap(event_queue_);
}
void WindowManagerState::ProcessEvent(const ui::Event& event) {
// If this is still waiting for an ack from a previously sent event, then
// queue up the event to be dispatched once the ack is received.
if (event_ack_timer_.IsRunning()) {
if (!event_queue_.empty() && !event_queue_.back()->processed_target &&
EventsCanBeCoalesced(*event_queue_.back()->event, event)) {
event_queue_.back()->event = CoalesceEvents(
std::move(event_queue_.back()->event), ui::Event::Clone(event));
return;
}
QueueEvent(event, nullptr);
return;
}
event_dispatcher_.ProcessEvent(event);
}
void WindowManagerState::OnEventAck(mojom::WindowTree* tree,
mojom::EventResult result) {
if (tree_awaiting_input_ack_ != tree) {
// TODO(sad): The ack must have arrived after the timeout. We should do
// something here, and in OnEventAckTimeout().
return;
}
tree_awaiting_input_ack_ = nullptr;
event_ack_timer_.Stop();
if (result == mojom::EventResult::UNHANDLED && post_target_accelerator_)
OnAccelerator(post_target_accelerator_->id(), *event_awaiting_input_ack_);
ProcessNextEventFromQueue();
}
WindowServer* WindowManagerState::window_server() {
return display_->window_server();
}
void WindowManagerState::OnEventAckTimeout() {
// TODO(sad): Figure out what we should do.
NOTIMPLEMENTED() << "Event ACK timed out.";
OnEventAck(tree_awaiting_input_ack_, mojom::EventResult::UNHANDLED);
}
void WindowManagerState::QueueEvent(
const ui::Event& event,
std::unique_ptr<ProcessedEventTarget> processed_event_target) {
std::unique_ptr<QueuedEvent> queued_event(new QueuedEvent);
queued_event->event = ui::Event::Clone(event);
queued_event->processed_target = std::move(processed_event_target);
event_queue_.push(std::move(queued_event));
}
void WindowManagerState::ProcessNextEventFromQueue() {
// Loop through |event_queue_| stopping after dispatching the first valid
// event.
while (!event_queue_.empty()) {
std::unique_ptr<QueuedEvent> queued_event = std::move(event_queue_.front());
event_queue_.pop();
if (!queued_event->processed_target) {
event_dispatcher_.ProcessEvent(*queued_event->event);
return;
}
if (queued_event->processed_target->IsValid()) {
DispatchInputEventToWindowImpl(
queued_event->processed_target->window(),
queued_event->processed_target->in_nonclient_area(),
*queued_event->event, queued_event->processed_target->accelerator());
return;
}
}
}
void WindowManagerState::DispatchInputEventToWindowImpl(
ServerWindow* target,
bool in_nonclient_area,
const ui::Event& event,
base::WeakPtr<Accelerator> accelerator) {
if (target == root_->parent())
target = root_.get();
if (event.IsMousePointerEvent()) {
DCHECK(event_dispatcher_.mouse_cursor_source_window());
int32_t cursor_id = 0;
if (event_dispatcher_.GetCurrentMouseCursor(&cursor_id))
display_->UpdateNativeCursor(cursor_id);
}
// If the event is in the non-client area the event goes to the owner of
// the window. Otherwise if the window is an embed root, forward to the
// embedded window.
WindowTree* tree =
in_nonclient_area
? window_server()->GetTreeWithId(target->id().connection_id)
: window_server()->GetTreeWithRoot(target);
if (!tree) {
if (in_nonclient_area) {
// Being the root of the tree means we may get events outside the bounds
// of the platform window. Because the root has a connection id of 0,
// no WindowTree is found for it and we have to special case it here.
DCHECK_EQ(target, root_.get());
tree = tree_;
} else {
tree = window_server()->GetTreeWithId(target->id().connection_id);
}
}
// TOOD(sad): Adjust this delay, possibly make this dynamic.
const base::TimeDelta max_delay = base::debug::BeingDebugged()
? base::TimeDelta::FromDays(1)
: GetDefaultAckTimerDelay();
event_ack_timer_.Start(FROM_HERE, max_delay, this,
&WindowManagerState::OnEventAckTimeout);
tree_awaiting_input_ack_ = tree;
if (accelerator) {
event_awaiting_input_ack_ = ui::Event::Clone(event);
post_target_accelerator_ = accelerator;
}
// Ignore |tree| because it will receive the event via normal dispatch.
window_server()->SendToEventObservers(event, user_id_, tree);
tree->DispatchInputEvent(target, event);
}
void WindowManagerState::AddDebugAccelerators() {
// Always register the accelerators, even if they only work in debug, so that
// keyboard behavior is the same in release and debug builds.
mojom::EventMatcherPtr matcher = CreateKeyMatcher(
mus::mojom::KeyboardCode::S,
mus::mojom::kEventFlagControlDown | mus::mojom::kEventFlagAltDown
| mus::mojom::kEventFlagShiftDown);
event_dispatcher_.AddAccelerator(kPrintWindowsDebugAcceleratorId,
std::move(matcher));
}
bool WindowManagerState::HandleDebugAccelerator(uint32_t accelerator_id) {
#if !defined(NDEBUG)
if (accelerator_id == kPrintWindowsDebugAcceleratorId) {
// Error so it will be collected in system logs.
LOG(ERROR) << "ServerWindow hierarchy:\n"
<< root()->GetDebugWindowHierarchy();
return true;
}
#endif
return false;
}
////////////////////////////////////////////////////////////////////////////////
// EventDispatcherDelegate:
void WindowManagerState::OnAccelerator(uint32_t accelerator_id,
const ui::Event& event) {
DCHECK(IsActive());
if (HandleDebugAccelerator(accelerator_id))
return;
tree_->OnAccelerator(accelerator_id, event);
}
void WindowManagerState::SetFocusedWindowFromEventDispatcher(
ServerWindow* new_focused_window) {
DCHECK(IsActive());
display_->SetFocusedWindow(new_focused_window);
}
ServerWindow* WindowManagerState::GetFocusedWindowForEventDispatcher() {
return display()->GetFocusedWindow();
}
void WindowManagerState::SetNativeCapture() {
DCHECK(IsActive());
platform_display_->SetCapture();
}
void WindowManagerState::ReleaseNativeCapture() {
platform_display_->ReleaseCapture();
}
void WindowManagerState::OnServerWindowCaptureLost(ServerWindow* window) {
DCHECK(window);
window_server()->ProcessLostCapture(window);
}
void WindowManagerState::OnMouseCursorLocationChanged(const gfx::Point& point) {
window_server()->display_manager()->GetUserDisplayManager(user_id_)->
OnMouseCursorLocationChanged(point);
}
void WindowManagerState::DispatchInputEventToWindow(ServerWindow* target,
bool in_nonclient_area,
const ui::Event& event,
Accelerator* accelerator) {
DCHECK(IsActive());
// TODO(sky): this needs to see if another wms has capture and if so forward
// to it.
if (event_ack_timer_.IsRunning()) {
std::unique_ptr<ProcessedEventTarget> processed_event_target(
new ProcessedEventTarget(target, in_nonclient_area, accelerator));
QueueEvent(event, std::move(processed_event_target));
return;
}
base::WeakPtr<Accelerator> weak_accelerator;
if (accelerator)
weak_accelerator = accelerator->GetWeakPtr();
DispatchInputEventToWindowImpl(target, in_nonclient_area, event,
weak_accelerator);
}
void WindowManagerState::OnEventTargetNotFound(const ui::Event& event) {
window_server()->SendToEventObservers(event, user_id_,
nullptr /* ignore_tree */);
}
} // namespace ws
} // namespace mus