blob: 6da000574c9876f5a20f7c2c3ce02aeb778ccdb9 [file] [log] [blame]
// Copyright (c) 2010 The Chromium OS 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 <cmath>
#include <vector>
#include <gflags/gflags.h>
#include <gtest/gtest.h>
#include "base/logging.h"
#include "cros/chromeos_wm_ipc_enums.h"
#include "window_manager/compositor/compositor.h"
#include "window_manager/event_loop.h"
#include "window_manager/screen_locker_handler.h"
#include "window_manager/test_lib.h"
#include "window_manager/window.h"
#include "window_manager/window_manager.h"
#include "window_manager/wm_ipc.h"
#include "window_manager/x11/mock_x_connection.h"
DEFINE_bool(logtostderr, false,
"Print debugging messages to stderr (suppressed otherwise)");
using std::vector;
namespace window_manager {
class ScreenLockerHandlerTest : public BasicWindowManagerTest {
protected:
virtual void SetUp() {
BasicWindowManagerTest::SetUp();
handler_ = wm_->screen_locker_handler_.get();
}
MockCompositor::TexturePixmapActor* GetSnapshotActor() {
return dynamic_cast<MockCompositor::TexturePixmapActor*>(
handler_->snapshot_actor_.get());
}
void TestActorConfiguredForSlowClose(
MockCompositor::TexturePixmapActor* actor) {
static const float kSizeRatio = ScreenLockerHandler::kSlowCloseSizeRatio;
CHECK(actor);
EXPECT_TRUE(actor->is_shown());
EXPECT_FLOAT_EQ(round(0.5 * (1.0 - kSizeRatio) * wm_->width()),
actor->x());
EXPECT_FLOAT_EQ(round(0.5 * (1.0 - kSizeRatio) * wm_->height()),
actor->y());
EXPECT_FLOAT_EQ(kSizeRatio, actor->scale_x());
EXPECT_FLOAT_EQ(kSizeRatio, actor->scale_y());
EXPECT_FLOAT_EQ(1.0, actor->opacity());
}
void TestActorConfiguredForUndoSlowClose(
MockCompositor::TexturePixmapActor* actor) {
CHECK(actor);
EXPECT_TRUE(actor->is_shown());
EXPECT_EQ(0, actor->x());
EXPECT_EQ(0, actor->y());
EXPECT_FLOAT_EQ(1.0, actor->scale_x());
EXPECT_FLOAT_EQ(1.0, actor->scale_y());
EXPECT_FLOAT_EQ(1.0, actor->opacity());
}
void TestActorConfiguredForFastClose(
MockCompositor::TexturePixmapActor* actor) {
CHECK(actor);
EXPECT_TRUE(actor->is_shown());
EXPECT_EQ(static_cast<int>(round(0.5 * wm_->width())), actor->x());
EXPECT_EQ(static_cast<int>(round(0.5 * wm_->height())), actor->y());
EXPECT_FLOAT_EQ(0.0, actor->scale_x());
EXPECT_FLOAT_EQ(0.0, actor->scale_y());
EXPECT_FLOAT_EQ(0.0, actor->opacity());
}
void TestActorConfiguredForFadeout(
MockCompositor::TexturePixmapActor* actor) {
CHECK(actor);
EXPECT_TRUE(actor->is_shown());
EXPECT_EQ(0, actor->x());
EXPECT_EQ(0, actor->y());
EXPECT_FLOAT_EQ(1.0, actor->scale_x());
EXPECT_FLOAT_EQ(1.0, actor->scale_y());
EXPECT_FLOAT_EQ(0.0, actor->opacity());
}
bool IsOnlyActiveVisibilityGroup(int group) {
if (compositor_->active_visibility_groups().size() !=
static_cast<size_t>(1)) {
return false;
}
return (*(compositor_->active_visibility_groups().begin()) == group);
}
ScreenLockerHandler* handler_;
};
TEST_F(ScreenLockerHandlerTest, BasicLock) {
// Create a regular toplevel window.
XWindow toplevel_xid = CreateSimpleWindow();
SendInitialEventsForWindow(toplevel_xid);
// The window's actor shouldn't be in any visibility groups, and the
// compositor shouldn't be restricting its drawing to a particular group.
MockCompositor::TexturePixmapActor* toplevel_actor =
GetMockActorForWindow(wm_->GetWindowOrDie(toplevel_xid));
EXPECT_TRUE(toplevel_actor->visibility_groups().empty());
EXPECT_TRUE(compositor_->active_visibility_groups().empty());
// Now create a screen locker window.
XWindow screen_locker_xid =
CreateBasicWindow(5, 5, wm_->width() - 5, wm_->height() - 5);
MockXConnection::WindowInfo* screen_locker_info =
xconn_->GetWindowInfoOrDie(screen_locker_xid);
wm_->wm_ipc()->SetWindowType(
screen_locker_xid, chromeos::WM_IPC_WINDOW_CHROME_SCREEN_LOCKER, NULL);
WmIpc::Message msg;
EXPECT_FALSE(
GetFirstWmIpcMessageOfType(
screen_locker_xid,
chromeos::WM_IPC_MESSAGE_CHROME_NOTIFY_SCREEN_REDRAWN_FOR_LOCK,
&msg));
const int initial_num_draws = compositor_->num_draws();
SendInitialEventsForWindow(screen_locker_xid);
Window* screen_locker_win = wm_->GetWindowOrDie(screen_locker_xid);
MockCompositor::TexturePixmapActor* screen_locker_actor =
GetMockActorForWindow(screen_locker_win);
// Check that the window was moved to (0, 0), resized to cover the whole
// screen, stacked correctly, and shown.
EXPECT_EQ(0, screen_locker_info->bounds.x);
EXPECT_EQ(0, screen_locker_info->bounds.y);
EXPECT_EQ(wm_->width(), screen_locker_info->bounds.width);
EXPECT_EQ(wm_->height(), screen_locker_info->bounds.height);
EXPECT_EQ(0, screen_locker_actor->GetX());
EXPECT_EQ(0, screen_locker_actor->GetY());
EXPECT_TRUE(WindowIsInLayer(screen_locker_win,
StackingManager::LAYER_SCREEN_LOCKER));
EXPECT_TRUE(screen_locker_actor->is_shown());
// This window's actor *should* be added to a group, and this should now
// be the only group that we're drawing.
EXPECT_EQ(static_cast<size_t>(1),
screen_locker_actor->visibility_groups().size());
EXPECT_TRUE(screen_locker_actor->visibility_groups().count(
WindowManager::VISIBILITY_GROUP_SCREEN_LOCKER));
EXPECT_TRUE(IsOnlyActiveVisibilityGroup(
WindowManager::VISIBILITY_GROUP_SCREEN_LOCKER));
// We should've redrawn the screen and sent the screen locker window a
// message letting it know that we did so. We should also update the
// _NET_WM_ACTIVE_WINDOW property so that the window's widgets know that it's
// active.
EXPECT_GT(compositor_->num_draws(), initial_num_draws);
EXPECT_TRUE(
GetFirstWmIpcMessageOfType(
screen_locker_xid,
chromeos::WM_IPC_MESSAGE_CHROME_NOTIFY_SCREEN_REDRAWN_FOR_LOCK,
&msg));
EXPECT_EQ(screen_locker_xid, GetActiveWindowProperty());
// We shouldn't animate a snapshot of the screen when we go directly from
// the unlocked to locked states (without seeing pre-lock) -- this
// probably means that the screen's getting locked because the system is
// about to be suspended, so we want to make sure that we're not showing
// the unlocked contents onscreen (http;//crosbug.com/8867). (It could
// also mean that we're running on a system that doesn't report power
// button releases correctly.)
EXPECT_TRUE(handler_->snapshot_actor_.get() == NULL);
// Now resize the root window and check that the screen locker window is
// also resized.
const XWindow root_xid = xconn_->GetRootWindow();
MockXConnection::WindowInfo* root_info = xconn_->GetWindowInfoOrDie(root_xid);
const int new_width = root_info->bounds.width + 20;
const int new_height = root_info->bounds.height + 20;
xconn_->ResizeWindow(root_xid, Size(new_width, new_height));
XEvent resize_event;
xconn_->InitConfigureNotifyEvent(&resize_event, root_xid);
wm_->HandleEvent(&resize_event);
EXPECT_EQ(new_width, screen_locker_info->bounds.width);
EXPECT_EQ(new_height, screen_locker_info->bounds.height);
// Unmap the screen locker window and check that the original toplevel
// window would be drawn again.
ASSERT_TRUE(xconn_->UnmapWindow(screen_locker_xid));
XEvent event;
xconn_->InitUnmapEvent(&event, screen_locker_xid);
wm_->HandleEvent(&event);
EXPECT_TRUE(compositor_->active_visibility_groups().empty());
}
TEST_F(ScreenLockerHandlerTest, AbortedLock) {
// Tell the window manager that the user started holding the power button
// to lock the screen.
WmIpc::Message msg(chromeos::WM_IPC_MESSAGE_WM_NOTIFY_POWER_BUTTON_STATE);
msg.set_param(0, chromeos::WM_IPC_POWER_BUTTON_PRE_LOCK);
SendWmIpcMessage(msg);
// We should have taken a snapshot of the screen.
MockCompositor::TexturePixmapActor* actor = GetSnapshotActor();
ASSERT_TRUE(actor != NULL);
TestActorConfiguredForSlowClose(actor);
EXPECT_NE(-1, handler_->destroy_snapshot_timeout_id_);
// The snapshot should be the only actor currently visible.
EXPECT_EQ(static_cast<size_t>(2), actor->visibility_groups().size());
EXPECT_TRUE(actor->visibility_groups().count(
WindowManager::VISIBILITY_GROUP_SCREEN_LOCKER) != 0);
EXPECT_TRUE(actor->visibility_groups().count(
WindowManager::VISIBILITY_GROUP_SESSION_ENDING) != 0);
EXPECT_TRUE(IsOnlyActiveVisibilityGroup(
WindowManager::VISIBILITY_GROUP_SCREEN_LOCKER));
// Now tell the WM that the button was released before being held long
// enough to lock.
msg.set_param(0, chromeos::WM_IPC_POWER_BUTTON_ABORTED_LOCK);
SendWmIpcMessage(msg);
// Check that we're still showing the same actor, and that it's being
// scaled back to its natural size.
ASSERT_EQ(actor, GetSnapshotActor());
TestActorConfiguredForUndoSlowClose(actor);
// Check that a timeout was registered to destroy the snapshot, and then
// invoke the callback and check that the actor was destroyed and we're
// displaying all actors again.
ASSERT_NE(-1, handler_->destroy_snapshot_timeout_id_);
if (!EventLoop::IsTimerFdSupported()) {
LOG(ERROR) << "Aborting test because of missing timerfd support";
return;
}
event_loop_->RunTimeoutForTesting(handler_->destroy_snapshot_timeout_id_);
EXPECT_EQ(-1, handler_->destroy_snapshot_timeout_id_);
EXPECT_EQ(static_cast<size_t>(0),
compositor_->active_visibility_groups().size());
}
TEST_F(ScreenLockerHandlerTest, SuccessfulLock) {
// Tell the window manager that we're in the pre-lock state.
WmIpc::Message msg(chromeos::WM_IPC_MESSAGE_WM_NOTIFY_POWER_BUTTON_STATE);
msg.set_param(0, chromeos::WM_IPC_POWER_BUTTON_PRE_LOCK);
SendWmIpcMessage(msg);
MockCompositor::TexturePixmapActor* actor = GetSnapshotActor();
ASSERT_TRUE(actor != NULL);
TestActorConfiguredForSlowClose(actor);
// Map a screen locker window.
XWindow screen_locker_xid = CreateSimpleWindow();
wm_->wm_ipc()->SetWindowType(
screen_locker_xid, chromeos::WM_IPC_WINDOW_CHROME_SCREEN_LOCKER, NULL);
SendInitialEventsForWindow(screen_locker_xid);
// We should still be showing the snapshot actor, but it should be
// getting scaled down to the center of the screen.
ASSERT_EQ(actor, GetSnapshotActor());
TestActorConfiguredForFastClose(actor);
// Invoke the timeout to destroy it and check that we're showing only the
// screen locker window.
ASSERT_NE(-1, handler_->destroy_snapshot_timeout_id_);
if (!EventLoop::IsTimerFdSupported()) {
LOG(ERROR) << "Aborting test because of missing timerfd support";
return;
}
event_loop_->RunTimeoutForTesting(handler_->destroy_snapshot_timeout_id_);
EXPECT_EQ(-1, handler_->destroy_snapshot_timeout_id_);
EXPECT_TRUE(GetSnapshotActor() == NULL);
EXPECT_TRUE(IsOnlyActiveVisibilityGroup(
WindowManager::VISIBILITY_GROUP_SCREEN_LOCKER));
}
TEST_F(ScreenLockerHandlerTest, AbortedShutdown) {
// Tell the window manager that the user started holding the power button
// to shut down the system.
WmIpc::Message msg(chromeos::WM_IPC_MESSAGE_WM_NOTIFY_POWER_BUTTON_STATE);
msg.set_param(0, chromeos::WM_IPC_POWER_BUTTON_PRE_SHUTDOWN);
SendWmIpcMessage(msg);
// We should have taken a snapshot of the screen.
MockCompositor::TexturePixmapActor* actor = GetSnapshotActor();
ASSERT_TRUE(actor != NULL);
TestActorConfiguredForSlowClose(actor);
EXPECT_NE(-1, handler_->destroy_snapshot_timeout_id_);
// The snapshot should be the only actor currently visible.
EXPECT_TRUE(actor->visibility_groups().count(
WindowManager::VISIBILITY_GROUP_SESSION_ENDING) != 0);
EXPECT_TRUE(IsOnlyActiveVisibilityGroup(
WindowManager::VISIBILITY_GROUP_SESSION_ENDING));
// Now tell the WM that the button was released before being held long
// enough to shut down.
msg.set_param(0, chromeos::WM_IPC_POWER_BUTTON_ABORTED_SHUTDOWN);
SendWmIpcMessage(msg);
// Check that we're still showing the same actor, and that it's being
// scaled back to its natural size.
ASSERT_EQ(actor, GetSnapshotActor());
TestActorConfiguredForUndoSlowClose(actor);
// Check that a timeout was registered to destroy the snapshot, and then
// invoke the callback and check that the actor was destroyed and we're
// displaying all actors again.
ASSERT_NE(-1, handler_->destroy_snapshot_timeout_id_);
if (!EventLoop::IsTimerFdSupported()) {
LOG(ERROR) << "Aborting test because of missing timerfd support";
return;
}
event_loop_->RunTimeoutForTesting(handler_->destroy_snapshot_timeout_id_);
EXPECT_EQ(-1, handler_->destroy_snapshot_timeout_id_);
EXPECT_EQ(static_cast<size_t>(0),
compositor_->active_visibility_groups().size());
// Now map a screen locker window so we can try the same thing from the
// locked state.
XWindow screen_locker_xid = CreateSimpleWindow();
wm_->wm_ipc()->SetWindowType(
screen_locker_xid, chromeos::WM_IPC_WINDOW_CHROME_SCREEN_LOCKER, NULL);
SendInitialEventsForWindow(screen_locker_xid);
ASSERT_TRUE(IsOnlyActiveVisibilityGroup(
WindowManager::VISIBILITY_GROUP_SCREEN_LOCKER));
// Enter the pre-shutdown state as before.
msg.set_param(0, chromeos::WM_IPC_POWER_BUTTON_PRE_SHUTDOWN);
SendWmIpcMessage(msg);
actor = GetSnapshotActor();
ASSERT_TRUE(actor != NULL);
TestActorConfiguredForSlowClose(actor);
EXPECT_TRUE(IsOnlyActiveVisibilityGroup(
WindowManager::VISIBILITY_GROUP_SESSION_ENDING));
// After aborting, we should be showing just the screen locker window.
msg.set_param(0, chromeos::WM_IPC_POWER_BUTTON_ABORTED_SHUTDOWN);
SendWmIpcMessage(msg);
ASSERT_NE(-1, handler_->destroy_snapshot_timeout_id_);
event_loop_->RunTimeoutForTesting(handler_->destroy_snapshot_timeout_id_);
EXPECT_EQ(-1, handler_->destroy_snapshot_timeout_id_);
EXPECT_TRUE(IsOnlyActiveVisibilityGroup(
WindowManager::VISIBILITY_GROUP_SCREEN_LOCKER));
}
// Test that we do stuff in response to notification that the system is
// shutting down.
TEST_F(ScreenLockerHandlerTest, HandleShutdown) {
// Go into the pre-shutdown state first.
WmIpc::Message msg(chromeos::WM_IPC_MESSAGE_WM_NOTIFY_POWER_BUTTON_STATE);
msg.set_param(0, chromeos::WM_IPC_POWER_BUTTON_PRE_SHUTDOWN);
SendWmIpcMessage(msg);
// Check that we've started the slow-close animation.
MockCompositor::TexturePixmapActor* actor = GetSnapshotActor();
ASSERT_TRUE(actor != NULL);
TestActorConfiguredForSlowClose(actor);
EXPECT_NE(-1, handler_->destroy_snapshot_timeout_id_);
EXPECT_TRUE(IsOnlyActiveVisibilityGroup(
WindowManager::VISIBILITY_GROUP_SESSION_ENDING));
// Notify the window manager that the system is being shut down.
msg.set_type(chromeos::WM_IPC_MESSAGE_WM_NOTIFY_SHUTTING_DOWN);
msg.set_param(0, 0);
SendWmIpcMessage(msg);
// Check that we grabbed the pointer and keyboard and assigned a transparent
// cursor to the root window.
XWindow root = xconn_->GetRootWindow();
EXPECT_EQ(root, xconn_->pointer_grab_xid());
EXPECT_EQ(root, xconn_->keyboard_grab_xid());
EXPECT_EQ(MockXConnection::kTransparentCursor,
xconn_->GetWindowInfoOrDie(root)->cursor);
// We should be using the same snapshot that we already grabbed, and we
// should be displaying the fast-close animation.
ASSERT_EQ(actor, GetSnapshotActor());
TestActorConfiguredForFastClose(actor);
EXPECT_TRUE(IsOnlyActiveVisibilityGroup(
WindowManager::VISIBILITY_GROUP_SESSION_ENDING));
// There's no need to destroy the snapshot after we're done with the
// animation; we're not going to be showing anything else onscreen.
EXPECT_EQ(-1, handler_->destroy_snapshot_timeout_id_);
}
// Test that we repeatedly try to grab the pointer and keyboard when the
// session is ending.
TEST_F(ScreenLockerHandlerTest, InputsAlreadyGrabbed) {
// Create a window and pretend that it's grabbed both inputs.
XWindow other_xid = CreateSimpleWindow();
xconn_->set_pointer_grab_xid(other_xid);
xconn_->set_keyboard_grab_xid(other_xid);
// Tell the window manager that the system is shutting down.
WmIpc::Message msg(chromeos::WM_IPC_MESSAGE_WM_NOTIFY_SHUTTING_DOWN);
SendWmIpcMessage(msg);
EXPECT_EQ(other_xid, xconn_->pointer_grab_xid());
EXPECT_EQ(other_xid, xconn_->keyboard_grab_xid());
// Now pretend that the pointer grab was released and check that after the
// timeout fires, the WM installs a pointer grab.
xconn_->set_pointer_grab_xid(0);
EXPECT_NE(-1, handler_->grab_inputs_timeout_id_);
event_loop_->RunTimeoutForTesting(handler_->grab_inputs_timeout_id_);
EXPECT_EQ(xconn_->GetRootWindow(), xconn_->pointer_grab_xid());
EXPECT_EQ(other_xid, xconn_->keyboard_grab_xid());
// Do the same thing with the keyboard.
xconn_->set_keyboard_grab_xid(0);
EXPECT_NE(-1, handler_->grab_inputs_timeout_id_);
event_loop_->RunTimeoutForTesting(handler_->grab_inputs_timeout_id_);
EXPECT_EQ(xconn_->GetRootWindow(), xconn_->pointer_grab_xid());
EXPECT_EQ(xconn_->GetRootWindow(), xconn_->keyboard_grab_xid());
// Both inputs are grabbed, so the timeout should be unregistered now.
EXPECT_EQ(-1, handler_->grab_inputs_timeout_id_);
}
// Test that we don't consider the screen to be locked until the screen
// locker window is actually visible.
TEST_F(ScreenLockerHandlerTest, DeferLockUntilWindowIsVisible) {
// Enable the sync request protocol on a screen locker window before
// mapping it so that we'll hold off on fetching its pixmap until it
// tells us that it's ready.
XWindow screen_locker_xid = CreateSimpleWindow();
wm_->wm_ipc()->SetWindowType(
screen_locker_xid, chromeos::WM_IPC_WINDOW_CHROME_SCREEN_LOCKER, NULL);
ConfigureWindowForSyncRequestProtocol(screen_locker_xid);
// We should continue showing all actors after the locker window is mapped.
SendInitialEventsForWindow(screen_locker_xid);
Window* screen_locker_win = wm_->GetWindowOrDie(screen_locker_xid);
ASSERT_FALSE(screen_locker_win->has_initial_pixmap());
EXPECT_FALSE(handler_->is_locked_);
EXPECT_EQ(static_cast<size_t>(0),
compositor_->active_visibility_groups().size());
// When we're notified that the pixmap is painted, we should switch to
// showing only the screen locker actor.
SendSyncRequestProtocolAlarm(screen_locker_xid);
ASSERT_TRUE(screen_locker_win->has_initial_pixmap());
EXPECT_TRUE(handler_->is_locked_);
EXPECT_TRUE(IsOnlyActiveVisibilityGroup(
WindowManager::VISIBILITY_GROUP_SCREEN_LOCKER));
}
// Check that when we see an override-redirect info bubble window that asks to
// remain visible while the screen is locked, we add it to the screen locker
// visibility group.
TEST_F(ScreenLockerHandlerTest, ShowSomeOtherWindowsWhileLocked) {
XWindow info_bubble_xid = CreateSimpleWindow();
xconn_->GetWindowInfoOrDie(info_bubble_xid)->override_redirect = true;
vector<int> params;
params.push_back(1); // show while locked
wm_->wm_ipc()->SetWindowType(
info_bubble_xid, chromeos::WM_IPC_WINDOW_CHROME_INFO_BUBBLE, &params);
xconn_->MapWindow(info_bubble_xid);
SendInitialEventsForWindow(info_bubble_xid);
MockCompositor::TexturePixmapActor* info_bubble_actor =
GetMockActorForWindow(wm_->GetWindowOrDie(info_bubble_xid));
EXPECT_TRUE(info_bubble_actor->visibility_groups().count(
WindowManager::VISIBILITY_GROUP_SCREEN_LOCKER));
// The actor should be removed from the visibility group when the window is
// unmapped.
XEvent event;
xconn_->InitUnmapEvent(&event, info_bubble_xid);
wm_->HandleEvent(&event);
EXPECT_FALSE(info_bubble_actor->visibility_groups().count(
WindowManager::VISIBILITY_GROUP_SCREEN_LOCKER));
}
// Test that we handle messages from Chrome notifying us that the user is
// signing out.
TEST_F(ScreenLockerHandlerTest, SigningOut) {
WmIpc::Message msg(chromeos::WM_IPC_MESSAGE_WM_NOTIFY_SIGNING_OUT);
SendWmIpcMessage(msg);
// We should grab the pointer and keyboard, assign a transparent cursor to the
// root window, and fade out a snapshot of the screen.
XWindow root = xconn_->GetRootWindow();
EXPECT_EQ(root, xconn_->pointer_grab_xid());
EXPECT_EQ(root, xconn_->keyboard_grab_xid());
EXPECT_EQ(MockXConnection::kTransparentCursor,
xconn_->GetWindowInfoOrDie(root)->cursor);
TestActorConfiguredForFadeout(GetSnapshotActor());
EXPECT_TRUE(IsOnlyActiveVisibilityGroup(
WindowManager::VISIBILITY_GROUP_SESSION_ENDING));
}
} // namespace window_manager
int main(int argc, char** argv) {
return window_manager::InitAndRunTests(&argc, argv, &FLAGS_logtostderr);
}